/* * 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 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[96], adir[128], cmd[2048]; snprintf(tmpdir, sizeof tmpdir, "/tmp/m4cmr_%d_%d", getpid(), i); snprintf(adir, sizeof adir, "%s/a", tmpdir); mkdir(tmpdir, 0755); mkdir(adir, 0755); if (write_file(adir, "a.ww", r->a_src) != 0) return -1; if (write_file(tmpdir, "b.ww", r->b_src) != 0) return -1; /* 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); snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir); runwait(cmd); return -1; } char outbin[160]; snprintf(outbin, sizeof outbin, "%s/b", tmpdir); int got = runwait(outbin); snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir); runwait(cmd); return got; } 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; }