wcc/ww: mangle imported symbols on dotted import path (#22 M1, #32)

Switch symbol mangling from the import leaf clause to the full dotted import path for directory packages; single-file imports keep package-clause mangling (isdir-gate: imported<=>directory-import). The root build unit's fn main stays bare, every other top-level decl mangles, closing #31's duplicate-main hazard by construction (#32). Both stages, byte-identical.

Single commit, not split: the bare rename (f244af3) is red on its own because it unmasks cross-module resolution gaps that do not reproduce pre-M1, so the fixes are intrinsic to making the rename correct. Included: wwstage fnret/fnparamslookupmod map import alias->path (#199b cross-module union-variant scrutinee resolved the wrong fn's union); cstage use_path prefers the referencing module's import for an ambiguous leaf alias (sha256 crypto.math vs strconv math). Tests table-driven: 989_m1mangle_run/_sym, 989_m1union_run (gate-visible per-arm exit codes + cs==ww byte-id).
This commit is contained in:
2026-06-15 17:37:18 +09:00
parent 64d6c15e41
commit f308818b4b
30 changed files with 1851 additions and 241 deletions

View File

@@ -164,14 +164,48 @@ fn declmod(file: *node, d: *node) str = {
if (file == nil) { return empty; };
let u: *node = file.list;
for (u != nil) {
// M1 #22: a decl is imported iff some `use` directive's full
// dotted import path equals the decl's module (now the path).
// Single-level packages have usepath == leaf so this is
// unchanged; nested (`encoding.utf8`) match here, not on leaf.
if (u.kind == nkind.N_USE) {
if (streq(u.str, d.nmod)) { return d.nmod; };
if (streq(u.usepath, d.nmod)) { return d.nmod; };
};
u = u.next;
};
return empty;
};
// usepath — map a `use` alias (leaf bareword the user writes, `utf8`)
// to the full dotted import path it binds (`encoding.utf8`), for the
// module-qualified resolution and codegen hint (M1 #22). Single-level
// packages have usepath == alias so the result is unchanged. The
// current tree has one occurrence per leaf, so the map is unambiguous.
fn usepathfor(file: *node, alias: str) str = {
let empty: str;
if (file == nil) { return empty; };
if (alias.len == 0) { return empty; };
let u: *node = file.list;
for (u != nil) {
if (u.kind == nkind.N_USE) {
if (streq(u.str, alias)) {
if (u.usepath.len != 0) { return u.usepath; };
return u.str;
};
};
u = u.next;
};
return empty;
};
// modkeyfor — usepathfor with leaf-alias fallback: the module key for a
// path-keyed scopelookupinmodule given the alias the user wrote (M1 #22).
fn modkeyfor(c: *checker, alias: str) str = {
let mk: str = usepathfor(c.file, alias);
if (mk.len == 0) { return alias; };
return mk;
};
// srcimports — does the source file that contributed decl-module
// `modtag` carry `use <name>;`? Mirrors cstage's src_imports —
// `modtag.len == 0` means primary, matching declmod's empty-str
@@ -188,7 +222,7 @@ fn srcimports(file: *node, modtag: str, name: str) bool = {
// module bareword and lib/fmt's own
// `fn bsprintf(fmt: str, ...)` is not a shadow.
if (u.nmod.len > 0) {
if (streq(u.nmod, u.str)) {
if (streq(u.nmod, u.usepath)) {
u = u.next;
continue;
};
@@ -277,7 +311,9 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
// pulls lack import->file->symbol provenance). Message byte-identical
// to cstage check.c.
if (k == nkind.N_USE) {
if (mod.len != 0 && streq(nm, mod)) {
// M1 #22: self-import ⟺ the imported path equals the use's own
// (owning) module path. Compares paths, not leaves.
if (mod.len != 0 && streq(d.usepath, mod)) {
cerr("self-import: package '"); cerr(mod);
cerr("' cannot import itself\n"); c.errs += 1i32;
};
@@ -486,7 +522,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 = scopelookupinmodule(c.cur, head, leaf);
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
};
};
};
@@ -884,7 +920,7 @@ fn aliassym(c: *checker, n: *node) *sym = {
let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1;
s = scopelookupinmodule(c.cur, head, leaf);
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
} else {
// #53: same-module preference. Mirrors cstage
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
@@ -1135,7 +1171,7 @@ fn scruttype(c: *checker, e: *node) *node = {
if (e.kind == nkind.N_DOT) {
if (e.lhs == nil) { return nil; };
if (e.lhs.kind != nkind.N_IDENT) { return nil; };
let s: *sym = scopelookupinmodule(c.cur, e.lhs.str, e.str);
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.str), e.str);
if (s == nil) { return nil; };
if (s.decl == nil) { return nil; };
return s.decl.lhs;
@@ -1674,7 +1710,7 @@ fn evaldefconst(c: *checker, n: *node, out: *u64, depth: i32) bool = {
if (k == nkind.N_DOT) {
if (n.lhs == nil) { return false; };
if (n.lhs.kind != nkind.N_IDENT) { return false; };
let s: *sym = scopelookupinmodule(c.cur, n.lhs.str, n.str);
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, n.lhs.str), n.str);
if (s == nil) { return false; };
if (s.skind != skind.SK_DEF) { return false; };
if (s.decl == nil) { return false; };
@@ -2791,7 +2827,7 @@ fn unoptype(c: *checker, e: *node) *node = {
// cstage's actual acceptance reason.
if (e.lhs.kind == nkind.N_DOT) {
if (e.lhs.lhs != nil && e.lhs.lhs.kind == nkind.N_IDENT) {
let fs: *sym = scopelookupinmodule(c.cur, e.lhs.lhs.str, e.lhs.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.lhs.str), e.lhs.str);
if (fs != nil) {
if (fs.skind == skind.SK_FN) {
if (fs.decl != nil) {
@@ -3449,7 +3485,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
// global-leaf path) and harec check_autodereference
// (ref/harec/src/check.c:1566-1581).
if (ms != nil && (ms.skind == skind.SK_USE || ms.use_alias != 0i32)) {
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
s = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
};
};
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
@@ -3509,7 +3545,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
// qualified resolution and the lenient checker policy
// keeps the silent miss documented at scruttype L656.
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, lhsn.str), e.str);
if (fs != nil) { if (fs.decl != nil) {
// #34: a module-qualified bare fn rvalue `mod.fn` types as
// its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is
@@ -5366,7 +5402,7 @@ fn calleefndecl(c: *checker, callee: *node) *node = {
};
if (ms != nil) {
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), callee.str);
if (fs != nil) {
if (fs.skind == skind.SK_FN) { return fs.decl; };
};
@@ -6320,29 +6356,25 @@ export fn checkfile(c: *checker, file: *node) void = {
d = d.next;
};
// Program-global, name-only, cross-module uniqueness on `main`.
// `main` lowers to ONE bare entry symbol, so a second top-level
// decl named `main` (any kind, any package) collides with the
// entry at link time — today a silent segfault / link-fail in
// both stages. The (name, module) duplicate rejects in installtop
// read a cross-package `foo.main` and the bare entry as distinct,
// so they miss this. Correct multi-main mangling (entry stays bare,
// the rest qualify) is deferred (task #32); reject loudly meanwhile
// (rule 7). Walks USER decls only — runs before the -T synth main
// is appended below — so a hosted-test build never false-counts.
// Twin of cmd/wcc/check.c.
// Program-global uniqueness on the ENTRY `main`. M1 #32: the entry is
// the ROOT-unit main (imported==0) — it alone lowers to the bare
// `main` symbol. An IMPORTED package's `main` (imported==1) mangles on
// its path (`foo.bar.main`) and may coexist, closing the old dup-main
// collision by construction (#31). Two ROOT entries still collide on
// the bare symbol → reject loud (rule 7). Walks USER decls only — runs
// before the -T synth main is appended below. Twin of cmd/wcc/check.c.
let firstmain: *node = nil;
let mm: *node = file.list;
for (mm != nil) {
let ismain: bool = (mm.kind == nkind.N_FNDECL
|| mm.kind == nkind.N_LET || mm.kind == nkind.N_DEF
|| mm.kind == nkind.N_TYPEDECL) && streq(mm.str, "main");
if (ismain) {
if (ismain && mm.imported == 0) {
if (firstmain == nil) {
firstmain = mm;
} else {
cerr(mm.file);
cerr(": error: duplicate top-level main: only the entry main may exist (task #32)\n");
cerr(": error: duplicate entry main: only one root main may exist (#32)\n");
c.errs += 1;
};
};