You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A brace block on a name that is also a local fails to parse inside parentheses #905
A brace block attached to a name that is also a local variable fails to parse when the call is inside parentheses. MRI calls the method and ignores the local.
defwithblock;yield;endwithblock=5p(withblock{7})
$ /opt/homebrew/opt/ruby@4.0/bin/ruby probe.rb7
$ rbgo probe.rbparse error at line 3: expected ), got "{" ({)
Measured through scripts/conformance/rubyspec/cmd/oracle against ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin25], with rbgo built from e5619211 (current main).
It needs all three lines
The brace block alone is fine — this is where the defect is not:
defwithblock;yield;endp(withblock{7})
=== 3 engines AGREED, byte for byte ===7
The assignment withblock = 5 is what breaks it. Once the parser knows the name as a local, withblock { 7 } inside (...) stops parsing. A block makes a receiver-less name an FCALL regardless of any local of that name — primary: fcall brace_block (parse.y-ruby_4_0:4458-4463) — so MRI resolves withblock to the method, yields, and prints 7.
Whether the parenthesis is essential or merely how it surfaces here is not established; the three lines above are the smallest form I measured.
Not a regression
rbgo at e5619211 and the branch of #904 fail identically, so this predates #904. I found it while writing that PR's test and nearly asserted the MRI answer as a row from memory — the probe is what caught it.
Where it is likely to matter
Any DSL or test helper whose block-taking method shares a name with a local in
the same scope, written with parentheses. The shape is ordinary Ruby:
config=load_configp(config{ ... })
Scope
Parser-side (go-ruby-parser), not the compiler: the failure is at parse time
and no bytecode is produced. The compiler's own local-vs-method rule for the parenthesised, blockless case is #904 and is separate from this.
Prior art in go-ruby-parser/parser, so whoever takes this starts from it
priorart --repo go-ruby-parser/parser "brace block parenthesised call" — 38 tracker entries. Three matter, and one nearly covers this.
⛔ It is NOT one of the nine gaps in parser#27, which is closed
parser#27 catalogued "Nine syntax gaps that make 12 ruby/spec language files unparseable" and is closed. This case is not among them. Gap 9 is the nearest neighbour, and the error shapes are close to mirror images:
parser#27 gap 9 p(-> a=a() { a }.call) expected {, got ")"
this issue withblock = 5; p(withblock { 7 }) expected ), got "{"
Gap 9 is a brace body after a default argument that calls a method, inside a lambda's parameter list. This one is a brace block after an identifier the parser already knows as a local, inside a call's parentheses — and it needs the assignment: without withblock = 5, p(withblock { 7 }) parses and three engines agree on 7.
So: an uncovered case, not a regression of a known one.
Call cannot distinguish foo from foo() → Call.Paren
⭐ #41/#43 are worth noting for a second reason: Call.Paren is what made #904 possible — the compiler could finally tell foobar from foobar(). The same distinction the parser learned to record is the one it mis-parses here when a brace block follows.
That the identifier must be a known local for the failure to appear points at the lexer's lvar state rather than at the grammar for brace_block, which is the same family #32 worked in.
Not implemented here
A grammar/lexer-state change in a parser with 38 prior entries deserves its own pass with a reproduction harness, not an append to an unrelated session. The measurement in the issue above stands: three lines, rbgo at e5619211 and later fail identically, MRI prints 7.
Activity
tannevaled commentedon Nov 9, 2026
Prior art in
go-ruby-parser/parser, so whoever takes this starts from itpriorart --repo go-ruby-parser/parser "brace block parenthesised call"— 38 tracker entries. Three matter, and one nearly covers this.⛔ It is NOT one of the nine gaps in parser#27, which is closed
parser#27 catalogued "Nine syntax gaps that make 12 ruby/spec language files unparseable" and is closed. This case is not among them. Gap 9 is the nearest neighbour, and the error shapes are close to mirror images:
Gap 9 is a brace body after a default argument that calls a method, inside a lambda's parameter list. This one is a brace block after an identifier the parser already knows as a local, inside a call's parentheses — and it needs the assignment: without
withblock = 5,p(withblock { 7 })parses and three engines agree on7.So: an uncovered case, not a regression of a known one.
The neighbouring machinery
doblock its own CMDARG state, separate from COND — the block/lexer-state interaction, fordorather than bracesargpositionfoofromfoo()→Call.Paren⭐ #41/#43 are worth noting for a second reason:
Call.Parenis what made #904 possible — the compiler could finally tellfoobarfromfoobar(). The same distinction the parser learned to record is the one it mis-parses here when a brace block follows.That the identifier must be a known local for the failure to appear points at the lexer's lvar state rather than at the grammar for
brace_block, which is the same family #32 worked in.Not implemented here
A grammar/lexer-state change in a parser with 38 prior entries deserves its own pass with a reproduction harness, not an append to an unrelated session. The measurement in the issue above stands: three lines,
rbgoate5619211and later fail identically, MRI prints7.🤖 Generated with Claude Code