From 184afa32430a2814b0fb3f6201f8a1ea9db34cf6 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 17:15:41 +0900 Subject: [PATCH] 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. --- internal/wwfixture/types.ww | 8 +- lib/ww/syntax/expr.ww | 11 ++ selfhost/cmd/wcc/cgenexpr.ww | 103 ++++++++++++------ selfhost/cmd/wcc/check.ww | 8 ++ .../wcc/data/r700_match_multi_pattern/case.ww | 27 +++++ 5 files changed, 120 insertions(+), 37 deletions(-) create mode 100644 test/wcc/data/r700_match_multi_pattern/case.ww diff --git a/internal/wwfixture/types.ww b/internal/wwfixture/types.ww index 75cd07f2..fe172a38 100644 --- a/internal/wwfixture/types.ww +++ b/internal/wwfixture/types.ww @@ -1,13 +1,13 @@ package wwfixture; def protocolversion: i32 = 1; -def corpuscount: i32 = 1731; +def corpuscount: i32 = 1732; def errorcount: i32 = 343; def compilecount: i32 = 22; def runcount: i32 = 209; -def runexitcount: i32 = 1157; -def nativecount: i32 = 3462; -def corpushash: str = "6786916caba20c07c47eaaa0507dd2707fd162b708904b6e72187afb02233bf9"; +def runexitcount: i32 = 1158; +def nativecount: i32 = 3464; +def corpushash: str = "df3862c647aae744b549089ab4a180b36371fe898f5996c13099f52da203265e"; type directive = enum i32 { ERROR = 0, diff --git a/lib/ww/syntax/expr.ww b/lib/ww/syntax/expr.ww index 228d4bd7..320b8a14 100644 --- a/lib/ww/syntax/expr.ww +++ b/lib/ww/syntax/expr.ww @@ -205,6 +205,17 @@ fn parseprimary(p: *parser) *node = { mc.lhs = parsetype(p); } else { if (p.curkind != tkind.TK_FATARROW) { mc.lhs = parsetype(p); + // `case T1 | T2 | ... =>`: extra types chain + // through mc.list. Binding arms stay single-type + // (a binding over a multi-pattern would need the + // union type) — mirrors cmd/wcc/parse.c:643. + let alttail: *node = nil; + for (accepttok(p, tkind.TK_PIPE)) { + let more: *node = parsetype(p); + if (mc.list == nil) { mc.list = more; } + else { alttail.next = more; }; + alttail = more; + }; };}; expecttok(p, tkind.TK_FATARROW, "expected '=>' in match arm"); mc.body = parsestmt(p); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index b875ba2b..eb76d430 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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. diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index d800a86f..fdf17201 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -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; diff --git a/test/wcc/data/r700_match_multi_pattern/case.ww b/test/wcc/data/r700_match_multi_pattern/case.ww new file mode 100644 index 00000000..8cd2c9f2 --- /dev/null +++ b/test/wcc/data/r700_match_multi_pattern/case.ww @@ -0,0 +1,27 @@ +//ww:run-exit 18 +// Migrated from 700_e2e row 72. +package main; +fn pick(n: i64) (i64 | i32 | u32) = { + if (n < 0) { return 1: i32; }; + if (n == 0) { return 2: u32; }; + return n; +}; +fn main() i32 = { + let r1: (i64 | i32 | u32) = pick(0); + let r2: (i64 | i32 | u32) = pick(-1); + let r3: (i64 | i32 | u32) = pick(7); + let acc: i32 = 0; + match (r1) { + case let v: i64 => acc += 100; + case i32 | u32 => acc += 1; + }; + match (r2) { + case let v: i64 => acc += 100; + case i32 | u32 => acc += 10; + }; + match (r3) { + case let v: i64 => acc += v: i32; + case i32 | u32 => acc += 100; + }; + return acc; +};