ww: add file-scoped import aliases

This commit is contained in:
2026-08-14 10:56:34 +09:00
parent 351f4a25bc
commit 792b6ecbe5
14 changed files with 587 additions and 430 deletions

View File

@@ -60,6 +60,8 @@ lookup_builtin(const char *name)
static const char *decl_mod(Node *file, Node *d);
static const char *use_path(Node *file, const char *curmod, int source,
const char *alias);
static const char *find_use_path(Node *file, const char *curmod, int source,
const char *alias, int mark);
static int src_imports(Node *file, const char *modtag, int source,
const char *name);
static Sym *lookup_visible(Checker *c, const char *name);
@@ -2911,7 +2913,8 @@ decl_mod(Node *file, Node *d)
* Returns NULL if the referencing package has no such `use`.
*/
static const char *
use_path(Node *file, const char *curmod, int source, const char *alias)
find_use_path(Node *file, const char *curmod, int source, const char *alias,
int mark)
{
if (file == NULL || alias == NULL) return NULL;
/* Legacy inline multi-package units may spell a package's own
@@ -2931,13 +2934,19 @@ use_path(Node *file, const char *curmod, int source, const char *alias)
int same = (um == NULL) ? (curmod == NULL)
: (curmod != NULL && strcmp(um, curmod) == 0);
if (same) {
u->used = 1;
if (mark) u->used = 1;
return p;
}
}
return NULL;
}
static const char *
use_path(Node *file, const char *curmod, int source, const char *alias)
{
return find_use_path(file, curmod, source, alias, 1);
}
/*
* resolve_typedecl — resolve d's body into its installed TY_NAMED
* placeholder. Reached from check_file's typedecl pass AND on demand
@@ -3019,29 +3028,6 @@ src_imports(Node *file, const char *modtag, int source, const char *name)
return 0;
}
/* A flattened interface symbol is source-visible as a bare name only when
* the referencing package directly imports the symbol's defining path. The
* strict scope helpers above handle lexical locals, builtins and same-package
* declarations first; this second pass restores WW's existing direct-import
* convenience without admitting a transitive interface by accident. */
static int
direct_module_visible(Checker *c, const char *mod)
{
if (c == NULL || mod == NULL || mod[0] == '\0') return 0;
for (Node *u = c->file->list; u; u = u->next) {
if (u->kind != N_USE || u->sourceid != c->cur_source) continue;
const char *um = decl_mod(c->file, u);
int same = c->cur_mod == NULL ? um == NULL
: um != NULL && strcmp(um, c->cur_mod) == 0;
const char *path = u->usepath ? u->usepath : u->str;
if (same && path != NULL && strcmp(path, mod) == 0) {
u->used = 1;
return 1;
}
}
return 0;
}
static Sym *
lookup_visible(Checker *c, const char *name)
{
@@ -3053,24 +3039,16 @@ lookup_visible(Checker *c, const char *name)
if (s->decl != NULL) return s;
builtin = s;
}
/* N_USE entries are historically coalesced by leaf in the flat scope,
* so the retained symbol may carry another source package's owner. The
* source-owned alias map is authoritative: if this package directly
* imports NAME, return the coalesced module marker only as a marker; the
* N_DOT path maps the alias to the correct full path again. */
if (use_path(c->file, c->cur_mod, c->cur_source, name) != NULL) {
/* Flat scope installation may coalesce equal qualifiers from distinct
* files. The source-owned binding is authoritative, but a bare mention is
* not usage; only the enclosing qualified lookup may mark it. */
if (find_use_path(c->file, c->cur_mod, c->cur_source, name, 0) != NULL) {
for (Scope *p = c->cur; p; p = p->parent)
for (Sym *b = p->first; b; b = b->next)
if (strcmp(b->name, name) == 0
&& (b->kind == SK_USE || b->use_alias))
return b;
}
for (Scope *p = c->cur; p; p = p->parent) {
for (Sym *b = p->first; b; b = b->next)
if (strcmp(b->name, name) == 0
&& direct_module_visible(c, b->mod))
return b;
}
return builtin;
}
@@ -3079,14 +3057,6 @@ lookup_visible_type(Checker *c, const char *name)
{
Sym *s = scope_lookup_type(c->cur, c->cur_mod, name);
if (s != NULL) return s;
for (Scope *p = c->cur; p; p = p->parent) {
for (Sym *b = p->first; b; b = b->next)
if (b->kind == SK_TYPE && strcmp(b->name, name) == 0
&& direct_module_visible(c, b->mod)
&& (b->decl == NULL || !b->decl->imported
|| b->decl->export))
return b;
}
return NULL;
}
@@ -3162,6 +3132,44 @@ top_decl_kind(Node *d)
|| d->kind == N_FNDECL || d->kind == N_LET);
}
/* Import usage is a property of the file-local qualifier occurrence. Record
* qualified syntax before resolving declaration bodies so import diagnostics
* retain production Go's source order without making a failed bare lookup a
* use. WW already rejects lexical bindings that shadow an import qualifier. */
static void
mark_import_uses_node(Checker *c, Node *n, const char *owner, int source)
{
if (n == NULL) return;
if (n->kind == N_TNAME && n->str != NULL) {
const char *dot = strrchr(n->str, '.');
if (dot != NULL) {
char *head = astrndup(c->a, n->str, (size_t)(dot - n->str));
(void)find_use_path(c->file, owner, source, head, 1);
}
} else if (n->kind == N_DOT && n->lhs != NULL
&& n->lhs->kind == N_IDENT && n->lhs->str != NULL) {
(void)find_use_path(c->file, owner, source, n->lhs->str, 1);
}
for (Node *p = n->attr; p; p = p->next)
mark_import_uses_node(c, p, owner, source);
mark_import_uses_node(c, n->lhs, owner, source);
mark_import_uses_node(c, n->rhs, owner, source);
mark_import_uses_node(c, n->cond, owner, source);
mark_import_uses_node(c, n->body, owner, source);
mark_import_uses_node(c, n->els, owner, source);
for (Node *p = n->list; p; p = p->next)
mark_import_uses_node(c, p, owner, source);
}
static void
mark_import_uses(Checker *c, Node *file)
{
for (Node *d = file->list; d; d = d->next) {
if (d->kind == N_USE) continue;
mark_import_uses_node(c, d, decl_mod(file, d), d->sourceid);
}
}
static void
check_import_alt(Node *d, const char *name)
{
@@ -3204,7 +3212,8 @@ check_import_usage_and_collisions(Checker *c, Node *file)
for (Node *u = file->list; u; u = u->next) {
if (u->kind != N_USE || u->imported || u->used || u->str == NULL)
continue;
const char *path = u->usepath ? u->usepath : u->str;
const char *path = u->usesource ? u->usesource
: u->usepath ? u->usepath : u->str;
const char *dot = strrchr(path, '.');
const char *leaf = dot ? dot + 1 : path;
if (strcmp(u->str, leaf) == 0)
@@ -3220,7 +3229,8 @@ check_import_usage_and_collisions(Checker *c, Node *file)
if (u->kind != N_USE || u->imported || u->str == NULL
|| strcmp(d->str, u->str) != 0)
continue;
const char *path = u->usepath ? u->usepath : u->str;
const char *path = u->usesource ? u->usesource
: u->usepath ? u->usepath : u->str;
err(c, d->pos,
"%s already declared through import of package %s (\"%s\")",
d->str, u->str, path);
@@ -3248,11 +3258,12 @@ check_file(Checker *c, Node *file)
/* The synthetic runner belongs to file->sourceid. An import in an
* earlier module-reset section neither supplies nor uses its binding. */
for (Node *u = file->list; u; u = u->next) {
if (c->test_target != NULL && u->kind == N_USE
if (u->kind == N_USE
&& !u->imported && u->sourceid == file->sourceid
&& u->usepath
&& (strcmp(u->usepath, c->test_target) == 0
|| strcmp(u->usepath, c->test_module) == 0))
&& (strcmp(u->usepath, c->test_module) == 0
|| (c->test_target != NULL
&& strcmp(u->usepath, c->test_target) == 0)))
u->used = 1;
if (u->kind == N_USE && !u->imported
&& u->sourceid == file->sourceid
@@ -3264,15 +3275,18 @@ check_file(Checker *c, Node *file)
Node *usenode = newnode(c->a, N_USE, file->pos);
usenode->str = c->test_module;
usenode->strlen = strlen(c->test_module);
usenode->usesource = c->test_module;
usenode->usepath = c->test_module;
usenode->pkgname = file->pkgname;
usenode->sourceid = file->sourceid;
if (c->test_target != NULL) usenode->used = 1;
usenode->used = 1;
usenode->next = file->list;
file->list = usenode;
}
}
mark_import_uses(c, file);
check_import_redeclarations(c, file);
check_import_usage_and_collisions(c, file);
/* pass 1: install names (types first, then defs/fns).
* For self-referential types we install the named-type placeholder
@@ -3915,8 +3929,6 @@ check_file(Checker *c, Node *file)
}
c->cur_mod = NULL;
c->cur_source = 0;
check_import_usage_and_collisions(c, file);
/*
* #6 harec-fidelity (ref/harec/src/check.c:3941): a @test fn is
* fully checked above — pass 2 walked its body like every fn — but

View File

@@ -1315,30 +1315,40 @@ parseblock(Parser *p)
return n;
}
/* `import encoding.utf8;` — the driver resolves the dotted path to a
* directory; the checker only needs the leaf (`utf8`) as the module
* bareword for n_use→decl disambiguation, mirroring Hare's
* `use encoding::utf8;` → `utf8::name` (ref/hare/hare/ast/import.ha:7
* stores `ident: []str` but identifier-resolution uses the last
* component). */
/* The optional alias is source-local; the dotted path remains the dependency
* identity supplied to the package driver. */
static Node *
parseuse(Parser *p)
{
Pos pp = p->cur.pos;
expect(p, TK_USE);
Node *n = newnode(p->a, N_USE, pp);
/* M1 #22: accumulate the full dotted import path in usepath. `str`
* starts as its leaf; direct export metadata later installs the imported
* declaration's default name without changing canonical identity. */
const char *leaf = expectident(p);
const char *alias = NULL;
const char *first;
if (p->cur.kind == TK_UNDER) {
errorf(p->cur.pos, "blank import alias _ is not implemented");
p->errs++;
alias = "_";
advance(p);
first = expectident(p);
} else {
first = expectident(p);
if (p->cur.kind == TK_IDENT) {
alias = first;
first = expectident(p);
}
}
const char *leaf = first;
const char *path = leaf;
while (accept(p, TK_DOT)) {
leaf = expectident(p);
path = aprintf(p->a, "%s.%s", path, leaf);
}
n->str = leaf;
n->strlen = strlen(leaf);
n->str = alias ? alias : leaf;
n->strlen = strlen(n->str);
n->usesource = path;
n->usepath = path;
n->usealias = alias;
expect(p, TK_SEMI);
return n;
}

View File

@@ -343,11 +343,12 @@ struct Node {
* let) carry it. On an N_USE node
* this is the importing (owning)
* module. */
const char *usepath; /* M1 #22: on an N_USE node, the full
* canonical import path (`encoding.utf8`);
* `str` is the declared default qualifier. Drives
* the path-keyed decl_mod match and the
* qualified-ref codegen hint. */
const char *usesource; /* N_USE: immutable dotted source spelling. */
const char *usepath; /* N_USE: canonical, vendor-expanded identity;
* initially equal to `usesource`. */
const char *usealias; /* N_USE: explicit file-local alias, or NULL. */
const char *usepkgname; /* N_USE: imported declared package name,
* independent of the visible binding in `str`. */
const char *pkgname; /* declared package name for this source/export
* section; independent of canonical `module`. */
int sourceid; /* lexical source-file scope within the parsed