Skip to content

Fix generic arguments lost through inherited methods - #1329

Open
apiology wants to merge 6 commits into
castwide:masterfrom
apiology:fix-generic-superclass-substitution-clean
Open

apiology wants to merge 6 commits into
castwide:masterfrom
apiology:fix-generic-superclass-substitution-clean

Conversation

@apiology

Copy link
Copy Markdown
Contributor

Claude:

A class reference with a concrete generic argument doesn't propagate that argument through a method inherited from its superclass:

$ bundle exec solargraph pin 'NoMethodError<String>#receiver' --rbs
untyped

NoMethodError<T> < NameError<T>, and NameError#receiver is declared to return T. Given NoMethodError<String>, #receiver should resolve to String (or String | nil) — instead it erases to untyped.

Root cause: UniqueType#resolve_generics erased a class's own generic type parameter to ComplexType::UNDEFINED whenever the querying context supplied no concrete value and no default existed, rather than leaving it as an unbound placeholder. resolve_generics is called twice on this path (ApiMap#inner_get_methods_from_reference) — once resolving the superclass reference itself, once resolving the returned method pins — and the fallback nuked the intermediate value before the second call could use it.

After the fix:

$ bundle exec solargraph pin 'NoMethodError<String>#receiver' --rbs
(::String | nil)

A bare, unrooted query (NoMethodError#receiver, no type args) still shows untyped via the CLI pin command — that comes from ApiMap#erase_generics, a separate, deliberate policy (see spec/source_map/clip_spec.rb's "erases unresolvable class generics" example), and is unaffected by this change.

Full spec suite: 1624 examples, 0 failures. Rubocop: 2 pre-existing offenses, both outside this diff (Lint/UnreachableCode at lines 151 and 379, unrelated to the change at line ~466).

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

…ntyped

UniqueType#resolve_generics erased a class's own generic type param to
ComplexType::UNDEFINED whenever the querying context supplied no concrete
value and no default existed, rather than leaving it as an unbound
placeholder. This fires whenever a method is reached only through a
superclass/mixin reference (ApiMap#inner_get_methods_from_reference), so a
concretely-parametrized reference like NoMethodError<String>#receiver
still resolved to untyped even though NoMethodError<String> supplies T,
because resolve_generics is called twice in that path (once while
resolving the superclass reference itself, once while resolving the
returned method pins) and the fallback nuked the intermediate value before
the second call could use it.

bundle exec solargraph pin 'NoMethodError<String>#receiver' --rbs now
shows `(::String | nil)` instead of `untyped`. A bare, unrooted query
(`NoMethodError#receiver`, no type args) still shows `untyped` via the CLI
`pin` command -- that comes from ApiMap#erase_generics, a separate and
deliberate policy (see spec/source_map/clip_spec.rb's "erases unresolvable
class generics" example that intentionally erases an unbound generic for
real type-inference call sites, and is unaffected by this change.
EOF
)
Comment thread lib/solargraph/complex_type/unique_type.rb
Demonstrates the case fixed in the previous commit: a class with two
@Generic params, referenced with no type arguments, must leave the
non-first generic as generic<V> instead of erasing it to undefined.
The existing spec exercises resolve_generics directly; add one
closer to the PR description repro of
NoMethodError<String>#receiver, going through real workspace source
and ApiMap so the fix is also demonstrated at the level a user would
actually hit it.
@apiology
apiology marked this pull request as ready for review August 26, 2026 19:51
apiology added a commit to apiology/solargraph that referenced this pull request Aug 27, 2026
unresolved instead of erasing to untyped
@apiology
apiology marked this pull request as draft August 31, 2026 19:20
Shrink the resolve_generics else-branch comment from 8 lines to
2, and cut the clip_spec.rb example comment from 4 lines to 1,
per Vince's pending review directives on PR 1329.
@apiology apiology changed the title Leave an unbound namespace generic unresolved instead of erasing to untyped Fix generic arguments lost through inherited methods Sep 6, 2026
@apiology
apiology marked this pull request as ready for review September 6, 2026 16:22
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