From 6c380a53ea4186be1bc7937f035cdfec42466e7c Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 12 Jun 2026 11:21:39 +0900 Subject: [PATCH] wcc/ww: reject tuple-literal arity mismatch (checktuplearrfits) A tuple literal with the wrong arity was silently accepted and the extra/missing elements mis-stored. Mirror cstage type.c:416 + check.c:2386. --- selfhost/cmd/w6c/main.combined.ww | 25 +++++++++++++++++++++++++ selfhost/cmd/wcc/check.ww | 25 +++++++++++++++++++++++++ selfhost/cmd/wwdump/main.combined.ww | 25 +++++++++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index e9f28870..3a188784 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -15503,6 +15503,31 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = { fn checktuplearrfits(c: *checker, ttn: *node, tup: *node) void = { if (ttn == nil) { return; }; if (tup == nil) { return; }; + // #38/F2 (review item 10): a tuple literal whose arity differs from the + // declared tuple is LOUD. cstage type_assignable's tuple arm requires + // both param chains to end together (cmd/wcc/type.c:416, consumed as + // "init not assignable" at check.c:2386-2391); wwstage's isassignable + // has NO tuple arm, so an over/short literal fell to the lenient + // catch-all and the lockstep walk below silently ignored the leftovers. + // The short direction was the live miscompile (cgen stored a stale, + // never-popped register into the missing slot). Count both chains and + // reject a mismatch before the per-element fits walk. Recursion through + // this one choke point gates every nesting depth. + let dn: u64 = 0u64; + let dc: *node = ttn.list; + for (dc != nil) { dn += 1u64; dc = dc.next; }; + let vn: u64 = 0u64; + let vc: *node = tup.list; + for (vc != nil) { vn += 1u64; vc = vc.next; }; + if (dn != vn) { + cerr("tuple literal has "); + cerr(strconv.u64tos(vn, strconv.base.DEC)); + cerr(" elements but declared tuple holds "); + cerr(strconv.u64tos(dn, strconv.base.DEC)); + cerr("\n"); + c.errs += 1; + return; + }; let dt: *node = ttn.list; let vt: *node = tup.list; for (dt != nil && vt != nil) { diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index ea923d74..56518a88 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -5083,6 +5083,31 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = { fn checktuplearrfits(c: *checker, ttn: *node, tup: *node) void = { if (ttn == nil) { return; }; if (tup == nil) { return; }; + // #38/F2 (review item 10): a tuple literal whose arity differs from the + // declared tuple is LOUD. cstage type_assignable's tuple arm requires + // both param chains to end together (cmd/wcc/type.c:416, consumed as + // "init not assignable" at check.c:2386-2391); wwstage's isassignable + // has NO tuple arm, so an over/short literal fell to the lenient + // catch-all and the lockstep walk below silently ignored the leftovers. + // The short direction was the live miscompile (cgen stored a stale, + // never-popped register into the missing slot). Count both chains and + // reject a mismatch before the per-element fits walk. Recursion through + // this one choke point gates every nesting depth. + let dn: u64 = 0u64; + let dc: *node = ttn.list; + for (dc != nil) { dn += 1u64; dc = dc.next; }; + let vn: u64 = 0u64; + let vc: *node = tup.list; + for (vc != nil) { vn += 1u64; vc = vc.next; }; + if (dn != vn) { + cerr("tuple literal has "); + cerr(strconv.u64tos(vn, strconv.base.DEC)); + cerr(" elements but declared tuple holds "); + cerr(strconv.u64tos(dn, strconv.base.DEC)); + cerr("\n"); + c.errs += 1; + return; + }; let dt: *node = ttn.list; let vt: *node = tup.list; for (dt != nil && vt != nil) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 4296dc1f..725f7f61 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -15503,6 +15503,31 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = { fn checktuplearrfits(c: *checker, ttn: *node, tup: *node) void = { if (ttn == nil) { return; }; if (tup == nil) { return; }; + // #38/F2 (review item 10): a tuple literal whose arity differs from the + // declared tuple is LOUD. cstage type_assignable's tuple arm requires + // both param chains to end together (cmd/wcc/type.c:416, consumed as + // "init not assignable" at check.c:2386-2391); wwstage's isassignable + // has NO tuple arm, so an over/short literal fell to the lenient + // catch-all and the lockstep walk below silently ignored the leftovers. + // The short direction was the live miscompile (cgen stored a stale, + // never-popped register into the missing slot). Count both chains and + // reject a mismatch before the per-element fits walk. Recursion through + // this one choke point gates every nesting depth. + let dn: u64 = 0u64; + let dc: *node = ttn.list; + for (dc != nil) { dn += 1u64; dc = dc.next; }; + let vn: u64 = 0u64; + let vc: *node = tup.list; + for (vc != nil) { vn += 1u64; vc = vc.next; }; + if (dn != vn) { + cerr("tuple literal has "); + cerr(strconv.u64tos(vn, strconv.base.DEC)); + cerr(" elements but declared tuple holds "); + cerr(strconv.u64tos(dn, strconv.base.DEC)); + cerr("\n"); + c.errs += 1; + return; + }; let dt: *node = ttn.list; let vt: *node = tup.list; for (dt != nil && vt != nil) {