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

@@ -67,38 +67,63 @@ parseinput(Arena *a, const char *file, char *buf, u64 len,
return f;
}
/* Export data carries canonical owner and declared package name separately.
* Search every direct interface's package-clause markers because its
* self-contained fact closure can also name a transitive owner. */
static int
canonical_pkgname(const char *path, const char *name)
{
static const char hex[] = "0123456789abcdef";
const char *prefix = "__wwi_";
for (int i = 0; prefix[i]; i++)
if (*name++ != prefix[i]) return 0;
for (const unsigned char *p = (const unsigned char *)path; *p; p++) {
if (*name++ != hex[*p >> 4] || *name++ != hex[*p & 15])
return 0;
}
return *name == '\0';
}
static void
consider_pkgname(const char *path, const char *candidate,
const char **name, const char **placeholder, int *conflict)
{
if (canonical_pkgname(path, candidate)) {
*placeholder = candidate;
return;
}
if (*name != NULL && strcmp(*name, candidate) != 0) {
*conflict = 1;
return;
}
*name = candidate;
}
/* A paired interface owns PATH's declared name. Compiler-private names keep
* transitive fact sections semantic and are ignored when a real name exists. */
static const char *
import_pkgname(struct importin *imports, int nimports, const char *path,
Node *primary, int *conflict)
{
const char *name = NULL;
const char *placeholder = NULL;
for (int i = 0; i < nimports; i++) {
if (strcmp(imports[i].path, path) != 0) continue;
Node *file = imports[i].ast;
for (Node *p = file ? file->body : NULL; p; p = p->next) {
if (p->module == NULL || p->pkgname == NULL
|| strcmp(p->module, path) != 0)
continue;
if (name != NULL && strcmp(name, p->pkgname) != 0) {
*conflict = 1;
return NULL;
}
name = p->pkgname;
consider_pkgname(path, p->pkgname, &name, &placeholder,
conflict);
if (*conflict) return NULL;
}
}
for (Node *p = primary ? primary->body : NULL; p; p = p->next) {
if (p->module == NULL || p->pkgname == NULL
|| strcmp(p->module, path) != 0)
continue;
if (name != NULL && strcmp(name, p->pkgname) != 0) {
*conflict = 1;
return NULL;
}
name = p->pkgname;
consider_pkgname(path, p->pkgname, &name, &placeholder, conflict);
if (*conflict) return NULL;
}
return name;
return name ? name : placeholder;
}
static int
@@ -122,8 +147,9 @@ bind_import_names(Node *list, struct importin *imports, int nimports,
return -1;
}
if (name != NULL) {
u->str = name;
u->strlen = strlen(name);
u->usepkgname = name;
u->str = u->usealias ? u->usealias : name;
u->strlen = strlen(u->str);
} else if (!u->imported) {
fprintf(stderr,
"w6c: import %s has no declared package name in direct export data\n",
@@ -133,8 +159,8 @@ bind_import_names(Node *list, struct importin *imports, int nimports,
/* A closure-only import need not contribute declarations to this
* interface. Keep it canonical-path keyed without reinstalling
* the historical path-leaf qualifier. */
u->str = u->usepath;
u->strlen = strlen(u->usepath);
u->str = u->usealias ? u->usealias : u->usepath;
u->strlen = strlen(u->str);
}
}
return 0;
@@ -348,13 +374,13 @@ main(int argc, char **argv)
Node *file = parseinput(a, src, buf, len, NULL, testsupport,
commandpackage || entrymode, &bad);
if (bad) return 1;
/* Source keeps its effective spelling and position, while package
* resolution supplies the expanded canonical owner. Rewrite only the
* primary import key before imported interface nodes are prepended. */
/* Vendor expansion changes only canonical identity. Source spelling and
* an optional file-local alias remain independent facts. */
for (Node *u = file->list; u; u = u->next) {
if (u->kind != N_USE || u->usepath == NULL) continue;
const char *source = u->usesource ? u->usesource : u->usepath;
for (int i = 0; i < nmaps; i++)
if (strcmp(u->usepath, maps[i].source) == 0) {
if (strcmp(source, maps[i].source) == 0) {
u->usepath = maps[i].path;
maps[i].seen = 1;
break;

View File

@@ -68,19 +68,41 @@ wwi_use_path(Checker *c, const char *owner, int source, const char *alias)
return NULL;
}
static int
wwi_direct_mod_visible(Checker *c, const char *owner, int source,
const char *mod)
/* Export spelling is derived only from canonical identity. The bytewise hex
* encoding is injective, source-identifier-safe, and independent of a local
* alias or the dependency's declared package name. */
static void
wwi_canonical_alias(FILE *of, const char *path)
{
if (mod == NULL || mod[0] == '\0') return 0;
for (Node *u = c->file->list; u; u = u->next) {
if (u->kind != N_USE || u->sourceid != source) continue;
int same = owner == NULL ? u->imported == 0
: u->imported != 0 && wwi_mod_eq(u->module, owner);
const char *path = u->usepath ? u->usepath : u->str;
if (same && path != NULL && strcmp(path, mod) == 0) return 1;
static const char hex[] = "0123456789abcdef";
fputs("__wwi_", of);
for (const unsigned char *p = (const unsigned char *)path; *p; p++) {
fputc(hex[*p >> 4], of);
fputc(hex[*p & 15], of);
}
return 0;
}
static void
wwi_name(Checker *c, FILE *of, const char *owner, int source,
const char *name)
{
if (name == NULL) return;
const char *dot = strrchr(name, '.');
if (dot != NULL) {
size_t n = (size_t)(dot - name);
char *alias = malloc(n + 1);
if (alias == NULL) fatal("wwi: out of memory");
memcpy(alias, name, n);
alias[n] = '\0';
const char *path = wwi_use_path(c, owner, source, alias);
free(alias);
if (path != NULL) {
wwi_canonical_alias(of, path);
fputs(dot, of);
return;
}
}
fputs(name, of);
}
static Sym *
@@ -101,20 +123,6 @@ wwi_typesym(Checker *c, const char *owner, int source, const char *nm)
free(alias);
} else {
s = scope_lookup_type(c->cur, owner, nm);
if (s == NULL) {
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, nm) == 0
&& wwi_direct_mod_visible(c, owner, source,
b->mod)) {
s = b;
break;
}
}
if (s != NULL) break;
}
}
}
if (s && s->kind == SK_TYPE)
return s;
@@ -197,8 +205,8 @@ wwi_check_decl(Checker *c, Node *d)
/* type-expr unparse per rob §2.2; const-expr per rob §2.4. */
static void wwi_expr(FILE *of, Node *e);
static void wwi_type(FILE *of, Node *t);
static void wwi_expr(Checker*, FILE*, const char*, int, Node*);
static void wwi_type(Checker*, FILE*, const char*, int, Node*);
static void
wwi_quote(FILE *of, const char *s, u64 n)
@@ -248,7 +256,7 @@ wwi_rune(FILE *of, u64 cp)
}
static void
wwi_param(FILE *of, Node *p)
wwi_param(Checker *c, FILE *of, const char *owner, int source, Node *p)
{
if (p->str && strcmp(p->str, "...") == 0) { /* C-style FFI `...` */
fputs("...", of);
@@ -258,13 +266,13 @@ wwi_param(FILE *of, Node *p)
fputs(p->str, of);
fputs(": ", of);
}
wwi_type(of, p->lhs);
wwi_type(c, of, owner, source, p->lhs);
if (p->op == TK_ELLIPSIS) /* Hare `T...` variadic */
fputs("...", of);
}
static void
wwi_type(FILE *of, Node *t)
wwi_type(Checker *c, FILE *of, const char *owner, int source, Node *t)
{
if (t == NULL) { /* absent return type spells void */
fputs("void", of);
@@ -272,39 +280,40 @@ wwi_type(FILE *of, Node *t)
}
switch (t->kind) {
case N_TNAME:
fputs(t->str ? t->str : "void", of);
if (t->str) wwi_name(c, of, owner, source, t->str);
else fputs("void", of);
break;
case N_TPTR:
fputc('*', of);
wwi_type(of, t->lhs);
wwi_type(c, of, owner, source, t->lhs);
break;
case N_TSLICE:
fputs("[]", of);
wwi_type(of, t->lhs);
wwi_type(c, of, owner, source, t->lhs);
break;
case N_TARRAY:
fputc('[', of);
if (t->rhs) wwi_expr(of, t->rhs);
if (t->rhs) wwi_expr(c, of, owner, source, t->rhs);
else fputc('_', of);
fputc(']', of);
wwi_type(of, t->lhs);
wwi_type(c, of, owner, source, t->lhs);
break;
case N_TBANG:
fputc('!', of);
wwi_type(of, t->lhs);
wwi_type(c, of, owner, source, t->lhs);
break;
case N_TCHAN:
fputs("chan ", of);
wwi_type(of, t->lhs);
wwi_type(c, of, owner, source, t->lhs);
break;
case N_TFN:
fputs("fn(", of);
for (Node *p = t->list; p; p = p->next) {
if (p != t->list) fputs(", ", of);
wwi_param(of, p);
wwi_param(c, of, owner, source, p);
}
fputs(") ", of);
wwi_type(of, t->lhs);
wwi_type(c, of, owner, source, t->lhs);
break;
case N_TSTRUCT:
/* re-emit the `@packed` attr so the flag round-trips
@@ -316,7 +325,7 @@ wwi_type(FILE *of, Node *t)
fputs(f->str, of);
fputs(": ", of);
}
wwi_type(of, f->lhs);
wwi_type(c, of, owner, source, f->lhs);
}
fputs(" }", of);
break;
@@ -324,7 +333,7 @@ wwi_type(FILE *of, Node *t)
fputc('(', of);
for (Node *e = t->list; e; e = e->next) {
if (e != t->list) fputs(", ", of);
wwi_type(of, e);
wwi_type(c, of, owner, source, e);
}
fputc(')', of);
break;
@@ -337,14 +346,14 @@ wwi_type(FILE *of, Node *t)
* unparse/type.ha:290-300). */
if (e->op == TK_ELLIPSIS)
fputs("...", of);
wwi_type(of, e);
wwi_type(c, of, owner, source, e);
}
fputc(')', of);
break;
case N_TENUM:
fputs("enum ", of);
if (t->lhs) {
wwi_type(of, t->lhs);
wwi_type(c, of, owner, source, t->lhs);
fputc(' ', of);
}
fputs("{ ", of);
@@ -353,7 +362,7 @@ wwi_type(FILE *of, Node *t)
fputs(m->str, of);
if (m->lhs) {
fputs(" = ", of);
wwi_expr(of, m->lhs);
wwi_expr(c, of, owner, source, m->lhs);
}
}
fputs(" }", of);
@@ -364,7 +373,7 @@ wwi_type(FILE *of, Node *t)
}
static void
wwi_expr(FILE *of, Node *e)
wwi_expr(Checker *c, FILE *of, const char *owner, int source, Node *e)
{
if (e == NULL)
return;
@@ -375,10 +384,17 @@ wwi_expr(FILE *of, Node *e)
break;
case N_IDENT:
case N_TNAME:
fputs(e->str, of);
wwi_name(c, of, owner, source, e->str);
break;
case N_DOT:
wwi_expr(of, e->lhs);
if (e->lhs && e->lhs->kind == N_IDENT) {
const char *path = wwi_use_path(c, owner, source,
e->lhs->str);
if (path != NULL) wwi_canonical_alias(of, path);
else wwi_expr(c, of, owner, source, e->lhs);
} else {
wwi_expr(c, of, owner, source, e->lhs);
}
fputc('.', of);
fputs(e->str, of);
break;
@@ -393,20 +409,20 @@ wwi_expr(FILE *of, Node *e)
case N_RUNELIT: wwi_rune(of, e->uval); break;
case N_BIN:
fputc('(', of);
wwi_expr(of, e->lhs);
wwi_expr(c, of, owner, source, e->lhs);
fprintf(of, " %s ", tokname(e->op));
wwi_expr(of, e->rhs);
wwi_expr(c, of, owner, source, e->rhs);
fputc(')', of);
break;
case N_UN:
fputs(tokname(e->op), of);
wwi_expr(of, e->lhs);
wwi_expr(c, of, owner, source, e->lhs);
break;
case N_CAST:
fputc('(', of);
wwi_expr(of, e->lhs);
wwi_expr(c, of, owner, source, e->lhs);
fputs(": ", of);
wwi_type(of, e->rhs);
wwi_type(c, of, owner, source, e->rhs);
fputc(')', of);
break;
default:
@@ -428,7 +444,7 @@ wwi_attr_relevant(const char *nm)
}
static void
wwi_attrs(FILE *of, Node *d)
wwi_attrs(Checker *c, FILE *of, const char *owner, Node *d)
{
for (Node *a = d->attr; a; a = a->next) {
if (a->kind != N_ATTR || !wwi_attr_relevant(a->str))
@@ -439,7 +455,7 @@ wwi_attrs(FILE *of, Node *d)
fputc('(', of);
for (Node *arg = a->list; arg; arg = arg->next) {
if (arg != a->list) fputs(", ", of);
wwi_expr(of, arg);
wwi_expr(c, of, owner, d->sourceid, arg);
}
fputc(')', of);
}
@@ -448,34 +464,34 @@ wwi_attrs(FILE *of, Node *d)
}
static void
wwi_decl(FILE *of, Node *d)
wwi_decl(Checker *c, FILE *of, const char *owner, Node *d)
{
switch (d->kind) {
case N_FNDECL:
wwi_attrs(of, d);
wwi_attrs(c, of, owner, d);
fputs(d->export ? "export fn " : "fn ", of);
fputs(d->str, of);
fputc('(', of);
for (Node *p = d->list; p; p = p->next) {
if (p != d->list) fputs(", ", of);
wwi_param(of, p);
wwi_param(c, of, owner, d->sourceid, p);
}
fputs(") ", of);
wwi_type(of, d->lhs);
wwi_type(c, of, owner, d->sourceid, d->lhs);
fputs(";\n", of);
break;
case N_TYPEDECL:
fputs(d->export ? "export type " : "type ", of);
fputs(d->str, of);
fputs(" = ", of);
wwi_type(of, d->lhs);
wwi_type(c, of, owner, d->sourceid, d->lhs);
fputs(";\n", of);
break;
case N_DEF:
fputs(d->export ? "export def " : "def ", of);
fputs(d->str, of);
fputs(": ", of);
wwi_type(of, d->lhs);
wwi_type(c, of, owner, d->sourceid, d->lhs);
/* An aggregate initializer (N_STRUCTLIT/N_ARRLIT) is a DATA-
* global (#52): emit a value-LESS prototype `export def X: T;`.
* The defining package's own .o emits the struct/array DATA; the
@@ -491,7 +507,7 @@ wwi_decl(FILE *of, Node *d)
fputs(";\n", of);
} else {
fputs(" = ", of);
wwi_expr(of, d->rhs);
wwi_expr(c, of, owner, d->sourceid, d->rhs);
fputs(";\n", of);
}
break;
@@ -502,7 +518,7 @@ wwi_decl(FILE *of, Node *d)
if (d->lhs == NULL)
fatal("wwi: exported let '%s' has no declared type",
d->str);
wwi_type(of, d->lhs);
wwi_type(c, of, owner, d->sourceid, d->lhs);
fputs(";\n", of);
break;
default:
@@ -616,11 +632,6 @@ wwi_valuesym(Checker *c, const char *owner, int source, const char *name)
if (s->kind == SK_DEF && strcmp(s->name, name) == 0
&& wwi_mod_eq(s->mod, owner))
return s;
for (Scope *p = c->top; p; p = p->parent)
for (Sym *s = p->first; s; s = s->next)
if (s->kind == SK_DEF && strcmp(s->name, name) == 0
&& wwi_direct_mod_visible(c, owner, source, s->mod))
return s;
return NULL;
}
@@ -812,7 +823,9 @@ wwi_emit_imports(FILE *of, Node *file, const char *owner, int source,
const char *previous = NULL;
for (int i = 0; i < nuse; i++) {
if (previous && strcmp(previous, us[i].path) == 0) continue;
fprintf(of, "import %s;\n", us[i].path);
fputs("import ", of);
wwi_canonical_alias(of, us[i].path);
fprintf(of, " %s;\n", us[i].path);
previous = us[i].path;
}
free(us);
@@ -865,16 +878,16 @@ wwi_emit_primary_section(Checker *c, FILE *of, Node *file,
wwi_emit_imports(of, file, owner, source, 0);
for (int i = 0; i < fs->nprivate; i++)
if (fs->privatefacts[i].d->sourceid == source)
wwi_decl(of, fs->privatefacts[i].d);
wwi_decl(c, of, NULL, fs->privatefacts[i].d);
if (c->is_test_package)
for (Node *d = file->list; d; d = d->next)
if (wwi_primary(d) && d->sourceid == source
&& d->kind == N_FNDECL && !d->export
&& wwi_has_attr(d, "test"))
wwi_decl(of, d);
wwi_decl(c, of, NULL, d);
for (int i = 0; i < nexports; i++)
if (exports[i].d->sourceid == source)
wwi_decl(of, exports[i].d);
wwi_decl(c, of, NULL, exports[i].d);
}
int
@@ -964,16 +977,15 @@ wwi_emit(Checker *c, FILE *of, Node *file)
struct factent *f = &fs.facts[i];
if (lastmod == NULL || strcmp(lastmod, f->mod) != 0
|| lastsource != f->d->sourceid) {
const char *dot = strrchr(f->mod, '.');
const char *factpkg = f->d->pkgname && f->d->pkgname[0]
? f->d->pkgname : (dot ? dot + 1 : f->mod);
fprintf(of, "//ww:module %s\n", f->mod);
fprintf(of, "package %s;\n", factpkg);
fputs("package ", of);
wwi_canonical_alias(of, f->mod);
fputs(";\n", of);
wwi_emit_imports(of, file, f->mod, f->d->sourceid, 1);
lastmod = f->mod;
lastsource = f->d->sourceid;
}
wwi_decl(of, f->d);
wwi_decl(c, of, f->mod, f->d);
}
free(fs.privatefacts);
free(fs.facts);

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

View File

@@ -2076,8 +2076,10 @@ use_node_cmp(const void *a, const void *b)
{
const Node *x = *(Node *const *)a;
const Node *y = *(Node *const *)b;
const char *xp = x->usepath ? x->usepath : x->str;
const char *yp = y->usepath ? y->usepath : y->str;
const char *xp = x->usesource ? x->usesource
: x->usepath ? x->usepath : x->str;
const char *yp = y->usesource ? y->usesource
: y->usepath ? y->usepath : y->str;
int r = strcmp(xp, yp);
if (r != 0) return r;
r = strcmp(x->pos.file ? x->pos.file : "",
@@ -2503,7 +2505,8 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
int rc = 0;
for (int i = 0; i < nuse && rc == 0; i++) {
Node *u = uses[i];
const char *name = u->usepath ? u->usepath : u->str;
const char *name = u->usesource ? u->usesource
: u->usepath ? u->usepath : u->str;
if (reserved_import_path(name)) {
errorf(u->pos, "package path %s is reserved", name);
rc = -1;
@@ -3663,7 +3666,7 @@ static void
workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm)
{
snprintf(buf, bufsz, "ww workdir fmt %d mode %s asm %d\n",
is_test ? 13 : 14, is_test ? "test" : "build", emit_asm);
is_test ? 14 : 15, is_test ? "test" : "build", emit_asm);
}
/* A stale global builder identity invalidates every committed unit voucher in