wcc_ww/check: c3 spread-member + &len/cap base walks chase the full alias chain

F2a batch-4 c3, the check.ww behavior pair (exact set). Both cs twins
ALREADY full-chase at this SHA — ww-only align-up, no fuse:

B1 (check.ww:2038 census line; peel was at :2041 post-c1): the
`...inner` spread member peeled ONE level, so a 2-level-alias inner
union escaped the TY_TAGGED splice test and rode as a SURFACE member —
the outer box sized off the inner union's own header (probe sp_alias2:
runtime 0/0 BOTH but byte-DIVERGE, ww frame $48 vs $32). cs twin
chases (check.c:755 type_chase_named, the spec's :660-667 cite moved).
Fix: vu = tichase(vu) under the isspread gate, mirroring cs's
`spread ? type_chase_named(vt) : vt`. sp_alias2 graduates 0/0 byte-id;
sp_alias1 1-level control held throughout.

B2 (check.ww:2273/:2275): the &x.len/&x.cap base walk hand-peeled
NAMED->PTR->NAMED one level per hop — 2-level alias bases fell out of
the slice/str detect and took the generic *opt typing. cs twin chases
both hops (check.c:1198-1201). Fix: tichase per hop, exact cs mirror.

B2 ROUTE-TRACE (flagged, not edited): the spec's "post 0/0" cell for
amplen2 is NOT achievable inside this grant — OBSERVED pre-fix that
ww dies LATER at the cgen ADDRESS tail ("unsupported address-of
shape") for ALL alias bases 1+ LEVEL (cs runs 0): task #96 (ken
b4-oracle), a cgenexpr.ww site outside c3's check.ww set. The chase
here aligns the stamped type only (latent until #96); rows pinned
LOUD-HOLD via the new K_RUN_CS_WWERR kind (cs builds+runs; the ww
build must keep FAILING loud with the pinned diagnostic — a silent
ww accept-and-run is the regression the bound row exists to catch),
citing #96; they graduate K_RUN with it. Also OBSERVED: the let-init
spelling (`let s: sl1 = [1,2,3]`) is blocked further upstream by the
rows use the param route.

Pin rows (944): sp_alias1 K_RUN hold, sp_alias2 K_RUN graduation,
amplen_plain K_RUN control hold, amplen1/amplen2/ampcap2
K_RUN_CS_WWERR LOUD-HOLD bound on #96 (ww leg asserts "unsupported
address-of shape"). 944: 196/196.

Corpus: five-mains NEUTRAL vs base on identical inputs, cs==ww holds
(both clusters zero-in-corpus). combined.ww regens ride along.
This commit is contained in:
2026-06-06 00:12:50 +09:00
parent 4e174099bd
commit 10d886906f
4 changed files with 145 additions and 25 deletions

View File

@@ -12308,11 +12308,11 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
if (circularnamed(c, vt, v)) { vt = c.tc.tyerr; };
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; };
};
};
// Full chase (F2a batch-4 c3-B1): the single peel left a
// 2-level-alias inner union a SURFACE member — the box
// sized off the inner union's own header, not its
// spliced variants (cs twin check.c:755 chases).
if (isspread) { vu = tichase(vu); };
if (isspread && vu != nil && vu.kind == tykind.TY_TAGGED) {
// #209: a `...inner` spread's PAYLOAD is its
// members, not the whole inner union — size/align
@@ -12544,10 +12544,17 @@ fn unoptype(c: *checker, e: *node) *node = {
if (streq(fld, "len") || streq(fld, "cap")) {
let base: *node = e.lhs.lhs;
if (base != nil && base.type_ != nil) {
let bu: *tinfo = base.type_: *tinfo;
if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
// Full chase per hop (F2a batch-4 c3-B2):
// the one-peel-per-hop walk lost 2-level
// alias bases out of the slice/str detect
// (cs twin check.c:1198-1201 chases both
// hops). NOTE the ww cgen ADDRESS tail
// stays loud for ALL alias bases (task
// #96) — this aligns the stamped type
// only; rows graduate with #96.
let bu: *tinfo = tichase(base.type_: *tinfo);
if (bu != nil && bu.kind == tykind.TY_PTR) { bu = bu.sub; };
if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
bu = tichase(bu);
if (bu != nil) {
if (bu.kind == tykind.TY_SLICE || bu.kind == tykind.TY_STR) {
let pp: *node = newnode(nkind.N_TPTR, "", 0, 0);

View File

@@ -2036,11 +2036,11 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
if (circularnamed(c, vt, v)) { vt = c.tc.tyerr; };
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; };
};
};
// Full chase (F2a batch-4 c3-B1): the single peel left a
// 2-level-alias inner union a SURFACE member — the box
// sized off the inner union's own header, not its
// spliced variants (cs twin check.c:755 chases).
if (isspread) { vu = tichase(vu); };
if (isspread && vu != nil && vu.kind == tykind.TY_TAGGED) {
// #209: a `...inner` spread's PAYLOAD is its
// members, not the whole inner union — size/align
@@ -2272,10 +2272,17 @@ fn unoptype(c: *checker, e: *node) *node = {
if (streq(fld, "len") || streq(fld, "cap")) {
let base: *node = e.lhs.lhs;
if (base != nil && base.type_ != nil) {
let bu: *tinfo = base.type_: *tinfo;
if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
// Full chase per hop (F2a batch-4 c3-B2):
// the one-peel-per-hop walk lost 2-level
// alias bases out of the slice/str detect
// (cs twin check.c:1198-1201 chases both
// hops). NOTE the ww cgen ADDRESS tail
// stays loud for ALL alias bases (task
// #96) — this aligns the stamped type
// only; rows graduate with #96.
let bu: *tinfo = tichase(base.type_: *tinfo);
if (bu != nil && bu.kind == tykind.TY_PTR) { bu = bu.sub; };
if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
bu = tichase(bu);
if (bu != nil) {
if (bu.kind == tykind.TY_SLICE || bu.kind == tykind.TY_STR) {
let pp: *node = newnode(nkind.N_TPTR, "", 0, 0);

View File

@@ -12308,11 +12308,11 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
if (circularnamed(c, vt, v)) { vt = c.tc.tyerr; };
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; };
};
};
// Full chase (F2a batch-4 c3-B1): the single peel left a
// 2-level-alias inner union a SURFACE member — the box
// sized off the inner union's own header, not its
// spliced variants (cs twin check.c:755 chases).
if (isspread) { vu = tichase(vu); };
if (isspread && vu != nil && vu.kind == tykind.TY_TAGGED) {
// #209: a `...inner` spread's PAYLOAD is its
// members, not the whole inner union — size/align
@@ -12544,10 +12544,17 @@ fn unoptype(c: *checker, e: *node) *node = {
if (streq(fld, "len") || streq(fld, "cap")) {
let base: *node = e.lhs.lhs;
if (base != nil && base.type_ != nil) {
let bu: *tinfo = base.type_: *tinfo;
if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
// Full chase per hop (F2a batch-4 c3-B2):
// the one-peel-per-hop walk lost 2-level
// alias bases out of the slice/str detect
// (cs twin check.c:1198-1201 chases both
// hops). NOTE the ww cgen ADDRESS tail
// stays loud for ALL alias bases (task
// #96) — this aligns the stamped type
// only; rows graduate with #96.
let bu: *tinfo = tichase(base.type_: *tinfo);
if (bu != nil && bu.kind == tykind.TY_PTR) { bu = bu.sub; };
if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
bu = tichase(bu);
if (bu != nil) {
if (bu.kind == tykind.TY_SLICE || bu.kind == tykind.TY_STR) {
let pp: *node = newnode(nkind.N_TPTR, "", 0, 0);

View File

@@ -61,7 +61,11 @@
* K_RUN rows also assert cstage/wwstage asm byte-id (acceptance
* graduations land byte-identical — F0's accept side was wwstage).
* K_RUN_CS / K_BUILDERR_CS rows run the cstage driver only, each
* citing the task that graduates them. binop_alias_vs_alias is
* citing the task that graduates them. K_RUN_CS_WWERR rows also run
* the wwstage driver expecting a LOUD build failure (the bound row's
* job is preventing a silent ww regression while the leg is parked);
* K_RUN_NOID rows run both but waive the byte-id cell, citing the
* blocking task at the row. binop_alias_vs_alias is
* CS-only loud: the wwstage checker stays silent on typed mismatches
* by design (5-lite discipline) — cs pins the harec-parity reject.
* NNN<950, self-contained (/tmp, no imports) — rule-14's selfhost-
@@ -105,6 +109,9 @@ slurp_eq(const char *a, const char *b)
#define K_BUILDERR_CS 3 /* cstage-only loud (ww silent by design) */
#define K_RUN_NOID 4 /* build+run BOTH, exit==want, byte-id
* SKIPPED — cite the blocking task */
#define K_RUN_CS_WWERR 5 /* LOUD-HOLD bound row: cstage build+run,
* exit==want; WWSTAGE build must FAIL
* with experr — cite the blocking task */
struct row { const char *label; const char *src; int want;
int kind; const char *experr; };
@@ -1018,6 +1025,87 @@ static const struct row rows[] = {
" return 1;\n"
"};\n", 0, K_BUILDERR,
"bare source structurally matches >=2 NAMED variants" },
/* ---- F2a batch-4 c3: check.ww behavior pair (ww-only align-up;
* both cs twins already full-chase: spread check.c:755, &len/cap
* check.c:1198-1201). */
{ "sp_alias1", /* 1-level spread control — held throughout */
"package main;\n"
"type inner = (i64 | str);\n"
"type outer = (...inner | void);\n"
"export fn main() i32 = {\n"
" let v: outer = 5: i64;\n"
" if (v is i64) { return 0; };\n"
" return 1;\n"
"};\n", 0, K_RUN, NULL },
{ "sp_alias2", /* 2-level-alias inner union spread into outer
* tagged: pre = runtime 0/0 BUT byte-DIVERGE (ww
* sized the box off the SURFACE member — $48 vs $32
* frame); post = 0/0 byte-id (spliced). */
"package main;\n"
"type inner = (i64 | str);\n"
"type inner2 = inner;\n"
"type outer = (...inner2 | void);\n"
"export fn main() i32 = {\n"
" let v: outer = 5: i64;\n"
" if (v is i64) { return 0; };\n"
" return 1;\n"
"};\n", 0, K_RUN, NULL },
{ "amplen_plain", /* &s.len plain []i64 base control */
"package main;\n"
"export fn main() i32 = {\n"
" let s: []i64 = [1, 2, 3];\n"
" let p: *i64 = &s.len;\n"
" if (*p == 3) { return 0; };\n"
" return 1;\n"
"};\n", 0, K_RUN, NULL },
/* &len/cap through alias bases (param route — the let-init
* spelling is blocked upstream by the #83-documented "let: not
* assignable" acceptance divergence): the c3-B2 chase aligns the
* CHECKER stamp to cs, but the ww cgen ADDRESS tail stays loud
* ("unsupported address-of shape") for ALL alias bases 1+ level —
* task #96 (ken b4-oracle), outside check.ww's grant. Rows
* graduate K_RUN when #96 lands. */
{ "amplen1_bound96",
"package main;\n"
"type sl1 = []i64;\n"
"fn check(s: sl1) i32 = {\n"
" let p: *i64 = &s.len;\n"
" if (*p == 3) { return 0; };\n"
" return 1;\n"
"};\n"
"export fn main() i32 = {\n"
" let b: []i64 = [1, 2, 3];\n"
" return check(b);\n"
"};\n", 0, K_RUN_CS_WWERR,
"unsupported address-of shape" }, /* ww: task #96 */
{ "amplen2_bound96",
"package main;\n"
"type sl1 = []i64;\n"
"type sl2 = sl1;\n"
"fn check(s: sl2) i32 = {\n"
" let p: *i64 = &s.len;\n"
" if (*p == 3) { return 0; };\n"
" return 1;\n"
"};\n"
"export fn main() i32 = {\n"
" let b: []i64 = [1, 2, 3];\n"
" return check(b);\n"
"};\n", 0, K_RUN_CS_WWERR,
"unsupported address-of shape" }, /* ww: task #96 */
{ "ampcap2_bound96",
"package main;\n"
"type sl1 = []i64;\n"
"type sl2 = sl1;\n"
"fn check(s: sl2) i32 = {\n"
" let p: *i64 = &s.cap;\n"
" if (*p == 3) { return 0; };\n"
" return 1;\n"
"};\n"
"export fn main() i32 = {\n"
" let b: []i64 = [1, 2, 3];\n"
" return check(b);\n"
"};\n", 0, K_RUN_CS_WWERR,
"unsupported address-of shape" }, /* ww: task #96 */
/* BOUND expected-state row (#199α + #90): untyped literal into a
* NESTED-tagged alias-wrapped variant. cs CHECKER loud-rejects —
* the #199α ww-stricter no-transitive-drill rule (type.c:316-324);
@@ -1207,6 +1295,17 @@ main(void)
if (rows[i].kind == K_RUN_CS
|| rows[i].kind == K_BUILDERR_CS)
continue; /* K_RUN_NOID: ww half RUNS */
if (rows[i].kind == K_RUN_CS_WWERR) {
/* LOUD-HOLD: the parked ww leg must keep
* failing LOUD with experr — a silent ww
* accept-and-run is the regression this
* row exists to catch (cite at row). */
struct row tmp = rows[i];
tmp.kind = K_BUILDERR;
total++;
if (run_driver(wdrv, &tmp, i) != 0) fail++;
continue;
}
total++;
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
}