Skip to content

Commit 4c68de7

Browse files
committed
Fix double-wrapped array type for list arguments with a typed prepare method
GraphqlMutation/GraphqlInputObject would generate T::Array[T::Array[X]] for a list argument whose `prepare:` method has a sig, because the prepare method's return type (already the fully-coerced list value) was wrapped in T::Array[...] a second time.
1 parent 47f5e8f commit 4c68de7

3 files changed

Lines changed: 110 additions & 1 deletion

File tree

lib/tapioca/dsl/helpers/graphql_type_helper.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,20 @@ def type_for(type, ignore_nilable_wrapper: false, prepare_method: nil)
9191
"T.untyped"
9292
end
9393

94+
prepared = false
9495
if prepare_method
9596
prepare_signature = Runtime::Reflection.signature_of(prepare_method)
9697
prepare_return_type = prepare_signature&.return_type
9798
if valid_return_type?(prepare_return_type)
9899
parsed_type = prepare_return_type&.to_s
100+
prepared = true
99101
end
100102
end
101103

102-
if type.list?
104+
# A `prepare` method receives (and returns) the argument's fully coerced value, list
105+
# wrapper included, so its return type already accounts for `type.list?` and must not be
106+
# wrapped again.
107+
if type.list? && !prepared
103108
parsed_type = "T::Array[#{parsed_type}]"
104109
end
105110

spec/tapioca/dsl/compilers/graphql_input_object_spec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,39 @@ def post_id; end
111111
assert_equal(expected, rbi_for(:CreateCommentInput))
112112
end
113113

114+
it "generates correct RBI for a list argument with a prepare method returning the same list type" do
115+
add_ruby_file("create_comment_input.rb", <<~RUBY)
116+
class CreateCommentInput < GraphQL::Schema::InputObject
117+
extend T::Sig
118+
119+
class << self
120+
extend T::Sig
121+
sig { params(tags: T::Array[String], _context: T.untyped).returns(T::Array[String]) }
122+
def prepare_tags(tags, _context)
123+
tags.map(&:downcase)
124+
end
125+
end
126+
127+
argument :tags, [String], "Tags for the comment", prepare: :prepare_tags, required: true
128+
129+
def resolve(tags:)
130+
# ...
131+
end
132+
end
133+
RUBY
134+
135+
expected = <<~RBI
136+
# typed: strong
137+
138+
class CreateCommentInput
139+
sig { returns(T::Array[::String]) }
140+
def tags; end
141+
end
142+
RBI
143+
144+
assert_equal(expected, rbi_for(:CreateCommentInput))
145+
end
146+
114147
it "doesn't fail when input object is anonymous" do
115148
add_ruby_file("create_comment_input.rb", <<~RUBY)
116149
class CreateCommentInput < GraphQL::Schema::InputObject

spec/tapioca/dsl/compilers/graphql_mutation_spec.rb

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,77 @@ def resolve(min:, max:, other:, proc:); end
224224
assert_equal(expected, rbi_for(:CreateComment))
225225
end
226226

227+
it "generates correct RBI for a list argument with a prepare method returning the same list type" do
228+
add_ruby_file("create_comment.rb", <<~RUBY)
229+
class CreateComment < GraphQL::Schema::Mutation
230+
extend T::Sig
231+
232+
class << self
233+
extend T::Sig
234+
sig { params(tags: T::Array[String], _context: T.untyped).returns(T::Array[String]) }
235+
def prepare_tags(tags, _context)
236+
tags.map(&:downcase)
237+
end
238+
end
239+
240+
argument :tags, [String], "Tags for the comment", prepare: :prepare_tags
241+
242+
def resolve(tags:)
243+
# ...
244+
end
245+
end
246+
RUBY
247+
248+
expected = <<~RBI
249+
# typed: strong
250+
251+
class CreateComment
252+
sig { params(tags: T::Array[::String]).returns(T.untyped) }
253+
def resolve(tags:); end
254+
end
255+
RBI
256+
257+
assert_equal(expected, rbi_for(:CreateComment))
258+
end
259+
260+
it "generates correct RBI for a list argument with a prepare method that has no valid return type" do
261+
add_ruby_file("create_comment.rb", <<~RUBY)
262+
class CreateComment < GraphQL::Schema::Mutation
263+
extend T::Sig
264+
265+
class << self
266+
extend T::Sig
267+
sig { params(tags: T::Array[String], _context: T.untyped).void }
268+
def prepare_tags_void(tags, _context)
269+
tags.map(&:downcase)
270+
end
271+
272+
def prepare_tags_untyped(tags, _context)
273+
tags.map(&:downcase)
274+
end
275+
end
276+
277+
argument :tags, [String], "Tags for the comment", prepare: :prepare_tags_void
278+
argument :other_tags, [String], "Other tags for the comment", prepare: :prepare_tags_untyped
279+
280+
def resolve(tags:, other_tags:)
281+
# ...
282+
end
283+
end
284+
RUBY
285+
286+
expected = <<~RBI
287+
# typed: strong
288+
289+
class CreateComment
290+
sig { params(tags: T::Array[::String], other_tags: T::Array[::String]).returns(T.untyped) }
291+
def resolve(tags:, other_tags:); end
292+
end
293+
RBI
294+
295+
assert_equal(expected, rbi_for(:CreateComment))
296+
end
297+
227298
it "generates correct RBI arguments with a prepare method on the argument class" do
228299
add_ruby_file("create_comment.rb", <<~RUBY)
229300
class CommentInput < GraphQL::Schema::InputObject

0 commit comments

Comments
 (0)