Skip to content

Fix Hash#fetch generic leak from unmatched RBS interface overloads - #65

Draft
apiology wants to merge 2 commits into
masterfrom
fix-issue-1227
Draft

Fix Hash#fetch generic leak from unmatched RBS interface overloads#65
apiology wants to merge 2 commits into
masterfrom
fix-issue-1227

Conversation

@apiology

Copy link
Copy Markdown
Owner

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

Fixes castwide#1227

Summary

Pin::Parameter#compatible_arg? rejected every argument against an RBS-interface-typed parameter (e.g. Hash#fetch's _Key) unless the argument's type was nominally included via CoreFills::INCLUDES. Since Hash#fetch's plain (no default, no block) overload takes a _Key-typed parameter, no overload of h.fetch(k) ever matched during argument-type inference, and Pin::Method#return_type fell back to unioning every overload's return type together — leaking the unbound generic X from the default-value and block overloads into the inferred type.

# @param h [Hash{String => Integer}]
# @return [Integer]
def fetch_it(h)
  h.fetch('a')
end

was reported as inferring ::Integer, generic<X> against the declared ::Integer return.

Fix

Add :allow_unmatched_interface to the rules passed to conforms_to? in compatible_arg?, matching the leniency TypeChecker already applies by default everywhere except its :alpha level (Rules#require_interfaces_resolved?). This is scoped to overload argument matching only — compatible_arg? has a single call site (Source::Chain::Call#inferred_pins).

Test plan

  • Added specs in spec/type_checker/levels/strong_spec.rb covering Hash#fetch (fixed) and Hash#[] (control, unaffected)
  • bundle exec rspec — 1626 examples, 0 failures, 60 pending (no new failures)
  • bundle exec rubocop on changed files — no offenses

Pin::Parameter#compatible_arg? rejected any argument against an
RBS-interface-typed parameter (e.g. Hash#fetch's _Key) unless the
argument's type was nominally included via CoreFills::INCLUDES. Since
Hash#fetch's non-block, non-default overload takes a _Key-typed
parameter, no overload of a plain h.fetch(k) call ever matched, and
Pin::Method#return_type fell back to unioning every overload's return
type together - including the unbound generic X from the two
overloads that require a default value or a block.

Add :allow_unmatched_interface to the rules passed to conforms_to? in
compatible_arg?, matching the leniency TypeChecker itself already
applies by default (see Rules#require_interfaces_resolved?) everywhere
except the :alpha level.

Fixes castwide#1227
Chain::Hash#resolve returned the bare, unparameterized ::Hash type
for every hash literal, so a chained fetch/[] off a hash literal
leaked generic<T>/generic<U> instead of resolving to a concrete
value type. Chain::Array already infers Array<T> from its elements
the same way; this brings Chain::Hash to parity by chaining each
pair's key and value node (NodeChainer#hash_pairs) and inferring
Hash{K=>V} from their actual types (Chain::Hash#inferred_type).

Stacked on castwide#65 (fix-issue-1227), which added the
_receiver_path param to Chain::Hash#resolve that this commit merges
with.

Follow-on to PR 65: this covers the generic<T>/generic<U> leak PR 65
doesn't, tracked as tool-limitation:pr-65-follow-on.
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.

Hash#fetch on Hash{Symbol => Class<X>} miscomputes generics when rbs >= 4.1.0

1 participant