From d4b1248f65549020073d6f59d9d555b1b0dac387 Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 1 Jun 2026 00:12:11 +0300 Subject: [PATCH] parser: parenthesized match-arm value vs payload capture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A match arm `case PAT: (expr)` — e.g. `case 0: (5)` — failed to parse: parseMatchBody unconditionally consumed an `(` after `case PAT:` as a payload-capture `(ident)`, so a non-identifier first token produced "expected capture name". Disambiguate: treat `(` as a capture only when it encloses exactly a lone identifier — `( ident )` — via a new isLoneIdentParen() helper (peekTag-based two-token lookahead). Otherwise the parens belong to the arm-body expression. Payload capture (`case .b: (v) { ... }`, examples/128) still binds. This fixes the scalar paren arm value (`case 0: (5)` now parses and runs). The tuple arm-value form (`case .X: (a, b)`) additionally needs a tuple literal in statement/binding position, tracked separately as issue 0059. Tests: two inline parser unit tests (paren arm value is not a capture; lone `(ident)` still binds). Gates: zig build, zig build test, 273/273 examples. --- src/parser.zig | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/parser.zig b/src/parser.zig index caed4b8..f59cec2 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -1270,6 +1270,13 @@ pub const Parser = struct { return tok.tag; } + /// With `self.current` at `(`, true iff the parens enclose exactly a single + /// identifier — `( ident )`. Distinguishes a match-arm payload capture from + /// a parenthesized / tuple arm-value expression (`(5)`, `(a, b)`). + fn isLoneIdentParen(self: *Parser) bool { + return self.peekTag(1) == .identifier and self.peekTag(2) == .r_paren; + } + fn foreignRuntimeForOffset(self: *Parser, offset: usize) ?ast.ForeignRuntime { const tag = self.peekTag(offset); return switch (tag) { @@ -3093,13 +3100,15 @@ pub const Parser = struct { } else try self.parsePrimary(); // .variant try self.expect(.colon); - // Optional payload capture: (ident) + // Optional payload capture: `(ident)`. Disambiguated from a + // parenthesized / tuple arm-value expression (`(5)`, `(1, 1)`): + // a capture is exactly `( )`; anything else is the + // arm body (an expression) and is left for the body parse below. var capture: ?[]const u8 = null; - if (self.current.tag == .l_paren) { - self.advance(); - if (self.current.tag != .identifier) return self.fail("expected capture name"); + if (self.current.tag == .l_paren and self.isLoneIdentParen()) { + self.advance(); // '(' capture = self.tokenSlice(self.current); - self.advance(); + self.advance(); // ident try self.expect(.r_paren); }