diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 26d91f79..daf43f20 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -14649,6 +14649,35 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = { }; }; +// checktuplearrfits — #20/#25/#26: walk a declared tuple type's +// element types (ttn.list, each N_TPARAM-wrapped → type on .lhs) +// lockstep with an N_TUPLE rhs's values (tup.list, chained directly). +// An array-literal element runs the alias-aware over-fill +// checkarrlitfits; a nested-tuple element (declared N_TTUPLE vs rhs +// N_TUPLE) recurses. Shared by checkletassign (let) and +// checkretassign (return). Mirrors cstage's element-wise +// type_assignable count-reject; no-ops on scalar elements. +fn checktuplearrfits(c: *checker, ttn: *node, tup: *node) void = { + if (ttn == nil) { return; }; + if (tup == nil) { return; }; + let dt: *node = ttn.list; + let vt: *node = tup.list; + for (dt != nil && vt != nil) { + if (vt.kind == nkind.N_ARRLIT) { + checkarrlitfits(c, dt.lhs, vt); + } else { + if (vt.kind == nkind.N_TUPLE) { + let drt: *node = resolvealias(c, unwrapbang(dt.lhs)); + if (drt != nil && drt.kind == nkind.N_TTUPLE) { + checktuplearrfits(c, drt, vt); + }; + }; + }; + dt = dt.next; + vt = vt.next; + }; +}; + // desugararrayslice — #258. The single shared lowering for the implicit // [N]T -> []T borrow. isassignable already admits an array with a defined // length into a matching []T slot (see isassignable's #258 arm); here we @@ -15005,24 +15034,16 @@ fn checkletassign(c: *checker, n: *node) void = { // #20: array-typed TUPLE element with an overlong array literal — // `let t:([2]int,i32) = ([1,2,3],5)` was silently accepted (the tuple // position wasn't wired to checkarrlitfits, unlike the direct-array - // let above). Walk declared-tuple element types vs rhs values; over- - // fill-check each (checkarrlitfits chases aliases #106, recurses - // nested arrays #251, and no-ops on non-array elements). Mirror - // cstage's tuple element-wise reject. No early return — the rest of - // checkletassign still runs for the tuple. N_TTUPLE elements wrap - // their type on .lhs (N_TPARAM chain, stamptuplebinds:311); the - // N_TUPLE rhs values chain directly on .list. + // let above). #26: the walk now lives in checktuplearrfits, which also + // recurses into a nested-tuple element (checkarrlitfits chases aliases + // #106, recurses nested arrays #251; the helper no-ops on non-array, + // non-tuple elements). Mirror cstage's tuple element-wise reject. No + // early return — the rest of checkletassign still runs for the tuple. + // N_TTUPLE elements wrap their type on .lhs (N_TPARAM chain, + // stamptuplebinds:311); the N_TUPLE rhs values chain directly on .list. if (llhs != nil && llhs.kind == nkind.N_TTUPLE && n.rhs.kind == nkind.N_TUPLE) { - let dt: *node = llhs.list; - let vt: *node = n.rhs.list; - for (dt != nil && vt != nil) { - if (vt.kind == nkind.N_ARRLIT) { - checkarrlitfits(c, dt.lhs, vt); - }; - dt = dt.next; - vt = vt.next; - }; + checktuplearrfits(c, llhs, n.rhs); }; // #25/#31: an array literal initialising a SLICE local. Re-stamp the // literal as [count]T (the slice element) so the #258 borrow's exact- diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 05eb7299..07a06efa 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -4368,6 +4368,35 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = { }; }; +// checktuplearrfits — #20/#25/#26: walk a declared tuple type's +// element types (ttn.list, each N_TPARAM-wrapped → type on .lhs) +// lockstep with an N_TUPLE rhs's values (tup.list, chained directly). +// An array-literal element runs the alias-aware over-fill +// checkarrlitfits; a nested-tuple element (declared N_TTUPLE vs rhs +// N_TUPLE) recurses. Shared by checkletassign (let) and +// checkretassign (return). Mirrors cstage's element-wise +// type_assignable count-reject; no-ops on scalar elements. +fn checktuplearrfits(c: *checker, ttn: *node, tup: *node) void = { + if (ttn == nil) { return; }; + if (tup == nil) { return; }; + let dt: *node = ttn.list; + let vt: *node = tup.list; + for (dt != nil && vt != nil) { + if (vt.kind == nkind.N_ARRLIT) { + checkarrlitfits(c, dt.lhs, vt); + } else { + if (vt.kind == nkind.N_TUPLE) { + let drt: *node = resolvealias(c, unwrapbang(dt.lhs)); + if (drt != nil && drt.kind == nkind.N_TTUPLE) { + checktuplearrfits(c, drt, vt); + }; + }; + }; + dt = dt.next; + vt = vt.next; + }; +}; + // desugararrayslice — #258. The single shared lowering for the implicit // [N]T -> []T borrow. isassignable already admits an array with a defined // length into a matching []T slot (see isassignable's #258 arm); here we @@ -4724,24 +4753,16 @@ fn checkletassign(c: *checker, n: *node) void = { // #20: array-typed TUPLE element with an overlong array literal — // `let t:([2]int,i32) = ([1,2,3],5)` was silently accepted (the tuple // position wasn't wired to checkarrlitfits, unlike the direct-array - // let above). Walk declared-tuple element types vs rhs values; over- - // fill-check each (checkarrlitfits chases aliases #106, recurses - // nested arrays #251, and no-ops on non-array elements). Mirror - // cstage's tuple element-wise reject. No early return — the rest of - // checkletassign still runs for the tuple. N_TTUPLE elements wrap - // their type on .lhs (N_TPARAM chain, stamptuplebinds:311); the - // N_TUPLE rhs values chain directly on .list. + // let above). #26: the walk now lives in checktuplearrfits, which also + // recurses into a nested-tuple element (checkarrlitfits chases aliases + // #106, recurses nested arrays #251; the helper no-ops on non-array, + // non-tuple elements). Mirror cstage's tuple element-wise reject. No + // early return — the rest of checkletassign still runs for the tuple. + // N_TTUPLE elements wrap their type on .lhs (N_TPARAM chain, + // stamptuplebinds:311); the N_TUPLE rhs values chain directly on .list. if (llhs != nil && llhs.kind == nkind.N_TTUPLE && n.rhs.kind == nkind.N_TUPLE) { - let dt: *node = llhs.list; - let vt: *node = n.rhs.list; - for (dt != nil && vt != nil) { - if (vt.kind == nkind.N_ARRLIT) { - checkarrlitfits(c, dt.lhs, vt); - }; - dt = dt.next; - vt = vt.next; - }; + checktuplearrfits(c, llhs, n.rhs); }; // #25/#31: an array literal initialising a SLICE local. Re-stamp the // literal as [count]T (the slice element) so the #258 borrow's exact- diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 67717181..866e94a4 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -14649,6 +14649,35 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = { }; }; +// checktuplearrfits — #20/#25/#26: walk a declared tuple type's +// element types (ttn.list, each N_TPARAM-wrapped → type on .lhs) +// lockstep with an N_TUPLE rhs's values (tup.list, chained directly). +// An array-literal element runs the alias-aware over-fill +// checkarrlitfits; a nested-tuple element (declared N_TTUPLE vs rhs +// N_TUPLE) recurses. Shared by checkletassign (let) and +// checkretassign (return). Mirrors cstage's element-wise +// type_assignable count-reject; no-ops on scalar elements. +fn checktuplearrfits(c: *checker, ttn: *node, tup: *node) void = { + if (ttn == nil) { return; }; + if (tup == nil) { return; }; + let dt: *node = ttn.list; + let vt: *node = tup.list; + for (dt != nil && vt != nil) { + if (vt.kind == nkind.N_ARRLIT) { + checkarrlitfits(c, dt.lhs, vt); + } else { + if (vt.kind == nkind.N_TUPLE) { + let drt: *node = resolvealias(c, unwrapbang(dt.lhs)); + if (drt != nil && drt.kind == nkind.N_TTUPLE) { + checktuplearrfits(c, drt, vt); + }; + }; + }; + dt = dt.next; + vt = vt.next; + }; +}; + // desugararrayslice — #258. The single shared lowering for the implicit // [N]T -> []T borrow. isassignable already admits an array with a defined // length into a matching []T slot (see isassignable's #258 arm); here we @@ -15005,24 +15034,16 @@ fn checkletassign(c: *checker, n: *node) void = { // #20: array-typed TUPLE element with an overlong array literal — // `let t:([2]int,i32) = ([1,2,3],5)` was silently accepted (the tuple // position wasn't wired to checkarrlitfits, unlike the direct-array - // let above). Walk declared-tuple element types vs rhs values; over- - // fill-check each (checkarrlitfits chases aliases #106, recurses - // nested arrays #251, and no-ops on non-array elements). Mirror - // cstage's tuple element-wise reject. No early return — the rest of - // checkletassign still runs for the tuple. N_TTUPLE elements wrap - // their type on .lhs (N_TPARAM chain, stamptuplebinds:311); the - // N_TUPLE rhs values chain directly on .list. + // let above). #26: the walk now lives in checktuplearrfits, which also + // recurses into a nested-tuple element (checkarrlitfits chases aliases + // #106, recurses nested arrays #251; the helper no-ops on non-array, + // non-tuple elements). Mirror cstage's tuple element-wise reject. No + // early return — the rest of checkletassign still runs for the tuple. + // N_TTUPLE elements wrap their type on .lhs (N_TPARAM chain, + // stamptuplebinds:311); the N_TUPLE rhs values chain directly on .list. if (llhs != nil && llhs.kind == nkind.N_TTUPLE && n.rhs.kind == nkind.N_TUPLE) { - let dt: *node = llhs.list; - let vt: *node = n.rhs.list; - for (dt != nil && vt != nil) { - if (vt.kind == nkind.N_ARRLIT) { - checkarrlitfits(c, dt.lhs, vt); - }; - dt = dt.next; - vt = vt.next; - }; + checktuplearrfits(c, llhs, n.rhs); }; // #25/#31: an array literal initialising a SLICE local. Re-stamp the // literal as [count]T (the slice element) so the #258 borrow's exact- diff --git a/test/wcc/832_tuple_elem_overlong.c b/test/wcc/832_tuple_elem_overlong.c index 109df811..ed7e4376 100644 --- a/test/wcc/832_tuple_elem_overlong.c +++ b/test/wcc/832_tuple_elem_overlong.c @@ -20,15 +20,17 @@ * asm; selfhost has no overlong tuple-elements, so 990-997 byte-id is * untouched. Do NOT chase message parity. * - * neg row | shape | gate - * -----------------+------------------------------------------------+-------- - * tuple_arr_over | let t:([2]int,i32)=([1,2,3],5) | b. FAIL - * tuple_nested_arr | let t:([2][3]int,i32)=([[..],[..],[..]],5) | b. FAIL + * neg row | shape | gate + * -------------------+----------------------------------------------+-------- + * tuple_arr_over | let t:([2]int,i32)=([1,2,3],5) | b. FAIL + * tuple_nested_arr | let t:([2][3]int,i32)=([[..],[..],[..]],5) | b. FAIL + * tuple_in_tuple | let t:([2]int,([2]int,i32))=([..],([1,2,3],.))| #26 FAIL * - * pos row | shape | want - * -----------------+------------------------------------------------+------ - * tuple_arr_exact | let t:([2]int,i32)=([1,2],5); t.1 | 5 - * tuple_scalar | let t:(i32,i32)=(1,2); t.1 | 2 + * pos row | shape | want + * -------------------+----------------------------------------------+------ + * tuple_arr_exact | let t:([2]int,i32)=([1,2],5); t.1 | 5 + * tuple_scalar | let t:(i32,i32)=(1,2); t.1 | 2 + * tuple_nested_exact | let t:(i32,([2]int,i32))=(9,([3,4],7)); t.0 | 9 */ #include #include @@ -72,6 +74,22 @@ static const struct row rows[] = { "\treturn t.1;\n" "};\n", 2 }, + + /* #26 positive control — a VALID nested tuple with an exact-length + * inner [2]int must NOT be over-rejected by the new recursion arm. + * Readout is the top-level scalar t.0 (=9), NOT a leaf through the + * inner tuple / array element (those hit pre-existing cgen read bugs + * that miscompile on BOTH stages — filed, byte-id-blind, see the + * tuple_arr_exact note). The point here is that checktuplearrfits + * recurses the inner ([2]int,i32), runs the count check, and lets the + * exact-length build proceed: build succeeds + program runs. */ + { "tuple_nested_exact", + "package main;\n" + "export fn main() i32 = {\n" + "\tlet t: (i32, ([2]int, i32)) = (9, ([3, 4], 7));\n" + "\treturn t.0;\n" + "};\n", + 9 }, }; /* An overlong array literal in a tuple element — both stages must FAIL the @@ -91,6 +109,13 @@ static const char *neg[] = { "([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 5);\n" "\treturn t.0[0][0]: i32;\n" "};\n", + /* tuple_in_tuple (#26) — nested tuple element; inner [2]int over- + * filled by 3. The walk must RECURSE the nested tuple (let pos). */ + "package main;\n" + "export fn main() i32 = {\n" + "\tlet t: ([2]int, ([2]int, i32)) = ([1, 2], ([3, 4, 5], 6));\n" + "\treturn t.0[0]: i32;\n" + "};\n", }; static int