/* * 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. * * 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 * * 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 */ #include #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[] = { /* 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. */ { "tuple_scalar", "package main;\n" "export fn main() i32 = {\n" "\tlet t: (i32, i32) = (1, 2);\n" "\treturn t.1;\n" "};\n", 2 }, }; /* 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", }; static int run_driver(const char *driver, const struct row *r, int i) { char src[64], tmpdir[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/teo_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/teo_%d_d_%d", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", tmpdir, driver, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); unlink(src); rmdir(tmpdir); return -1; } const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char outbin[128]; snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); char *dot = strrchr(outbin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; int got = runwait(outbin); unlink(src); unlink(outbin); rmdir(tmpdir); 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 s[64], tmpdir[64], cmd[1024]; snprintf(s, sizeof s, "/tmp/teon_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/teon_%d_d_%d", getpid(), i); FILE *f = fopen(s, "wb"); if (!f) return -1; fputs(src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", tmpdir, driver, s); int rc = runwait(cmd); unlink(s); const char *base = strrchr(s, '/'); base = base ? base + 1 : s; char outbin[128]; snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); char *dot = strrchr(outbin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; unlink(outbin); rmdir(tmpdir); 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; }