wcc/check: #26 recurse over-fill walk into nested tuple element (wwstage)

extracts the shared checktuplearrfits helper (also used by #25); wwstage-only checker reject-align, cstage already louds.
This commit is contained in:
2026-06-09 15:00:03 +09:00
parent 46e8354056
commit 89f3e58458
4 changed files with 144 additions and 56 deletions

View File

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

View File

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

View File

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

View File

@@ -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 <stdio.h>
#include <stdlib.h>
@@ -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