Skip to content

Destructure block parameter groups and keep union-typed tuple elements in one position - #60

Draft
apiology wants to merge 4 commits into
apiology-1196-literal-inferencefrom
destructuring-on-1223
Draft

Destructure block parameter groups and keep union-typed tuple elements in one position#60
apiology wants to merge 4 commits into
apiology-1196-literal-inferencefrom
destructuring-on-1223

Conversation

@apiology

@apiology apiology commented Aug 16, 2026

Copy link
Copy Markdown
Owner

|(name, count), memo| produced no local pins at all for name/count (every reference was an unresolved call), and a Hash#each block over a union-keyed hash reported false "Wrong argument type ... received Array(String, nil, V)" errors because the key union's members were spliced into extra tuple positions.

# @return [Hash{String, Symbol => Integer}]
def dict; { 'a' => 1, b: 2 }; end

dict.each { |key, count| key.to_s; count.succ }  # was: count unresolved
dict.each_with_object({}) do |(key, count), memo|  # was: key/count not locals at all

Two changes on top of castwide#1223: (1) a destructured group registers as one :mlhs parameter holding its signature position; each variable inside becomes a local typed by projecting the group's tuple type per element index (nesting supported via an index path). (2) Generic resolution rebuilds type parameters per position, so a union binding one tuple slot stays one slot.

Makes three previously-pending clip specs pass (un-pended here).

Known limitations: tag rendering of a multi-item position is still ambiguous (Array(String, nil, Integer)), so positions survive in memory but not a to_s/parse round trip — follow-up. The partial-result fallback applies only when the yielded types correspond to the block's parameters one for one (a tuple destructured across them, or one yielded type per parameter); a single auto-splatted value whose arity cannot be matched leaves the parameters undefined rather than assigning the whole value to parameter 0.

Test plan: 10 new specs (fail-before verified), including four pinning plain multi-parameter destructuring; full suite 1,610 examples, 0 failures.

🤖 This PR was written by Claude (Anthropic's AI assistant) on behalf of @apiology.

🤖 Generated with Claude Code

https://claude.ai/code/session_01H1FEjW6nMpZrWPmeWX9miT

A destructured group (|(a, b), c|) previously produced one garbage
Parameter named with the raw sexp text and no local pins at all for the
variables inside it, so every reference to them was an unresolved call.

The group now registers as a single :mlhs Parameter (holding its
position in the block signature), and each variable inside it becomes a
local Parameter carrying an mlhs_path - the group's position plus the
element index at each nesting level - typed by projecting the group's
tuple type per position. typify_parameters also keeps its best partial
result instead of discarding everything when a single yield type (e.g.
each_with_object's unbound U generic) fails to resolve.

SKIP=Solargraph: strong self-typecheck of the touched files carries 16
problems on the parent commit already (fallout of re-enabling tuple
inference, which castwide#1223 mitigates); this diff nets
that down to 15. PoC branch - the real PR should land atop castwide#1223.
…tion

Substituting a generic type parameter with a union (e.g. Hash#each
yielding [K, V] where K is 'String, Symbol' or 'String, nil') spliced
the union's members into separate tuple positions, inflating
Array(K, V) into a 3-arity tuple. That broke block destructuring
(arity mismatch) and produced false 'Wrong argument type ... received
Array(A, B, C)' errors at strong level.

Both rebuild sites - resolve_param_generics_from_context and
UniqueType#transform - now rebuild parameters per position, wrapping
however many types a position's transformation produces back into that
single position.

Known limitation: the tag rendering is unchanged, so a multi-item
position still PRINTS ambiguously (Array(String, Symbol, Integer));
positions survive in memory but not a to_s/parse round trip.
apiology added a commit that referenced this pull request Aug 17, 2026
# Conflicts:
#	lib/solargraph/pin/parameter.rb
The partial-result fallback added in this branch returned whatever
destructure_yield_types produced even when the yielded types could not
be matched to the block's parameters. In that fallback Ruby auto-splats
a single yielded value across several parameters, but the code assigns
the WHOLE value to position 0 and leaves the rest undefined - garbage
the previous all-or-nothing gate happened to discard.

Three real downstream sites regressed from "parameters untyped" to
"parameter 0 wrongly typed as the whole value":

  hash[k] = v            # Wrong argument type: expected String,
                         #   received Array<Object, NilClass>
  proc { |exception, try, elapsed, next_int| ... }
                         # exception expected Exception, received Array

Only keep a partial result when the positions genuinely correspond -
one yielded type per parameter, or a tuple whose arity matches the
parameter count. Otherwise fall back to undefined, as before.

Four specs pin the plain multi-parameter shapes (Hash#each pair,
Array-of-tuples, 4-parameter proc, non-matching arity).
apiology added a commit that referenced this pull request Aug 17, 2026
The #60 merge (86ccceb) brought in the partial-result fallback, which
regressed three plate-spinner sites from 'parameters untyped' to
'parameter 0 typed as the whole yielded value' (jell.rb:35,
my_asana_wip.rb:292, asana_retry.rb:57). Verified against this pin: the
fix removes exactly those three errors and introduces none.
yield_types.first is ComplexType, nil; calling tuple?/all_params on it
directly failed strong self-typecheck (block.rb:84-85) once merged into
the integration branch, whose narrowing surfaces it. CI on the
integration merge reported exactly these two problems.

SKIP=Solargraph: the Overcommit hook wrapper errors out ('Solargraph
failed to run') in this worktree; the CI 'Solargraph / strong' job is
the authoritative gate.
apiology added a commit that referenced this pull request Aug 17, 2026
Fixes the two strong self-typecheck problems the previous merge
introduced at pin/block.rb:84-85 (yield_types.first is nilable).
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