w6c+selfhost: cross-module same-leaf type disambiguation via Sym.mod
This commit is contained in:
@@ -4540,6 +4540,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,
|
||||
@@ -4611,16 +4616,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; };
|
||||
@@ -4695,18 +4758,42 @@ fn seedprimitives(c: *checker) void = {
|
||||
scopedefine(c.top, "append", skind.SK_FN, nil, nil);
|
||||
};
|
||||
|
||||
// declmod — module-tag stamp for a top-level decl.
|
||||
//
|
||||
// The driver concatenates imported sources before the primary file and
|
||||
// emits `// MODULE: foo` directives the lexer pins onto each decl's
|
||||
// `module` field. We treat a decl as "imported" iff its module
|
||||
// directive matches some `use IDENT;` bareword in this compilation
|
||||
// unit. Primary-file decls return "" so they coexist (mod="") with
|
||||
// imported decls of the same leaf name in scopelookupinmodule.
|
||||
fn declmod(file: *node, d: *node) str = {
|
||||
let empty: str;
|
||||
if (d == nil) { return empty; };
|
||||
if (d.module.len == 0) { return empty; };
|
||||
if (file == nil) { return empty; };
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == nkind.N_USE) {
|
||||
if (streq(u.str, d.module)) { return d.module; };
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
|
||||
// installdecl — install the top-level decl's name into the top scope.
|
||||
// We don't compute its type yet (that's the resolve pass) — just bind
|
||||
// the name so forward references resolve.
|
||||
fn installdecl(c: *checker, d: *node) void = {
|
||||
fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
if (d == nil) { return; };
|
||||
let k: nkind = d.kind;
|
||||
let nm: str = d.str;
|
||||
let mod: str = declmod(file, d);
|
||||
if (k == nkind.N_USE) { scopedefine(c.top, nm, skind.SK_USE, nil, d); return; };
|
||||
if (k == nkind.N_DEF) { scopedefine(c.top, nm, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefine(c.top, nm, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefine(c.top, nm, skind.SK_FN, nil, d); return; };
|
||||
if (k == nkind.N_LET) { scopedefine(c.top, nm, skind.SK_VAR, nil, d); return; };
|
||||
if (k == nkind.N_DEF) { scopedefineinmodule(c.top, nm, mod, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_FN, nil, d); return; };
|
||||
if (k == nkind.N_LET) { scopedefineinmodule(c.top, nm, mod, skind.SK_VAR, nil, d); return; };
|
||||
};
|
||||
|
||||
// resolvewalk — recursive AST walk that, for every nkind.N_IDENT and
|
||||
@@ -4753,8 +4840,10 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookup(c.cur, nm);
|
||||
// `pkg.Type` — strip the last dot prefix and look up
|
||||
// the leaf if `pkg` is a use-imported name. Mirrors
|
||||
// cmd/wcc/check.c resolve_typename.
|
||||
// the leaf with a mod filter so same-leaf-name types
|
||||
// from different imports (`bufio.stream` vs
|
||||
// `io.stream`) disambiguate to the right one.
|
||||
// Mirrors cmd/wcc/check.c resolve_typename.
|
||||
if (s == nil) {
|
||||
let dot: i32 = nm.len - 1;
|
||||
for (dot >= 0) {
|
||||
@@ -4770,7 +4859,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + (dot + 1): u64;
|
||||
leaf.len = nm.len - (dot + 1);
|
||||
s = scopelookup(c.cur, leaf);
|
||||
s = scopelookupinmodule(c.cur, head, leaf);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -5558,7 +5647,7 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
// Pass 1: install all top-level names.
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
installdecl(c, d);
|
||||
installdecl(c, file, d);
|
||||
d = d.next;
|
||||
};
|
||||
|
||||
|
||||
@@ -64,18 +64,42 @@ fn seedprimitives(c: *checker) void = {
|
||||
scopedefine(c.top, "append", skind.SK_FN, nil, nil);
|
||||
};
|
||||
|
||||
// declmod — module-tag stamp for a top-level decl.
|
||||
//
|
||||
// The driver concatenates imported sources before the primary file and
|
||||
// emits `// MODULE: foo` directives the lexer pins onto each decl's
|
||||
// `module` field. We treat a decl as "imported" iff its module
|
||||
// directive matches some `use IDENT;` bareword in this compilation
|
||||
// unit. Primary-file decls return "" so they coexist (mod="") with
|
||||
// imported decls of the same leaf name in scopelookupinmodule.
|
||||
fn declmod(file: *node, d: *node) str = {
|
||||
let empty: str;
|
||||
if (d == nil) { return empty; };
|
||||
if (d.module.len == 0) { return empty; };
|
||||
if (file == nil) { return empty; };
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == nkind.N_USE) {
|
||||
if (streq(u.str, d.module)) { return d.module; };
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
|
||||
// installdecl — install the top-level decl's name into the top scope.
|
||||
// We don't compute its type yet (that's the resolve pass) — just bind
|
||||
// the name so forward references resolve.
|
||||
fn installdecl(c: *checker, d: *node) void = {
|
||||
fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
if (d == nil) { return; };
|
||||
let k: nkind = d.kind;
|
||||
let nm: str = d.str;
|
||||
let mod: str = declmod(file, d);
|
||||
if (k == nkind.N_USE) { scopedefine(c.top, nm, skind.SK_USE, nil, d); return; };
|
||||
if (k == nkind.N_DEF) { scopedefine(c.top, nm, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefine(c.top, nm, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefine(c.top, nm, skind.SK_FN, nil, d); return; };
|
||||
if (k == nkind.N_LET) { scopedefine(c.top, nm, skind.SK_VAR, nil, d); return; };
|
||||
if (k == nkind.N_DEF) { scopedefineinmodule(c.top, nm, mod, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_FN, nil, d); return; };
|
||||
if (k == nkind.N_LET) { scopedefineinmodule(c.top, nm, mod, skind.SK_VAR, nil, d); return; };
|
||||
};
|
||||
|
||||
// resolvewalk — recursive AST walk that, for every nkind.N_IDENT and
|
||||
@@ -122,8 +146,10 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookup(c.cur, nm);
|
||||
// `pkg.Type` — strip the last dot prefix and look up
|
||||
// the leaf if `pkg` is a use-imported name. Mirrors
|
||||
// cmd/wcc/check.c resolve_typename.
|
||||
// the leaf with a mod filter so same-leaf-name types
|
||||
// from different imports (`bufio.stream` vs
|
||||
// `io.stream`) disambiguate to the right one.
|
||||
// Mirrors cmd/wcc/check.c resolve_typename.
|
||||
if (s == nil) {
|
||||
let dot: i32 = nm.len - 1;
|
||||
for (dot >= 0) {
|
||||
@@ -139,7 +165,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + (dot + 1): u64;
|
||||
leaf.len = nm.len - (dot + 1);
|
||||
s = scopelookup(c.cur, leaf);
|
||||
s = scopelookupinmodule(c.cur, head, leaf);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -927,7 +953,7 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
// Pass 1: install all top-level names.
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
installdecl(c, d);
|
||||
installdecl(c, file, d);
|
||||
d = d.next;
|
||||
};
|
||||
|
||||
|
||||
@@ -4540,6 +4540,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,
|
||||
@@ -4611,16 +4616,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; };
|
||||
@@ -4695,18 +4758,42 @@ fn seedprimitives(c: *checker) void = {
|
||||
scopedefine(c.top, "append", skind.SK_FN, nil, nil);
|
||||
};
|
||||
|
||||
// declmod — module-tag stamp for a top-level decl.
|
||||
//
|
||||
// The driver concatenates imported sources before the primary file and
|
||||
// emits `// MODULE: foo` directives the lexer pins onto each decl's
|
||||
// `module` field. We treat a decl as "imported" iff its module
|
||||
// directive matches some `use IDENT;` bareword in this compilation
|
||||
// unit. Primary-file decls return "" so they coexist (mod="") with
|
||||
// imported decls of the same leaf name in scopelookupinmodule.
|
||||
fn declmod(file: *node, d: *node) str = {
|
||||
let empty: str;
|
||||
if (d == nil) { return empty; };
|
||||
if (d.module.len == 0) { return empty; };
|
||||
if (file == nil) { return empty; };
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == nkind.N_USE) {
|
||||
if (streq(u.str, d.module)) { return d.module; };
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
|
||||
// installdecl — install the top-level decl's name into the top scope.
|
||||
// We don't compute its type yet (that's the resolve pass) — just bind
|
||||
// the name so forward references resolve.
|
||||
fn installdecl(c: *checker, d: *node) void = {
|
||||
fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
if (d == nil) { return; };
|
||||
let k: nkind = d.kind;
|
||||
let nm: str = d.str;
|
||||
let mod: str = declmod(file, d);
|
||||
if (k == nkind.N_USE) { scopedefine(c.top, nm, skind.SK_USE, nil, d); return; };
|
||||
if (k == nkind.N_DEF) { scopedefine(c.top, nm, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefine(c.top, nm, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefine(c.top, nm, skind.SK_FN, nil, d); return; };
|
||||
if (k == nkind.N_LET) { scopedefine(c.top, nm, skind.SK_VAR, nil, d); return; };
|
||||
if (k == nkind.N_DEF) { scopedefineinmodule(c.top, nm, mod, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_FN, nil, d); return; };
|
||||
if (k == nkind.N_LET) { scopedefineinmodule(c.top, nm, mod, skind.SK_VAR, nil, d); return; };
|
||||
};
|
||||
|
||||
// resolvewalk — recursive AST walk that, for every nkind.N_IDENT and
|
||||
@@ -4753,8 +4840,10 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookup(c.cur, nm);
|
||||
// `pkg.Type` — strip the last dot prefix and look up
|
||||
// the leaf if `pkg` is a use-imported name. Mirrors
|
||||
// cmd/wcc/check.c resolve_typename.
|
||||
// the leaf with a mod filter so same-leaf-name types
|
||||
// from different imports (`bufio.stream` vs
|
||||
// `io.stream`) disambiguate to the right one.
|
||||
// Mirrors cmd/wcc/check.c resolve_typename.
|
||||
if (s == nil) {
|
||||
let dot: i32 = nm.len - 1;
|
||||
for (dot >= 0) {
|
||||
@@ -4770,7 +4859,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + (dot + 1): u64;
|
||||
leaf.len = nm.len - (dot + 1);
|
||||
s = scopelookup(c.cur, leaf);
|
||||
s = scopelookupinmodule(c.cur, head, leaf);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -5558,7 +5647,7 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
// Pass 1: install all top-level names.
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
installdecl(c, d);
|
||||
installdecl(c, file, d);
|
||||
d = d.next;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user