From 329481c92001c758df7e8d030246f5aabb071129 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 6 Jun 2026 06:54:41 +0900 Subject: [PATCH] wcc_ww/check: W3 #105 nested-arrlit gate chases the alias elem type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit checkarrlitfits' nested recursion keyed on the raw elemtn kind; a named-alias element type ([2]row, row=[2]int) arrives as N_TNAME, so the inner overlong literal skipped the count+range checks and the module static-DATA route emitted silently TRUNCATED data (ken's m7c_global: DATAW 1,2,4,5 — exit-masked once the #60 read fix removed the segv; cstage loud-rejects every spelling via its typed-literal assignability net). #105: the W1 fill gate never runs on this route, severity raised post-#60. Fix: chase elemtn through resolvealias (transitive) at the recursion gate — alias spellings of any depth take the same checks as the direct shape at all four contexts funneling through the choke point (module let / local let / def / struct-field). A direct N_TARRAY passes through resolvealias unchanged, so accepted shapes are byte-identical base→tip (m7c_global_ok + exact-fit alias field/def/2lvl probed ASM-ID vs a base scratch build). The m7/m7b local overlong rows stay loud, now via the earlier count-naming checker diagnostic instead of the cgen #270-1c fatal. The out-of-range narrow inner element louds "array element out of range" exactly as the direct spelling already did on wwstage. 808_arrlit_overlong: 37 -> 50 checks (+1 accept control alias_exact_module = ken's m7c_global_ok with a byte-id cell, +4 loud flips alias_nested_{module,2lvl,def,field} pinning per-stage texts, +1 REVIEW AMENDMENT alias_nested_local pinning the m7/m7b text move — pre-fix ww was loud via the late cgen #270-1c fatal; the row reds if the diag regresses off the checker count text). 989 ratchet zero flips — no lib module-level literal trips the gate. Filed sibling, not folded: OUTER alias-of-array overlong (let g: arr = [5 elems], arr=[4]int) still ww-silent-truncates at the alias-blind call-site N_TARRAY gates; cs louds with the count text. --- selfhost/cmd/w6c/main.combined.ww | 14 +++-- selfhost/cmd/wcc/check.ww | 14 +++-- selfhost/cmd/wwdump/main.combined.ww | 14 +++-- test/wcc/808_arrlit_overlong.c | 92 +++++++++++++++++++++++++++- 4 files changed, 121 insertions(+), 13 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 7039e92f..f59bfde1 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -14423,11 +14423,17 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = { // which wwstage's untyped elements have no analog of; the // silent accept emitted corrupted DATA / smashed frames. // Recursion through the one choke point closes any depth. - // A named-alias element type ([2]row) still bypasses — the - // elemtn node is N_IDENT, not N_TARRAY — task #16. - if (elemtn != nil && elemtn.kind == nkind.N_TARRAY + // #105: a named-alias element type ([2]row) arrives as + // N_TNAME and bypassed the kind test — the module + // static-DATA emitter then silently TRUNCATED the overlong + // inner literal (exit-masked once the #60 read fix removed + // the segv). resolvealias is transitive, so alias spellings + // of any depth take the same recursion; a direct N_TARRAY + // passes through unchanged. + let eltr: *node = resolvealias(c, elemtn); + if (eltr != nil && eltr.kind == nkind.N_TARRAY && ev != nil && ev.kind == nkind.N_ARRLIT) { - checkarrlitfits(c, elemtn, ev); + checkarrlitfits(c, eltr, ev); e = e.next; continue; }; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index a6db7ca6..d0660028 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -4142,11 +4142,17 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = { // which wwstage's untyped elements have no analog of; the // silent accept emitted corrupted DATA / smashed frames. // Recursion through the one choke point closes any depth. - // A named-alias element type ([2]row) still bypasses — the - // elemtn node is N_IDENT, not N_TARRAY — task #16. - if (elemtn != nil && elemtn.kind == nkind.N_TARRAY + // #105: a named-alias element type ([2]row) arrives as + // N_TNAME and bypassed the kind test — the module + // static-DATA emitter then silently TRUNCATED the overlong + // inner literal (exit-masked once the #60 read fix removed + // the segv). resolvealias is transitive, so alias spellings + // of any depth take the same recursion; a direct N_TARRAY + // passes through unchanged. + let eltr: *node = resolvealias(c, elemtn); + if (eltr != nil && eltr.kind == nkind.N_TARRAY && ev != nil && ev.kind == nkind.N_ARRLIT) { - checkarrlitfits(c, elemtn, ev); + checkarrlitfits(c, eltr, ev); e = e.next; continue; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 0d11a8c8..81ca7894 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -14423,11 +14423,17 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = { // which wwstage's untyped elements have no analog of; the // silent accept emitted corrupted DATA / smashed frames. // Recursion through the one choke point closes any depth. - // A named-alias element type ([2]row) still bypasses — the - // elemtn node is N_IDENT, not N_TARRAY — task #16. - if (elemtn != nil && elemtn.kind == nkind.N_TARRAY + // #105: a named-alias element type ([2]row) arrives as + // N_TNAME and bypassed the kind test — the module + // static-DATA emitter then silently TRUNCATED the overlong + // inner literal (exit-masked once the #60 read fix removed + // the segv). resolvealias is transitive, so alias spellings + // of any depth take the same recursion; a direct N_TARRAY + // passes through unchanged. + let eltr: *node = resolvealias(c, elemtn); + if (eltr != nil && eltr.kind == nkind.N_TARRAY && ev != nil && ev.kind == nkind.N_ARRLIT) { - checkarrlitfits(c, elemtn, ev); + checkarrlitfits(c, eltr, ev); e = e.next; continue; }; diff --git a/test/wcc/808_arrlit_overlong.c b/test/wcc/808_arrlit_overlong.c index d2dfdff4..fb021859 100644 --- a/test/wcc/808_arrlit_overlong.c +++ b/test/wcc/808_arrlit_overlong.c @@ -23,10 +23,27 @@ * narrowed to non-array targets so repeat literals into arrays run the * same checks wwstage always ran. * + * #105 (the W3 fold): a NAMED-ALIAS element type ([2]row, row=[2]int) + * dodged wwstage's nested recursion — the choke point's inner gate + * keyed on the raw elemtn kind (N_TNAME, not N_TARRAY), so the module + * static-DATA emitter silently TRUNCATED the overlong inner literal + * (exit-masked once #60 removed the read-side segv; cstage loud- + * rejects every spelling via its typed-literal assignability net). + * Fixed by chasing elemtn through resolvealias (transitive) before + * the recursion gate — alias spellings of any depth now take the same + * count + range checks as the direct shape, at all four declaration + * contexts. The alias_nested_* rows pin the flip; alias_exact_module + * is the 0/0 byte-id control (ken's m7c_global_ok). + * * Out of scope, probed + filed separately: UNDER-long literals (no * `...`) stay accepted in both stages (Hare rejects); `[0]`/`[_]` * alen==0 sentinel conflation; wwstage assign/call-arg overlong - * acceptance (cstage already rejects those positions). + * acceptance (cstage already rejects those positions); OUTER alias- + * of-array overlong (`let g: arr = [5 elems]`, arr=[4]int) — the + * call-site N_TARRAY gates are alias-blind, ww still truncates + * (#105-sibling, filed); cstage accepting the out-of-range NESTED + * narrow element ww rejects (range net asymmetry, pre-existing on + * the direct spelling). * * accept rows | want * ----------------------------------+------ @@ -37,6 +54,7 @@ * repeat_fill [4]int = [9...] | runs, 0 * repeat_partial [4]int = [1,2...] | runs, 0 * infer_len [_]int = [1,2,3] | runs, 0 + * alias_exact_module [2]row exact | runs, 0 (#105 control) * * reject rows (build must FAIL on both drivers, and stderr must name * both counts — per-stage expected substring: cstage catches the @@ -49,6 +67,12 @@ * nested module `[2][2]int = [[1,2,3],[4,5]]` (inner overlong — * pre-fix wwstage emitted corrupted DATA: 1,2,4,5) * nested field struct{f:[2][2]int} inner overlong + * alias nested module / 2lvl / def / field — the #105 flips: same + * inner-overlong shapes spelled through `type row = [2]int` (and + * `row2 = row`); pre-fix wwstage silently truncated (module DATA) + * or smashed frames (cstage was already loud on every row) + * alias nested local — ww pre-fix loud via the LATE cgen #270-1c + * fatal; pins the text move to the checker count diag */ #include #include @@ -125,6 +149,21 @@ static const struct row rows[] = { "\treturn a.len: i32 - 3;\n" "};\n", 0 }, + + /* #105 control (ken's m7c_global_ok): alias-elem module global, + * exact fit — must keep building 0/0 byte-id through the chased + * recursion gate. `N: int` cast spelling: the bare-int nested + * accept is cstage-rejected (#17), out of this row's scope. */ + { "alias_exact_module", + "package main;\n" + "type row = [2]int;\n" + "let g: [2]row = [[1: int, 2: int], [4: int, 5: int]];\n" + "export fn main() i32 = {\n" + "\tif (g[0][0] != 1) { return 1; };\n" + "\tif (g[1][1] != 5) { return 2; };\n" + "\treturn 0;\n" + "};\n", + 0 }, }; /* Overlong literals — both stages must FAIL the build (the old accept @@ -210,6 +249,57 @@ static const struct negrow neg[] = { "\treturn 0;\n" "};\n", "not assignable", OVERLONG_3V2 }, + + /* #105 flips: alias-elem inner overlong, the four declaration + * contexts through the one (now chased) choke point. Pre-fix + * wwstage built all four silently — the module row emitted + * truncated DATA (1,2,4,5; ken's m7c_global, exit-masked since + * #60). cstage was already loud on each via assignability. */ + { "alias_nested_module", + "package main;\n" + "type row = [2]int;\n" + "let g: [2]row = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n" + "export fn main() i32 = { return 0; };\n", + "not assignable", OVERLONG_3V2 }, + + /* 2-level alias — pins the transitive chase */ + { "alias_nested_2lvl", + "package main;\n" + "type row = [2]int;\n" + "type row2 = row;\n" + "let g: [2]row2 = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n" + "export fn main() i32 = { return 0; };\n", + "not assignable", OVERLONG_3V2 }, + + { "alias_nested_def", + "package main;\n" + "type row = [2]int;\n" + "def TAB: [2]row = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n" + "export fn main() i32 = { return 0; };\n", + "not assignable", OVERLONG_3V2 }, + + /* local let — ww was ALREADY loud pre-fix, but via the cgen + * #270-1c aggregate-element fatal; the chase moves it to the + * checker count diag (ken's m7/m7b text move). This row pins the + * new text so a regression back to the late generic fatal reds. */ + { "alias_nested_local", + "package main;\n" + "type row = [2]int;\n" + "export fn main() i32 = {\n" + "\tlet g: [2]row = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n" + "\treturn 0;\n" + "};\n", + "not assignable", OVERLONG_3V2 }, + + { "alias_nested_field", + "package main;\n" + "type row = [2]int;\n" + "type s = struct { f: [2]row };\n" + "export fn main() i32 = {\n" + "\tlet v: s = s{ f = [[1: int, 2: int, 3: int], [4: int, 5: int]] };\n" + "\treturn 0;\n" + "};\n", + "not assignable", OVERLONG_3V2 }, }; static int