Skip to content

Type destructured declared tuples by position - #341

Closed
apiology wants to merge 6 commits into
masterfrom
type-declared-tuples-by-position
Closed

apiology wants to merge 6 commits into
masterfrom
type-declared-tuples-by-position

Conversation

@apiology

@apiology apiology commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

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

Problem: Destructuring a declared tuple gives every variable the first element's type:

class Foo; end
class Bar; end

class Holder
  # @return [Array(Foo, Bar)]
  def pair
    [Foo.new, Bar.new]
  end

  # Declared return type ::Bar does not match inferred type ::Foo for Holder#second
  # @return [Bar]
  def second
    _h, sel = pair
    sel
  end
end

A declared Array(A, B) is read as Array<A> for mass assignment and destructured block parameters such as Hash#each { |k, v| }.

Solution: Re-enable TypeMethods#tuple?, which only declared types can satisfy now that array literals infer a bare Array, and give targets after a splat the union of the tuple's elements, since their position depends on the tuple's length.

Out of scope: pair.first and pair[0] stay Foo, Bar, nil; positional element methods need the tuple fill removed in castwide#1201.

Typed block parameters expose Solargraph gaps in its own code. Two are marked with the fix PRs that clear them: unique_type.rb:431 (castwide#1451) and flow_sensitive_typing.rb:234 (castwide#1266 with castwide#1451). One remains unmarked:

  • complex_type/unique_type.rb:426: Unresolved call to map

Markers cleared: 1

TypeMethods#tuple? always returned false, so destructuring an
Array(Foo, Bar) gave every target the first element's type. Array
literals no longer produce fixed-parameter types, so a tuple only comes
from a YARD or RBS declaration and tuple? can report it again.

Targets after a splat now carry a negative index, and a tuple gives
them the union of its elements, since which element they get depends
on the tuple's length.

This also types block parameters destructured from a yielded tuple
(Hash#each), fixing three pending specs, and clears two ignore markers
in RbsMap::Conversions#rebound_alias?. The new subtraction in
MasgnNode gets a marker for the bigdecimal Integer#- defect fixed by
#286.
CI's strong typecheck reports the marker on SourceMap::Mapper's comment
position as unneeded now that destructured comments are typed.
@apiology
apiology marked this pull request as ready for review October 24, 2026 23:08
The BigDecimal Integer#- false positive it covered appears only in a
local Ruby 3.2.6 setup; CI never reports it.
Tuple typing now types ut in resolve_param_generics_from_context, which
exposes the nil-vs-NilClass argument check that castwide#1451 fixes.
Destructured facts are now typed, exposing Hash#fetch(key, nil)
returning generic<X> and the nil argument check. Both castwide#1266
and castwide#1451 are needed to clear it.
castwide#1258 narrows a repeated pure call with a local
variable argument, which is the cp[i] shape this line hits.
@apiology apiology closed this Oct 27, 2026
@apiology

Copy link
Copy Markdown
Owner Author

Claude: Opened upstream as castwide#1461.

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