test: port 787/929 cross-module match carriers to corpus fixtures

787_xmod_variant_match.c -> r787_xmod_variant_no_default (run-exit 21),
  r787_xmod_variant_bind_default (42), r787_xmod_variant_shape_a (12)
  (pkg/errs module dirs), r787_xmod_variant_foreign_qualifier (staged
  error: w6c 'is not a variant of', w6c_ww rejects via the #95
  ambiguity guard — the carrier asserted reject polarity only)
929_match_4arm_cross_module_run.c -> r929_match_{4arm_shadowed_
  canonical,3arm_shadowed,5arm_shadowed,6arm_shadowed,4arm_mixed_kinds,
  4arm_shadowed_reverse} (run; single-file a+b packages, dropping the
  carrier's accidental doubled 'package b;' line)

929's byte-id upgrades from 994-delegated to per-fixture via
test-data-byteid; the asm-level distinct-CMPQ sentinel remains 728's
separate concern. All rows probed green on both stages.
This commit is contained in:
2026-08-08 14:29:49 +09:00
parent c78bf6ebca
commit 2680fba303
17 changed files with 279 additions and 801 deletions

View File

@@ -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,

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
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 <stem>.sepwork/<pkg>.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;
}

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
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;
}

View File

@@ -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;
};

View File

@@ -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; };

View File

@@ -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()); };

View File

@@ -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());
};

View File

@@ -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; };

View File

@@ -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());
};

View File

@@ -0,0 +1,3 @@
// the foreign variant module for the r787 Shape A scenario.
package errs;
export type bad = !void;

View File

@@ -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; };

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};