Skip to content

feat(parser): accept parenthesised singleton def receiver - #14

Merged
tannevaled merged 1 commit into
mainfrom
feat/parse-paren-singleton-def
Jul 31, 2026
Merged

feat(parser): accept parenthesised singleton def receiver#14
tannevaled merged 1 commit into
mainfrom
feat/parse-paren-singleton-def

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Gap

MRI's `def` grammar allows the singleton receiver to be a parenthesised arbitrary expression: `def (expr).method(...) ... end`. The parser only accepted an unparenthesised name (`def self.m`, `def Const.m`, `def obj.m`, ivar/cvar/gvar), rejecting the parenthesised form with expected method name after def.

```ruby
obj = Object.new
def (obj).kw(a:); a; end # MRI defines singleton kw on obj; parser errored
def (String).foo; "hi"; end # parenthesised const receiver
```

Change

Add the `def ( ) . ` production. A `(` right after `def` can only open this form (a method name is never a paren group), so it is parsed as the receiver: a single expression (local, constant, method call, `self`, ternary, `and`/`or`, assignment) evaluating to the receiver object, exactly as MRI. A compound group (`def (a; b).m`) is a syntax error in MRI and is rejected here too.

Same AST as the bare form

The production emits the identical `MethodDef` shape as the bare `def obj.m` form — `Singleton=false` with the parsed receiver in `Recv` — so the compiler lowers it with no special case (rbgo `compileSingletonReceiver` already handles an arbitrary-expression receiver). When the receiver is a known local or a constant, the node is byte-identical to the bare form's; a table test asserts this.

Verification

  • MRI `ruby` 4.0.x matched on paren-local, paren-const, paren-method-call, `(self)`, endless `= expr`, and compound-receiver rejection.
  • Tests: paren-local/const/method-call/self receivers, positional/keyword/paren-less params, endless body, compound rejection.
  • 100% statement coverage; `go vet` clean; builds on 6 64-bit arches + js/wasm + wasip1/wasm.

MRI's `def` grammar allows the singleton receiver to be a parenthesised
arbitrary expression: `def (expr).method(...) ... end`. The parser only
accepted an unparenthesised name (`def self.m`, `def Const.m`, `def
obj.m`, ivar/cvar/gvar), rejecting the parenthesised form with "expected
method name after def".

Add the `def ( <expr> ) . <method>` production. A `(` right after `def`
can only open this form (a method name is never a paren group), so it is
parsed as the receiver: a single expression (local, constant, method
call, `self`, ternary, and/or, assignment) evaluating to the receiver
object, exactly as MRI does. A compound group (`def (a; b).m`) is a
syntax error in MRI and is rejected here too.

The production emits the identical MethodDef shape as the bare
`def obj.m` form — Singleton=false with the parsed receiver in Recv — so
the compiler lowers it with no special case (rbgo compileSingletonReceiver
already handles an arbitrary-expression receiver). When the receiver is a
known local or a constant, the node is byte-identical to the bare form's.

Tests: paren-local/const/method-call/self receivers, positional/keyword/
paren-less params, endless `= expr` body, and compound-receiver
rejection; a table test asserts the parenthesised node equals the bare
form's. 100% statement coverage; builds on 6 64-bit arches + wasm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit b7d9c70 into main Jul 31, 2026
11 checks passed
@tannevaled
tannevaled deleted the feat/parse-paren-singleton-def branch July 31, 2026 20:05
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