Skip to content

Prefer a block signature that declares parameters - #61

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

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

Conversation

@apiology

Copy link
Copy Markdown
Owner

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

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

# in the workspace
# @!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 the pins into one carrying two signatures. The gem's
bare &block yields { () -> }, and Chain::Call took the first, never
reading the stub's @yieldparam.

Signatures whose block declares parameters now sort ahead. That changes the
outcome only where both already matched the call's arity and arguments and the
winner was arbitrary.

Two adjacent shapes remain uncovered: a stub without @return, whose
signatures combine_signatures discards as all-undefined, and a gem using
def self.build against a stub using class << self, where the pins never
combine.

Based on castwide#1288, whose store combining produces the two-signature pin. On
master the @!parse pin is discarded by [stack.first].compact before
selection runs, so the two-signature pin does not exist and this sort has
nothing to reorder — the symptom is the same there, by a different mechanism.

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
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