cstage+selfhost+test: refuse let/param shadow of imported module (#19)
When `use fmt;` is in scope and a local/param named `fmt` shadows it, `fmt.X` in the body silently resolved to the str-typed value sym and emitted `CALL AX` through str.ptr → runtime crash. Surfaced during #15 (lib/log's printfln family); worked around by renaming the param `fmt`→`format`. Per rob + user, option (C): "value names and module names are disjoint." Refuse the shadow at the decl site. Single rule, no non-local reasoning, no silent footgun if a future lib/X exports a new leaf. cstage: src_imports walks file->list for N_USE entries (skipping self-imports where u->module == u->str — same-module fixtures like lib/fmt/fmttest.ww carry these); check_module_shadow runs before each SK_PARAM / SK_VAR scope_define (param, clet, mlet, forrange single + tuple, mcase). Wwstage mirror in check.ww; wwdump-only diagnostic today, full enforcement waits on #11 checkfile pass. Bootstrap byte-id holds — no codegen change. One source patch in selfhost/cmd/w6a/main.ww renames an outer `let asm: asm_;` to `s` to sidestep task #27 (cstage localoff scope-blind dedup); unrelated to #19 but the new rule's first run flagged it as a self-shadow. Test 708 (param_shadow_mod): 4 rows — neg_param (param shadow errs at fn decl line), neg_let (let shadow errs at let decl), pos_rename (rename compiles + runs), pos_selfimp (in-module use is skipped). 4 wired sites without dedicated rows deferred to task #28. Follow-up: lib/log can revert format→fmt now that the silent crash is impossible.
This commit is contained in:
10
test/wcc/data/paramshadowmod/neg_let.ww
Normal file
10
test/wcc/data/paramshadowmod/neg_let.ww
Normal file
@@ -0,0 +1,10 @@
|
||||
// neg_let — local `let shadowmod: i32 = ...` shadows the imported
|
||||
// module from inside a fn body. Same rule fires for nested-scope
|
||||
// let binds, not just params.
|
||||
|
||||
use shadowmod;
|
||||
|
||||
export fn main() i32 = {
|
||||
let shadowmod: i32 = 0i32;
|
||||
return shadowmod;
|
||||
};
|
||||
13
test/wcc/data/paramshadowmod/neg_param.ww
Normal file
13
test/wcc/data/paramshadowmod/neg_param.ww
Normal file
@@ -0,0 +1,13 @@
|
||||
// neg_param — fn param `shadowmod: str` shadows the imported module.
|
||||
// Under the "value names and module names are disjoint" rule the
|
||||
// build must fail with a clear diagnostic at the param decl site.
|
||||
|
||||
use shadowmod;
|
||||
|
||||
fn probe(shadowmod: str) i32 = {
|
||||
return shadowmod.len;
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
return probe("hi");
|
||||
};
|
||||
14
test/wcc/data/paramshadowmod/pos_rename.ww
Normal file
14
test/wcc/data/paramshadowmod/pos_rename.ww
Normal file
@@ -0,0 +1,14 @@
|
||||
// pos_rename — positive case. The fn param is renamed away from the
|
||||
// imported module's bareword, so the rule doesn't fire and the body
|
||||
// can call `shadowmod.say()` cleanly. Built + run; exit code = 42.
|
||||
|
||||
use shadowmod;
|
||||
|
||||
fn probe(s: str) i32 = {
|
||||
let _ = s;
|
||||
return shadowmod.say();
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
return probe("hi");
|
||||
};
|
||||
7
test/wcc/data/paramshadowmod/selfimp/selfimp.ww
Normal file
7
test/wcc/data/paramshadowmod/selfimp/selfimp.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
// pos_selfimp/selfimp.ww — minimal "module" body. The interesting
|
||||
// scenario lives in the sibling selfimptest.ww file, which carries
|
||||
// `use selfimp;` from inside the same module.
|
||||
|
||||
export fn touch() i32 = {
|
||||
return 0i32;
|
||||
};
|
||||
19
test/wcc/data/paramshadowmod/selfimp/selfimptest.ww
Normal file
19
test/wcc/data/paramshadowmod/selfimp/selfimptest.ww
Normal file
@@ -0,0 +1,19 @@
|
||||
// pos_selfimp/selfimptest.ww — same-module self-import case. This
|
||||
// file's MODULE tag is "selfimp" (parent dir basename), and it
|
||||
// carries `use selfimp;` — exactly the lib/fmt/fmttest.ww shape that
|
||||
// originally surfaced check_module_shadow's over-trigger on
|
||||
// `fn bsprintf(... fmt: str, ...)`.
|
||||
//
|
||||
// src_imports' self-import skip (u->module == u->str) drops these
|
||||
// entries from the import scan, so the param `selfimp: str` here
|
||||
// must NOT be flagged as shadowing — build + run, exit = 7.
|
||||
|
||||
use selfimp;
|
||||
|
||||
fn probe(selfimp: str) i32 = {
|
||||
return selfimp.len;
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
return probe("regress");
|
||||
};
|
||||
7
test/wcc/data/paramshadowmod/shadowmod/shadowmod.ww
Normal file
7
test/wcc/data/paramshadowmod/shadowmod/shadowmod.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
// paramshadowmod/shadowmod — a tiny module the negative/positive
|
||||
// fixtures import as `use shadowmod;`. Carries one fn so the leaf
|
||||
// resolves through the module dot path when name resolution succeeds.
|
||||
|
||||
export fn say() i32 = {
|
||||
return 42i32;
|
||||
};
|
||||
Reference in New Issue
Block a user