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:
@@ -725,6 +725,18 @@ resolve_type(Checker *c, Node *n)
|
||||
* packed-tuple miscompile family (#32/#33/#48). */
|
||||
if (tp->type) {
|
||||
Type *eu = type_chase_named(tp->type);
|
||||
/* #24: a composite element (array/struct/nested
|
||||
* tuple >8B) cannot ride the 8B cursor slot — the
|
||||
* #60 layout drops it on construction and segvs on
|
||||
* t.N[i] read. Reject until #60/DISP-A inlines it.
|
||||
* Hare allows it (harec type_store.c anon-struct). */
|
||||
if (eu && (eu->kind == TY_ARRAY
|
||||
|| eu->kind == TY_STRUCT
|
||||
|| eu->kind == TY_TUPLE))
|
||||
err(c, n->pos, "tuple element must be a "
|
||||
"scalar, str, slice, or tagged-union "
|
||||
"(composite element deferred to task "
|
||||
"#60)");
|
||||
if (eu && (eu->kind == TY_STR
|
||||
|| eu->kind == TY_SLICE
|
||||
|| eu->kind == TY_TAGGED))
|
||||
@@ -2182,6 +2194,20 @@ cexpr(Checker *c, Node *n)
|
||||
Type *ed = type_default(tp->type);
|
||||
if (ed && ed->align > al) al = ed->align;
|
||||
Type *eu = type_chase_named(ed);
|
||||
/* #24: an INFERRED composite element (array/struct/
|
||||
* nested tuple >8B) drops on construction + segvs on
|
||||
* the t.N read, exactly as the explicit N_TTUPLE twin
|
||||
* (resolve_type) — wwstage's tinfofornode choke-point
|
||||
* catches declared AND inferred, so cstage must reject
|
||||
* the inferred literal here too (rule-10 symmetry).
|
||||
* Reject until #60/DISP-A inlines it. */
|
||||
if (eu && (eu->kind == TY_ARRAY
|
||||
|| eu->kind == TY_STRUCT
|
||||
|| eu->kind == TY_TUPLE))
|
||||
err(c, n->pos, "tuple element must be a "
|
||||
"scalar, str, slice, or tagged-union "
|
||||
"(composite element deferred to task "
|
||||
"#60)");
|
||||
if (eu && (eu->kind == TY_STR || eu->kind == TY_SLICE
|
||||
|| eu->kind == TY_TAGGED))
|
||||
sz += (eu->size + 7) & ~(u64)7;
|
||||
|
||||
@@ -12245,6 +12245,18 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
let pt: *tinfo = tinfofornode(c, p.lhs);
|
||||
// #62/#69: tuple-member value cycle — loud, cstage twin.
|
||||
if (circularnamed(c, pt, p.lhs)) { pt = c.tc.tyerr; };
|
||||
// #24: a composite element (array/struct/nested tuple
|
||||
// >8B) cannot ride the 8B cursor slot — the #60 layout
|
||||
// drops it on construction and segvs on t.N[i] read.
|
||||
// Reject until #60/DISP-A inlines it; cstage twin.
|
||||
let cu: *tinfo = tichase(pt);
|
||||
if (cu != nil && (cu.kind == tykind.TY_ARRAY
|
||||
|| cu.kind == tykind.TY_STRUCT
|
||||
|| cu.kind == tykind.TY_TUPLE)) {
|
||||
cerr("error: tuple element must be a scalar, str, slice, or tagged-union (composite element deferred to task #60)\n");
|
||||
c.errs += 1;
|
||||
pt = c.tc.tyerr;
|
||||
};
|
||||
let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!;
|
||||
if (teh == nil) { teh = te; } else { tet.tnext = te; };
|
||||
tet = te;
|
||||
|
||||
@@ -1964,6 +1964,18 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
let pt: *tinfo = tinfofornode(c, p.lhs);
|
||||
// #62/#69: tuple-member value cycle — loud, cstage twin.
|
||||
if (circularnamed(c, pt, p.lhs)) { pt = c.tc.tyerr; };
|
||||
// #24: a composite element (array/struct/nested tuple
|
||||
// >8B) cannot ride the 8B cursor slot — the #60 layout
|
||||
// drops it on construction and segvs on t.N[i] read.
|
||||
// Reject until #60/DISP-A inlines it; cstage twin.
|
||||
let cu: *tinfo = tichase(pt);
|
||||
if (cu != nil && (cu.kind == tykind.TY_ARRAY
|
||||
|| cu.kind == tykind.TY_STRUCT
|
||||
|| cu.kind == tykind.TY_TUPLE)) {
|
||||
cerr("error: tuple element must be a scalar, str, slice, or tagged-union (composite element deferred to task #60)\n");
|
||||
c.errs += 1;
|
||||
pt = c.tc.tyerr;
|
||||
};
|
||||
let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!;
|
||||
if (teh == nil) { teh = te; } else { tet.tnext = te; };
|
||||
tet = te;
|
||||
|
||||
@@ -12245,6 +12245,18 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
let pt: *tinfo = tinfofornode(c, p.lhs);
|
||||
// #62/#69: tuple-member value cycle — loud, cstage twin.
|
||||
if (circularnamed(c, pt, p.lhs)) { pt = c.tc.tyerr; };
|
||||
// #24: a composite element (array/struct/nested tuple
|
||||
// >8B) cannot ride the 8B cursor slot — the #60 layout
|
||||
// drops it on construction and segvs on t.N[i] read.
|
||||
// Reject until #60/DISP-A inlines it; cstage twin.
|
||||
let cu: *tinfo = tichase(pt);
|
||||
if (cu != nil && (cu.kind == tykind.TY_ARRAY
|
||||
|| cu.kind == tykind.TY_STRUCT
|
||||
|| cu.kind == tykind.TY_TUPLE)) {
|
||||
cerr("error: tuple element must be a scalar, str, slice, or tagged-union (composite element deferred to task #60)\n");
|
||||
c.errs += 1;
|
||||
pt = c.tc.tyerr;
|
||||
};
|
||||
let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!;
|
||||
if (teh == nil) { teh = te; } else { tet.tnext = te; };
|
||||
tet = te;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1368,8 +1368,11 @@ static const struct row rows[] = {
|
||||
/* rule-7 (ken demand 1): a NESTED composite element (tuple-in-
|
||||
* tuple here) occupies more than the one GP word the restage walk
|
||||
* counts — pre-guard the checker accepted it and it ran WRONG
|
||||
* (inner words skewed; wwstage SIGSEGV'd). Loud until a consumer
|
||||
* motivates the wiring. */
|
||||
* (inner words skewed; wwstage SIGSEGV'd). #24 now outlaws the
|
||||
* composite-element tuple TYPE at resolution (both stages), so the
|
||||
* reject fires here before the later "tuple arg element kind
|
||||
* unsupported" arg-pass loud — experr migrated to the #24 text.
|
||||
* Full inline support deferred to task #60. */
|
||||
{ "t2_reject_nested_elem_arg",
|
||||
"package main;\n"
|
||||
"fn f(t: ((i64, i64), i64)) i64 = {\n"
|
||||
@@ -1381,7 +1384,7 @@ static const struct row rows[] = {
|
||||
" if (f(t) != 9) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "tuple arg element kind unsupported" },
|
||||
K_BUILDERR, "tuple element must be a scalar" },
|
||||
/* variadic-of-tuples ((i64,i64)...): `ts[0].0` is an indexed-slice
|
||||
* tuple-element FIELD read — #121 leg (a) now resolves it (was LOUD
|
||||
* "unsupported field-read shape"; the dispatch fired only for an
|
||||
@@ -1920,13 +1923,19 @@ static const struct row rows[] = {
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
/* nested tuple element: slot rule gives an inner tuple one 8B
|
||||
* eightbyte (tuple_eslot parity) — pins the outer layout. */
|
||||
/* #24: an INFERRED nested-tuple element `((4,2),36)` drops the inner
|
||||
* tuple on construction (build-proved 76 not 42 reading t.0) and is now
|
||||
* outlawed on BOTH stages — same DISP-B reject as 832's tuple_nested_-
|
||||
* exact, but via the inferred-literal path (check.c N_TUPLE expr +
|
||||
* wwstage tinfofornode). Migrated K_RUN -> K_BUILDERR. Full inline
|
||||
* support deferred to task #60. */
|
||||
{ "t44_inferred_nested",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t = ((4, 2), 36);\n"
|
||||
" if (t.1 != 36) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
"};\n", 0, K_BUILDERR, "tuple element must be a scalar" },
|
||||
/* #57: an IN-CAP tuple literal's cursor fill keyed each element on
|
||||
* its STAMPED type (element-constructed, check.c N_TUPLE), so a
|
||||
* declared-TAGGED element from a concrete rvalue (`(5: size, 9)`)
|
||||
|
||||
Reference in New Issue
Block a user