wcc/check: #24 reject composite-element tuple (array/struct/tuple), declared+inferred, both stages

A tuple whose element chases to TY_ARRAY/STRUCT/TUPLE (>8B) silently
miscompiled both stages: t.0[i] read segfaulted and construction dropped
the payload into the 8B slot. Reject the type at resolution (DISP-B);
faithful inline layout deferred to #60. cstage resolve_type N_TTUPLE
(declared) + N_TUPLE expr (inferred literal, was a cstage-only silent
miscompile + cs!=ww asymmetry); wwstage tinfofornode covers both.
test/wcc/832 + 941 migrated.
This commit is contained in:
2026-06-09 17:29:11 +09:00
parent f1cd0a3555
commit 785fe342fa
6 changed files with 144 additions and 33 deletions

View File

@@ -20,6 +20,17 @@
* asm; selfhost has no overlong tuple-elements, so 990-997 byte-id is
* untouched. Do NOT chase message parity.
*
* #24 (DISP-B broad reject, rob spec .ai/rob-24-spec.md): a tuple whose
* ELEMENT is a composite (array / struct / nested-tuple >8B) cannot ride the
* 8B cursor slot (#60 layout) — it silently DROPS on construction and SEGVs
* on the t.N[i] read. Both stages now REJECT such a type at N_TTUPLE
* resolution (kind ∈ {TY_ARRAY, TY_STRUCT, TY_TUPLE} after TY_NAMED chase),
* converting two silent miscompiles into one loud checker error. This FLIPS
* the former tuple_arr_exact / tuple_nested_exact positive controls to the
* neg table (their types are now outlawed) and adds slice/str/tagged-element
* positive controls proving DISP-B does NOT over-reject the inline-header
* kinds. Full inline support deferred to task #60 / DISP-A.
*
* neg row | shape | gate
* -------------------+----------------------------------------------+--------
* tuple_arr_over | let t:([2]int,i32)=([1,2,3],5) | b. FAIL
@@ -27,12 +38,16 @@
* tuple_in_tuple | let t:([2]int,([2]int,i32))=([..],([1,2,3],.))| #26 FAIL
* tuple_return_over | fn()([2]int,i32){return([1,2,3],5)} | #25 FAIL
* tuple_return_nested| fn()([2]int,([2]int,i32)){return(..,([..3],.))| #25 FAIL
* tuple_arr_exact | let t:([2]int,i32)=([1,2],5) | #24 FAIL
* tuple_nested_exact | let t:(i32,([2]int,i32))=(9,([3,4],7)) | #24 FAIL
* tuple_struct_elem | type P=struct{x:int}; let t:(P,i32)=(P{x=1},5)| #24 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
* tuple_nested_exact | let t:(i32,([2]int,i32))=(9,([3,4],7)); t.0 | 9
* tuple_slice_elem | let t:([]u8,i32)=(a,5); t.1 | 5
* tuple_str_elem | let t:(str,i32)=("hi",7); t.1 | 7
* tuple_tagged_elem | let t:((void|size),i32)=(3,9); t.1 | 9
*/
#include <stdio.h>
#include <stdlib.h>
@@ -53,22 +68,7 @@ runwait(const char *cmd)
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
/* exact-length array element in a tuple still ACCEPTS, builds + runs.
* Readout is the scalar t.1 (=5), NOT t.0[1]: indexing an array
* element THROUGH a tuple is a separate pre-existing cgen read bug
* that segfaults on BOTH stages (w6c byte-identical, so not a #20
* regression — filed). The point of this control is that the #20
* over-fill walk does NOT over-reject the valid exact-length tuple-
* with-array-element: the build must succeed and the program run. */
{ "tuple_arr_exact",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: ([2]int, i32) = ([1, 2], 5);\n"
"\treturn t.1;\n"
"};\n",
5 },
/* a scalar-only tuple has no array element — the #20 walk no-ops. */
/* a scalar-only tuple — all elements ride the 8B slot. */
{ "tuple_scalar",
"package main;\n"
"export fn main() i32 = {\n"
@@ -77,19 +77,36 @@ static const struct row rows[] = {
"};\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",
/* #24 positive control — a SLICE element is DISP-B-allowed (its 24B
* header rides the cursor). Must NOT be over-rejected. Readout is the
* scalar t.1 (=5). */
{ "tuple_slice_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: (i32, ([2]int, i32)) = (9, ([3, 4], 7));\n"
"\treturn t.0;\n"
"\tlet hb: [8]u8;\n"
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;\n"
"\tlet t: ([]u8, i32) = (a, 5);\n"
"\treturn t.1;\n"
"};\n",
5 },
/* #24 positive control — a STR element is DISP-B-allowed (24B header).
* Readout is the scalar t.1 (=7). */
{ "tuple_str_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: (str, i32) = (\"hi\", 7);\n"
"\treturn t.1;\n"
"};\n",
7 },
/* #24 positive control — a TAGGED-UNION element is DISP-B-allowed (its
* tag+payload box rides the slot). Readout is the scalar t.1 (=9). */
{ "tuple_tagged_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: ((void | size), i32) = (3, 9);\n"
"\treturn t.1;\n"
"};\n",
9 },
};
@@ -137,6 +154,29 @@ static const char *neg[] = {
"\tlet t = f();\n"
"\treturn t.0[0]: i32;\n"
"};\n",
/* tuple_arr_exact (#24) — was a GREEN positive control; the DISP-B
* broad reject now OUTLAWS an ARRAY tuple element (silent-drop on
* construction + segv on t.0[i] read). MIGRATED to the neg table. */
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: ([2]int, i32) = ([1, 2], 5);\n"
"\treturn t.1;\n"
"};\n",
/* tuple_nested_exact (#24) — was a GREEN positive control; a NESTED
* TUPLE element is now outlawed by DISP-B. MIGRATED to the neg table. */
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: (i32, ([2]int, i32)) = (9, ([3, 4], 7));\n"
"\treturn t.0;\n"
"};\n",
/* tuple_struct_elem (#24) — a STRUCT tuple element is now a checker
* loud (was a cgen "unsupported field-read shape" loud). */
"package main;\n"
"type P = struct { x: int };\n"
"export fn main() i32 = {\n"
"\tlet t: (P, i32) = (P { x = 1 }, 5);\n"
"\treturn t.1;\n"
"};\n",
};
static int