Skip to content

Commit 36e7a3d

Browse files
hyperpolymathclaude
andcommitted
feat(grammar): qualified Type::Variant patterns in match arms (Refs #122)
`pattern_primary` accepted only bare `upper_ident` / `upper_ident(p..)` constructors, never the qualified `Type::Variant` form. So every qualified match arm was a parse error — `match c { Color::Red => .. }`, recursive ADTs (`IntList::Cons(h, t) => ..`), and stdlib/traits.affine's `Ordering::Less => ..`. This blocked both `= match {..};` expression bodies and block-body matches. Add `upper_ident COLONCOLON upper_ident` and the payload form to pattern_primary. The type qualifier is discarded — PatCon carries only the variant name, exactly matching ExprVariant and the codegen tag dispatch (`scrut.tag === "Variant"`, payload at `.values[i]`), so the qualified and bare forms are interchangeable and consistent with the enum factory emission. Regression: tests/codegen-deno/match_enum.{affine,harness.mjs} — nullary qualified patterns in an expression-body match, plus recursive `len`/`sum` over a payload ADT (verified len=3, sum=60). `dune runtest` unchanged (same 2 pre-existing E2E Node-CJS vscode failures, 214 tests, zero new regressions). Deno-ESM suite green. Refs #122. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent d2578fe commit 36e7a3d

3 files changed

Lines changed: 58 additions & 0 deletions

File tree

lib/parser.mly

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,17 @@ pattern_primary:
918918
| name = upper_ident { PatCon (mk_ident name $startpos $endpos, []) }
919919
| name = upper_ident LPAREN pats = separated_list(COMMA, pattern) RPAREN
920920
{ PatCon (mk_ident name $startpos(name) $endpos(name), pats) }
921+
/* Qualified variant patterns `Type::Variant` / `Type::Variant(p, ..)`
922+
(issue #122 v2). The type qualifier is discarded — PatCon carries
923+
only the variant name, matching ExprVariant and the codegen tag
924+
dispatch (`scrut.tag === "Variant"`). Without these, `match c {
925+
Color::Red => .. }` (and stdlib/traits.affine's `Ordering::Less`)
926+
was a parse error. */
927+
| _ty = upper_ident COLONCOLON name = upper_ident
928+
{ PatCon (mk_ident name $startpos(name) $endpos(name), []) }
929+
| _ty = upper_ident COLONCOLON name = upper_ident
930+
LPAREN pats = separated_list(COMMA, pattern) RPAREN
931+
{ PatCon (mk_ident name $startpos(name) $endpos(name), pats) }
921932
| LPAREN RPAREN { PatTuple [] }
922933
| LPAREN p = pattern RPAREN { p }
923934
| LPAREN p = pattern COMMA ps = separated_nonempty_list(COMMA, pattern) RPAREN
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// issue #122 v2.4 regression: qualified `Type::Variant` patterns in
3+
// match arms (nullary + payload), as both `= match {..};` expression
4+
// bodies and recursive functions. Previously a parse error
5+
// (pattern_primary had no `Type::Variant` production).
6+
7+
pub enum Color { Red, Green, Blue }
8+
9+
pub fn rank(c: Color) -> Int = match c {
10+
Color::Red => 1,
11+
Color::Green => 2,
12+
Color::Blue => 3
13+
};
14+
15+
pub enum IntList { Nil, Cons(Int, IntList) }
16+
17+
pub fn len(xs: IntList) -> Int {
18+
match xs {
19+
IntList::Nil => 0,
20+
IntList::Cons(h, t) => 1 + len(t)
21+
}
22+
}
23+
24+
pub fn sum(xs: IntList) -> Int {
25+
match xs {
26+
IntList::Nil => 0,
27+
IntList::Cons(h, t) => h + sum(t)
28+
}
29+
}
30+
31+
// Construction uses the bare enum-factory names (Nil / Cons) emitted by
32+
// the enum codegen; qualified `Type::Variant` is exercised in the match
33+
// patterns above (the v2.4 feature under test).
34+
pub fn demo_list() -> IntList = Cons(10, Cons(20, Cons(30, Nil)));
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// issue #122 v2.4 — qualified Type::Variant patterns + recursive ADTs.
3+
import assert from "node:assert/strict";
4+
import { rank, Red, Blue, len, sum, demo_list } from "./match_enum.deno.js";
5+
6+
assert.equal(rank(Red), 1, "match expr-body, nullary qualified pattern");
7+
assert.equal(rank(Blue), 3, "match expr-body, last arm");
8+
9+
const xs = demo_list(); // Cons(10, Cons(20, Cons(30, Nil)))
10+
assert.equal(len(xs), 3, "recursive len over ADT, payload pattern");
11+
assert.equal(sum(xs), 60, "recursive sum binds payload (h, t)");
12+
13+
console.log("match_enum.harness.mjs OK");

0 commit comments

Comments
 (0)