/* * 706_param_shadow_mod — "value names and module names are disjoint." * * Pre-fix (task #19): a fn param / local-let named `shadowmod` while * the same source carried `use shadowmod;` would compile cleanly and * silently miscompile any `shadowmod.X` body lookup — cstage's cexpr * N_DOT path resolved the inner ident through the shadow's value * bits, then emitted CALL through the str's .ptr field. Symptom in * the field was a SIGSEGV inside lib/log's lprintfln (worked around * by renaming `fmt: str` → `format: str` at commit 6b6d7dd). * * Post-fix: cstage check.c (and wwstage check.ww, run by wwdump_ww) * refuse the bind at the decl site with a ` '' shadows * imported module ''` diagnostic. Same-leaf top-level decls * (e.g. `use fnmatch; fn fnmatch(...)`) are exempt — the rule fires * only for nested-scope binds whose declaring source file imports * the module. * * row | shape | gate * ---------------------+--------------------------------------+-------- * neg_param | `fn p(shadowmod: str)` | fail * neg_let | `let shadowmod: i32 = 0;` | fail * neg_mlet | `let (shadowmod, x) = pair();` | fail * neg_forrange_single | `for (let shadowmod .. s)` | fail * neg_forrange_tuple | `for (let (shadowmod, x) .. s)` | fail * neg_mcase | `match (r) { case let shadowmod ... }` | fail * pos_rename | rename param away from `shadowmod` | exit=42 * * Fixtures live in test/wcc/data/paramshadowmod/. Cstage-only: * wwstage's check.ww runs only inside wwdump_ww (diagnostic), and * the actual selfhost compile pipeline (994_w6c_ww) doesn't trip * because wwstage's cgen takes the module-qualified emit path for * any N_DOT-callee bare ident — see STATUS.md's `Wwstage no- * checkfile-pass smell` note + #11 (deferred wwstage checkfile pass). */ #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; } static int run_neg(const char *driver, const char *fixdir, const char *tag) { char src[64]; snprintf(src, sizeof src, "neg_%s.ww", tag); char cmd[2048]; /* cd into the fixture dir so the driver's source-dir-first * import search resolves `use shadowmod;`. */ snprintf(cmd, sizeof cmd, "cd %s && %s build %s >/dev/null 2>&1", fixdir, driver, src); int rc = runwait(cmd); if (rc == 0) { fprintf(stderr, "param_shadow_mod[neg_%s]: build unexpectedly succeeded " "— shadow rule did not fire\n", tag); return 1; } return 0; } static int run_pos(const char *driver, const char *fixdir) { char cmd[2048]; snprintf(cmd, sizeof cmd, "cd %s && %s build pos_rename.ww >/dev/null 2>&1", fixdir, driver); if (runwait(cmd) != 0) { fprintf(stderr, "param_shadow_mod[pos_rename]: build failed — rule " "over-triggered on the rename\n"); return 1; } char bin[2048]; snprintf(bin, sizeof bin, "%s/pos_rename", fixdir); int got = runwait(bin); unlink(bin); if (got != 42) { fprintf(stderr, "param_shadow_mod[pos_rename]: exit=%d want=42\n", got); return 1; } return 0; } /* * pos_selfimp — same-module self-import. selfimp/selfimptest.ww * carries `use selfimp;` from inside the module whose tag is also * "selfimp" (matches lib/fmt/fmttest.ww's shape that surfaced the * over-trigger originally). check_module_shadow's u->module == * u->str skip must drop the directive from the import scan, so the * `selfimp: str` param does NOT trip the rule. */ static int run_pos_selfimp(const char *driver, const char *fixdir) { char cmd[2048]; snprintf(cmd, sizeof cmd, "cd %s && %s build selfimp/selfimptest.ww >/dev/null 2>&1", fixdir, driver); if (runwait(cmd) != 0) { fprintf(stderr, "param_shadow_mod[pos_selfimp]: build failed — " "self-import skip regressed\n"); return 1; } char bin[2048]; snprintf(bin, sizeof bin, "%s/selfimptest", fixdir); int got = runwait(bin); unlink(bin); if (got != 7) { fprintf(stderr, "param_shadow_mod[pos_selfimp]: exit=%d want=7\n", got); return 1; } return 0; } 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]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); char fixdir[1024]; if (getcwd(fixdir, sizeof fixdir) == NULL) return 1; size_t cwd_n = strlen(fixdir); const char *rel = "/test/wcc/data/paramshadowmod"; if (cwd_n + strlen(rel) + 1 >= sizeof fixdir) return 1; memcpy(fixdir + cwd_n, rel, strlen(rel) + 1); int fail = 0; fail += run_neg(cdrv, fixdir, "param"); fail += run_neg(cdrv, fixdir, "let"); fail += run_neg(cdrv, fixdir, "mlet"); fail += run_neg(cdrv, fixdir, "forrange_single"); fail += run_neg(cdrv, fixdir, "forrange_tuple"); fail += run_neg(cdrv, fixdir, "mcase"); fail += run_pos(cdrv, fixdir); fail += run_pos_selfimp(cdrv, fixdir); if (fail) { fprintf(stderr, "param_shadow_mod: %d row(s) failed\n", fail); return 1; } printf("param_shadow_mod: 8/8 ok\n"); return 0; }