/* * 792_spread_variant_match — project #209 close. Pins that the wwstage * checker accepts a `match` over a tagged union with a `...inner` SPREAD * variant, byte-identically with cstage (rule-10). * * THE BUG (wwstage-CHECKER-only, cs!=ww): a spread variant * `type field = (...inner | str)` flattens its inner union's members * into `field` (i64|bool|rune|str). cstage's resolve_type performs the * flatten at type-build (cmd/wcc/check.c:651-674), so its match-arm * validity + exhaustiveness see the flattened members. wwstage's checker * (selfhost/cmd/wcc/check.ww checkmatchexhaust) walked the RAW AST * `u.list`, which keeps the spread unexpanded — every member arm tripped * "case: not a variant of scrutinee" and the phantom spread node tripped * "match: variant not handled". So any module that matches over a spread * union (e.g. fmt's `field = (...formattable | *mods)`) was * wwstage-uncompilable. cstage compiled it fine. Hare flattens spreads; * align UP to cstage (a too-strict checker, NOT a down-align). * * THE FIX (#209): casevariantin + a checkvariantcovered coverage helper * recurse into a spread variant's inner-union members (resolvealias → * N_TTAGGED → walk members), mirroring cstage's flatten at the AST * layer; the #61a tinfo build additionally sizes the union off the * flattened MEMBERS (not the whole inner union) so the slot matches * cstage's. cgen already dispatches off the flattened tinfo.params * (flatvariantidxt), so no cgen change is needed — checker-only. * * The PRE-FIX failure mode is a wwstage CHECKER REJECT, so the * discriminator is `w6c_ww` on the driver-produced combined.ww * succeeding AT ALL — pre-fix it errored out; post-fix it succeeds AND * is byte-id with cstage's w6c. The probe stays on the param+match shape * (a fn taking the spread union and matching it, exhaustive, no * default): that is exactly what #209's checker fix covers and is * byte-id. (Spread-union CONSTRUCTION/return-ABI is a separate, * pre-existing both-stage cgen path — out of #209 scope — so it is * deliberately NOT exercised here.) Self-contained single-file probe. */ #define _GNU_SOURCE #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; } fclose(fa); fclose(fb); return rc; } static const char SRC[] = "package main;\n" "\n" "type inner = (i64 | bool | rune);\n" "type field = (...inner | str);\n" "\n" "fn classify(f: field) i64 = {\n" " match (f) {\n" " case let n: i64 => return n;\n" " case let b: bool => return 1;\n" " case let r: rune => return 2;\n" " case let s: str => return (s.len: i64);\n" " };\n" "};\n" "\n" "export fn main() i32 = { return 0; };\n"; 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 w6c[2100], w6c_ww[2100]; snprintf(w6c, sizeof w6c, "%s/w6c", bin); snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); if (access(w6c_ww, X_OK) != 0) { fprintf(stderr, "792: w6c_ww missing — cannot run the cs==ww " "byte-id gate (the whole point of this test)\n"); return 1; } char dir[] = "/tmp/ww792_XXXXXX"; if (mkdtemp(dir) == NULL) { fprintf(stderr, "792: mkdtemp\n"); return 1; } char path[1024], cmd[4096], comb[1024], cs_s[1024], ws_s[1024]; int rc = 0; snprintf(path, sizeof path, "%s/main.ww", dir); FILE *f = fopen(path, "wb"); if (!f) { fprintf(stderr, "792: write main.ww\n"); rc = 1; goto done; } fputs(SRC, f); fclose(f); snprintf(comb, sizeof comb, "%s/main.combined.ww", dir); /* cstage driver build: produces the combined.ww + must accept. */ snprintf(cmd, sizeof cmd, "cd %s && %s/ww build -I %s %s/main.ww", dir, bin, dir, dir); if (runwait(cmd) != 0) { fprintf(stderr, "792: cstage build failed\n"); rc = 1; goto done; } if (access(comb, 0) != 0) { fprintf(stderr, "792: no combined.ww produced\n"); rc = 1; goto done; } /* The #209 discriminator: raw w6c_ww on the combined. Pre-fix the * wwstage checker REJECTED the spread-union match arms (non-zero * exit). Post-fix it accepts AND is byte-id with cstage's w6c. */ snprintf(cs_s, sizeof cs_s, "%s/cs.s", dir); snprintf(ws_s, sizeof ws_s, "%s/ww.s", dir); snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, comb); if (runwait(cmd) != 0) { fprintf(stderr, "792: w6c on combined failed\n"); rc = 1; goto done; } snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, ws_s, comb); if (runwait(cmd) != 0) { fprintf(stderr, "792: w6c_ww on combined failed " "(#209 spread-union match reject?)\n"); rc = 1; goto done; } if (slurp_eq(cs_s, ws_s) != 0) { fprintf(stderr, "792: cs.s/ww.s DIFFER (rule-10 byte-id " "violation)\n"); rc = 1; goto done; } printf("spread_variant_match: ok (w6c_ww accepts spread match + " "cs==ww byte-id)\n"); done: snprintf(cmd, sizeof cmd, "rm -rf %s", dir); (void)runwait(cmd); return rc; }