Skip to content

Commit 922d073

Browse files
apiologyclaude
andcommitted
Fix @Generic return type lost when method also declares a block param
Pin::Callable#arity_matches? rejected any call missing a block whenever the method signature had block info attached, even when that info came from a bare &block formal parameter with no @yield tags. Ruby never requires callers to pass a block for such a parameter, so this caused the sole matching signature to be discarded, skipping generic resolution and leaving the return type as unresolved generic<T>. Add Pin::Callable#block_required?, true only for RBS-sourced signatures with a non-optional block ({ ... } vs ?{ ... }), and gate the arity check on it instead of bare block presence. YARD-derived signatures have no way to express a required block, so they default to false. Fixes castwide#1265 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNnFsUN4Uryo6Xqh7xv2sr
1 parent 8fda633 commit 922d073

4 files changed

Lines changed: 42 additions & 4 deletions

File tree

lib/solargraph/pin/callable.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,17 @@ class Callable < Closure
1414
# @param block [Signature, nil]
1515
# @param return_type [ComplexType, nil]
1616
# @param parameters [::Array<Pin::Parameter>]
17+
# @param block_required [Boolean] Whether callers must pass a block for
18+
# this signature to apply. Only ever true for RBS-sourced signatures
19+
# with a non-optional block (`{ ... }` rather than `?{ ... }`); a bare
20+
# `&block` parameter or YARD @yield tag never makes a block mandatory.
1721
# @param [Hash{Symbol => Object}] splat
18-
def initialize block: nil, return_type: nil, parameters: [], **splat
22+
def initialize block: nil, return_type: nil, parameters: [], block_required: false, **splat
1923
super(**splat)
2024
@block = block
2125
@return_type = return_type
2226
@parameters = parameters
27+
@block_required = block_required
2328
end
2429

2530
def reset_generated!
@@ -55,6 +60,7 @@ def combine_blocks other
5560
def combine_with other, attrs = {}
5661
new_attrs = {
5762
block: combine_blocks(other),
63+
block_required: block_required? || other.block_required?,
5864
return_type: combine_return_type(other)
5965
}.merge(attrs)
6066
new_attrs[:parameters] = choose_parameters(other).clone.freeze unless new_attrs.key?(:parameters)
@@ -241,7 +247,7 @@ def arity_matches? arguments, with_block
241247
argcount = arguments.length
242248
parcount = mandatory_positional_param_count
243249
parcount -= 1 if !parameters.empty? && parameters.last.block?
244-
return false if block? && !with_block
250+
return false if block? && block_required? && !with_block
245251
# @todo this and its caller should be changed so that this can
246252
# look at the kwargs provided and check names against what
247253
# we acccept
@@ -267,6 +273,11 @@ def block?
267273
!!@block
268274
end
269275

276+
# @return [Boolean]
277+
def block_required?
278+
!!@block_required
279+
end
280+
270281
protected
271282

272283
attr_writer :block

lib/solargraph/rbs_map/conversions.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,8 @@ def method_def_to_sigs decl, pin
551551
Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs,
552552
type_location: type_location, closure: pin)
553553
end
554-
Pin::Signature.new(generics: generics, parameters: signature_parameters, return_type: signature_return_type, block: block, source: :rbs,
554+
Pin::Signature.new(generics: generics, parameters: signature_parameters, return_type: signature_return_type, block: block,
555+
block_required: overload.method_type.block&.required || false, source: :rbs,
555556
type_location: type_location, closure: pin)
556557
end
557558
end

lib/solargraph/rbs_translator.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ def self.to_signature method_type, closure, parameter_names = []
9191
block_return_type = to_complex_type(method_type.block.type.return_type)
9292
Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, type_location: closure.location, closure: closure)
9393
end
94-
Pin::Signature.new(generics: generics, parameters: parameters, return_type: return_type, block: block, source: :rbs, type_location: closure.location, closure: closure)
94+
Pin::Signature.new(generics: generics, parameters: parameters, return_type: return_type, block: block,
95+
block_required: method_type.block&.required || false, source: :rbs, type_location: closure.location, closure: closure)
9596
end
9697

9798
# @param type_name [RBS::TypeName]

spec/source/chain/call_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,31 @@ def baz
280280
expect(type.tag).to eq('String')
281281
end
282282

283+
it 'infers generic return types from @generic tag when method also takes an unused block param' do
284+
source = Solargraph::Source.load_string(%(
285+
class Foo
286+
def initialize; end
287+
def foo_method; 1; end
288+
end
289+
290+
class Repro
291+
# @generic T
292+
# @param clazz [Class<generic<T>>]
293+
# @return [generic<T>]
294+
def create_object(clazz, &unused)
295+
clazz.new
296+
end
297+
end
298+
299+
Repro.new.create_object(Foo)
300+
), 'test.rb')
301+
api_map = Solargraph::ApiMap.new
302+
api_map.map source
303+
chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(15, 20))
304+
type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals)
305+
expect(type.tag).to eq('Foo')
306+
end
307+
283308
it 'infers generic return types from block from yield being a return node' do
284309
pending('deeper inference support')
285310

0 commit comments

Comments
 (0)