Skip to content

Prefer a block signature that declares parameters - #61

Closed
apiology wants to merge 7 commits into
fix-1286-generic-cross-file-parsefrom
fix-combined-block-signature
Closed

Prefer a block signature that declares parameters#61
apiology wants to merge 7 commits into
fix-1286-generic-cross-file-parsefrom
fix-combined-block-signature

Conversation

@apiology

@apiology apiology commented Aug 19, 2026

Copy link
Copy Markdown
Owner

A block parameter resolves as undefined when a gem's yardoc and a workspace
@!parse stub declare the same method and only the stub documents the yield:

module Widgetbox
  class << self
    # @return [String]
    def build(&block) = 'x'
  end
end

# @!parse
#   module Widgetbox
#     class << self
#       # @yieldparam config [String]
#       # @return [String]
#       def build(&block); end
#     end
#   end

Widgetbox.build { |config| config.upcase } # Unresolved call to upcase

ApiMap::Store combines both docs into one pin with two signatures;
Chain::Call took the bare &block first, missing the stub's @yieldparam.

Signatures whose block declares parameters now sort ahead, changing outcomes
only where the winner was already arbitrary.

Two shapes remain uncovered: a stub without @return, and def self.build
with class << self, whose pins never combine. Needs castwide#1288's combining;
master discards the pin earlier, so nothing reorders.

This PR was written by Claude Code on behalf of @apiology.

apiology and others added 2 commits August 19, 2026 16:21
When a method has more than one block-carrying signature, Chain::Call
selected the first one in source order. A signature built from a bare
`&block` parameter yields `{ () -> }` - it says the method takes a block
but nothing about what the block receives - so when it sorted ahead of a
sibling signature carrying @yieldparam types, the block's parameters
resolved as undefined.

This shows up wherever a gem's own yardoc and a workspace `@!parse` stub
both declare the same method: ApiMap::Store combines them into one pin
with two signatures, the gem's `&block` signature first, and the
annotation's @yieldparam types were never consulted.

Within the block-carrying group, signatures whose block declares
parameters are now tried before those whose block declares none. This
only changes which signature wins where both already match the call's
arity and argument types, and the previous winner was whichever happened
to be first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015AsvDi68YqsKoBtS2kg9ch
Chain::Call tries a method pin's signatures in preference order and
stops as soon as one produces a defined return type. When none does -
every signature returning untyped, as RBS `untyped` maps to
ComplexType::UNDEFINED - the loop ran to the end and the last signature
to match supplied the pin passed to `with_single_signature`, discarding
the better earlier match.

A blockless signature matches a call that passes a block, since Ruby
accepts a block for any method. So where a gem's bare `def build; end`
combines with a workspace `@!parse` stub declaring @yieldparam types,
the blockless signature was tried second and won, and the block's
parameters resolved as undefined.

A later signature now replaces an earlier match only when it produces a
return type. This is complementary to the preference ordering added in
the previous commit: that orders the block-carrying group internally,
which is a no-op when the group holds a single signature, as it does
here.

The full spec suite is unchanged (1631 -> 1632 examples with the new
spec, 0 failures) and `solargraph typecheck --level strong` over this
codebase reports the same 577 problems, so nothing here relied on a
later overload overwriting an earlier one under an undefined-return
tie. The added lines are kept tight to stay under the existing
Metrics/BlockLength limit for this block.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015AsvDi68YqsKoBtS2kg9ch
apiology added a commit that referenced this pull request Aug 22, 2026
…-08-04

# Conflicts:
#	lib/solargraph/source/chain/call.rb
Chain::Call#inferred_pins tried block-taking overload signatures
before no-block ones unconditionally, on the assumption that
Callable#arity_matches? would reject a block-taking overload when
the call site passed no block. That assumption held until commit
922d073 (castwide#1274) made block_required? default
false for YARD-derived signatures, since YARD has no syntax to mark
a block mandatory. A YARD @!override with two @overload tags
differing only by block presence now resolves a no-block call site
to the block overload return type (often an unresolved generic)
regardless of declaration order.

Fix: only try block-taking overloads first when the call site
actually passed a block; try no-block overloads first otherwise.
Regression spec added to strong_spec.rb.

Extracted the overload-ordering logic into a new dispatch_order
helper so inferred_pins stays under the Metrics/AbcSize,
Metrics/PerceivedComplexity, and Metrics/BlockLength thresholds
the added branching pushed it over.
Call#inferred_pins prefers a block-taking overload signature that
declares yielded parameters over a sibling that yields { () -> },
and keeps the first matching signature unless a later match
supplies the return type the first one could not. Both existed
only as clip_spec cases exercised through combined gem/annotation
pins; add direct call_spec coverage for the dispatch order and the
selected_signature_pin replacement rule.
…rse' into fix-combined-block-signature

# Conflicts:
#	lib/solargraph/source/chain/call.rb
#	spec/type_checker/levels/strong_spec.rb
Several comments this branch added ran long and narrated internal
mechanism step-by-step (e.g. how new_signature_pin gets set and when
the loop breaks) instead of stating the non-obvious why in a few
lines. Comments here are cut to what a reader needs beyond the code
and the spec titles, which already carry the intent.
@apiology apiology closed this Sep 8, 2026
@apiology

apiology commented Sep 8, 2026

Copy link
Copy Markdown
Owner Author

Claude: Closed in favor of a fix at the actual root cause — combine_signatures_by_type_arity buckets block signatures apart before any merge is attempted, rather than picking between them at call time as this PR did. That fix landed as #94, which was folded into castwide#1288.

@apiology
apiology deleted the fix-combined-block-signature branch September 8, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant