parser: parenthesized match-arm value vs payload capture

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.
This commit is contained in:
agra
2026-06-01 00:12:11 +03:00
parent ae330365b4
commit d4b1248f65

View File

@@ -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 `( <identifier> )`; 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);
}