/* * 794_xmod_ident_prefer — project #55 close. Pins that the wwstage * checker resolves a BARE value-ident with same-MODULE preference * (selfhost/cmd/wcc/check.ww exprtype N_IDENT), mirroring cstage's * scope_lookup_prefer (cmd/wcc/check.c:66). * * THE BUG (wwstage-CHECKER-only, #55): exprtype's N_IDENT branch looked * a bare value-ident up via the flat-scope `scopelookup`, which bucket- * walks and returns whichever same-leaf symbol heads the bucket — the * LAST-registered one (scopedefineinmodule prepends). In a combined * unit the root `main` package registers last, so a bare `v` referenced * inside an IMPORTED module `aa` (curmod=aa) resolved to `main.v`, not * `aa.v` — dragging the wrong decl's type. cstage routes the same site * through scope_lookup_prefer(cur, cur_mod, name) and binds aa.v, so it * built fine; wwstage mis-typed the use and REJECTED. This is the * exprtype-N_IDENT member of the #55 bare-leaf cluster (sibling #56 * fixed the N_CALL-callee path, #53 the bare-TNAME path). * * THE FIX (#55): scopelookup(c.cur, e.str) -> scopelookupprefer(c.cur, * c.curmod, e.str) at the single exprtype N_IDENT site. * * THE FIXTURE: `aa` exports a global `v: i32` and `fn getv() i32 = * { return v; }`. The root `main` declares a same-leaf `fn v() i64` and * calls aa.getv(). Registration order puts main.v (the i64 fn) at the * head of the flat-scope bucket, so a non-preferring lookup binds it. * - cstage builds (prefer -> aa.v: i32, `return v` is i32) and runs: * main returns aa.getv() == 7. * - PRE-fix wwstage resolved `v` -> main.v (i64) and rejected * `return: not assignable (i64 -> i32)` on the combined unit. * - POST-fix wwstage prefers curmod=aa -> aa.v (i32) and ACCEPTS. * * DISCRIMINATOR (787 #13 model): the cstage driver emits the combined * unit (its checker is already correct), then we re-check that combined * with w6c_ww. PRE-fix it errored out (non-zero); POST-fix it succeeds. * w6c_ww REJECTING the combined is the #55 red. * * NOTE — no cs==ww byte-id assertion here, deliberately: a minimal * cross-module same-leaf VALUE-ident reference also trips a SEPARATE, * still-open cgen-side bare-leaf bug (cgenexpr.ww cgident likewise uses * the non-preferring scopelookup, so the value-load resolves the wrong * symbol/width), which would diverge the asm independently of this * CHECKER fix. The real #55 fmt manifestation flows through checker- * stamped tagged-union variant indices (not a value-load), so it does * not hit the cgen path; this minimal probe does, hence the reject-> * accept polarity is the sound discriminator for the checker fix. The * cgen-side sibling is tracked separately (the #55 cluster residual). * * GATE POLARITY: must stay GREEN. Red means either the cstage routing * regressed (build/run) or wwstage rejected a valid same-module-preferred * ident again (w6c_ww rejects the combined). */ #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 file { const char *name; const char *src; }; static const struct file files[] = { { "aa.ww", "package aa;\n" "export let v: i32 = 7;\n" "export fn getv() i32 = {\n" " return v;\n" "};\n" }, { "main.ww", "package main;\n" "import aa;\n" "fn v() i64 = { return 100; };\n" "export fn main() i32 = {\n" " return aa.getv();\n" "};\n" }, { NULL, NULL } }; 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, "794: w6c_ww missing — cannot run the wwstage " "accept gate (the whole point of this test)\n"); return 1; } char dir[] = "/tmp/ww794_XXXXXX"; if (mkdtemp(dir) == NULL) { fprintf(stderr, "794: mkdtemp failed\n"); return 1; } char path[1024], cmd[4096]; int rc = 0; for (int i = 0; files[i].name; i++) { snprintf(path, sizeof path, "%s/%s", dir, files[i].name); FILE *f = fopen(path, "wb"); if (!f) { fprintf(stderr, "794: write %s\n", files[i].name); rc = 1; goto done; } fputs(files[i].src, f); fclose(f); } /* cstage driver build (prefer is correct) emits the combined unit. */ snprintf(cmd, sizeof cmd, "cd %s && %s/ww build -I %s %s/main.ww", dir, bin, dir, dir); if (runwait(cmd) != 0) { fprintf(stderr, "794: cstage build failed\n"); rc = 1; goto done; } /* cstage runtime pins the routing: main returns aa.getv() == 7. */ snprintf(path, sizeof path, "%s/main", dir); int got = runwait(path); if (got != 7) { fprintf(stderr, "794: cstage exit %d, want 7\n", got); rc = 1; goto done; } char comb[1024]; snprintf(comb, sizeof comb, "%s/main.combined.ww", dir); if (access(comb, 0) != 0) { fprintf(stderr, "794: no combined.ww emitted\n"); rc = 1; goto done; } /* Sanity: cstage's own backend re-accepts its combined. */ char cs_s[1024], ws_s[1024]; 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, "794: w6c on combined failed\n"); rc = 1; goto done; } /* The #55 discriminator: w6c_ww must ACCEPT the combined unit. * PRE-fix it rejected (bare `v` -> main.v: i64 -> return mismatch); * POST-fix it prefers curmod=aa -> aa.v: i32 and accepts. */ snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, ws_s, comb); if (runwait(cmd) != 0) { fprintf(stderr, "794: w6c_ww REJECTED the combined (#55: bare " "value-ident resolved the wrong module's same-leaf symbol)\n"); rc = 1; goto done; } done: snprintf(cmd, sizeof cmd, "rm -rf %s", dir); (void)runwait(cmd); if (rc) { fprintf(stderr, "794 xmod_ident_prefer: FAILED\n"); return 1; } printf("xmod_ident_prefer: ok (cstage run==7 + w6c_ww accepts combined)\n"); return 0; }