Skip to content

Stop reporting a duck type's own method as unresolved - #1280

Open
apiology wants to merge 2 commits into
castwide:masterfrom
apiology:fix-1257-duck-type-own-method
Open

Stop reporting a duck type's own method as unresolved#1280
apiology wants to merge 2 commits into
castwide:masterfrom
apiology:fix-1257-duck-type-own-method

Conversation

@apiology

@apiology apiology commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Problem: A call to a method that a duck-typed parameter itself declares is reported as unresolved.

# @param thing [#read_body]
def fetch(thing)
  thing.read_body
end

# solargraph typecheck --level strict
# => Unresolved call to read_body on #read_body

It fires at strict and above on every duck-typed parameter, even though ApiMap#get_complex_type_methods already resolves the same call correctly for completion.

Solution: Chain::Call#resolve synthesizes a Pin::DuckMethod when the receiver is a duck type naming the called method, mirroring get_complex_type_methods; the pin is explicit: false so arity checking is skipped, since a duck type declares that the method exists, not what signature it has.

Fixes #1257

🤖 Generated with Claude Code

@apiology
apiology marked this pull request as ready for review August 11, 2026 17:15
apiology added a commit to apiology/solargraph that referenced this pull request Aug 11, 2026
…d method

Chain::Call#resolve converted duck-typed receivers to Object and
searched Object's real method stack, so a call matching the duck
type's own declared method (e.g. `# @PARAM thing [#read_body]` then
`thing.read_body`) was reported as an unresolved call at strict
typecheck level and above, even though ApiMap#get_complex_type_methods
already handled this correctly for completion. Special-cases duck-type
receivers to synthesize a matching Pin::DuckMethod, marked
explicit: false so arity checking (which has no real signature to
check against) is skipped.

Fixes castwide#1257

Conflict in lib/solargraph/source/chain/call.rb: incoming's branch,
based directly on castwide/master, special-cased duck types inline in
Call#resolve via `binder.each_unique_type.map { ... }`, predating this
branch's method_pins_for_binder/method_stack_pins refactor (which
generically handles unions/intersections and dedups by path). Kept
this branch's structure and moved the duck-type special case into
method_stack_pins's single-UniqueType branch instead, so duck typing
gets the same union/intersection handling as every other receiver type
rather than a separate parallel code path.

Verified: spec/type_checker/levels/alpha_spec.rb,
spec/type_checker/levels/strict_spec.rb, spec/source/chain/call_spec.rb
(141 examples, 0 failures, 10 pending), and a broader safety net -
spec/type_checker, spec/source, spec/source_map/clip_spec.rb,
spec/api_map_spec.rb, spec/api_map_method_spec.rb, spec/pin (880
examples, 1 failure, 29 pending). The 1 failure
(spec/api_map_spec.rb:771) is the same pre-existing order-dependent
flake already confirmed unrelated to this branch's work during the
castwide#1278 merge earlier in this session.
apiology added a commit to apiology/solargraph that referenced this pull request Aug 13, 2026
…er return type in Pin::Method#typify

Pin::DuckMethod.new never sets closure:, so the unconditional closure.gates
call crashed with NoMethodError on duck-typed method calls. Use the pin's
own gates accessor (already nil-safe, equivalent to the inline
closure&.gates || [''] this branch had) instead.

typify_from_super also returned an ancestor pin's raw, unqualified
return_type instead of calling pin.typify(api_map) on it, so a borrowed
type would be qualified against the duck pin's own fabricated gates
rather than the ancestor's real ones.

Removes 12 @sg-ignore "Not enough arguments to Module#protected" comments
across Chain::Call/Hash/If/Link/Literal/Or#equality_fields that this fix
made unneeded - strong typecheck problem count back to 230, matching
pre-merge baseline.
apiology added a commit to apiology/solargraph that referenced this pull request Aug 13, 2026
@apiology
apiology marked this pull request as draft August 31, 2026 20:36
apiology added a commit to apiology/solargraph that referenced this pull request Sep 5, 2026
Brings castwide#1280 up to its current head. Seven of its
eight commits are already here; the outstanding one is 66adce5,
removing the dead TypeMethods#qualify.

That method is the undercover node this PR owns,
complex_type/type_methods.rb:248-258 at 0%. It closes by deletion rather
than by coverage - nothing called it.

Also carries a comment rewrite on UniqueType::BOT, naming which
operations a wrong @rooted would actually break rather than leaving it
general.

No conflicts. 2200 examples, 0 failures, 45 pending.
@apiology apiology changed the title Resolve calls to a duck type param's own declared method Stop reporting a duck type's own method as unresolved Sep 5, 2026
@apiology
apiology force-pushed the fix-1257-duck-type-own-method branch from 684cc6e to 10ea7dd Compare September 6, 2026 02:40
@apiology
apiology marked this pull request as ready for review September 6, 2026 03:47
apiology and others added 2 commits September 8, 2026 17:14
Chain::Call#resolve converted a duck-typed receiver to Object and
searched Object's method stack, so a call to a method the duck type
itself declares was reported as an unresolved call at strict level and
above:

    # @PARAM thing [#read_body]
    def fetch(thing)
      thing.read_body
    end
    # => Unresolved call to read_body on #read_body

ApiMap#get_complex_type_methods already handles this correctly for
completion, so the same call resolved for autocomplete while failing
typecheck.

Special-case a duck-type receiver whose name matches the called method
and synthesize a Pin::DuckMethod, mirroring get_complex_type_methods.
The pin is explicit: false so arity checking is skipped -- a duck type
declares that the method exists, not what signature it has, so checking
arity against it would turn "unresolved call" into a false "too many
arguments".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1280 dispatches a duck call to Pin::DuckMethod for the first time. That
pin has no closure, so typify_from_super walks nil and raises
NoMethodError on any duck tag Object also answers, such as #to_s.

It also synthesized a zero-arg signature, which no real call matches.

Ported from #72, which carries the same two changes
on the intersection branch. Neither references intersections.
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.

Chain::Call#resolve can't resolve calls to a YARD duck-type param's own declared method

1 participant