Skip to content

Kernel#send doesn't work with splat blocks - #68

Open
apiology wants to merge 3 commits into
masterfrom
fix-yield-arity-truncation
Open

Kernel#send doesn't work with splat blocks#68
apiology wants to merge 3 commits into
masterfrom
fix-yield-arity-truncation

Conversation

@apiology

Copy link
Copy Markdown
Owner

Problem:

3.times { |i| i }          # i: Integer (correct)
3.send(:times) { |i| i }   # i: Array (wrong — should be undefined)

Cause:

# def send: (name interned, *arg_1 Array, **arg_2 Hash[Symbol, Object])
#   { (*arg ::Array) -> untyped } -> untyped

Pin::Block#typify_parameters treated the splat's return_type as a genuine single yielded value, so the .send-dispatched block bound the whole Array to its parameter instead of leaving it undefined.

Solution:

destructure_yield_types now also takes the yielding method's own block parameter pins, not just their resolved types, so a new single_unknown_arity_splat? check can tell a lone splat (*arg) apart from a genuine single yielded value.

When it fires, every declared block parameter is left UNDEFINED instead of bound to the whole Array.

Claude: opened on behalf of the repo owner while auditing @sg-ignore suppressions in a downstream project.

The Kernel#send RBS block signature is a real splat -
(*arg ::Array) -> untyped - meaning "however many values the
dynamically-dispatched method yields," not "one value, typed as an
Array, was yielded." Block::typify_parameters treated that single
splat parameter's return_type the same as a genuine single yielded
value, so a .send(...) { |name| ... } block bound the whole Array
type to name instead of leaving it undefined.

Ruby itself truncates to the first yielded value here; Solargraph
cannot know what .send actually dispatches to, so the correct
inference is undefined, not the whole Array.

destructure_yield_types now also takes the yielding method's own
block parameter pins (not just their types) so it can tell a lone
splat apart from a genuine single value, and falls back to
undefined for each declared block parameter unless the block itself
is also declared with a splat.
apiology added a commit that referenced this pull request Aug 27, 2026
Merge-resolved a conflict in pin/block.rb: two independent helper
methods (per_position_yield_types? from an already-merged PR, and
this PR's own single_unknown_arity_splat?) collided at the same
insertion point; kept both. Removed 3 now-unneeded @sg-ignore
markers this PR's branch had added.

spec/pin/parameter_spec.rb:82 fails locally on this exact merge
result, but also fails identically on the pre-merge tip (62eb9b6)
with this PR's changes fully reverted, and CI's own rspec matrix
(including the Ruby 3.2 combos matching local) already passed on
that commit. Cause of the local-only failure is unknown; not caused
by this merge.
apiology added a commit that referenced this pull request Aug 27, 2026
CI (Ruby 4.0) failed on the #68 merge with two typecheck findings
that are the mirror image of what local Ruby 3.2.6 reported:
typify_parameters' return-type-mismatch ignore, removed locally as
unneeded, is still needed on 4.0; destructure_yield_types' own
ignore, kept from #68's branch, is unneeded on 4.0 but was needed
locally. Restored the first, removed the second, matching CI's
verdict per this repo's standing local/CI disagreement convention.
Comments this PR added ran well past the repo's 1-3 line budget and
included a multi-line @sg-ignore, against the single-line convention
in lib/solargraph/type_checker/rules.rb. Condensed the
destructure_yield_types docstring, its splat-detection inline
comment, and the block_spec.rb #send example's comment to their core
claims; no code or @type changes.

Full original @sg-ignore text, preserved here since it was
compressed in the source:

Declared return type does not match inferred - adding the
splat-detection return below changes how Solargraph merges this
method's multiple return paths, and it now infers Enumerator
instead of Array for the tail expression even though that
expression is unchanged from before this method had more than
one return path, when it typechecked cleanly.

This may be worth filing upstream as a real Solargraph gap: merging
multiple return paths of a method appears to infer Enumerator rather
than Array for a tail expression that is otherwise unchanged.
@apiology
apiology marked this pull request as ready for review September 7, 2026 01:00
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