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

@@ -123,10 +123,13 @@ fn aliaslookup(c: *cgen, name: str) *node = {
let leaf: str;
leaf.ptr = name.ptr + ((i + 1): u64);
leaf.len = name.len - (i + 1);
// M1 #22: map the embedded use ALIAS (`utf8`) to the
// dotted import PATH the decl's module now carries.
let pkgmod: str = usehint(c, pkg);
let b: *aliasent = c.aliases;
for (b != nil) {
if (streq(b.aname, leaf)) {
if (streq(b.amod, pkg)) {
if (streq(b.amod, pkgmod)) {
return b.target;
};
};
@@ -311,10 +314,13 @@ fn enumlookup(c: *cgen, name: str) *enumtype = {
let leaf: str;
leaf.ptr = name.ptr + ((i + 1): u64);
leaf.len = name.len - (i + 1);
// M1 #22: map the embedded use ALIAS (`utf8`) to the
// dotted import PATH the decl's module now carries.
let pkgmod: str = usehint(c, pkg);
let b: *enumtype = c.enums;
for (b != nil) {
if (streq(b.ename, leaf)) {
if (streq(b.emod, pkg)) {
if (streq(b.emod, pkgmod)) {
return b;
};
};
@@ -485,6 +491,9 @@ type cgen = struct {
enums: *enumtype,
mods: *modent, // fn (any export status) + non-exported
// let/def/type decls → originating module
uses: *modent, // M1 #22: N_USE alias → dotted import path,
// for the qualified-ref codegen hint
// (mname=alias, nmod=path)
lets: *letvar, // top-level mutable scalar `let` bindings
fnname: str,
curmod: str, // current fn's `// MODULE: foo` directive (len=0
@@ -2860,7 +2869,7 @@ fn emittuplerowrelocs(c: *cgen, name: str, module: str, backing: bool, rowoff: i
// the leaf with the MODULE ident; same-module `&fn`
// stays on curmod.
if (ev.lhs.kind == nkind.N_DOT) {
emitfnname(c, ev.lhs.str, ev.lhs.lhs.str);
emitfnname(c, ev.lhs.str, usehint(c, ev.lhs.lhs.str));
} else {
emitfnname(c, ev.lhs.str, c.curmod);
};
@@ -3047,7 +3056,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
// leaf with the MODULE ident; same-module `&fn`
// stays on curmod.
if (r.lhs.kind == nkind.N_DOT) {
emitfnname(c, r.lhs.str, r.lhs.lhs.str);
emitfnname(c, r.lhs.str, usehint(c, r.lhs.lhs.str));
} else {
emitfnname(c, r.lhs.str, c.curmod);
};
@@ -3518,11 +3527,24 @@ fn fnretlookup(c: *cgen, name: str) *node = {
// the scrutinee tagged type to `(rune | done)` — flatvariantidx then
// can't see arms 2/3 and collapses them onto tag 0 (task #31).
fn fnretlookupmod(c: *cgen, name: str, mod: str) *node = {
if (mod.len > 0) {
// M1 #22 (#199b): the qualifier may be the import ALIAS the user
// wrote (`utf8`); fn decls register f.fmod under the dotted import
// PATH (`encoding.utf8`). Map alias->path so a nested-package callee
// matches its own module instead of falling back to the name-only
// pass — which a same-leaf caller-module fn (e.g. strings.next vs
// utf8.next) otherwise wins, resolving a match scrutinee to the
// caller's union and collapsing arms 2+. usehint is idempotent on a
// path / c.curmod (returns the input when no `use` matches), so the
// already-mapped callers (cgenexpr.ww:4309/5229) and the bare-ident
// c.curmod callers are unaffected. The choke-point twin of the
// struct/alias/enum usehint splitters (cgen.ww:128/319,
// cgenutil.ww:2173) — closes the whole fnret class by construction.
let mk: str = usehint(c, mod);
if (mk.len > 0) {
let f: *fnret = c.fnrets;
for (f != nil) {
if (streq(f.fname, name)) {
if (streq(f.fmod, mod)) { return f.rtype; };
if (streq(f.fmod, mk)) { return f.rtype; };
};
f = f.frnext;
};
@@ -3582,11 +3604,15 @@ fn samemodfn(c: *cgen, name: str) bool = {
// matching module is registered — mirrors aliaslookup's two-pass shape
// (cgen.ww:75, fixed in #27).
fn fnparamslookupmod(c: *cgen, name: str, mod: str) *node = {
if (mod.len > 0) {
// M1 #22 (#199b): map import alias -> dotted path, identical to
// fnretlookupmod (the param-side twin). usehint is idempotent on a
// path / c.curmod so existing callers are unaffected.
let mk: str = usehint(c, mod);
if (mk.len > 0) {
let f: *fnret = c.fnrets;
for (f != nil) {
if (streq(f.fname, name)) {
if (streq(f.fmod, mod)) { return f.params; };
if (streq(f.fmod, mk)) { return f.params; };
};
f = f.frnext;
};
@@ -3766,9 +3792,17 @@ type modent = struct {
fn collectmods(c: *cgen, file: *node) void = {
c.mods = nil;
c.uses = nil;
if (file == nil) { return; };
let d: *node = file.list;
for (d != nil) {
// M1 #22: record alias→path for the qualified-ref hint.
if (d.kind == nkind.N_USE) {
if (d.usepath.len > 0) {
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, mnext=c.uses})!;
c.uses = um;
};
};
// Mirror collectfnrets' shape exactly (plain prepend in one
// branch). Earlier nested-if/early-return variants tickled a
// wwstage cgen bug that dropped most prepends.
@@ -3786,7 +3820,9 @@ fn collectmods(c: *cgen, file: *node) void = {
a = a.next;
};
if (!isffi) {
if (!streq(d.str, "main")) {
// M1 #32: the ROOT main (imported==0) stays bare;
// an IMPORTED `fn main` mangles on its path.
if (!streq(d.str, "main") || d.imported != 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
@@ -3833,6 +3869,19 @@ fn modlookup(c: *cgen, name: str) str = {
return empty;
};
// usehint — M1 #22: map a qualified-ref alias (`utf8`) to its dotted
// import path (`encoding.utf8`) so the codegen hint keys the path-keyed
// mods map. For single-level packages alias == path (no-op). Returns the
// alias unchanged when no matching `use` exists.
fn usehint(c: *cgen, alias: str) str = {
let m: *modent = c.uses;
for (m != nil) {
if (streq(m.mname, alias)) { return m.nmod; };
m = m.mnext;
};
return alias;
};
// modlookupforfn — hint-aware lookup for fn names. Walks c.mods
// preferring entries where module matches `hint`; falls back to the
// first leaf-name match when nothing matches the hint (legacy single-