wwstage: expand spread variants in match check + size them off flattened members (#209)

The wwstage checker walked a match's raw AST variant list and never expanded a ...inner spread variant, so it rejected fmt's match over field = (...formattable | *mods) ('not a variant of scrutinee'). cstage's resolve_type flattens the spread at type-build. Mirror that in the AST exhaustiveness walk (casevariantin + a recursive checkvariantcovered): when a variant resolves to N_TTAGGED via a spread, recurse into its members. Additive + spread-gated -- typeeqast / casevariantpairmatch (#13) / casecovers untouched, so non-spread matches and 990-997 byte-id are unaffected. Also size a spread N_TTAGGED off each flattened member (mirror cstage check.c), dropping the inner union tag word (field 40B to 32B). Closes the #209 CHECKER reject; full fmt-byte-id still awaits cgen cluster #226 (io.read nominal-remap) + #227 (spread-widen ABI), so fmt tests stay cstage-only with retargeted comments. Adds test 792; regenerates w6c/wwdump combined.ww.
This commit is contained in:
2026-05-31 20:31:40 +09:00
parent 497f1fa0d3
commit 3d44f742a0
10 changed files with 471 additions and 113 deletions

View File

@@ -11943,10 +11943,6 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
let v: *node = n.list;
for (v != nil) {
let vt: *tinfo = tinfofornode(c, v);
if (vt != nil) {
if (vt.size > maxsz) { maxsz = vt.size; };
if (vt.align > al) { al = vt.align; };
};
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
let vu: *tinfo = vt;
if (isspread) {
@@ -11955,16 +11951,31 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
};
};
if (isspread && vu != nil && vu.kind == tykind.TY_TAGGED) {
// Spliced variants carry the inner union's
// already-stamped iserror; no re-derivation.
// #209: a `...inner` spread's PAYLOAD is its
// members, not the whole inner union — size/align
// off each spliced member (cstage check.c:660-667
// st->size), NOT the surface member vt (which would
// over-size by the inner union's own 8B tag word and
// desync the `field` slot from cstage's). Spliced
// variants carry the inner union's already-stamped
// iserror; no re-derivation.
let src: *tparam = vu.params;
for (src != nil) {
let st: *tinfo = src.type_;
if (st != nil) {
if (st.size > maxsz) { maxsz = st.size; };
if (st.align > al) { al = st.align; };
};
let tp: *tparam = alloc(tparam{name="", type_=src.type_, iserror=src.iserror, tnext=nil})!;
if (head == nil) { head = tp; } else { tail.tnext = tp; };
tail = tp;
src = src.tnext;
};
} else {
if (vt != nil) {
if (vt.size > maxsz) { maxsz = vt.size; };
if (vt.align > al) { al = vt.align; };
};
let ve: bool = varianterr(c, v);
let tp: *tparam = alloc(tparam{name="", type_=vt, iserror=ve, tnext=nil})!;
if (head == nil) { head = tp; } else { tail.tnext = tp; };
@@ -13509,9 +13520,30 @@ fn errmatchvariant(c: *checker, n: *node, vname: *node) void = {
// casevariantin — true iff `pat` (a `case T` pattern, including
// each alt of a multi-pattern) names a variant of the tagged
// union `tagged`.
fn casevariantin(tagged: *node, pat: *node, unionmod: str) bool = {
//
// #209: a `...inner` spread variant (v.op == TK_ELLIPSIS, parse.ww:284)
// is NOT a variant in itself — its inner tagged union's MEMBERS are. The
// AST `tagged.list` keeps the spread unexpanded (only the #61a tinfo
// build flattens it), so recurse into the inner union's members,
// attributing them the inner union's defining module. Mirrors cstage's
// resolve_type flatten (cmd/wcc/check.c:651-674) at the AST layer; the
// cgen side already dispatches off the flattened tinfo.params (#61a).
fn casevariantin(c: *checker, tagged: *node, pat: *node, unionmod: str) bool = {
let v: *node = tagged.list;
for (v != nil) {
if (v.op == tkind.TK_ELLIPSIS) {
let inner: *node = resolvealias(c, unwrapbang(v));
if (inner != nil) {
if (inner.kind == nkind.N_TTAGGED) {
if (casevariantin(c, inner, pat,
taggeddefmod(c, v))) {
return true;
};
v = v.next;
continue;
};
};
};
if (typeeqast(v, pat)) { return true; };
if (casevariantpairmatch(v, pat, unionmod)) { return true; };
v = v.next;
@@ -13550,12 +13582,12 @@ fn checkmatchexhaust(c: *checker, n: *node) void = {
let cs0: *node = n.list;
for (cs0 != nil) {
if (cs0.lhs != nil) {
if (!casevariantin(u, cs0.lhs, unionmod)) {
if (!casevariantin(c, u, cs0.lhs, unionmod)) {
errbadcase(c, cs0.lhs);
};
let alt: *node = cs0.list;
for (alt != nil) {
if (!casevariantin(u, alt, unionmod)) {
if (!casevariantin(c, u, alt, unionmod)) {
errbadcase(c, alt);
};
alt = alt.next;
@@ -13572,21 +13604,46 @@ fn checkmatchexhaust(c: *checker, n: *node) void = {
// For each variant of u, look for a covering case.
let v: *node = u.list;
for (v != nil) {
let covered: bool = false;
let cs2: *node = n.list;
for (cs2 != nil) {
if (casecovers(c, cs2, v, unionmod)) {
covered = true;
cs2 = nil;
} else {
cs2 = cs2.next;
};
};
if (!covered) { errmatchvariant(c, n, v); };
checkvariantcovered(c, n, v, unionmod);
v = v.next;
};
};
// checkvariantcovered — emit "variant not handled" unless some case arm
// covers `v`. #209: a `...inner` spread variant expands to its inner
// union's members (each attributed the inner union's defining module),
// so the phantom spread node is never itself reported uncovered — its
// members are checked instead. Mirrors the casevariantin spread recursion
// + cstage's flattened u->params exhaustiveness walk (cmd/wcc/check.c:
// 1648-1669). Leaf variants keep their NODE so errmatchvariant names them.
fn checkvariantcovered(c: *checker, n: *node, v: *node, unionmod: str) void = {
if (v.op == tkind.TK_ELLIPSIS) {
let inner: *node = resolvealias(c, unwrapbang(v));
if (inner != nil) {
if (inner.kind == nkind.N_TTAGGED) {
let im: str = taggeddefmod(c, v);
let m: *node = inner.list;
for (m != nil) {
checkvariantcovered(c, n, m, im);
m = m.next;
};
return;
};
};
};
let covered: bool = false;
let cs2: *node = n.list;
for (cs2 != nil) {
if (casecovers(c, cs2, v, unionmod)) {
covered = true;
cs2 = nil;
} else {
cs2 = cs2.next;
};
};
if (!covered) { errmatchvariant(c, n, v); };
};
// ---- let init / return assignability --------------------------------
//
// AST-level approximation: when we can infer src's type and dst is
@@ -13827,7 +13884,7 @@ fn checkisas(c: *checker, n: *node) void = {
if (utinfo != nil) { if (wanttinfo != nil) {
if (flatvariantidxt(utinfo, wanttinfo) >= 0) { return; };
}; };
if (casevariantin(u, want, taggeddefmod(c, st))) { return; };
if (casevariantin(c, u, want, taggeddefmod(c, st))) { return; };
os.write(2, "is/as: not a variant of operand".ptr, 31u64);
if (want.kind == nkind.N_TNAME) {
os.write(2, " (".ptr, 2u64);