syntax+w6c: multi-type match patterns in wwstage (align up to cstage)

`case T1 | T2 =>` (binding-less; cstage parse.c:635-650 chains extra
types through cs->list, bindings stay single-type by design). Three
wwstage layers, no AST change (node.list already exists):
- parser (expr.ww): pipe loop after the non-let arm's first
  parsetype;
- checker (check.ww): resolvewalk N_MCASE now walks n.list so each
  alt gets its type_ stamp (exhaustiveness/casecovers were already
  alt-aware, built ahead of the parser);
- cgen (cgenexpr.ww): the single-pattern want-computation moves
  VERBATIM into matcharmwant; a cs.list arm emits CMPQ/JE per alt
  funneling into one match_body label, mirroring cgen.c:11118-11141
  incl. label mint order and the tag<0 clamp. Nullable arms keep
  ignoring alts (both stages).

Graduates the held e2e row (pin 1732/1158/3464); byte-identical on
the repro and the single-pattern control.
This commit is contained in:
2026-08-08 17:15:41 +09:00
parent bbdd8bede6
commit 184afa3243
5 changed files with 120 additions and 37 deletions

View File

@@ -3090,6 +3090,41 @@ fn cgslice(c: *cgen, n: *syntax.node) void = {
};
};
// matcharmwant — variant tag a non-nullable match pattern selects on
// the scrutinee's tagged union; 0 when unresolvable (cstage's tag<0
// clamp, cmd/w6c/cgen.c:11124). Serves the single-pattern arm and each
// `case T1 | T2` alt identically.
fn matcharmwant(c: *cgen, scrutt: *syntax.node, pat: *syntax.node) i32 = {
let want: i32 = 0;
if (scrutt != nil) {
// #67: gate on the stamped tinfo, not the node kind
// — matchscrutt now returns the scrutinee node itself
// for an N_DOT field (its .type_ is the tagged tinfo)
// rather than the resolved N_TTAGGED node.
if (istaggedtype(c, scrutt)) {
let r: i32 = -1;
let pattype: *syntax.tinfo = pat.type_: *syntax.tinfo;
if (pattype != nil) {
// #179: kind-agnostic dispatch on the resolved
// tinfo. Cstage cg_tag_for_variant works on
// Type, so N_TPTR / N_TFN / N_TPTR(N_TFN) case-
// patterns all reach typeeq; the prior pat.kind
// gate dropped them to r=-1 → tag 0 collapse.
// Slice arm still goes through flatslicevariantidx
// (task #19 untyped-elem fallback when typeeq
// can't match a (scalar | []T) shape).
if (syntax.typeisslice(pattype)) {
r = flatslicevariantidx(c, scrutt, pat.lhs);
} else {
r = flatvariantidxt(scrutt.type_: *syntax.tinfo, pattype, false);
};
};
if (r >= 0) { want = r; };
};
};
return want;
};
fn cgmatch(c: *cgen, n: *syntax.node) void = {
// match (e) { case let v: T => stmt; ... }
//
@@ -3326,42 +3361,44 @@ fn cgmatch(c: *cgen, n: *syntax.node) void = {
emitline(nxt);
emitline("\n");
} else {
let want: i32 = 0;
if (scrutt != nil) {
// #67: gate on the stamped tinfo, not the node kind
// — matchscrutt now returns the scrutinee node itself
// for an N_DOT field (its .type_ is the tagged tinfo)
// rather than the resolved N_TTAGGED node.
if (istaggedtype(c, scrutt)) {
let r: i32 = -1;
let pattype: *syntax.tinfo = pat.type_: *syntax.tinfo;
if (pattype != nil) {
// #179: kind-agnostic dispatch on the resolved
// tinfo. Cstage cg_tag_for_variant works on
// Type, so N_TPTR / N_TFN / N_TPTR(N_TFN) case-
// patterns all reach typeeq; the prior pat.kind
// gate dropped them to r=-1 → tag 0 collapse.
// Slice arm still goes through flatslicevariantidx
// (task #19 untyped-elem fallback when typeeq
// can't match a (scalar | []T) shape).
if (syntax.typeisslice(pattype)) {
r = flatslicevariantidx(c, scrutt, pat.lhs);
} else {
r = flatvariantidxt(scrutt.type_: *syntax.tinfo, pattype, false);
};
};
if (r >= 0) { want = r; };
};
};
let want: i32 = matcharmwant(c, scrutt, pat);
emitline("\tMOVQ\t");
emitoff(scrutoff: i64);
emitline("(BP), AX\n");
emitline("\tCMPQ\t$");
emitint(want: i64);
emitline(", AX\n");
emitline("\tJNE\t");
emitline(nxt);
emitline("\n");
if (cs.list != nil) {
// Multi-pattern `case T1 | T2 =>`: any
// matching alt funnels into one body
// label. Mirrors cmd/w6c/cgen.c:11118.
let bodyl: str = mklabel(c, "match_body");
emitline("\tCMPQ\t$");
emitint(want: i64);
emitline(", AX\n");
emitline("\tJE\t");
emitline(bodyl);
emitline("\n");
let alt: *syntax.node = cs.list;
for (alt != nil) {
let awant: i32 = matcharmwant(c, scrutt, alt);
emitline("\tCMPQ\t$");
emitint(awant: i64);
emitline(", AX\n");
emitline("\tJE\t");
emitline(bodyl);
emitline("\n");
alt = alt.next;
};
emitline("\tJMP\t");
emitline(nxt);
emitline("\n");
emitlabel(bodyl);
} else {
emitline("\tCMPQ\t$");
emitint(want: i64);
emitline(", AX\n");
emitline("\tJNE\t");
emitline(nxt);
emitline("\n");
};
};
};
// Bind `let v: T` from the slot, if requested.

View File

@@ -671,6 +671,14 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
// Mirrors cmd/wcc/check.c's newscope/saved-restore around cstmt.
if (k == syntax.nkind.N_MCASE) {
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
// `case T1 | T2` alts ride n.list; each needs its type_
// stamp or cgmatch's tag lookup collapses to 0. Mirrors
// cmd/wcc/check.c:2140 (per-alt resolve).
let alt: *syntax.node = n.list;
for (alt != nil) {
resolvewalk(c, alt);
alt = alt.next;
};
let outer: *syntax.scope = c.cur;
c.cur = syntax.newscope(outer);
let nm: str = n.str;