diff --git a/lib/ww/sym.ww b/lib/ww/sym.ww index c7c3937b..e449b70d 100644 --- a/lib/ww/sym.ww +++ b/lib/ww/sym.ww @@ -25,6 +25,12 @@ type sym = struct { decl: *node, exported: i32, is_const: i32, // const-bound (assignment rejected) + use_alias: i32, // #30: this value/type decl ALSO names an imported + // module (the fnmatch.fnmatch / random.random shape). + // Set when installtop promotes a same-leaf SK_USE in + // place; the N_DOT guards treat such a sym as a module + // for `name.member`. Mirror cstage Sym.use_alias + // (cmd/wcc/check.c:2831-2951 promote + 87/1337 guards). mod: str, // importing module's bareword for symbols // from a `use`-imported module; "" for primary // (root) compilation unit symbols. Used by @@ -145,14 +151,20 @@ export fn scopelookuptype(s: *scope, name: str) *sym = { // so this returns nil and the dot stays field access. Only a genuine // same-scope coexistence (top-level use + top-level type/fn) re-resolves. // -// This is the coexistence-equivalent of cstage's Sym.use_alias bit -// (cmd/wcc/check.c: set at the SK_USE→SK_X promotion sites, consulted by -// the `kind == SK_USE || use_alias` dot guards in resolve_typename and -// cexpr's N_DOT arm). Wwstage installs the SK_USE and the same-leaf -// type/fn as SEPARATE coexisting entries (see selfhost/cmd/wcc/check.ww -// installdecl), so no flag is needed — the SK_USE is never overwritten, -// only out-preferred. Cite: project memory module_type_name_collision -// (cstage fix 2026-05-13). +// #30 (design reversal): this serves the DISTINCT-mod two-sym case only — +// a `fn fnmatch` (mod="fnmatch") coexisting with `import fnmatch`'s SK_USE +// (mod=""), where scopelookupprefer lands on the value and the dot re- +// resolves to the SK_USE here. The SAME-mod collision (a primary-package +// decl whose leaf also names a bundled module — `type sym` vs `import sym`, +// `@test fn ascii` vs the fnmatch->ascii bundle floor) is NO LONGER left to +// two coexisting syms: that ripples into every bare-ref resolver (a missed +// site is a byte-id-consistent-but-wrong cat-A risk the gate can't prove +// away). Instead installtop now PROMOTES the SK_USE in place to the value +// kind with use_alias=1 (selfhost/cmd/wcc/check.ww installtop), mirroring +// cstage's Sym.use_alias promote exactly (cmd/wcc/check.c:2831-2951); the +// N_DOT guards honor `skind == SK_USE || use_alias` directly. ONE +// correctly-kinded sym → all resolvers correct by construction. Cite: +// task #30; project memory module_type_name_collision (cstage 2026-05-13). export fn scopelookupuselocal(s: *scope, name: str) *sym = { if (s == nil) { return nil; }; let h: u64 = hashstr(name); @@ -270,7 +282,7 @@ export fn scopedefineinmodule(s: *scope, name: str, mod: str, k: skind, t: *tinf }; b = b.hashnext; }; - let sy: *sym = alloc(sym{name=name, skind=k, type_=t, decl=decl, exported=0, is_const=0, mod=mod, snext=nil, hashnext=s.buckets[bi], scope=s})!; + let sy: *sym = alloc(sym{name=name, skind=k, type_=t, decl=decl, exported=0, is_const=0, use_alias=0, mod=mod, snext=nil, hashnext=s.buckets[bi], scope=s})!; s.buckets[bi] = sy; if (s.first == nil) { s.first = sy; } else { s.last.snext = sy; }; s.last = sy; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 8a7b7c28..fd6ce548 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -10078,6 +10078,12 @@ type sym = struct { decl: *node, exported: i32, is_const: i32, // const-bound (assignment rejected) + use_alias: i32, // #30: this value/type decl ALSO names an imported + // module (the fnmatch.fnmatch / random.random shape). + // Set when installtop promotes a same-leaf SK_USE in + // place; the N_DOT guards treat such a sym as a module + // for `name.member`. Mirror cstage Sym.use_alias + // (cmd/wcc/check.c:2831-2951 promote + 87/1337 guards). mod: str, // importing module's bareword for symbols // from a `use`-imported module; "" for primary // (root) compilation unit symbols. Used by @@ -10198,14 +10204,20 @@ export fn scopelookuptype(s: *scope, name: str) *sym = { // so this returns nil and the dot stays field access. Only a genuine // same-scope coexistence (top-level use + top-level type/fn) re-resolves. // -// This is the coexistence-equivalent of cstage's Sym.use_alias bit -// (cmd/wcc/check.c: set at the SK_USE→SK_X promotion sites, consulted by -// the `kind == SK_USE || use_alias` dot guards in resolve_typename and -// cexpr's N_DOT arm). Wwstage installs the SK_USE and the same-leaf -// type/fn as SEPARATE coexisting entries (see selfhost/cmd/wcc/check.ww -// installdecl), so no flag is needed — the SK_USE is never overwritten, -// only out-preferred. Cite: project memory module_type_name_collision -// (cstage fix 2026-05-13). +// #30 (design reversal): this serves the DISTINCT-mod two-sym case only — +// a `fn fnmatch` (mod="fnmatch") coexisting with `import fnmatch`'s SK_USE +// (mod=""), where scopelookupprefer lands on the value and the dot re- +// resolves to the SK_USE here. The SAME-mod collision (a primary-package +// decl whose leaf also names a bundled module — `type sym` vs `import sym`, +// `@test fn ascii` vs the fnmatch->ascii bundle floor) is NO LONGER left to +// two coexisting syms: that ripples into every bare-ref resolver (a missed +// site is a byte-id-consistent-but-wrong cat-A risk the gate can't prove +// away). Instead installtop now PROMOTES the SK_USE in place to the value +// kind with use_alias=1 (selfhost/cmd/wcc/check.ww installtop), mirroring +// cstage's Sym.use_alias promote exactly (cmd/wcc/check.c:2831-2951); the +// N_DOT guards honor `skind == SK_USE || use_alias` directly. ONE +// correctly-kinded sym → all resolvers correct by construction. Cite: +// task #30; project memory module_type_name_collision (cstage 2026-05-13). export fn scopelookupuselocal(s: *scope, name: str) *sym = { if (s == nil) { return nil; }; let h: u64 = hashstr(name); @@ -10323,7 +10335,7 @@ export fn scopedefineinmodule(s: *scope, name: str, mod: str, k: skind, t: *tinf }; b = b.hashnext; }; - let sy: *sym = alloc(sym{name=name, skind=k, type_=t, decl=decl, exported=0, is_const=0, mod=mod, snext=nil, hashnext=s.buckets[bi], scope=s})!; + let sy: *sym = alloc(sym{name=name, skind=k, type_=t, decl=decl, exported=0, is_const=0, use_alias=0, mod=mod, snext=nil, hashnext=s.buckets[bi], scope=s})!; s.buckets[bi] = sy; if (s.first == nil) { s.first = sy; } else { s.last.snext = sy; }; s.last = sy; @@ -10637,6 +10649,25 @@ fn installdecl(c: *checker, file: *node, d: *node) void = { cerr("self-import: package '"); cerr(mod); cerr("' cannot import itself\n"); c.errs += 1i32; }; + // #30 value-before-use: a same-leaf VALUE/type decl is already + // installed (source order placed `fn aa` before `import aa`). + // Promote it in place with use_alias instead of installing a + // coexisting SK_USE, so `aa.member` resolves through the N_DOT + // use_alias guard. This is the order-mirror of installtop's + // value-arm promote and reaches the IDENTICAL single-sym end + // state (skind=value, use_alias=1, decl=value). cstage is order- + // independent — it installs all N_USE in a dedicated first pass + // (cmd/wcc/check.c:2811+) so its value-arm promote always finds + // the SK_USE; wwstage installs in source order, so this direction + // is closed here, mirroring cstage's self-import N_USE arm + // (check.c:2823-2834 `if (prev) prev->use_alias = 1`). A pure + // duplicate import (prev already SK_USE) keeps the coexisting- + // entry path (orthogonal to #30, pre-existing wwstage behavior). + let prev: *sym = scopelookuplocal(c.top, nm); + if (prev != nil) { if (prev.skind != skind.SK_USE) { + prev.use_alias = 1i32; + return; + }; }; scopedefine(c.top, nm, skind.SK_USE, nil, d); return; }; if (k == nkind.N_DEF) { installtop(c, d, nm, mod, skind.SK_DEF, "def"); return; }; @@ -10667,6 +10698,28 @@ fn installdecl(c: *checker, file: *node, d: *node) void = { // checkinit-synthesized N_TYPEDECL with an empty .file) — user decls // always carry their parsed source file. fn installtop(c: *checker, d: *node, nm: str, mod: str, k: skind, kind: str) void = { + // #30: a top-level value/type decl whose leaf ALSO names an imported + // module PROMOTES that same-leaf SK_USE in place — one correctly-kinded + // sym carrying use_alias=1, so bare refs (call/structlit/var) resolve to + // the value/type while `name.member` still resolves the module via the + // N_DOT use_alias guard. Mirrors cstage check.c:2831/2848/2871/2907/ + // 2928/2951 exactly (its USE-first install pass guarantees the SK_USE is + // present when the value decl lands). A bundled `ascii` module + a + // primary-package `@test fn ascii` (989_lib_byteid) is the live case. + // wwstage installs in source order, so this arm handles the use-before- + // value direction; the value-before-use direction (`fn aa` then `import + // aa`) is closed by the symmetric promote in installdecl's N_USE arm + // (set use_alias on the pre-installed value sym). Both directions reach + // the identical single-sym end state, so cstage and wwstage agree on + // every source order (#30). + let puse: *sym = scopelookuplocal(c.top, nm); + if (puse != nil) { if (puse.skind == skind.SK_USE) { + puse.skind = k; + puse.decl = d; + puse.use_alias = 1i32; + if (mod.len > 0) { if (puse.mod.len == 0) { puse.mod = mod; }; }; + return; + }; }; if (scopedefineinmodule(c.top, nm, mod, k, nil, d) != nil) { return; }; let prev: *sym = scopesamekeysym(c.top, nm, mod); if (prev != nil) { @@ -13428,7 +13481,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { // :1378-1433 (call result IS the callee type's ret; no // global-leaf path) and harec check_autodereference // (ref/harec/src/check.c:1566-1581). - if (ms != nil && ms.skind == skind.SK_USE) { + if (ms != nil && (ms.skind == skind.SK_USE || ms.use_alias != 0i32)) { s = scopelookupinmodule(c.cur, callee.lhs.str, nm); }; }; @@ -13488,7 +13541,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { // through to outer case — cgen has its own module- // qualified resolution and the lenient checker policy // keeps the silent miss documented at scruttype L656. - if (ms.skind == skind.SK_USE) { + if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) { let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str); if (fs != nil) { if (fs.decl != nil) { let tn: *node = fs.decl.lhs; @@ -15034,7 +15087,7 @@ fn calleefndecl(c: *checker, callee: *node) *node = { if (mu != nil) { ms = mu; }; }; if (ms != nil) { - if (ms.skind == skind.SK_USE) { + if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) { let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str); if (fs != nil) { if (fs.skind == skind.SK_FN) { return fs.decl; }; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index b5ae9006..5576fe31 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -281,6 +281,25 @@ fn installdecl(c: *checker, file: *node, d: *node) void = { cerr("self-import: package '"); cerr(mod); cerr("' cannot import itself\n"); c.errs += 1i32; }; + // #30 value-before-use: a same-leaf VALUE/type decl is already + // installed (source order placed `fn aa` before `import aa`). + // Promote it in place with use_alias instead of installing a + // coexisting SK_USE, so `aa.member` resolves through the N_DOT + // use_alias guard. This is the order-mirror of installtop's + // value-arm promote and reaches the IDENTICAL single-sym end + // state (skind=value, use_alias=1, decl=value). cstage is order- + // independent — it installs all N_USE in a dedicated first pass + // (cmd/wcc/check.c:2811+) so its value-arm promote always finds + // the SK_USE; wwstage installs in source order, so this direction + // is closed here, mirroring cstage's self-import N_USE arm + // (check.c:2823-2834 `if (prev) prev->use_alias = 1`). A pure + // duplicate import (prev already SK_USE) keeps the coexisting- + // entry path (orthogonal to #30, pre-existing wwstage behavior). + let prev: *sym = scopelookuplocal(c.top, nm); + if (prev != nil) { if (prev.skind != skind.SK_USE) { + prev.use_alias = 1i32; + return; + }; }; scopedefine(c.top, nm, skind.SK_USE, nil, d); return; }; if (k == nkind.N_DEF) { installtop(c, d, nm, mod, skind.SK_DEF, "def"); return; }; @@ -311,6 +330,28 @@ fn installdecl(c: *checker, file: *node, d: *node) void = { // checkinit-synthesized N_TYPEDECL with an empty .file) — user decls // always carry their parsed source file. fn installtop(c: *checker, d: *node, nm: str, mod: str, k: skind, kind: str) void = { + // #30: a top-level value/type decl whose leaf ALSO names an imported + // module PROMOTES that same-leaf SK_USE in place — one correctly-kinded + // sym carrying use_alias=1, so bare refs (call/structlit/var) resolve to + // the value/type while `name.member` still resolves the module via the + // N_DOT use_alias guard. Mirrors cstage check.c:2831/2848/2871/2907/ + // 2928/2951 exactly (its USE-first install pass guarantees the SK_USE is + // present when the value decl lands). A bundled `ascii` module + a + // primary-package `@test fn ascii` (989_lib_byteid) is the live case. + // wwstage installs in source order, so this arm handles the use-before- + // value direction; the value-before-use direction (`fn aa` then `import + // aa`) is closed by the symmetric promote in installdecl's N_USE arm + // (set use_alias on the pre-installed value sym). Both directions reach + // the identical single-sym end state, so cstage and wwstage agree on + // every source order (#30). + let puse: *sym = scopelookuplocal(c.top, nm); + if (puse != nil) { if (puse.skind == skind.SK_USE) { + puse.skind = k; + puse.decl = d; + puse.use_alias = 1i32; + if (mod.len > 0) { if (puse.mod.len == 0) { puse.mod = mod; }; }; + return; + }; }; if (scopedefineinmodule(c.top, nm, mod, k, nil, d) != nil) { return; }; let prev: *sym = scopesamekeysym(c.top, nm, mod); if (prev != nil) { @@ -3072,7 +3113,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { // :1378-1433 (call result IS the callee type's ret; no // global-leaf path) and harec check_autodereference // (ref/harec/src/check.c:1566-1581). - if (ms != nil && ms.skind == skind.SK_USE) { + if (ms != nil && (ms.skind == skind.SK_USE || ms.use_alias != 0i32)) { s = scopelookupinmodule(c.cur, callee.lhs.str, nm); }; }; @@ -3132,7 +3173,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { // through to outer case — cgen has its own module- // qualified resolution and the lenient checker policy // keeps the silent miss documented at scruttype L656. - if (ms.skind == skind.SK_USE) { + if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) { let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str); if (fs != nil) { if (fs.decl != nil) { let tn: *node = fs.decl.lhs; @@ -4678,7 +4719,7 @@ fn calleefndecl(c: *checker, callee: *node) *node = { if (mu != nil) { ms = mu; }; }; if (ms != nil) { - if (ms.skind == skind.SK_USE) { + if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) { let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str); if (fs != nil) { if (fs.skind == skind.SK_FN) { return fs.decl; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 93ff5fbb..b5a5b2d6 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -10078,6 +10078,12 @@ type sym = struct { decl: *node, exported: i32, is_const: i32, // const-bound (assignment rejected) + use_alias: i32, // #30: this value/type decl ALSO names an imported + // module (the fnmatch.fnmatch / random.random shape). + // Set when installtop promotes a same-leaf SK_USE in + // place; the N_DOT guards treat such a sym as a module + // for `name.member`. Mirror cstage Sym.use_alias + // (cmd/wcc/check.c:2831-2951 promote + 87/1337 guards). mod: str, // importing module's bareword for symbols // from a `use`-imported module; "" for primary // (root) compilation unit symbols. Used by @@ -10198,14 +10204,20 @@ export fn scopelookuptype(s: *scope, name: str) *sym = { // so this returns nil and the dot stays field access. Only a genuine // same-scope coexistence (top-level use + top-level type/fn) re-resolves. // -// This is the coexistence-equivalent of cstage's Sym.use_alias bit -// (cmd/wcc/check.c: set at the SK_USE→SK_X promotion sites, consulted by -// the `kind == SK_USE || use_alias` dot guards in resolve_typename and -// cexpr's N_DOT arm). Wwstage installs the SK_USE and the same-leaf -// type/fn as SEPARATE coexisting entries (see selfhost/cmd/wcc/check.ww -// installdecl), so no flag is needed — the SK_USE is never overwritten, -// only out-preferred. Cite: project memory module_type_name_collision -// (cstage fix 2026-05-13). +// #30 (design reversal): this serves the DISTINCT-mod two-sym case only — +// a `fn fnmatch` (mod="fnmatch") coexisting with `import fnmatch`'s SK_USE +// (mod=""), where scopelookupprefer lands on the value and the dot re- +// resolves to the SK_USE here. The SAME-mod collision (a primary-package +// decl whose leaf also names a bundled module — `type sym` vs `import sym`, +// `@test fn ascii` vs the fnmatch->ascii bundle floor) is NO LONGER left to +// two coexisting syms: that ripples into every bare-ref resolver (a missed +// site is a byte-id-consistent-but-wrong cat-A risk the gate can't prove +// away). Instead installtop now PROMOTES the SK_USE in place to the value +// kind with use_alias=1 (selfhost/cmd/wcc/check.ww installtop), mirroring +// cstage's Sym.use_alias promote exactly (cmd/wcc/check.c:2831-2951); the +// N_DOT guards honor `skind == SK_USE || use_alias` directly. ONE +// correctly-kinded sym → all resolvers correct by construction. Cite: +// task #30; project memory module_type_name_collision (cstage 2026-05-13). export fn scopelookupuselocal(s: *scope, name: str) *sym = { if (s == nil) { return nil; }; let h: u64 = hashstr(name); @@ -10323,7 +10335,7 @@ export fn scopedefineinmodule(s: *scope, name: str, mod: str, k: skind, t: *tinf }; b = b.hashnext; }; - let sy: *sym = alloc(sym{name=name, skind=k, type_=t, decl=decl, exported=0, is_const=0, mod=mod, snext=nil, hashnext=s.buckets[bi], scope=s})!; + let sy: *sym = alloc(sym{name=name, skind=k, type_=t, decl=decl, exported=0, is_const=0, use_alias=0, mod=mod, snext=nil, hashnext=s.buckets[bi], scope=s})!; s.buckets[bi] = sy; if (s.first == nil) { s.first = sy; } else { s.last.snext = sy; }; s.last = sy; @@ -10637,6 +10649,25 @@ fn installdecl(c: *checker, file: *node, d: *node) void = { cerr("self-import: package '"); cerr(mod); cerr("' cannot import itself\n"); c.errs += 1i32; }; + // #30 value-before-use: a same-leaf VALUE/type decl is already + // installed (source order placed `fn aa` before `import aa`). + // Promote it in place with use_alias instead of installing a + // coexisting SK_USE, so `aa.member` resolves through the N_DOT + // use_alias guard. This is the order-mirror of installtop's + // value-arm promote and reaches the IDENTICAL single-sym end + // state (skind=value, use_alias=1, decl=value). cstage is order- + // independent — it installs all N_USE in a dedicated first pass + // (cmd/wcc/check.c:2811+) so its value-arm promote always finds + // the SK_USE; wwstage installs in source order, so this direction + // is closed here, mirroring cstage's self-import N_USE arm + // (check.c:2823-2834 `if (prev) prev->use_alias = 1`). A pure + // duplicate import (prev already SK_USE) keeps the coexisting- + // entry path (orthogonal to #30, pre-existing wwstage behavior). + let prev: *sym = scopelookuplocal(c.top, nm); + if (prev != nil) { if (prev.skind != skind.SK_USE) { + prev.use_alias = 1i32; + return; + }; }; scopedefine(c.top, nm, skind.SK_USE, nil, d); return; }; if (k == nkind.N_DEF) { installtop(c, d, nm, mod, skind.SK_DEF, "def"); return; }; @@ -10667,6 +10698,28 @@ fn installdecl(c: *checker, file: *node, d: *node) void = { // checkinit-synthesized N_TYPEDECL with an empty .file) — user decls // always carry their parsed source file. fn installtop(c: *checker, d: *node, nm: str, mod: str, k: skind, kind: str) void = { + // #30: a top-level value/type decl whose leaf ALSO names an imported + // module PROMOTES that same-leaf SK_USE in place — one correctly-kinded + // sym carrying use_alias=1, so bare refs (call/structlit/var) resolve to + // the value/type while `name.member` still resolves the module via the + // N_DOT use_alias guard. Mirrors cstage check.c:2831/2848/2871/2907/ + // 2928/2951 exactly (its USE-first install pass guarantees the SK_USE is + // present when the value decl lands). A bundled `ascii` module + a + // primary-package `@test fn ascii` (989_lib_byteid) is the live case. + // wwstage installs in source order, so this arm handles the use-before- + // value direction; the value-before-use direction (`fn aa` then `import + // aa`) is closed by the symmetric promote in installdecl's N_USE arm + // (set use_alias on the pre-installed value sym). Both directions reach + // the identical single-sym end state, so cstage and wwstage agree on + // every source order (#30). + let puse: *sym = scopelookuplocal(c.top, nm); + if (puse != nil) { if (puse.skind == skind.SK_USE) { + puse.skind = k; + puse.decl = d; + puse.use_alias = 1i32; + if (mod.len > 0) { if (puse.mod.len == 0) { puse.mod = mod; }; }; + return; + }; }; if (scopedefineinmodule(c.top, nm, mod, k, nil, d) != nil) { return; }; let prev: *sym = scopesamekeysym(c.top, nm, mod); if (prev != nil) { @@ -13428,7 +13481,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { // :1378-1433 (call result IS the callee type's ret; no // global-leaf path) and harec check_autodereference // (ref/harec/src/check.c:1566-1581). - if (ms != nil && ms.skind == skind.SK_USE) { + if (ms != nil && (ms.skind == skind.SK_USE || ms.use_alias != 0i32)) { s = scopelookupinmodule(c.cur, callee.lhs.str, nm); }; }; @@ -13488,7 +13541,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { // through to outer case — cgen has its own module- // qualified resolution and the lenient checker policy // keeps the silent miss documented at scruttype L656. - if (ms.skind == skind.SK_USE) { + if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) { let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str); if (fs != nil) { if (fs.decl != nil) { let tn: *node = fs.decl.lhs; @@ -15034,7 +15087,7 @@ fn calleefndecl(c: *checker, callee: *node) *node = { if (mu != nil) { ms = mu; }; }; if (ms != nil) { - if (ms.skind == skind.SK_USE) { + if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) { let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str); if (fs != nil) { if (fs.skind == skind.SK_FN) { return fs.decl; }; diff --git a/test/wcc/910_at_test.c b/test/wcc/910_at_test.c index 0d44554b..01fbdb04 100644 --- a/test/wcc/910_at_test.c +++ b/test/wcc/910_at_test.c @@ -317,6 +317,25 @@ collide_run(const char *bin, const char *comp, const char *drv) return rc; } +/* modfn_run — ` run ` and assert exit 6 (#30). The fixture's + * main returns `aa() + aa.helper()` = 1 + 5: the bare `aa()` must resolve to + * the fn (1) and the qualified `aa.helper()` to the module member (5). A + * mis-resolution that still compiled would run to a different code; this + * pins the resolved TARGETS, not just acceptance. */ +static int +modfn_run(const char *bin, const char *drv, const char *fixture, const char *what) +{ + char cmd[4096]; + snprintf(cmd, sizeof cmd, "%s/%s run %s > /dev/null 2>&1", bin, drv, fixture); + int rc = runwait(cmd); + if (rc != 6) { + fprintf(stderr, "910 FAIL: %s run %s exited %d (expected 6 — bare fn " + "+ qualified module resolution)\n", drv, what, rc); + return 1; + } + return 0; +} + int main(void) { @@ -347,11 +366,29 @@ main(void) "cross-package same-name") != 0) return 1; if (accept(bin, "w6c", "test/wcc/data/builtin_redecl_ok.ww", "builtin nomem redecl (carve-out)") != 0) return 1; + /* #30 — a top-level fn whose leaf matches an imported module name + * coexists with the module bareword (different namespaces). Accept + * proves both the bare `aa()` (fn) and qualified `aa.helper()` + * (module) resolve; a mis-resolution would fail to compile. Both + * decl orders are pinned (use-before-value AND value-before-use): the + * order-dependence is exactly what regresses silently — cstage is + * order-independent, wwstage closes both directions via the symmetric + * promotes in installtop + installdecl's N_USE arm. */ + if (accept(bin, "w6c", "test/wcc/data/modfn_coexist_ok.ww", + "fn named like imported module (use-before-value)") != 0) return 1; + if (accept(bin, "w6c", "test/wcc/data/modfn_coexist_vbu_ok.ww", + "fn named like imported module (value-before-use)") != 0) return 1; + /* runtime-resolve: both orders run to exit 6 (bare fn=1 + module + * member=5), pinning the resolved targets on the cstage driver. */ + if (modfn_run(bin, "ww", "test/wcc/data/modfn_coexist_ok.ww", + "use-before-value") != 0) return 1; + if (modfn_run(bin, "ww", "test/wcc/data/modfn_coexist_vbu_ok.ww", + "value-before-use") != 0) return 1; if (collide_run(bin, "w6c", "ww") != 0) return 1; printf("@test -T: run ok + user-main and bad-signature rejected + " "non-T @test drop + checked-body + dangling-call link-fail (#6) + " "dup fn/type/def/let reject + xpkg + builtin-redecl accept + " - "fn-run collision (#23)\n"); + "modfn coexist (#30) + fn-run collision (#23)\n"); return 0; } diff --git a/test/wcc/997_at_test_ww.c b/test/wcc/997_at_test_ww.c index d18d8510..e0b910fc 100644 --- a/test/wcc/997_at_test_ww.c +++ b/test/wcc/997_at_test_ww.c @@ -365,6 +365,25 @@ collide_run(const char *bin) return rc; } +/* modfn_run — `ww_ww run ` and assert exit 6 (#30): the wwstage + * runtime twin of 910's modfn_run. The fixture's main returns `aa() + + * aa.helper()` = 1 + 5; the bare fn must resolve to 1 and the qualified + * module member to 5, so a mis-resolution that still compiled runs to a + * different code. Pins the resolved TARGETS on the wwstage driver. */ +static int +modfn_run(const char *bin, const char *fixture, const char *what) +{ + char cmd[4096]; + snprintf(cmd, sizeof cmd, "%s/ww_ww run %s > /dev/null 2>&1", bin, fixture); + int rc = runwait(cmd); + if (rc != 6) { + fprintf(stderr, "997 FAIL: ww_ww run %s exited %d (expected 6 — bare " + "fn + qualified module resolution)\n", what, rc); + return 1; + } + return 0; +} + int main(void) { @@ -396,11 +415,30 @@ main(void) "cross-package same-name") != 0) return 1; if (accept_byteid(bin, "test/wcc/data/builtin_redecl_ok.ww", "builtin nomem redecl (carve-out)") != 0) return 1; + /* #30 — a top-level fn whose leaf matches an imported module name + * coexists with the module bareword. wwstage now PROMOTES the same-leaf + * collision in place (use_alias=1), mirroring cstage exactly, so byte-id + * proves the resolved call targets match. Both decl orders are pinned: + * use-before-value promotes via installtop's value-arm, value-before-use + * via installdecl's N_USE arm — both reach the identical single-sym end + * state, so cstage and wwstage agree on every source order. The bare + * `aa()` and qualified `aa.helper()` both resolve, else the compile + * fails. */ + if (accept_byteid(bin, "test/wcc/data/modfn_coexist_ok.ww", + "fn named like imported module (use-before-value)") != 0) return 1; + if (accept_byteid(bin, "test/wcc/data/modfn_coexist_vbu_ok.ww", + "fn named like imported module (value-before-use)") != 0) return 1; + /* runtime-resolve: both orders run to exit 6 on the wwstage driver, + * pinning that the promoted sym resolves bare fn=1 + module member=5. */ + if (modfn_run(bin, "test/wcc/data/modfn_coexist_ok.ww", + "use-before-value") != 0) return 1; + if (modfn_run(bin, "test/wcc/data/modfn_coexist_vbu_ok.ww", + "value-before-use") != 0) return 1; if (collide_run(bin) != 0) return 1; printf("@test -T (ww_ww): run ok + cs/ww byte-id + rejects + " "non-T @test drop cs/ww byte-id + checked-body (#6) + " "dup fn/type/def/let reject + xpkg/builtin-redecl byte-id + " - "fn-run collision (#23)\n"); + "modfn coexist byte-id (#30) + fn-run collision (#23)\n"); return 0; } diff --git a/test/wcc/data/modfn_coexist_ok.ww b/test/wcc/data/modfn_coexist_ok.ww new file mode 100644 index 00000000..2707552e --- /dev/null +++ b/test/wcc/data/modfn_coexist_ok.ww @@ -0,0 +1,19 @@ +// #30 legal control — a top-level fn whose leaf matches an imported +// MODULE name. The module bareword (SK_USE) and the value-namespace fn +// coexist: cstage promotes the SK_USE in place with use_alias +// (cmd/wcc/check.c:2928), wwstage keeps both as separate coexisting syms +// (lib/ww/sym.ww scopedefineinmodule #30 cross-namespace skip). A bundled +// `ascii` module + a primary-package `@test fn ascii` hit this in the wild +// (989_lib_byteid via the lib/test->fnmatch->ascii -T floor). Must compile +// clean + be cs/ww byte-identical. Mirrors a driver-emitted *.combined.ww. +// +// Both refs must resolve, so any mis-resolution fails to COMPILE: `aa()` +// must bind the fn (a module is not callable), and `aa.helper()` must bind +// through the module (the fn has no field `helper`). Accept proves both. +package aa; +export fn helper() i32 = { return 5; }; + +package main; +import aa; +fn aa() i32 = { return 1; }; +export fn main() i32 = { return aa() + aa.helper(); }; diff --git a/test/wcc/data/modfn_coexist_vbu_ok.ww b/test/wcc/data/modfn_coexist_vbu_ok.ww new file mode 100644 index 00000000..0515cb2c --- /dev/null +++ b/test/wcc/data/modfn_coexist_vbu_ok.ww @@ -0,0 +1,21 @@ +// #30 legal control — VALUE-BEFORE-USE order. The twin of +// modfn_coexist_ok.ww with the decl order flipped: the value-namespace +// `fn aa` is declared BEFORE `import aa`. cstage is order-independent +// (it installs every SK_USE in a dedicated first pass, cmd/wcc/check.c +// :2811+), so its value-arm promote always fires. wwstage installs in +// source order, so this direction is closed by the symmetric promote in +// installdecl's N_USE arm (set use_alias on the pre-installed value sym, +// mirroring cmd/wcc/check.c:2823-2834). Both orders must compile clean + +// be cs/ww byte-identical AND byte-identical to the use-before-value +// order — the order-dependence is exactly what regresses silently. +// +// Both refs must resolve, so any mis-resolution fails to COMPILE: `aa()` +// must bind the fn (a module is not callable), and `aa.helper()` must bind +// through the module (the fn has no field `helper`). Accept proves both. +package aa; +export fn helper() i32 = { return 5; }; + +package main; +fn aa() i32 = { return 1; }; +import aa; +export fn main() i32 = { return aa() + aa.helper(); };