Skip to content

Check **rest in patterns for duplicates and alternations - #2

Closed
yuki-300 wants to merge 1 commit into
masterfrom
fix/pattern-kwrest-check
Closed

yuki-300 wants to merge 1 commit into
masterfrom
fix/pattern-kwrest-check

Conversation

@yuki-300

@yuki-300 yuki-300 commented Sep 14, 2026 •

Copy link
Copy Markdown
Owner

(No Redmine ticket yet; I'll add the [Bug #NNNNN] prefix once it's filed.)

p_kwrest is the only binding rule in patterns that doesn't go through
error_duplicate_pattern_variable. p_variable, p_rest and the {a:}
shorthand in p_kw all do, but for **rest the name is handed straight to
assignable in new_hash_pattern_tail, so it never lands in pvtbl, is
never checked against in_alt_pattern, and never sets capture_in_pattern.
parse.y therefore accepts these while Prism rejects them:

in { a: x, **x }          # parse.y: runs, x ends up bound to the rest hash
                          # Prism:   duplicated variable name

in 1 | { a: 1, **rest }   # parse.y: Syntax OK under -c (compile.c still stops it at run time)
                          # Prism:   variable capture in alternative pattern

The duplicate case isn't a 4.0 regression, parse.y in 3.4.9 accepts it too
(Prism 3.4.9 already rejected it). The alternation case only shows up in 4.0
because that's where the parse-time check from [Feature #21572] came in;
under 3.4.9 both parsers let it through -c.

The fix is one line: call error_duplicate_pattern_variable from p_kwrest,
exactly like p_rest does. **nil is a different rule (p_kwnorest) and
doesn't bind anything, so in { a: 1, **nil } | 1 is still accepted.

Tests: assert_syntax_error for in {a: x, **x} next to the other
duplicated-variable cases in test_var_pattern, and assert_in_out_err(['-c'], ...)
for { a: 1, **rest } | 1 and 1 | { a: 1, **rest } in
test_alternative_pattern. test_pattern_matching.rb, test_syntax.rb and
test/ripper pass with RUN_OPTS=--parser=parse.y, and test_pattern_matching.rb
still passes under Prism.

This is kept separate from #1 (Bug #22310) because it turns code parse.y has
accepted since at least 3.4 into a SyntaxError, which isn't something I'd want
in a backport. It should go in after #1 though: once **rest sets
capture_in_pattern, the sibling leak that #1 fixes would otherwise start
rejecting in [ { **rest }, 1 | 2 ] as well. With both applied, parse.y and
Prism agree on every form I tried.

`p_kwrest` was the only binding rule in patterns that did not go
through `error_duplicate_pattern_variable`, so parse.y accepted
`{ a: x, **x }` (which then runs, with `x` bound to the rest hash)
and `1 | { a: 1, **rest }` at parse time, while Prism rejects both.
Route it through the same check as `*rest`.

The duplicate case is accepted by parse.y in 3.4.9 as well; the
alternation case only became visible when the parse-time check was
added in 4.0 ([Feature #21572]).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@yuki-300
yuki-300 force-pushed the fix/pattern-kwrest-check branch from cb16c10 to e6be517 Compare September 14, 2026 17:09
@yuki-300 yuki-300 closed this Sep 14, 2026
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