diff --git a/internal/wwfixture/types.ww b/internal/wwfixture/types.ww index 8701b322..0ffa7b09 100644 --- a/internal/wwfixture/types.ww +++ b/internal/wwfixture/types.ww @@ -1,13 +1,13 @@ package wwfixture; def protocolversion: i32 = 1; -def corpuscount: i32 = 1659; -def errorcount: i32 = 338; +def corpuscount: i32 = 1669; +def errorcount: i32 = 339; def compilecount: i32 = 18; -def runcount: i32 = 200; -def runexitcount: i32 = 1103; -def nativecount: i32 = 3318; -def corpushash: str = "c9c9361b3f799c1439fb9009f8c6edfc7dbf36a263b789e65f239b3984661ffd"; +def runcount: i32 = 206; +def runexitcount: i32 = 1106; +def nativecount: i32 = 3338; +def corpushash: str = "2c2ef7841c6da289337a72816041bdc594fa84da18d4f1e11327db3d578e1d14"; type directive = enum i32 { ERROR = 0, diff --git a/test/wcc/787_xmod_variant_match.c b/test/wcc/787_xmod_variant_match.c deleted file mode 100644 index ea79c38b..00000000 --- a/test/wcc/787_xmod_variant_match.c +++ /dev/null @@ -1,403 +0,0 @@ -/* - * 787_xmod_variant_match — project #13 close. Pins that a `package main` - * can decompose an IMPORTED union's individual VARIANTS cross-module - * (`match (e: pkg.u) { case pkg.a => ...; case let o: pkg.b => ... }`), - * on BOTH stages, byte-identically (rule-10). - * - * THE BUG (wwstage-CHECKER-only, cs!=ww): wwstage's match-arm validity - * (selfhost/cmd/wcc/check.ww casevariantin) and exhaustiveness - * (casecovers) compared via typeeqast, whose N_TNAME arm is a raw - * streq. A union's variant is written UNQUALIFIED in its defining - * module (`a` in pkg.u's body); the cross-module case pattern is the - * dotted `pkg.a` (one N_TNAME). streq("a","pkg.a") -> false -> wwstage - * rejected "case: not a variant of scrutinee". cstage compares - * resolved-Type identity (variant_match, cmd/wcc/check.c:1651), so the - * qualifier is irrelevant and it built+routed correctly. Hare allows - * cross-module variant decomposition; align UP to cstage (a too-strict - * checker, NOT a down-align that would forbid the feature). - * - * THE FIX (#13): casevariantpairmatch reduces BOTH pattern and variant - * to a (module, leaf) pair — a dotted name keeps its own qualifier, a - * bare name is attributed the union's defining module (taggeddefmod via - * aliassym .decl.nmod) — and matches the pairs. This accepts a - * cross-module `case errors.unsupported` (vs bare `unsupported`) while - * REJECTING a foreign `othermod.unsupported`, and keeps a dotted body - * variant matching its own qualifier (Shape A). Wired into BOTH - * casevariantin (validity) and casecovers (exhaustiveness). #10-family - * (AST-name vs cstage tinfo); precise Type identity is the #10 endgame. - * - * The PRE-FIX failure mode is a wwstage CHECKER REJECT, so the - * discriminator is the `w6c_ww` build of the two-module unit succeeding - * at all — pre-fix it errored out; post-fix it succeeds AND is byte-id - * with cstage. Self-contained 2-module fixtures (no lib coupling). - * - * scenario | shape | exit | byte-id - * ------------------+----------------------------------------+------+-------- - * no_default | match pkg.u=!(a|b), bare arms, NO | 21 | cs==ww - * | default — exercises casecovers | | - * | exhaustiveness cross-module | | - * bind_and_default | match pkg.u2=!(a|b|c): bare arm + a | 42 | cs==ww - * | `case let o: pkg.b` BOUND arm + a | | - * | `case =>` default; if-checks each arm | | - * foreign_qualifier | `case o.a` vs pkg.u (o.a is a DIFFERENT | both | (reject - * | module's same-leaf type) — BOTH |reject| net) - * | stages must REJECT; guards the fix | | - * | against a false-ACCEPT (bare leaf- | | - * | strip would wrongly accept) | | - * shape_a_dotted_var| union BODY holds dotted variant | 12 | cs==ww - * | pkg.e=!(errs.bad|local); case errs.bad| | - * | keeps OWN qualifier (drew Shape A) — | | - * | sole positive lock on qualmod's | | - * | dotted-variant branch | | - * - * GATE POLARITY: must stay GREEN. Red means wwstage rejected a valid - * cross-module variant match again (w6c_ww build fails) or the routing - * diverged (byte-id / runtime). - */ -#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; -} - -static int -slurp_eq(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 rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - if (ferror(fa) || ferror(fb)) rc = -1; - if (fclose(fa) != 0) rc = -1; - if (fclose(fb) != 0) rc = -1; - return rc; -} - -struct file { const char *name; const char *src; }; - -struct scenario { - const char *label; - const struct file *files; /* name==NULL terminates */ - int want_exit; - int expect_reject; /* 1 = BOTH stages must REJECT (checker) */ -}; - -/* ---- no_default: bare cross-module arms, no default (exhaustiveness) */ -static const struct file no_default_files[] = { - { "pkg.ww", - "package pkg;\n" - "export type a = !void;\n" - "export type b = !void;\n" - "export type u = !(a | b);\n" - "export fn mka() u = { let x: a; return x; };\n" - "export fn mkb() u = { let y: b; return y; };\n" }, - { "main.ww", - "package main;\n" - "import pkg;\n" - "fn classify(e: pkg.u) i32 = {\n" - " match (e) {\n" - " case pkg.a => return 1;\n" - " case pkg.b => return 2;\n" - " };\n" - "};\n" - "export fn main() i32 = {\n" - " return classify(pkg.mkb()) * 10 + classify(pkg.mka());\n" - "};\n" }, - { NULL, NULL } -}; - -/* ---- bind_and_default: bound arm + default cross-module ----------- */ -static const struct file bind_default_files[] = { - { "pkg.ww", - "package pkg;\n" - "export type a = !void;\n" - "export type b = !void;\n" - "export type c = !void;\n" - "export type u2 = !(a | b | c);\n" - "export fn mka() u2 = { let x: a; return x; };\n" - "export fn mkb() u2 = { let y: b; return y; };\n" - "export fn mkc() u2 = { let z: c; return z; };\n" }, - { "main.ww", - "package main;\n" - "import pkg;\n" - "fn classify(e: pkg.u2) i32 = {\n" - " match (e) {\n" - " case pkg.a => return 1;\n" - " case let o: pkg.b => return 2;\n" - " case => return 9;\n" - " };\n" - "};\n" - "export fn main() i32 = {\n" - " if (classify(pkg.mka()) != 1) { return 11; };\n" - " if (classify(pkg.mkb()) != 2) { return 12; };\n" - " if (classify(pkg.mkc()) != 9) { return 13; };\n" - " return 42;\n" - "};\n" }, - { NULL, NULL } -}; - -/* ---- foreign_qualifier: a same-leaf variant from a DIFFERENT module - * is NOT a variant of the scrutinee — BOTH stages must REJECT. Guards - * the #13 fix against trading the false-reject for a false-ACCEPT: a - * bare leaf-strip would wrongly accept `o.a` (leaf "a" matches pkg's - * variant `a`); the (module,leaf)-pair match rejects it (o != pkg). */ -static const struct file foreign_qualifier_files[] = { - { "pkg.ww", - "package pkg;\n" - "export type a = !void;\n" - "export type b = !void;\n" - "export type u = !(a | b);\n" - "export fn mka() u = { let x: a; return x; };\n" }, - { "o.ww", - "package o;\n" - "export type a = !void;\n" }, - { "main.ww", - "package main;\n" - "import pkg;\n" - "import o;\n" - "fn classify(e: pkg.u) i32 = {\n" - " match (e) {\n" - " case o.a => return 1;\n" - " case => return 0;\n" - " };\n" - "};\n" - "export fn main() i32 = { return classify(pkg.mka()); };\n" }, - { NULL, NULL } -}; - -/* ---- shape_a_dotted_variant: the union's BODY itself holds a dotted - * cross-module variant (`pkg.e = !(errs.bad | local)`), matched with a - * dotted `case errs.bad` AND a bare-variant `case pkg.local`. This is - * drew's Shape A (cf. io.error nesting errors.error): the dotted body - * variant `errs.bad` must keep ITS OWN qualifier (errs), NOT be forced - * onto the union's defining module (pkg) — else `case errs.bad` would - * false-REJECT. The other three scenarios carry only BARE body variants, - * so this is the sole positive lock on qualmod's dotted-variant branch; - * a regression that attributes unionmod to every variant breaks here - * alone. exit = classify(mkbad())*10 + classify(mklocal()) = 1*10+2. */ -static const struct file shape_a_files[] = { - { "errs.ww", - "package errs;\n" - "export type bad = !void;\n" }, - { "pkg.ww", - "package pkg;\n" - "import errs;\n" - "export type local = !void;\n" - "export type e = !(errs.bad | local);\n" - "export fn mkbad() e = { let x: errs.bad; return x; };\n" - "export fn mklocal() e = { let y: local; return y; };\n" }, - { "main.ww", - "package main;\n" - "import pkg;\n" - "import errs;\n" - "fn classify(x: pkg.e) i32 = {\n" - " match (x) {\n" - " case errs.bad => return 1;\n" - " case pkg.local => return 2;\n" - " };\n" - "};\n" - "export fn main() i32 = {\n" - " return classify(pkg.mkbad()) * 10 + classify(pkg.mklocal());\n" - "};\n" }, - { NULL, NULL } -}; - -static const struct scenario scenarios[] = { - { "no_default", no_default_files, 21, 0 }, - { "bind_and_default", bind_default_files, 42, 0 }, - { "foreign_qualifier", foreign_qualifier_files, 0, 1 }, - { "shape_a_dotted_var", shape_a_files, 12, 0 }, -}; - -static int -run_scenario(const char *bin, const char *cdrv, const char *wdrv, - const struct scenario *sc) -{ - (void)bin; - char dir[] = "/tmp/ww787_XXXXXX"; - if (mkdtemp(dir) == NULL) { - fprintf(stderr, "787[%s]: mkdtemp failed\n", sc->label); - return -1; - } - - char path[1024], cmd[4096]; - int rc = 0; - - for (int i = 0; sc->files[i].name; i++) { - snprintf(path, sizeof path, "%s/%s", dir, sc->files[i].name); - FILE *f = fopen(path, "wb"); - if (!f) { fprintf(stderr, "787[%s]: write %s\n", sc->label, - sc->files[i].name); rc = -1; goto done; } - int bad = fputs(sc->files[i].src, f) == EOF; - if (fclose(f) != 0) bad = 1; - if (bad) { fprintf(stderr, "787[%s]: write %s\n", sc->label, - sc->files[i].name); rc = -1; goto done; } - } - - if (sc->expect_reject) { - /* #94 sep layout: there is no combined unit to re-check. The - * checker reject now fires inside each driver's w6c pass, - * so BOTH driver builds MUST fail. w6c_ww (via ww_ww) accepting - * here is the #13 false-ACCEPT regression. */ - snprintf(cmd, sizeof cmd, - "cd %s && %s build -I %s " - "-o %s/main %s/main.ww >/dev/null 2>&1", - dir, cdrv, dir, dir, dir); - if (runwait(cmd) == 0) { - fprintf(stderr, "787[%s]: cstage build SUCCEEDED, " - "expected reject\n", sc->label); - rc = -1; goto done; - } - snprintf(cmd, sizeof cmd, - "cd %s && %s build -I %s " - "-o %s/mainww %s/main.ww >/dev/null 2>&1", - dir, wdrv, dir, dir, dir); - if (runwait(cmd) == 0) { - fprintf(stderr, "787[%s]: ww_ww ACCEPTED foreign variant " - "(#13 false-accept), expected reject\n", sc->label); - rc = -1; goto done; - } - goto done; - } - - /* cstage driver build + run: pins runtime routing. Pin - * all outputs stay under the scratch dir. */ - snprintf(cmd, sizeof cmd, - "cd %s && %s build -I %s -o %s/main %s/main.ww", - dir, cdrv, dir, dir, dir); - if (runwait(cmd) != 0) { - fprintf(stderr, "787[%s]: cstage build failed\n", sc->label); - rc = -1; goto done; - } - snprintf(path, sizeof path, "%s/main", dir); - int got = runwait(path); - if (got != sc->want_exit) { - fprintf(stderr, "787[%s]: cstage exit %d, want %d\n", - sc->label, got, sc->want_exit); - rc = -1; - } - - /* The #13 discriminator: the wwstage driver's build runs the - * wwstage checker over the same units. Pre-fix it REJECTED the - * cross-module variant arms (build fails); post-fix it accepts AND - * its per-package w6c_ww asm is byte-id with cstage's. The root + - * each imported pkg compile to separate .sepwork/.s; - * concat (sorted glob, identical set both stages) for the compare. */ - snprintf(cmd, sizeof cmd, - "cd %s && %s build -I %s -o %s/mainww %s/main.ww", - dir, wdrv, dir, dir, dir); - if (runwait(cmd) != 0) { - fprintf(stderr, "787[%s]: ww_ww build failed " - "(#13 cross-module variant reject?)\n", sc->label); - rc = -1; goto done; - } - char cs_s[1024], ws_s[1024]; - snprintf(cs_s, sizeof cs_s, "%s/all_cs.s", dir); - snprintf(ws_s, sizeof ws_s, "%s/all_ww.s", dir); - snprintf(cmd, sizeof cmd, - "cat %s/main.sepwork/*.s > %s 2>/dev/null && test -s %s", - dir, cs_s, cs_s); - if (runwait(cmd) != 0) { - fprintf(stderr, "787[%s]: cstage asm aggregation failed\n", - sc->label); - rc = -1; goto done; - } - snprintf(cmd, sizeof cmd, - "cat %s/mainww.sepwork/*.s > %s 2>/dev/null && test -s %s", - dir, ws_s, ws_s); - if (runwait(cmd) != 0) { - fprintf(stderr, "787[%s]: wwstage asm aggregation failed\n", - sc->label); - rc = -1; goto done; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, "787[%s]: cs.s/ww.s DIFFER (rule-10 byte-id " - "violation)\n", sc->label); - rc = -1; - } - -done: - snprintf(cmd, sizeof cmd, "rm -rf %s/main.sepwork", dir); - if (runwait(cmd) != 0) { fprintf(stderr, "787[%s]: cleanup main.sepwork\n", - sc->label); rc = -1; } - snprintf(cmd, sizeof cmd, "rm -rf %s/mainww.sepwork", dir); - if (runwait(cmd) != 0) { fprintf(stderr, "787[%s]: cleanup mainww.sepwork\n", - sc->label); rc = -1; } - for (int i = 0; sc->files[i].name; i++) { - snprintf(path, sizeof path, "%s/%s", dir, sc->files[i].name); - if (unlink(path) != 0 && errno != ENOENT) { - fprintf(stderr, "787[%s]: cleanup %s\n", sc->label, - sc->files[i].name); - rc = -1; - } - } - const char *children[] = { "main", "mainww", "all_cs.s", "all_ww.s", NULL }; - for (int i = 0; children[i]; i++) { - snprintf(path, sizeof path, "%s/%s", dir, children[i]); - if (unlink(path) != 0 && errno != ENOENT) { - fprintf(stderr, "787[%s]: cleanup %s\n", sc->label, - children[i]); - rc = -1; - } - } - if (rmdir(dir) != 0) { - fprintf(stderr, "787[%s]: cleanup directory\n", sc->label); - rc = -1; - } - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[2048]; - 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[2100], wdrv[2100]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - if (access(wdrv, X_OK) != 0) { - fprintf(stderr, "787: ww_ww missing — cannot run the cs==ww " - "byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = (int)(sizeof scenarios / sizeof scenarios[0]); - int fail = 0; - for (int i = 0; i < n; i++) { - if (run_scenario(bin, cdrv, wdrv, &scenarios[i]) != 0) - fail++; - } - - if (fail) { - fprintf(stderr, "787 xmod_variant_match: %d/%d scenarios failed\n", - fail, n); - return 1; - } - printf("xmod_variant_match: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -} diff --git a/test/wcc/929_match_4arm_cross_module_run.c b/test/wcc/929_match_4arm_cross_module_run.c deleted file mode 100644 index 742ecb0c..00000000 --- a/test/wcc/929_match_4arm_cross_module_run.c +++ /dev/null @@ -1,392 +0,0 @@ -/* - * 929_match_4arm_cross_module_run — Class B semantic test for task #31. - * Pre-fix wwstage's matchscrutt resolved `match (mod.fn(...))` by - * name-only fnretlookup, so when the caller fn shadowed the callee's - * name across modules the match dispatch saw the wrong (caller's) - * tagged type and arms past the caller's variant count silently - * collapsed onto tag 0 (their bodies were unreachable even when the - * runtime tag matched). - * - * Pure runtime test: build through both drivers (cstage `ww`, wwstage - * `ww_ww`) and assert each arm's body actually fires for its matching - * input. 728_match_4arm_cross_module pins the asm-level distinct-CMPQ - * sentinel; this file pins end-to-end behavior across the rob matrix: - * - 3-arm boundary (does arm 2 collapse?). - * - 4-arm canonical (the probe shape). - * - 5/6-arm scaling. - * - Mixed variant kinds. - * - Reverse arm-order in match source. - */ -#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 *a_src; /* callee module source — written as a/a.ww */ - const char *b_src; /* caller module + main — written as b.ww */ - int want; -}; - -static const struct row rows[] = { - /* Canonical 4-arm with shadowing `next`. Drives the callee to - * return each of the 4 variants in turn (via a selector arg) and - * checks the caller dispatched to the right arm. Arm 2/3 reaching - * their bodies is the post-fix invariant. */ - { "4arm_shadowed_canonical", - /* a.ww */ - "package a;\n" - "export type more = void;\n" - "export type invalid = !void;\n" - "export type done = void;\n" - "export fn next(k: i32) (rune | done | more | invalid) = {\n" - " if (k == 0) { return 0x41u32: rune; };\n" - " if (k == 1) { let v: done; return v; };\n" - " if (k == 2) { let v: more; return v; };\n" - " let v: invalid; return v;\n" - "};\n", - /* b.ww */ - "package b;\n" - "package b;\n" - "import a;\n" - "type done = void;\n" - "fn next(k: i32) i32 = {\n" - " match (a.next(k)) {\n" - " case let r: rune => return 100i32 + (r: i32);\n" - " case let dn: a.done => return 200;\n" - " case let m: a.more => return 300;\n" - " case let e: a.invalid => return 400;\n" - " };\n" - "};\n" - "export fn main() i32 = {\n" - " if (next(0) != 165) { return 11; };\n" - " if (next(1) != 200) { return 12; };\n" - " if (next(2) != 300) { return 13; };\n" - " if (next(3) != 400) { return 14; };\n" - " return 0;\n" - "};\n", - 0 }, - /* 3-arm boundary: arm 2 must reach its body. */ - { "3arm_shadowed", - "package a;\n" - "export type more = void;\n" - "export type done = void;\n" - "export fn next(k: i32) (rune | done | more) = {\n" - " if (k == 0) { return 0x42u32: rune; };\n" - " if (k == 1) { let v: done; return v; };\n" - " let v: more; return v;\n" - "};\n", - "package b;\n" - "import a;\n" - "type done = void;\n" - "fn next(k: i32) i32 = {\n" - " match (a.next(k)) {\n" - " case let r: rune => return 1i32 + (r: i32);\n" - " case let dn: a.done => return 2;\n" - " case let m: a.more => return 3;\n" - " };\n" - "};\n" - "export fn main() i32 = {\n" - " if (next(0) != 67) { return 11; };\n" - " if (next(1) != 2) { return 12; };\n" - " if (next(2) != 3) { return 13; };\n" - " return 0;\n" - "};\n", - 0 }, - /* 5-arm scaling: arms 3 and 4 must each reach their body. */ - { "5arm_shadowed", - "package a;\n" - "export type more = void;\n" - "export type invalid = !void;\n" - "export type done = void;\n" - "export type stop = void;\n" - "export fn next(k: i32) (rune | done | more | invalid | stop) = {\n" - " if (k == 0) { return 0x43u32: rune; };\n" - " if (k == 1) { let v: done; return v; };\n" - " if (k == 2) { let v: more; return v; };\n" - " if (k == 3) { let v: invalid; return v; };\n" - " let v: stop; return v;\n" - "};\n", - "package b;\n" - "import a;\n" - "type done = void;\n" - "fn next(k: i32) i32 = {\n" - " match (a.next(k)) {\n" - " case let r: rune => return 100i32 + (r: i32);\n" - " case let dn: a.done => return 2;\n" - " case let m: a.more => return 3;\n" - " case let e: a.invalid => return 4;\n" - " case let s: a.stop => return 5;\n" - " };\n" - "};\n" - "export fn main() i32 = {\n" - " if (next(0) != 167) { return 11; };\n" - " if (next(1) != 2) { return 12; };\n" - " if (next(2) != 3) { return 13; };\n" - " if (next(3) != 4) { return 14; };\n" - " if (next(4) != 5) { return 15; };\n" - " return 0;\n" - "};\n", - 0 }, - /* 6-arm: extra row to demonstrate the bug doesn't scale with arm - * count — every arm beyond the caller's variant count was broken, - * not just arm 2 / arm 3. */ - { "6arm_shadowed", - "package a;\n" - "export type more = void;\n" - "export type invalid = !void;\n" - "export type done = void;\n" - "export type stop = void;\n" - "export type eof = void;\n" - "export fn next(k: i32) (rune | done | more | invalid | stop | eof) = {\n" - " if (k == 0) { return 0x44u32: rune; };\n" - " if (k == 1) { let v: done; return v; };\n" - " if (k == 2) { let v: more; return v; };\n" - " if (k == 3) { let v: invalid; return v; };\n" - " if (k == 4) { let v: stop; return v; };\n" - " let v: eof; return v;\n" - "};\n", - "package b;\n" - "import a;\n" - "type done = void;\n" - "fn next(k: i32) i32 = {\n" - " match (a.next(k)) {\n" - " case let r: rune => return 100i32 + (r: i32);\n" - " case let dn: a.done => return 2;\n" - " case let m: a.more => return 3;\n" - " case let e: a.invalid => return 4;\n" - " case let s: a.stop => return 5;\n" - " case let f: a.eof => return 6;\n" - " };\n" - "};\n" - "export fn main() i32 = {\n" - " if (next(0) != 168) { return 11; };\n" - " if (next(1) != 2) { return 12; };\n" - " if (next(2) != 3) { return 13; };\n" - " if (next(3) != 4) { return 14; };\n" - " if (next(4) != 5) { return 15; };\n" - " if (next(5) != 6) { return 16; };\n" - " return 0;\n" - "};\n", - 0 }, - /* Mixed variant kinds. Callee returns (i32 | str | rune | u8); - * caller `next` shadows. Confirms the shadowed-resolution fix - * isn't shape-specific. */ - { "4arm_mixed_kinds", - "package a;\n" - "export fn next(k: i32) (i32 | str | rune | u8) = {\n" - " if (k == 0) { return 7; };\n" - " if (k == 1) { return \"hi\"; };\n" - " if (k == 2) { return 0x45u32: rune; };\n" - " return 9u8;\n" - "};\n", - "package b;\n" - "import a;\n" - "fn next(k: i32) i32 = {\n" - " match (a.next(k)) {\n" - " case let n: i32 => return 100 + n;\n" - " case let s: str => return 200i32 + (s.len: i32);\n" - " case let r: rune => return 300i32 + (r: i32);\n" - " case let c: u8 => return 400i32 + (c: i32);\n" - " };\n" - "};\n" - "export fn main() i32 = {\n" - " if (next(0) != 107) { return 11; };\n" - " if (next(1) != 202) { return 12; };\n" - " if (next(2) != 369) { return 13; };\n" - " if (next(3) != 409) { return 14; };\n" - " return 0;\n" - "};\n", - 0 }, - /* Reverse arm-order in the match source. Confirms the bug - * follows scrutinee-resolution (fnretlookupmod), not source - * order — emitted CMPQ tags follow the callee's variant indices - * regardless of how the arms were written. */ - { "4arm_shadowed_reverse", - "package a;\n" - "export type more = void;\n" - "export type invalid = !void;\n" - "export type done = void;\n" - "export fn next(k: i32) (rune | done | more | invalid) = {\n" - " if (k == 0) { return 0x46u32: rune; };\n" - " if (k == 1) { let v: done; return v; };\n" - " if (k == 2) { let v: more; return v; };\n" - " let v: invalid; return v;\n" - "};\n", - "package b;\n" - "import a;\n" - "type done = void;\n" - "fn next(k: i32) i32 = {\n" - " match (a.next(k)) {\n" - " case let e: a.invalid => return 4;\n" - " case let m: a.more => return 3;\n" - " case let dn: a.done => return 2;\n" - " case let r: rune => return 100i32 + (r: i32);\n" - " };\n" - "};\n" - "export fn main() i32 = {\n" - " if (next(0) != 170) { return 11; };\n" - " if (next(1) != 2) { return 12; };\n" - " if (next(2) != 3) { return 13; };\n" - " if (next(3) != 4) { return 14; };\n" - " return 0;\n" - "};\n", - 0 }, - /* utf8.next regression row deferred: a direct repro of the - * originally-failing probe_strings_iter shape (caller next(rune|done) - * shadows callee utf8.next(rune|done|more|invalid)) compiles cleanly - * post-#31 but wwstage segfaults at runtime through utf8.next. - * Cstage runs fine. Separate latent wwstage stomp in the bigger utf8 - * iterator shape — task #31's fix is correct in isolation; the 4arm - * canonical row above covers the structural pattern. Filed as a - * follow-up so the utf8 regression marker doesn't gate this commit. */ -}; - -static int -write_file(const char *dir, const char *name, const char *body) -{ - char path[512]; - snprintf(path, sizeof path, "%s/%s", dir, name); - FILE *f = fopen(path, "wb"); - if (!f) return -1; - fputs(body, f); - fclose(f); - return 0; -} - -static int -run_driver(const char *driver, const struct row *r, int i) -{ - char tmpdir[128], adir[160], asrc[192], bsrc[192]; - char outbin[192], scratch[208], cmd[2048]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/m4cmr_%d_%d_XXXXXX", - getpid(), i); - if (mkdtemp(tmpdir) == NULL) { - fprintf(stderr, "row[%s]: temporary directory acquisition failed\n", - r->label); - return -1; - } - snprintf(adir, sizeof adir, "%s/a", tmpdir); - snprintf(asrc, sizeof asrc, "%s/a.ww", adir); - snprintf(bsrc, sizeof bsrc, "%s/b.ww", tmpdir); - snprintf(outbin, sizeof outbin, "%s/b", tmpdir); - snprintf(scratch, sizeof scratch, "%s/b.sepwork", tmpdir); - - int result = -1, adir_owned = 0; - if (mkdir(adir, 0755) != 0) { - fprintf(stderr, "row[%s]: module directory creation failed\n", - r->label); - goto cleanup; - } - adir_owned = 1; - if (write_file(adir, "a.ww", r->a_src) != 0) { - fprintf(stderr, "row[%s]: cannot write a/a.ww\n", r->label); - goto cleanup; - } - if (write_file(tmpdir, "b.ww", r->b_src) != 0) { - fprintf(stderr, "row[%s]: cannot write b.ww\n", r->label); - goto cleanup; - } - - /* Build via the driver from inside tmpdir so `use a;` resolves to - * ./a/a.ww and ww's source-dir search hits b's siblings first. */ - snprintf(cmd, sizeof cmd, - "cd %s && %s build b.ww 2>/dev/null", tmpdir, driver); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: build via %s failed\n", - r->label, driver); - goto cleanup; - } - - result = runwait(outbin); - -cleanup: - { - int bad = 0; - snprintf(cmd, sizeof cmd, "rm -rf %s", scratch); - if (runwait(cmd) != 0) bad = 1; - if (unlink(outbin) != 0 && errno != ENOENT) bad = 1; - if (unlink(bsrc) != 0 && errno != ENOENT) bad = 1; - if (adir_owned) { - if (unlink(asrc) != 0 && errno != ENOENT) bad = 1; - if (rmdir(adir) != 0) bad = 1; - } - if (rmdir(tmpdir) != 0) bad = 1; - if (bad) { - fprintf(stderr, "row[%s]: temporary cleanup failed\n", - r->label); - if (result == r->want) result = -1; - } - } - return result; -} - -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]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - char wdrv[640]; - 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 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, - "match_4arm_cross_module_run: 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, - "match_4arm_cross_module_run[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (fail) { - fprintf(stderr, - "match_4arm_cross_module_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("match_4arm_cross_module_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/data/r787_xmod_variant_bind_default/case.ww b/test/wcc/data/r787_xmod_variant_bind_default/case.ww new file mode 100644 index 00000000..99b63f84 --- /dev/null +++ b/test/wcc/data/r787_xmod_variant_bind_default/case.ww @@ -0,0 +1,17 @@ +//ww:run-exit 42 +// migrated from test/wcc/787_xmod_variant_match.c: bare arm + bound `case let o: pkg.b` + default over an imported 3-variant union. +package main; +import pkg; +fn classify(e: pkg.u2) i32 = { + match (e) { + case pkg.a => return 1; + case let o: pkg.b => return 2; + case => return 9; + }; +}; +export fn main() i32 = { + if (classify(pkg.mka()) != 1) { return 11; }; + if (classify(pkg.mkb()) != 2) { return 12; }; + if (classify(pkg.mkc()) != 9) { return 13; }; + return 42; +}; diff --git a/test/wcc/data/r787_xmod_variant_bind_default/pkg/pkg.ww b/test/wcc/data/r787_xmod_variant_bind_default/pkg/pkg.ww new file mode 100644 index 00000000..c564d08e --- /dev/null +++ b/test/wcc/data/r787_xmod_variant_bind_default/pkg/pkg.ww @@ -0,0 +1,9 @@ +// exports the 3-variant union for the r787 bind_and_default scenario. +package pkg; +export type a = !void; +export type b = !void; +export type c = !void; +export type u2 = !(a | b | c); +export fn mka() u2 = { let x: a; return x; }; +export fn mkb() u2 = { let y: b; return y; }; +export fn mkc() u2 = { let z: c; return z; }; diff --git a/test/wcc/data/r787_xmod_variant_foreign_qualifier/case.ww b/test/wcc/data/r787_xmod_variant_foreign_qualifier/case.ww new file mode 100644 index 00000000..086e4071 --- /dev/null +++ b/test/wcc/data/r787_xmod_variant_foreign_qualifier/case.ww @@ -0,0 +1,19 @@ +//ww:error c "is not a variant of" ww "ambiguous without nominal layout" +// migrated from test/wcc/787_xmod_variant_match.c: a same-leaf variant from a DIFFERENT module is NOT a variant of the scrutinee — guards #13 against bare leaf-strip false-accept. +package pkg; +export type a = !void; +export type b = !void; +export type u = !(a | b); +export fn mka() u = { let x: a; return x; }; +package o; +export type a = !void; +package main; +import pkg; +import o; +fn classify(e: pkg.u) i32 = { + match (e) { + case o.a => return 1; + case => return 0; + }; +}; +export fn main() i32 = { return classify(pkg.mka()); }; diff --git a/test/wcc/data/r787_xmod_variant_no_default/case.ww b/test/wcc/data/r787_xmod_variant_no_default/case.ww new file mode 100644 index 00000000..7a332312 --- /dev/null +++ b/test/wcc/data/r787_xmod_variant_no_default/case.ww @@ -0,0 +1,13 @@ +//ww:run-exit 21 +// migrated from test/wcc/787_xmod_variant_match.c: cross-module bare-variant arms with NO default — casecovers exhaustiveness across modules (#13). +package main; +import pkg; +fn classify(e: pkg.u) i32 = { + match (e) { + case pkg.a => return 1; + case pkg.b => return 2; + }; +}; +export fn main() i32 = { + return classify(pkg.mkb()) * 10 + classify(pkg.mka()); +}; diff --git a/test/wcc/data/r787_xmod_variant_no_default/pkg/pkg.ww b/test/wcc/data/r787_xmod_variant_no_default/pkg/pkg.ww new file mode 100644 index 00000000..fe564eb3 --- /dev/null +++ b/test/wcc/data/r787_xmod_variant_no_default/pkg/pkg.ww @@ -0,0 +1,7 @@ +// exports !void variants + union for the r787 no-default scenario. +package pkg; +export type a = !void; +export type b = !void; +export type u = !(a | b); +export fn mka() u = { let x: a; return x; }; +export fn mkb() u = { let y: b; return y; }; diff --git a/test/wcc/data/r787_xmod_variant_shape_a/case.ww b/test/wcc/data/r787_xmod_variant_shape_a/case.ww new file mode 100644 index 00000000..0f8c9daf --- /dev/null +++ b/test/wcc/data/r787_xmod_variant_shape_a/case.ww @@ -0,0 +1,14 @@ +//ww:run-exit 12 +// migrated from test/wcc/787_xmod_variant_match.c: Shape A — a dotted body variant (errs.bad) keeps ITS OWN qualifier in case matching. +package main; +import pkg; +import errs; +fn classify(x: pkg.e) i32 = { + match (x) { + case errs.bad => return 1; + case pkg.local => return 2; + }; +}; +export fn main() i32 = { + return classify(pkg.mkbad()) * 10 + classify(pkg.mklocal()); +}; diff --git a/test/wcc/data/r787_xmod_variant_shape_a/errs/errs.ww b/test/wcc/data/r787_xmod_variant_shape_a/errs/errs.ww new file mode 100644 index 00000000..0bd5805b --- /dev/null +++ b/test/wcc/data/r787_xmod_variant_shape_a/errs/errs.ww @@ -0,0 +1,3 @@ +// the foreign variant module for the r787 Shape A scenario. +package errs; +export type bad = !void; diff --git a/test/wcc/data/r787_xmod_variant_shape_a/pkg/pkg.ww b/test/wcc/data/r787_xmod_variant_shape_a/pkg/pkg.ww new file mode 100644 index 00000000..c94438a7 --- /dev/null +++ b/test/wcc/data/r787_xmod_variant_shape_a/pkg/pkg.ww @@ -0,0 +1,7 @@ +// union whose BODY holds a dotted cross-module variant (errs.bad | local). +package pkg; +import errs; +export type local = !void; +export type e = !(errs.bad | local); +export fn mkbad() e = { let x: errs.bad; return x; }; +export fn mklocal() e = { let y: local; return y; }; diff --git a/test/wcc/data/r929_match_3arm_shadowed/case.ww b/test/wcc/data/r929_match_3arm_shadowed/case.ww new file mode 100644 index 00000000..69cceb10 --- /dev/null +++ b/test/wcc/data/r929_match_3arm_shadowed/case.ww @@ -0,0 +1,26 @@ +//ww:run +// migrated from test/wcc/929_match_4arm_cross_module_run.c: 3-arm boundary — arm 2 must reach its body under the shadowed callee. +package a; +export type more = void; +export type done = void; +export fn next(k: i32) (rune | done | more) = { + if (k == 0) { return 0x42u32: rune; }; + if (k == 1) { let v: done; return v; }; + let v: more; return v; +}; +package b; +import a; +type done = void; +fn next(k: i32) i32 = { + match (a.next(k)) { + case let r: rune => return 1i32 + (r: i32); + case let dn: a.done => return 2; + case let m: a.more => return 3; + }; +}; +export fn main() i32 = { + if (next(0) != 67) { return 11; }; + if (next(1) != 2) { return 12; }; + if (next(2) != 3) { return 13; }; + return 0; +}; diff --git a/test/wcc/data/r929_match_4arm_mixed_kinds/case.ww b/test/wcc/data/r929_match_4arm_mixed_kinds/case.ww new file mode 100644 index 00000000..c8b57dbf --- /dev/null +++ b/test/wcc/data/r929_match_4arm_mixed_kinds/case.ww @@ -0,0 +1,26 @@ +//ww:run +// migrated from test/wcc/929_match_4arm_cross_module_run.c: mixed variant kinds (i32|str|rune|u8) — the shadowed-resolution fix is not shape-specific. +package a; +export fn next(k: i32) (i32 | str | rune | u8) = { + if (k == 0) { return 7; }; + if (k == 1) { return "hi"; }; + if (k == 2) { return 0x45u32: rune; }; + return 9u8; +}; +package b; +import a; +fn next(k: i32) i32 = { + match (a.next(k)) { + case let n: i32 => return 100 + n; + case let s: str => return 200i32 + (s.len: i32); + case let r: rune => return 300i32 + (r: i32); + case let c: u8 => return 400i32 + (c: i32); + }; +}; +export fn main() i32 = { + if (next(0) != 107) { return 11; }; + if (next(1) != 202) { return 12; }; + if (next(2) != 369) { return 13; }; + if (next(3) != 409) { return 14; }; + return 0; +}; diff --git a/test/wcc/data/r929_match_4arm_shadowed_canonical/case.ww b/test/wcc/data/r929_match_4arm_shadowed_canonical/case.ww new file mode 100644 index 00000000..b71a094e --- /dev/null +++ b/test/wcc/data/r929_match_4arm_shadowed_canonical/case.ww @@ -0,0 +1,30 @@ +//ww:run +// migrated from test/wcc/929_match_4arm_cross_module_run.c: canonical 4-arm — caller fn `next` shadows callee a.next; every arm body must fire (#31 fnretlookupmod). +package a; +export type more = void; +export type invalid = !void; +export type done = void; +export fn next(k: i32) (rune | done | more | invalid) = { + if (k == 0) { return 0x41u32: rune; }; + if (k == 1) { let v: done; return v; }; + if (k == 2) { let v: more; return v; }; + let v: invalid; return v; +}; +package b; +import a; +type done = void; +fn next(k: i32) i32 = { + match (a.next(k)) { + case let r: rune => return 100i32 + (r: i32); + case let dn: a.done => return 200; + case let m: a.more => return 300; + case let e: a.invalid => return 400; + }; +}; +export fn main() i32 = { + if (next(0) != 165) { return 11; }; + if (next(1) != 200) { return 12; }; + if (next(2) != 300) { return 13; }; + if (next(3) != 400) { return 14; }; + return 0; +}; diff --git a/test/wcc/data/r929_match_4arm_shadowed_reverse/case.ww b/test/wcc/data/r929_match_4arm_shadowed_reverse/case.ww new file mode 100644 index 00000000..74c581eb --- /dev/null +++ b/test/wcc/data/r929_match_4arm_shadowed_reverse/case.ww @@ -0,0 +1,30 @@ +//ww:run +// migrated from test/wcc/929_match_4arm_cross_module_run.c: reverse arm order — dispatch follows the callee's variant indices, not source order. +package a; +export type more = void; +export type invalid = !void; +export type done = void; +export fn next(k: i32) (rune | done | more | invalid) = { + if (k == 0) { return 0x46u32: rune; }; + if (k == 1) { let v: done; return v; }; + if (k == 2) { let v: more; return v; }; + let v: invalid; return v; +}; +package b; +import a; +type done = void; +fn next(k: i32) i32 = { + match (a.next(k)) { + case let e: a.invalid => return 4; + case let m: a.more => return 3; + case let dn: a.done => return 2; + case let r: rune => return 100i32 + (r: i32); + }; +}; +export fn main() i32 = { + if (next(0) != 170) { return 11; }; + if (next(1) != 2) { return 12; }; + if (next(2) != 3) { return 13; }; + if (next(3) != 4) { return 14; }; + return 0; +}; diff --git a/test/wcc/data/r929_match_5arm_shadowed/case.ww b/test/wcc/data/r929_match_5arm_shadowed/case.ww new file mode 100644 index 00000000..9367c4c4 --- /dev/null +++ b/test/wcc/data/r929_match_5arm_shadowed/case.ww @@ -0,0 +1,34 @@ +//ww:run +// migrated from test/wcc/929_match_4arm_cross_module_run.c: 5-arm scaling — arms 3 and 4 must each reach their body. +package a; +export type more = void; +export type invalid = !void; +export type done = void; +export type stop = void; +export fn next(k: i32) (rune | done | more | invalid | stop) = { + if (k == 0) { return 0x43u32: rune; }; + if (k == 1) { let v: done; return v; }; + if (k == 2) { let v: more; return v; }; + if (k == 3) { let v: invalid; return v; }; + let v: stop; return v; +}; +package b; +import a; +type done = void; +fn next(k: i32) i32 = { + match (a.next(k)) { + case let r: rune => return 100i32 + (r: i32); + case let dn: a.done => return 2; + case let m: a.more => return 3; + case let e: a.invalid => return 4; + case let s: a.stop => return 5; + }; +}; +export fn main() i32 = { + if (next(0) != 167) { return 11; }; + if (next(1) != 2) { return 12; }; + if (next(2) != 3) { return 13; }; + if (next(3) != 4) { return 14; }; + if (next(4) != 5) { return 15; }; + return 0; +}; diff --git a/test/wcc/data/r929_match_6arm_shadowed/case.ww b/test/wcc/data/r929_match_6arm_shadowed/case.ww new file mode 100644 index 00000000..181540b8 --- /dev/null +++ b/test/wcc/data/r929_match_6arm_shadowed/case.ww @@ -0,0 +1,38 @@ +//ww:run +// migrated from test/wcc/929_match_4arm_cross_module_run.c: 6-arm — every arm beyond the caller's variant count was broken, not just arm 2/3. +package a; +export type more = void; +export type invalid = !void; +export type done = void; +export type stop = void; +export type eof = void; +export fn next(k: i32) (rune | done | more | invalid | stop | eof) = { + if (k == 0) { return 0x44u32: rune; }; + if (k == 1) { let v: done; return v; }; + if (k == 2) { let v: more; return v; }; + if (k == 3) { let v: invalid; return v; }; + if (k == 4) { let v: stop; return v; }; + let v: eof; return v; +}; +package b; +import a; +type done = void; +fn next(k: i32) i32 = { + match (a.next(k)) { + case let r: rune => return 100i32 + (r: i32); + case let dn: a.done => return 2; + case let m: a.more => return 3; + case let e: a.invalid => return 4; + case let s: a.stop => return 5; + case let f: a.eof => return 6; + }; +}; +export fn main() i32 = { + if (next(0) != 168) { return 11; }; + if (next(1) != 2) { return 12; }; + if (next(2) != 3) { return 13; }; + if (next(3) != 4) { return 14; }; + if (next(4) != 5) { return 15; }; + if (next(5) != 6) { return 16; }; + return 0; +};