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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user