/* * 989_ampfncollide_run (#4, c2) — `&fn` synthesis must prefer the current * module's fn when a same-leaf fn is declared in a later module. * * THE BUG (review item #4, 2026-06-11): unoptype's TK_AMP fn-ident synth * (check.ww ~2677) and assignableaddrfn (check.ww ~4182) looked the fn up * with bare scopelookup — no (name,module) preference. scopedefineinmodule * PREPENDS, so a LATER module's same-leaf fn heads the bucket chain and the * bare lookup binds ITS signature. Two faces: * - LOUD: `let p: *hfn = &handler` in beta (hfn = fn(i32)i32, beta.handler * is i32->i32) is REJECTED by wwstage ("let: not assignable") because it * binds gamma.handler(str)str, while cstage (scope_lookup_prefer with * cur_mod, check.c:410/1305) binds beta.handler and accepts. Valid code * wrongly rejected. * - SILENT (cat-A): `let p: *gfn = &handler` where gfn = fn(str)str (the * FOREIGN sig) is REJECTED by cstage (beta.handler is i32->i32, not * assignable) but ACCEPTED by wwstage (it binds gamma.handler(str)str, * which matches gfn) — a type-confused fn pointer (LEAQ beta.handler * into a *fn(str)str slot) in a green build. * THE FIX: both sites use scopelookupprefer(c.cur, c.curmod, ...). * * row | shape | result (cs==ww) * -------+-----------------------------------------+----------------- * loud | *hfn = &handler (own-sig slot) | run 42 (was ww reject) * silent | *gfn = &handler (foreign-sig slot) | build FAIL (was ww ok) * * Single-file multi-package source is the sanctioned shape (cmd/ww/main.c). * want = -1 means "build must fail on both stages". */ #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 *src; int want; }; static const struct row rows[] = { { "loud", "package beta;\n" "export type hfn = fn(x: i32) i32;\n" "export fn handler(x: i32) i32 = { return x + 41; };\n" "export fn usebeta() i32 = {\n" " let p: *hfn = &handler;\n" " return (*p)(1);\n" "};\n" "package gamma;\n" "export fn handler(s: str) str = { return s; };\n" "package main;\n" "import beta;\n" "import gamma;\n" "fn main() int = { return beta.usebeta(): int; };\n", 42 }, { "silent", "package beta;\n" "export type gfn = fn(s: str) str;\n" "export fn handler(x: i32) i32 = { return x + 41; };\n" "export fn usebeta() i32 = {\n" " let p: *gfn = &handler;\n" " if (p == p) { return 1; };\n" " return 0;\n" "};\n" "package gamma;\n" "export fn handler(s: str) str = { return s; };\n" "package main;\n" "import beta;\n" "import gamma;\n" "fn main() int = { return beta.usebeta(): int; };\n", -1 }, }; static int run_build(const char *driver, const struct row *r, int i) { char src[64], tmpdir[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/afc_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/afc_%d_d_%d", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -2; fputs(r->src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", tmpdir, driver, src); int brc = runwait(cmd); const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char outbin[128]; snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); char *dot = strrchr(outbin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; int got = -1; if (brc == 0) got = runwait(outbin); unlink(src); unlink(outbin); rmdir(tmpdir); return brc == 0 ? got : -1; /* -1 == build failed */ } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[1024]; 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[1024], wdrv[1024]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); int have_ww = (access(wdrv, X_OK) == 0); int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int i = 0; i < n; i++) { total++; int gc = run_build(cdrv, &rows[i], i); if (gc != rows[i].want) { fprintf(stderr, "ampfncollide[cstage][%s]: got=%d want=%d\n", rows[i].label, gc, rows[i].want); fail++; } if (!have_ww) { fprintf(stderr, "ampfncollide: skip wwstage (no %s)\n", wdrv); continue; } int gw = run_build(wdrv, &rows[i], i); if (gw != gc) { fprintf(stderr, "ampfncollide[%s]: cs=%d != ww=%d " "(&fn cross-module same-leaf misbind — #4 c2)\n", rows[i].label, gc, gw); fail++; } if (gw != rows[i].want) { fprintf(stderr, "ampfncollide[wwstage][%s]: got=%d want=%d\n", rows[i].label, gw, rows[i].want); fail++; } } if (fail) { fprintf(stderr, "ampfncollide_run: %d/%d checks failed\n", fail, total); return 1; } printf("ampfncollide_run: %d/%d ok\n", total, total); return 0; }