From e0c0f75b2a06d2f1a4081136a86c34734a15fa61 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 23 May 2026 17:36:39 +0900 Subject: [PATCH] selfhost/cmd/wcc/check: flatten tagged spreads + iserror on tinfo.params (#61a) A.6.3 #61 prerequisite (additive, no consumer changes). The tagged-variant machinery (taggedvariantindex / flatvariant* / cgwidentagremap / cgmatch) is AST-keyed -- it walks N_TTAGGED.list and spread-flattens `...inner` at read time. To migrate it onto tinfo.params (#61b/c) the chain must first carry the flattened variant set + per-variant error mark, matching cstage's Type.params / Type.iserror. tinfofornode's TTAGGED arm now splices `...inner` tagged spreads into ti.params (dealias one NAMED level, require TY_TAGGED, inline its already- flattened variants in declaration order) -- mirror of cstage check.c:366-389. Each variant gets an iserror flag via varianterr (TBANG / `!`-aliased). size/align stay accounted off the surface member so ti.size is byte-identical to before; the flatten + iserror have zero readers this commit (the lone TY_TAGGED params reader, nullableptrtag, only fires on 2-variant nullable unions with no spreads). iserror rides the shared tparam struct rather than a sidecar: a cstage-mirror divergence from harec, which carries no per-variant flag (models `!T` as a STORAGE_ERROR type node, ref/harec/include/types.h:144, src/types.c:151-159). Faithful port filed as #62. Spread-only flatten (cstage check.c:373 also flattens non-spread anonymous-nested unions) is a known symmetry gap, inert in bootstrap, tracked for #61b. make test 133/133 (quiescent tree, byte-id 990-997 green). --- lib/ww/typ.ww | 9 +++++ selfhost/cmd/w6c/main.combined.ww | 54 +++++++++++++++++++++++----- selfhost/cmd/wcc/check.ww | 45 ++++++++++++++++++----- selfhost/cmd/wwdump/main.combined.ww | 54 +++++++++++++++++++++++----- 4 files changed, 135 insertions(+), 27 deletions(-) diff --git a/lib/ww/typ.ww b/lib/ww/typ.ww index a0aa6ee5..ce0033d7 100644 --- a/lib/ww/typ.ww +++ b/lib/ww/typ.ww @@ -71,6 +71,15 @@ type tfield = struct { type tparam = struct { name: str, type_: *tinfo, + // #61a: per-variant `!T` error mark for TY_TAGGED variants. + // cstage-MIRROR divergence: harec carries no per-variant flag — + // it models `!T` as a distinct STORAGE_ERROR type node + // (ref/harec/include/types.h:144, src/types.c:151-159 + // type_is_error). wwstage tinfo has no iserror field + // (check.ww TTAGGED arm), so the bit rides the shared param + // struct instead, matching cstage Type.iserror semantics. + // Faithful STORAGE_ERROR-node port filed as #62. + iserror: bool, tnext: *tparam, }; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 97226fcc..c8b9426e 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -6401,6 +6401,15 @@ type tfield = struct { type tparam = struct { name: str, type_: *tinfo, + // #61a: per-variant `!T` error mark for TY_TAGGED variants. + // cstage-MIRROR divergence: harec carries no per-variant flag — + // it models `!T` as a distinct STORAGE_ERROR type node + // (ref/harec/include/types.h:144, src/types.c:151-159 + // type_is_error). wwstage tinfo has no iserror field + // (check.ww TTAGGED arm), so the bit rides the shared param + // struct instead, matching cstage Type.iserror semantics. + // Faithful STORAGE_ERROR-node port filed as #62. + iserror: bool, tnext: *tparam, }; @@ -8427,12 +8436,19 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { r = newtype(tykind.TY_TAGGED); tinfocachebind(c.tc, n, r); // #50 / A.6.3f phase 1: populate ti.params as a tparam linked - // list (head=first source variant) in lock-step with the - // size/align accumulator. Mirrors cstage check.c:374-396 — - // same Tparam shape ww reuses across struct-fields / tuple- - // fields / fn-params / tagged-variants (sea-of-stars per rule - // 12). Phase 2 (#50b) retires cgenutil's AST-keyed - // nullableptrtag onto the chain. + // list (head=first source variant). #61a: flatten `...inner` + // tagged spreads into the chain and stamp each variant's + // iserror. Mirrors cstage check.c:366-389 — dealias one NAMED + // level, require TY_TAGGED, splice its (already-flattened) + // variants in declaration order; otherwise append the single + // variant. size/align stays accounted off the surface member + // (vt), so r.size is byte-identical to pre-#61a: the flatten + + // iserror are additive, with no #61a-stage readers (the variant + // machinery + cgwidentaggedstore/matchscrutt migrate onto the + // chain in #61b/c). Same shared Tparam shape ww reuses across + // struct-fields / tuple-fields / fn-params / tagged-variants + // (sea-of-stars per rule 12). Phase 2 (#50b) retired cgenutil's + // AST-keyed nullableptrtag onto the chain. let head: *tparam = nil; let tail: *tparam = nil; let maxsz: u64 = 0u64; @@ -8440,13 +8456,33 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { let v: *node = n.list; for (v != nil) { let vt: *tinfo = tinfofornode(c, v); - let tp: *tparam = alloc(tparam{name="", type_=vt, tnext=nil})!; - if (head == nil) { head = tp; } else { tail.tnext = tp; }; - tail = tp; 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) { + if (vu != nil) { + if (vu.kind == tykind.TY_NAMED) { vu = vu.under; }; + }; + }; + if (isspread && vu != nil && vu.kind == tykind.TY_TAGGED) { + // Spliced variants carry the inner union's + // already-stamped iserror; no re-derivation. + let src: *tparam = vu.params; + for (src != nil) { + 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 { + 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; }; + tail = tp; + }; v = v.next; }; r.params = head; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index b6cae1b6..6792fa16 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -1339,12 +1339,19 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { r = newtype(tykind.TY_TAGGED); tinfocachebind(c.tc, n, r); // #50 / A.6.3f phase 1: populate ti.params as a tparam linked - // list (head=first source variant) in lock-step with the - // size/align accumulator. Mirrors cstage check.c:374-396 — - // same Tparam shape ww reuses across struct-fields / tuple- - // fields / fn-params / tagged-variants (sea-of-stars per rule - // 12). Phase 2 (#50b) retires cgenutil's AST-keyed - // nullableptrtag onto the chain. + // list (head=first source variant). #61a: flatten `...inner` + // tagged spreads into the chain and stamp each variant's + // iserror. Mirrors cstage check.c:366-389 — dealias one NAMED + // level, require TY_TAGGED, splice its (already-flattened) + // variants in declaration order; otherwise append the single + // variant. size/align stays accounted off the surface member + // (vt), so r.size is byte-identical to pre-#61a: the flatten + + // iserror are additive, with no #61a-stage readers (the variant + // machinery + cgwidentaggedstore/matchscrutt migrate onto the + // chain in #61b/c). Same shared Tparam shape ww reuses across + // struct-fields / tuple-fields / fn-params / tagged-variants + // (sea-of-stars per rule 12). Phase 2 (#50b) retired cgenutil's + // AST-keyed nullableptrtag onto the chain. let head: *tparam = nil; let tail: *tparam = nil; let maxsz: u64 = 0u64; @@ -1352,13 +1359,33 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { let v: *node = n.list; for (v != nil) { let vt: *tinfo = tinfofornode(c, v); - let tp: *tparam = alloc(tparam{name="", type_=vt, tnext=nil})!; - if (head == nil) { head = tp; } else { tail.tnext = tp; }; - tail = tp; 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) { + if (vu != nil) { + if (vu.kind == tykind.TY_NAMED) { vu = vu.under; }; + }; + }; + if (isspread && vu != nil && vu.kind == tykind.TY_TAGGED) { + // Spliced variants carry the inner union's + // already-stamped iserror; no re-derivation. + let src: *tparam = vu.params; + for (src != nil) { + 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 { + 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; }; + tail = tp; + }; v = v.next; }; r.params = head; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index dd994544..9bf4859b 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -6401,6 +6401,15 @@ type tfield = struct { type tparam = struct { name: str, type_: *tinfo, + // #61a: per-variant `!T` error mark for TY_TAGGED variants. + // cstage-MIRROR divergence: harec carries no per-variant flag — + // it models `!T` as a distinct STORAGE_ERROR type node + // (ref/harec/include/types.h:144, src/types.c:151-159 + // type_is_error). wwstage tinfo has no iserror field + // (check.ww TTAGGED arm), so the bit rides the shared param + // struct instead, matching cstage Type.iserror semantics. + // Faithful STORAGE_ERROR-node port filed as #62. + iserror: bool, tnext: *tparam, }; @@ -8427,12 +8436,19 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { r = newtype(tykind.TY_TAGGED); tinfocachebind(c.tc, n, r); // #50 / A.6.3f phase 1: populate ti.params as a tparam linked - // list (head=first source variant) in lock-step with the - // size/align accumulator. Mirrors cstage check.c:374-396 — - // same Tparam shape ww reuses across struct-fields / tuple- - // fields / fn-params / tagged-variants (sea-of-stars per rule - // 12). Phase 2 (#50b) retires cgenutil's AST-keyed - // nullableptrtag onto the chain. + // list (head=first source variant). #61a: flatten `...inner` + // tagged spreads into the chain and stamp each variant's + // iserror. Mirrors cstage check.c:366-389 — dealias one NAMED + // level, require TY_TAGGED, splice its (already-flattened) + // variants in declaration order; otherwise append the single + // variant. size/align stays accounted off the surface member + // (vt), so r.size is byte-identical to pre-#61a: the flatten + + // iserror are additive, with no #61a-stage readers (the variant + // machinery + cgwidentaggedstore/matchscrutt migrate onto the + // chain in #61b/c). Same shared Tparam shape ww reuses across + // struct-fields / tuple-fields / fn-params / tagged-variants + // (sea-of-stars per rule 12). Phase 2 (#50b) retired cgenutil's + // AST-keyed nullableptrtag onto the chain. let head: *tparam = nil; let tail: *tparam = nil; let maxsz: u64 = 0u64; @@ -8440,13 +8456,33 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { let v: *node = n.list; for (v != nil) { let vt: *tinfo = tinfofornode(c, v); - let tp: *tparam = alloc(tparam{name="", type_=vt, tnext=nil})!; - if (head == nil) { head = tp; } else { tail.tnext = tp; }; - tail = tp; 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) { + if (vu != nil) { + if (vu.kind == tykind.TY_NAMED) { vu = vu.under; }; + }; + }; + if (isspread && vu != nil && vu.kind == tykind.TY_TAGGED) { + // Spliced variants carry the inner union's + // already-stamped iserror; no re-derivation. + let src: *tparam = vu.params; + for (src != nil) { + 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 { + 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; }; + tail = tp; + }; v = v.next; }; r.params = head;