/* * 925_nested_union_widen_run — widening a NAMED multi-variant union * value into an ENCLOSING union must tag the OUTER variant correctly * and lay the inner tagged value out as the payload at slot+8 (project * #218). * * Gate-blind hazard: the outer-widen of a NAMED multi-variant union * (`inner`) into `(size | inner)` took the tagged-SUBSET path — it * byte-copied the inner value at slot+0 and remapped its SUB-variants, * collapsing every inner sub-variant onto outer tag 0. The match * EXTRACT side already read the inner value from slot+8 (nested layout), * so store and extract disagreed and the outer arm mis-selected. The * byte-id (990-997) and cs==ww asm gates both passed because the * bootstrap never widens a multi-variant union into an enclosing one; * the #94 io eFinal error path (`let e: io.error = ; return e`) * is the first to exercise it. cstage cg_widen_tagged_store + * cg_variant_match (cmd/w6c/cgen.c) and the wwstage twin * (selfhost/cmd/wcc/cgenutil.ww cgwidentaggedstorebp + cgvariantmatch) * now detect the nested-variant case (the source's whole tagged type * matches one outer variant — recovered via a structural fallback after * the nominal-lossy collapse, project tinfo_lossy_nominal) and store * the inner value at slot+8 with the outer tag at slot+0. * * Two assertions per RUN row: * - RUNTIME: build with both `ww` (cstage) and `ww_ww` (wwstage), * run, compare the exit code. This is what was wrong pre-fix. * - ASM BYTE-ID: compile through `w6c` and `w6c_ww` and require * byte-identical .s (rule-10). * * ERR rows assert a COMPILE-ERROR on both stages (the drew collision * guard): when ≥2 outer variants are structurally identical, the * structural fallback cannot disambiguate them once nominal identity * is lost (#199b/B-full), so the compiler must STOP rather than silently * mis-tag. The over-match is unreachable under today's nominal-lossy * model but INVERTS when #199b lands — the guard pins the invariant now. * * Rows: * 1. outer_arm_select — let-widen `e: inner` into `(size | inner)`; * match selects the `inner` arm (the bug returned the `size` arm). * 2. destructure_return — `return e` widens through the tagged-return * ABI; the caller destructures the inner i32 payload (5) — proves * the payload survives the OUTER round-trip (the rule-7 gate: if * this fails it is the deep wrapped-slot-layout, not this fix). * 3. destructure_let — let-widen + nested match unwraps the inner i32 * payload (7) — the payload survives a non-return widen too. * 4. single_variant — scalar widen into `(i32 | bool)`; the non-nested * single-variant path is unperturbed (control + byte-id witness). * 5. collision_guard — two structurally-identical NAMED unions as * sibling variants `(a | b)`; widening must COMPILE-ERROR (the * guard), not silently pick one. */ #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; } /* experr != 0: the source must FAIL to compile on both stages. */ struct row { const char *label; const char *src; int want; int experr; }; static const struct row rows[] = { { "outer_arm_select", "type inner = !(i32|bool);\n" "export fn main() i32 = {\n" " let e: inner = (true: inner);\n" " let r: (size | inner) = e;\n" " match (r) {\n" " case let n: size => return 50;\n" " case let x: inner => return 0;\n" " };\n" "};\n", 0, 0 }, { "destructure_return", "type inner = !(i32|bool);\n" "fn g() (size | inner) = {\n" " let e: inner = (5i32: inner);\n" " return e;\n" "};\n" "export fn main() i32 = {\n" " match (g()) {\n" " case let s: size => return 90;\n" " case let x: inner => match (x) {\n" " case let i: i32 => return i;\n" " case let b: bool => return 99;\n" " };\n" " };\n" "};\n", 5, 0 }, { "destructure_let", "type inner = !(i32|bool);\n" "export fn main() i32 = {\n" " let e: inner = (7i32: inner);\n" " let r: (size | inner) = e;\n" " match (r) {\n" " case let n: size => return 50;\n" " case let x: inner => match (x) {\n" " case let i: i32 => return i;\n" " case let b: bool => return 88;\n" " };\n" " };\n" "};\n", 7, 0 }, { "single_variant", "export fn main() i32 = {\n" " let r: (i32 | bool) = 7i32;\n" " match (r) {\n" " case let n: i32 => return n;\n" " case let b: bool => return 88;\n" " };\n" "};\n", 7, 0 }, { "collision_guard", "type a = !(i32|bool);\n" "type b = !(i32|bool);\n" "export fn main() i32 = {\n" " let x: a = (1i32: a);\n" " let r: (a | b) = x;\n" " match (r) {\n" " case let p: a => return 1;\n" " case let q: b => return 2;\n" " };\n" "};\n", 0, 1 }, }; /* Write r->src to /.ww; returns 0 on success. */ static int write_src(const char *dir, const char *base, const struct row *r, char *out, size_t outsz) { snprintf(out, outsz, "%s/%s.ww", dir, base); FILE *f = fopen(out, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); return 0; } /* Build src with `driver build` inside its own subdir, run the binary, * return its exit code (or -1 on a build/exec failure). */ static int build_run(const char *driver, const char *src, const char *workdir) { char cmd[8192]; snprintf(cmd, sizeof cmd, "cd %s && %s build %s > /dev/null 2>&1", workdir, driver, src); if (runwait(cmd) != 0) return -1; const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char outbin[1024]; snprintf(outbin, sizeof outbin, "%s/%s", workdir, base); char *dot = strrchr(outbin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; return runwait(outbin); } static int files_equal(const char *a, const char *b) { FILE *fa = fopen(a, "rb"); FILE *fb = fopen(b, "rb"); if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } int ca, cb, eq = 1; do { ca = fgetc(fa); cb = fgetc(fb); if (ca != cb) { eq = 0; break; } } while (ca != EOF); fclose(fa); fclose(fb); return eq; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[512]; if (bin[0] != '/') { char cwd[256]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[640], wdrv[640], cw6[640], ww6[640]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); snprintf(cw6, sizeof cw6, "%s/w6c", bin); snprintf(ww6, sizeof ww6, "%s/w6c_ww", bin); int have_ww = (access(wdrv, X_OK) == 0); int have_w6cww = (access(ww6, X_OK) == 0); int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int i = 0; i < n; i++) { const struct row *r = &rows[i]; char dir[] = "/tmp/nuw.XXXXXX"; if (mkdtemp(dir) == NULL) { fprintf(stderr, "row[%s]: mkdtemp failed\n", r->label); fail++; total++; continue; } char src[1024]; if (write_src(dir, "p", r, src, sizeof src) != 0) { fprintf(stderr, "row[%s]: write src failed\n", r->label); fail++; total++; goto cleanup; } if (r->experr) { /* Collision guard: w6c (and w6c_ww) must REFUSE to * emit. A successful emit is the silent mis-tag the * guard exists to stop. */ char css[1024], wss[1024], cmd[8192]; snprintf(css, sizeof css, "%s/cs.s", dir); snprintf(wss, sizeof wss, "%s/ww.s", dir); snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1", cw6, css, src); total++; if (runwait(cmd) == 0) { fprintf(stderr, "row[%s][cstage]: emit SUCCEEDED, " "want compile-error (#218/#199b)\n", r->label); fail++; } if (have_w6cww) { snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1", ww6, wss, src); total++; if (runwait(cmd) == 0) { fprintf(stderr, "row[%s][wwstage]: emit SUCCEEDED, " "want compile-error (#218/#199b)\n", r->label); fail++; } } goto cleanup; } /* ASM byte-id: w6c vs w6c_ww .s must be identical. */ if (have_w6cww) { char css[1024], wss[1024], cmd[8192]; snprintf(css, sizeof css, "%s/cs.s", dir); snprintf(wss, sizeof wss, "%s/ww.s", dir); snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1", cw6, css, src); int rc1 = runwait(cmd); snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1", ww6, wss, src); int rc2 = runwait(cmd); total++; if (rc1 != 0 || rc2 != 0) { fprintf(stderr, "row[%s]: w6c/w6c_ww emit failed (%d/%d)\n", r->label, rc1, rc2); fail++; } else if (files_equal(css, wss) != 1) { fprintf(stderr, "row[%s]: cs.s != ww.s (rule-10 break)\n", r->label); fail++; } } /* RUNTIME: cstage. */ { char wk[1024]; snprintf(wk, sizeof wk, "%s/cs", dir); mkdir(wk, 0755); int got = build_run(cdrv, src, wk); total++; if (got != r->want) { fprintf(stderr, "row[%s][cstage]: exit=%d want=%d\n", r->label, got, r->want); fail++; } } /* RUNTIME: wwstage (the second half of rule-10). */ if (have_ww) { char wk[1024]; snprintf(wk, sizeof wk, "%s/ww", dir); mkdir(wk, 0755); int got = build_run(wdrv, src, wk); total++; if (got != r->want) { fprintf(stderr, "row[%s][wwstage]: exit=%d want=%d\n", r->label, got, r->want); fail++; } } cleanup: { char rm[1100]; snprintf(rm, sizeof rm, "rm -rf %s", dir); runwait(rm); } } if (fail) { fprintf(stderr, "nested_union_widen_run: %d/%d checks failed\n", fail, total); return 1; } printf("nested_union_widen_run: %d/%d ok\n", total, total); return 0; }