708 pinned param + clet + self-import-skip after #19 landed; the other 4 sites where check_module_shadow is wired (N_MLET, N_FORRANGE single, N_FORRANGE tuple, N_MCASE) had no dedicated negative-compile row. Worker-19's note that all 6 sites use identical call shape is true today but unpinned by tests; a future asymmetric edit could silently disable enforcement on one site. Adds neg_mlet (let-tuple binder shadows), neg_forrange_single (`for shadowmod of ...`), neg_forrange_tuple (`for (shadowmod, x) of pairs`), neg_mcase (`case let shadowmod:`). Each errors at the binder decl line with the canonical "<kind> '<name>' shadows imported module '<name>'" message. 708's main now runs 8/8 (param, let, mlet, forrange_single, forrange_tuple, mcase, pos_rename, pos_selfimp).
171 lines
5.3 KiB
C
171 lines
5.3 KiB
C
/*
|
|
* 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 `<kind> '<name>' shadows
|
|
* imported module '<name>'` 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.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
|
|
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;
|
|
}
|