wcc/ww: module-scope the cgen mangle-hint (#40)

use_hint/usehint were unit-global first-leaf-match: two directory-
packages exporting the same fn leaf, each imported by a different module
aliasing the same bareword, mis-routed every qualified call to whichever
use was collected first. Identically wrong on both stages (byte-id-green
#263-class). Key the hint on (owner-module, alias) and prefer cur_mod,
mirroring the checker's use_path curmod-preference (55f54fb).

989_m1usehint_run: two same-leaf pick() across a.math/b.math, each
module's call routes to its own import (111/222) + cs.s==ww.s.
This commit is contained in:
2026-06-15 19:01:51 +09:00
parent f308818b4b
commit e9c11cb5ae
6 changed files with 364 additions and 44 deletions

View File

@@ -1175,33 +1175,36 @@ typedef struct Use Use;
struct Use {
const char *alias;
const char *path;
const char *module; /* owning module of the `use` decl (#40) */
Use *next;
};
static Use *use_map;
/*
* Retained divergence (M1 #22): this map is file-global — when two
* modules in the same unit bind the same leaf alias to different paths
* (sha256's `import crypto.math` vs strconv's `import math`), the first
* match wins. The checker's twin (use_path, check.c) is module-scoped
* to fix exactly this for qualified-NAME resolution; the cgen mangle
* hint is NOT, because it is unreachable with an ambiguous alias: a
* cross-module qualified ref resolves only to an EXPORTED symbol, and
* exported non-fn decls stay bare (mod_collect skips them, so the hint
* is moot) while an exported-fn collision would require two same-leaf
* packages to export the same fn name AND a third caller — not present
* (the tree links cleanly). Left file-global to stay byte-identical
* with wwstage's symmetric usehint (cgen.ww), which is also file-global
* (rule 10). Module-scope both stages together if a real collision
* surfaces — filed as the M1 cgen-hint twin follow-up.
* use_hint — map a `use` alias to its dotted import path for the
* qualified-ref mangle hint. NOT file-global: two modules in one unit
* may bind the same leaf alias to different paths (#40 — module one's
* `import a.math` and module two's `import b.math` both alias `math`).
* The import declared in the SAME module as the reference (curmod) is
* authoritative; preferring it routes each `math.pick()` to its own
* package. Falls back to any matching alias when curmod has no own
* import (single-occurrence case). Mirrors the checker's use_path
* curmod-preference (check.c, M1 55f54fb). Returns the alias unchanged
* when no `use` matches.
*/
static const char *
use_hint(const char *alias)
use_hint(const char *curmod, const char *alias)
{
const char *any = NULL;
if (alias == NULL) return alias;
for (Use *u = use_map; u; u = u->next)
if (strcmp(u->alias, alias) == 0) return u->path;
return alias;
for (Use *u = use_map; u; u = u->next) {
if (strcmp(u->alias, alias) != 0) continue;
int same = (u->module == NULL) ? (curmod == NULL)
: (curmod != NULL && strcmp(u->module, curmod) == 0);
if (same) return u->path;
if (any == NULL) any = u->path;
}
return any ? any : alias;
}
/* Top-level `let` map. Populated alongside mod_map; consulted by the
@@ -1428,6 +1431,7 @@ mod_collect(Cg *c, Node *file)
Use *u = amalloc(c->a, sizeof *u);
u->alias = d->str;
u->path = d->usepath;
u->module = d->module;
u->next = use_map;
use_map = u;
continue;
@@ -4371,7 +4375,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (lu && lu->kind == TY_FN)
ins2(c, A_LEAQ,
mafn(c, opnd->str,
use_hint(opnd->lhs->str)),
use_hint(c->cur_mod, opnd->lhs->str)),
areg(D_AX));
else
/* #229: dotted-module value
@@ -4380,7 +4384,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* same-leaf collision. */
ins2(c, A_LEAQ,
mahint(c, opnd->str,
use_hint(opnd->lhs->str)),
use_hint(c->cur_mod, opnd->lhs->str)),
areg(D_AX));
break;
}
@@ -10232,7 +10236,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* same-leaf exports resolve correctly. */
ins1(c, A_CALL,
mafn(c, n->lhs->str,
use_hint(n->lhs->lhs->str)));
use_hint(c->cur_mod, n->lhs->lhs->str)));
} else {
cgexpr(c, n->lhs, locals); /* AX = fn ptr */
ins1(c, A_CALL, areg(D_AX));
@@ -11108,7 +11112,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
/* `mod.fn` address-of via N_DOT — pass the
* module bareword as the disambiguation hint. */
ins2(c, A_LEAQ,
mafn(c, n->str, use_hint(n->lhs->str)),
mafn(c, n->str, use_hint(c->cur_mod, n->lhs->str)),
areg(D_AX));
break;
}
@@ -11125,7 +11129,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
for (s = sdefs; s; s = s->next) {
if (strcmp(s->name, n->str) != 0)
continue;
if (sdef_mod_match_hint(s, use_hint(n->lhs->str)))
if (sdef_mod_match_hint(s, use_hint(c->cur_mod, n->lhs->str)))
break;
}
if (s == NULL) {
@@ -11157,11 +11161,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
* branch above already uses n->lhs->str via mafn. */
if (mqop == A_MOVQ) {
ins2(c, A_MOVQ,
mahint(c, n->str, use_hint(n->lhs->str)),
mahint(c, n->str, use_hint(c->cur_mod, n->lhs->str)),
areg(D_AX));
} else {
ins2(c, A_LEAQ,
mahint(c, n->str, use_hint(n->lhs->str)),
mahint(c, n->str, use_hint(c->cur_mod, n->lhs->str)),
areg(D_CX));
ins2(c, mqop, amem(D_CX, 0), areg(D_AX));
}
@@ -16049,7 +16053,7 @@ node_fnptr_sym(Cg *c, Node *ev)
return NULL;
Type *du = type_chase_named(opnd->type);
if (du == NULL || du->kind != TY_FN) return NULL;
return mod_mangle_fn(c, opnd->str, use_hint(opnd->lhs->str));
return mod_mangle_fn(c, opnd->str, use_hint(c->cur_mod, opnd->lhs->str));
}
if (opnd == NULL || opnd->kind != N_IDENT) return NULL;
Type *ou = type_chase_named(opnd->type);