w6c+selfhost: cross-module same-leaf type disambiguation via Sym.mod

This commit is contained in:
2026-05-15 10:26:20 +09:00
parent e349536f62
commit 66d6408cbe
15 changed files with 614 additions and 60 deletions

View File

@@ -27,6 +27,11 @@ type sym = struct {
decl: *node,
exported: i32,
is_const: i32, // const-bound (assignment rejected)
mod: str, // importing module's bareword for symbols
// from a `use`-imported module; "" for primary
// (root) compilation unit symbols. Used by
// scopelookupinmodule to disambiguate same-leaf-
// name types coming from different imports.
snext: *sym, // iteration order
hashnext: *sym, // hash bucket chain
scope: *scope,
@@ -98,16 +103,74 @@ export fn scopelookup(s: *scope, name: str) *sym = {
return nil;
};
// scopelookupinmodule — module-filtered chain walk.
//
// Same FNV bucket + hashnext chain + parent walk as scopelookup, plus
// a `b.mod.len > 0 && streq(b.mod, mod)` filter. When `mod` is empty
// we fall back to unfiltered scopelookup semantics, so callers that
// don't care about disambiguation get the default.
//
// Used by the dot-prefixed type-name lookup in selfhost/cmd/wcc/
// check.ww to pick the right same-leaf-name type when two imports
// each export it (`bufio.stream` vs `io.stream`).
export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
if (mod.len == 0) { return scopelookup(s, name); };
for (s != nil) {
let h: u64 = hashstr(name);
let bi: i32 = (h % (s.nbuckets: u64)): i32;
let b: *sym = s.buckets[bi];
for (b != nil) {
if (streq(b.name, name)) {
if (b.mod.len > 0) {
if (streq(b.mod, mod)) {
return b;
};
};
};
b = b.hashnext;
};
s = s.parent;
};
return nil;
};
export fn scopedefine(s: *scope, name: str, k: skind, t: *tinfo, decl: *node) *sym = {
if (scopelookuplocal(s, name) != nil) { return nil; };
let sy: *sym = amalloc(s.a, 80u64): *sym;
let empty: str;
return scopedefineinmodule(s, name, empty, k, t, decl);
};
// scopedefineinmodule — bucket insert with per-mod dedup.
//
// Same insertion as scopedefine, but the duplicate-rejection key is
// (name, mod) rather than name alone. This lets two imports each
// register their own `stream` SK_TYPE in the flat scope, and lets the
// primary register `stream` (mod="") alongside imported `stream`s.
//
// Within a single (name, mod) pair the first registration wins; later
// attempts return nil and the caller can flag the error.
export fn scopedefineinmodule(s: *scope, name: str, mod: str, k: skind, t: *tinfo, decl: *node) *sym = {
let h: u64 = hashstr(name);
let bi: i32 = (h % (s.nbuckets: u64)): i32;
let b: *sym = s.buckets[bi];
for (b != nil) {
if (streq(b.name, name)) {
if (b.mod.len == 0) {
if (mod.len == 0) { return nil; };
} else {
if (mod.len > 0) {
if (streq(b.mod, mod)) { return nil; };
};
};
};
b = b.hashnext;
};
let sy: *sym = amalloc(s.a, 112u64): *sym;
sy.name = name;
sy.skind = k;
sy.type_ = t;
sy.decl = decl;
sy.mod = mod;
sy.scope = s;
let h: u64 = hashstr(name);
let bi: i32 = (h % (s.nbuckets: u64)): i32;
sy.hashnext = s.buckets[bi];
s.buckets[bi] = sy;
if (s.first == nil) { s.first = sy; } else { s.last.snext = sy; };