/* * 832_tuple_elem_overlong — an OVERLONG array literal in a TUPLE ELEMENT * position is INVALID ww; BOTH stages must LOUDLY REJECT at check time * (#20, the #12 + #106 follow-up; rob spec .ai/rob-20-spec.md). #12/#106 * wired checkarrlitfits for a DIRECT array lhs (+ alias) at the decl / * return / call-arg positions, but the tuple-element position was never * wired — `let t: ([2]int, i32) = ([1,2,3], 5)` over-fills the [2]int slot * with 3 initialisers. * * WWSTAGE-ONLY fix — cstage already rejects (tuple element-wise * type_assignable counts elements: `[3]int` vs `[2]int`). Pre-fix wwstage * divergence (the mutation-sanity target): * - tuple_arr_over `let t:([2]int,i32)=([1,2,3],5)` : ww silently ACCEPTED * - tuple_nested_arr `let t:([2][3]int,i32)=([...x3],5)` : the outer [2] * slot over-filled by 3 sub-arrays (checkarrlitfits * nested-array recursion under the tuple walk) * * The diagnostic TEXT may differ between stages ("over-fill" vs "not * assignable") — byte-id-blind (stderr is not asm). Both REJECT and emit no * 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 * 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 * 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_scalar | let t:(i32,i32)=(1,2); t.1 | 2 * 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 #include #include #include #include static int runwait(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); return -1; } struct row { const char *label; const char *src; int want; }; static const struct row rows[] = { /* a scalar-only tuple — all elements ride the 8B slot. */ { "tuple_scalar", "package main;\n" "export fn main() i32 = {\n" "\tlet t: (i32, i32) = (1, 2);\n" "\treturn t.1;\n" "};\n", 2 }, /* #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 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 }, }; /* An overlong array literal in a tuple element — both stages must FAIL the * build (loud checker diagnostic, not silent accept). */ static const char *neg[] = { /* tuple_arr_over (the #20 repro) — [2]int slot gets 3 inits. */ "package main;\n" "export fn main() i32 = {\n" "\tlet t: ([2]int, i32) = ([1, 2, 3], 5);\n" "\treturn t.0[1]: i32;\n" "};\n", /* tuple_nested_arr — outer [2] slot over-filled by 3 sub-arrays * (checkarrlitfits recursion under the tuple walk). */ "package main;\n" "export fn main() i32 = {\n" "\tlet t: ([2][3]int, i32) = " "([[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", /* tuple_return_over (#25) — overlong [2]int in a tuple RETURN. */ "package main;\n" "fn f() ([2]int, i32) = {\n" "\treturn ([1, 2, 3], 5);\n" "};\n" "export fn main() i32 = {\n" "\tlet t = f();\n" "\treturn t.1;\n" "};\n", /* tuple_return_nested (#25 path × #26 recursion) — nested tuple in a * RETURN, inner [2]int over-filled. */ "package main;\n" "fn f() ([2]int, ([2]int, i32)) = {\n" "\treturn ([1, 2], ([3, 4, 5], 6));\n" "};\n" "export fn main() i32 = {\n" "\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 run_driver(const char *driver, const struct row *r, int i) { char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024]; snprintf(tmpdir, sizeof tmpdir, "/tmp/teo_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(src, sizeof src, "%s/teo_%d_%d.ww", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/teo_%d_%d", tmpdir, getpid(), i); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); FILE *f = fopen(src, "wb"); if (!f) { runwait(rmcmd); return -1; } fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null", driver, outbin, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); runwait(rmcmd); return -1; } int got = runwait(outbin); runwait(rmcmd); return got; } /* build_should_fail — an overlong tuple-element array must error on * `driver`; returns 0 when the build correctly FAILS, non-zero when it * wrongly succeeded. */ static int build_should_fail(const char *driver, const char *src, int i) { char tmpdir[64], s[128], outbin[128], rmcmd[160], cmd[1024]; snprintf(tmpdir, sizeof tmpdir, "/tmp/teon_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(s, sizeof s, "%s/teon_%d_%d.ww", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/teon_%d_%d", tmpdir, getpid(), i); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); FILE *f = fopen(s, "wb"); if (!f) { runwait(rmcmd); return -1; } fputs(src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null", driver, outbin, s); int rc = runwait(cmd); runwait(rmcmd); return rc == 0 ? -1 : 0; /* build must NOT succeed */ } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[1024]; if (bin[0] != '/') { char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[1024]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); char wdrv[1024]; snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); struct { const char *name; const char *path; int gated_on_existence; } drivers[] = { { "cstage", cdrv, 0 }, { "wwstage", wdrv, 1 }, { NULL, NULL, 0 }, }; int n = (int)(sizeof rows / sizeof rows[0]); int nn = (int)(sizeof neg / sizeof neg[0]); int total = 0, fail = 0; for (int d = 0; drivers[d].name; d++) { if (drivers[d].gated_on_existence && access(drivers[d].path, X_OK) != 0) { fprintf(stderr, "tuple_elem_overlong: skip %s (no %s)\n", drivers[d].name, drivers[d].path); continue; } for (int i = 0; i < n; i++) { int got = run_driver(drivers[d].path, &rows[i], i); total++; if (got != rows[i].want) { fprintf(stderr, "tuple_elem_overlong[%s][%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } for (int i = 0; i < nn; i++) { total++; if (build_should_fail(drivers[d].path, neg[i], 100 + i) != 0) { fprintf(stderr, "tuple_elem_overlong[%s][neg%d]: built ok, " "expected a loud error\n", drivers[d].name, i); fail++; } } } if (fail) { fprintf(stderr, "tuple_elem_overlong: %d/%d fixtures failed\n", fail, total); return 1; } printf("tuple_elem_overlong: %d/%d ok\n", total, total); return 0; }