wcc/ww: promote module-name SK_USE to value kind (use_alias), aligned to cstage

A primary-package top-level decl whose leaf also names a bundled module
(`type sym` vs `import sym`; the lib/test->fnmatch->ascii -T floor's
`ascii` module vs a `@test fn ascii`) collided in the flat scope: #23's
installtop dup-check false-fired "duplicate fn/type" where cstage
coexists. cstage keys the module name out of the value namespace by
PROMOTING the same-leaf SK_USE in place to the value kind with
use_alias=1 (cmd/wcc/check.c:2831/2848/2871/2907/2928/2951), so one
correctly-kinded sym serves bare refs (call/structlit/var) while
`name.member` still resolves the module via the `kind==SK_USE ||
use_alias` N_DOT guards (check.c:87/1337).

This REVERSES wwstage's documented two-sym coexistence design
(lib/ww/sym.ww scopelookupuselocal): keeping the SK_USE as a separate
coexisting sym ripples into every bare-reference resolver (~25
scopelookup sites), and a missed site is a byte-id-consistent-but-wrong
cat-A risk the gate cannot prove away — the same failure mode retired
with the name-keyed variant-match cluster. The promote model is correct
by construction: one sym of the right kind, identical to cstage.

sym gains a use_alias field; the three N_DOT/N_CALL module-qualified
guards honor use_alias. cstage installs every SK_USE in a dedicated
first pass, so its value-arm promote is order-INDEPENDENT; wwstage
installs in source order, so BOTH directions of the collision are
promoted to reach cstage's identical single-sym end state:
  - use-before-value (`import aa` then `fn aa`): installtop promotes the
    pre-installed SK_USE to the value kind (use_alias=1).
  - value-before-use (`fn aa` then `import aa`): installdecl's N_USE arm
    promotes the pre-installed value sym in place (set use_alias=1,
    no coexisting SK_USE), mirroring cstage's self-import N_USE arm
    (check.c:2823-2834 `if (prev) prev->use_alias = 1`).
Both orders compile + are cs/ww byte-identical AND byte-identical to
each other. Cannot split: promote without the guards leaves
`name.member` red on the promoted sym; the guards without promote are
inert (no use_alias is ever set) — no bisect-clean intermediate. (#30)

Pins (910/997): modfn_coexist_ok (use-before-value) AND
modfn_coexist_vbu_ok (value-before-use) both accept on cstage + are
cs/ww byte-id + run to exit 6 (bare fn and qualified module both
resolve); the dup_fn row still rejects both stages (regression). fnmatch
byte-id holds. The order-dependence is exactly what regresses silently,
so both orders are pinned.
This commit is contained in:
2026-06-11 04:51:07 +09:00
parent 14b33993c2
commit 64f0ddf01b
8 changed files with 312 additions and 38 deletions

View File

@@ -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; };

View File

@@ -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; };

View File

@@ -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; };