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).
This commit is contained in:
2026-05-23 17:36:39 +09:00
parent 38d6eb667c
commit e0c0f75b2a
4 changed files with 135 additions and 27 deletions

View File

@@ -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,
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;