ww: separate package identity from declared name

This commit is contained in:
2026-08-14 03:07:58 +09:00
parent 028da6323e
commit 6efe9b70d4
37 changed files with 1678 additions and 886 deletions

View File

@@ -1226,33 +1226,31 @@ struct Use {
const char *alias;
const char *path;
const char *module; /* owning module of the `use` decl (#40) */
int sourceid; /* owning lexical source-file scope */
Use *next;
};
static Use *use_map;
/*
* 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.
* use_hint — map a declared default qualifier to its canonical import path
* for qualified-ref mangling. It is source-file local: separate files may
* bind the same declared name to different paths. Raw non-package compilation
* retains its historical single-occurrence fallback. Returns the qualifier
* unchanged when no `use` matches.
*/
static const char *
use_hint(const char *curmod, const char *alias)
use_hint(Cg *c, 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) continue;
int same = (u->module == NULL) ? (curmod == NULL)
: (curmod != NULL && strcmp(u->module, curmod) == 0);
int same = u->sourceid == c->cur_source
&& ((u->module == NULL) ? (c->cur_mod == NULL)
: (c->cur_mod != NULL
&& strcmp(u->module, c->cur_mod) == 0));
if (same) return u->path;
if (any == NULL) any = u->path;
if (!c->sep_mode && any == NULL) any = u->path;
}
return any ? any : alias;
}
@@ -1484,6 +1482,7 @@ mod_collect(Cg *c, Node *file)
u->alias = d->str;
u->path = d->usepath;
u->module = d->module;
u->sourceid = d->sourceid;
u->next = use_map;
use_map = u;
continue;
@@ -4564,7 +4563,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (lu && lu->kind == TY_FN)
ins2(c, A_LEAQ,
mafn(c, opnd->str,
use_hint(c->cur_mod, opnd->lhs->str)),
use_hint(c, opnd->lhs->str)),
areg(D_AX));
else
/* #229: dotted-module value
@@ -4573,7 +4572,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* same-leaf collision. */
ins2(c, A_LEAQ,
mafn(c, opnd->str,
use_hint(c->cur_mod, opnd->lhs->str)),
use_hint(c, opnd->lhs->str)),
areg(D_AX));
break;
}
@@ -10976,7 +10975,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* same-leaf exports resolve correctly. */
ins1(c, A_CALL,
mafn(c, n->lhs->str,
use_hint(c->cur_mod, n->lhs->lhs->str)));
use_hint(c, n->lhs->lhs->str)));
} else {
cgexpr(c, n->lhs, locals); /* AX = fn ptr */
ins1(c, A_CALL, areg(D_AX));
@@ -11906,7 +11905,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(c->cur_mod, n->lhs->str)),
mafn(c, n->str, use_hint(c, n->lhs->str)),
areg(D_AX));
break;
}
@@ -11923,7 +11922,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(c->cur_mod, n->lhs->str)))
if (sdef_mod_match_hint(s, use_hint(c, n->lhs->str)))
break;
}
if (s == NULL) {
@@ -11955,11 +11954,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,
mafn(c, n->str, use_hint(c->cur_mod, n->lhs->str)),
mafn(c, n->str, use_hint(c, n->lhs->str)),
areg(D_AX));
} else {
ins2(c, A_LEAQ,
mafn(c, n->str, use_hint(c->cur_mod, n->lhs->str)),
mafn(c, n->str, use_hint(c, n->lhs->str)),
areg(D_CX));
ins2(c, mqop, amem(D_CX, 0), areg(D_AX));
}
@@ -15800,6 +15799,7 @@ cgfn(Cg *c, FILE *out, Node *fn)
c->head = c->tail = NULL;
c->fnname = fn->str;
c->cur_mod = (fn->module && fn->module[0]) ? fn->module : NULL;
c->cur_source = fn->sourceid;
c->labelseq = 0;
cg_stack_arg_cursor = 0;
ndefers = 0;
@@ -16913,7 +16913,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(c->cur_mod, opnd->lhs->str));
return mod_mangle_fn(c, opnd->str, use_hint(c, opnd->lhs->str));
}
if (opnd == NULL || opnd->kind != N_IDENT) return NULL;
Type *ou = type_chase_named(opnd->type);
@@ -17205,8 +17205,12 @@ emit_slice_data(FILE *out, Cg *c, const char *directive, const char *name,
static void
emit_lets(Cg *c, FILE *out, Node *file)
{
const char *save_mod = c->cur_mod;
int save_source = c->cur_source;
for (Node *d = file->list; d; d = d->next) {
if (d->kind != N_LET) continue;
c->cur_mod = (d->module && d->module[0]) ? d->module : NULL;
c->cur_source = d->sourceid;
if (d->str == NULL || d->str[0] == '\0') continue;
/* #22 M3 THE ONE REAL GUARD: a `.wwi` dep value-global is
* initializer-less; emitting a DATAW for it would DUPLICATE the
@@ -17377,6 +17381,8 @@ emit_lets(Cg *c, FILE *out, Node *file)
emit_data_row_zero(out, "DATAW",
mod_mangle_fn(c, d->str, d->module), sz);
}
c->cur_mod = save_mod;
c->cur_source = save_source;
}
/* Emit DATA directives for top-level `def` constants whose value
@@ -17392,8 +17398,12 @@ emit_lets(Cg *c, FILE *out, Node *file)
static void
emit_defs(Cg *c, FILE *out, Node *file)
{
const char *save_mod = c->cur_mod;
int save_source = c->cur_source;
for (Node *d = file->list; d; d = d->next) {
if (d->kind != N_DEF || d->rhs == NULL) continue;
c->cur_mod = (d->module && d->module[0]) ? d->module : NULL;
c->cur_source = d->sourceid;
/* #22 M3: a `.wwi` dep def with DATA storage (int-fold / float /
* struct / array) must NOT re-emit — the dep's own .o owns the
* symbol. Str defs are inline-spliced (sdef_collect), never
@@ -17455,7 +17465,8 @@ emit_defs(Cg *c, FILE *out, Node *file)
"asm.c:362); read-only `def` unsupported (#10, "
"rule 7)");
}
(void)c;
c->cur_mod = save_mod;
c->cur_source = save_source;
}
/* Collect str-typed `def`s so cgexpr N_IDENT can splice them inline.
@@ -17537,8 +17548,10 @@ let_pre_intern(Cg *c, Node *file)
* they did before. let_pre_intern itself only interns, so driving
* cur_mod here has no other effect. */
const char *save_mod = c->cur_mod;
int save_source = c->cur_source;
for (Node *d = file->list; d; d = d->next) {
c->cur_mod = (d->module && d->module[0]) ? d->module : NULL;
c->cur_source = d->sourceid;
/* #22 M3: skip imported deps so the strlit table (and its _S_
* sequence) is a pure function of THIS package's own decls. A
* dep's body initializer would intern here, but its `.wwi`
@@ -17658,6 +17671,7 @@ let_pre_intern(Cg *c, Node *file)
(void)intern_strlit(c, r->str, r->strlen);
}
c->cur_mod = save_mod;
c->cur_source = save_source;
}
void

View File

@@ -38,6 +38,8 @@ struct Cg {
* lib/foo binds to `foo.frob` regardless
* of which other modules also export `frob`.
* Set by cgfn before walking the body. */
int cur_source; /* lexical source-file scope of the current
* declaration; selects its own import bindings. */
int framesize; /* bytes of locals; 16-byte aligned */
int curoff; /* current top of locals */
Scope *locals; /* (name → offset) tracked via Sym */

View File

@@ -39,6 +39,7 @@ struct importin {
const char *file;
char *buf;
u64 len;
Node *ast;
};
struct importmap {
@@ -47,13 +48,6 @@ struct importmap {
int seen;
};
static const char *
importleaf(const char *path)
{
const char *dot = strrchr(path, '.');
return dot != NULL ? dot + 1 : path;
}
static Node *
parseinput(Arena *a, const char *file, char *buf, u64 len,
const char *mod, const char *testsupport, int commandpackage, int *bad)
@@ -73,6 +67,79 @@ 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 const char *
import_pkgname(struct importin *imports, int nimports, const char *path,
Node *primary, int *conflict)
{
const char *name = NULL;
for (int i = 0; i < nimports; i++) {
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;
}
}
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;
}
return name;
}
static int
bind_import_names(Node *list, struct importin *imports, int nimports,
Node *primary, const char *testsupport)
{
for (Node *u = list; u; u = u->next) {
if (u->kind != N_USE || u->usepath == NULL) continue;
/* The reserved test-support spelling is a compiler-owned alias, not
* source default-import syntax. */
if (testsupport != NULL && strcmp(testsupport, "__wwtest") == 0
&& strcmp(u->usepath, testsupport) == 0)
continue;
int conflict = 0;
const char *name = import_pkgname(imports, nimports, u->usepath,
primary, &conflict);
if (conflict) {
fprintf(stderr,
"w6c: package %s has conflicting declared names in export data\n",
u->usepath);
return -1;
}
if (name != NULL) {
u->str = name;
u->strlen = strlen(name);
} else if (!u->imported) {
fprintf(stderr,
"w6c: import %s has no declared package name in direct export data\n",
u->usepath);
return -1;
} else {
/* 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);
}
}
return 0;
}
static void
appendnodes(Node **head, Node **tail, Node *list)
{
@@ -90,6 +157,7 @@ main(int argc, char **argv)
const char *out = NULL;
const char *wwiout = NULL; /* -I <out.wwi>: M2 export-data producer */
const char *testsupport = NULL;
const char *testtarget = NULL;
int testmode = 0;
int testpackage = 0;
int commandpackage = 0;
@@ -127,6 +195,12 @@ main(int argc, char **argv)
return 2;
}
testsupport = argv[++i];
} else if (strcmp(a, "--test-target-package") == 0) {
if (i + 1 >= argc) {
fputs("w6c: --test-target-package requires arg\n", stderr);
return 2;
}
testtarget = argv[++i];
} else if (strcmp(a, "-c") == 0) {
sepmode = 1;
} else if (strcmp(a, "--import") == 0) {
@@ -156,7 +230,7 @@ main(int argc, char **argv)
}
}
if (src == NULL) {
fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [-c] [-I out.wwi] "
fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [--test-target-package path] [-c] [-I out.wwi] "
"[--import path dep.wwi]... [--import-map source path]... [-o out.s] file.ww\n", stderr);
return 2;
}
@@ -196,11 +270,6 @@ main(int argc, char **argv)
stderr);
return 2;
}
if (strcmp(importleaf(maps[i].source),
importleaf(maps[i].path)) != 0) {
fputs("w6c: --import-map must preserve import leaf\n", stderr);
return 2;
}
int direct = 0;
for (int j = 0; j < nimports; j++)
if (strcmp(maps[i].path, imports[j].path) == 0) {
@@ -219,6 +288,22 @@ main(int argc, char **argv)
fputs("w6c: invalid --test-support-module\n", stderr);
return 2;
}
if (testtarget != NULL && (!sepmode || !testmode
|| testtarget[0] == '\0')) {
fputs("w6c: invalid --test-target-package\n", stderr);
return 2;
}
if (testtarget != NULL) {
int direct = 0;
for (int i = 0; i < nimports; i++)
if (strcmp(imports[i].path, testtarget) == 0)
direct = 1;
if (!direct) {
fputs("w6c: --test-target-package is not a direct import\n",
stderr);
return 2;
}
}
Arena *a = newarena();
Checker c;
@@ -243,6 +328,10 @@ main(int argc, char **argv)
Node *f = parseinput(a, imports[i].file, imports[i].buf,
imports[i].len, imports[i].path, testsupport, 0, &bad);
if (bad) return 1;
imports[i].ast = f;
if (bind_import_names(f->list, imports, i + 1, NULL,
testsupport) < 0)
return 1;
appendnodes(&head, &tail, f->list);
}
@@ -259,7 +348,7 @@ 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 leaf alias, while package
/* 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. */
for (Node *u = file->list; u; u = u->next) {
@@ -276,6 +365,31 @@ main(int argc, char **argv)
fputs("w6c: --import-map source is not in primary input\n", stderr);
return 2;
}
/* Later direct interfaces may supply names for origin sections referenced
* by an earlier interface, so perform one complete metadata pass now. */
for (int i = 0; i < nimports; i++)
if (bind_import_names(imports[i].ast->list, imports, nimports,
NULL, testsupport) < 0)
return 1;
if (bind_import_names(file->list, imports, nimports, file,
testsupport) < 0)
return 1;
if (testtarget != NULL) {
int seen = 0;
for (Node *u = file->list; u; u = u->next) {
if (u->kind != N_USE || u->imported || u->usepath == NULL
|| strcmp(u->usepath, testtarget) != 0)
continue;
u->str = u->usepath;
u->strlen = strlen(u->usepath);
seen++;
}
if (seen != 1) {
fputs("w6c: generated test target import is not unique\n",
stderr);
return 2;
}
}
if (head != NULL) {
tail->next = file->list;
file->list = head;
@@ -285,6 +399,7 @@ main(int argc, char **argv)
c.is_test = testmode;
c.is_test_package = testpackage;
if (testsupport != NULL) c.test_module = testsupport;
c.test_target = testtarget;
c.sep_mode = sepmode;
check_file(&c, file);
if (c.errs) return 1;

View File

@@ -54,17 +54,11 @@ wwi_mod_eq(const char *a, const char *b)
* flat in the parser, so consulting every N_USE without this owner filter
* would let one dependency accidentally resolve another dependency's alias. */
static const char *
wwi_use_path(Checker *c, const char *owner, const char *alias)
wwi_use_path(Checker *c, const char *owner, int source, const char *alias)
{
if (owner && alias) {
const char *dot = strrchr(owner, '.');
const char *leaf = dot ? dot + 1 : owner;
if (strcmp(alias, leaf) == 0)
return owner;
}
for (Node *u = c->file->list; u; u = u->next) {
if (u->kind != N_USE || u->str == NULL
|| strcmp(u->str, alias) != 0)
|| u->sourceid != source || strcmp(u->str, alias) != 0)
continue;
int same = owner == NULL ? u->imported == 0
: u->imported != 0 && wwi_mod_eq(u->module, owner);
@@ -75,17 +69,22 @@ wwi_use_path(Checker *c, const char *owner, const char *alias)
}
static int
wwi_direct_mod_visible(Checker *c, const char *owner, const char *mod)
wwi_direct_mod_visible(Checker *c, const char *owner, int source,
const char *mod)
{
if (mod == NULL || mod[0] == '\0') return 0;
const char *dot = strrchr(mod, '.');
const char *alias = dot ? dot + 1 : mod;
const char *path = wwi_use_path(c, owner, alias);
return path != NULL && strcmp(path, mod) == 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;
}
return 0;
}
static Sym *
wwi_typesym(Checker *c, const char *owner, const char *nm)
wwi_typesym(Checker *c, const char *owner, int source, const char *nm)
{
if (nm == NULL) return NULL;
const char *dot = strrchr(nm, '.');
@@ -96,7 +95,7 @@ wwi_typesym(Checker *c, const char *owner, const char *nm)
if (alias == NULL) fatal("wwi: out of memory");
memcpy(alias, nm, n);
alias[n] = '\0';
const char *mod = wwi_use_path(c, owner, alias);
const char *mod = wwi_use_path(c, owner, source, alias);
if (mod)
s = scope_lookup_in_module(c->cur, mod, dot + 1);
free(alias);
@@ -107,7 +106,8 @@ wwi_typesym(Checker *c, const char *owner, const char *nm)
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, b->mod)) {
&& wwi_direct_mod_visible(c, owner, source,
b->mod)) {
s = b;
break;
}
@@ -563,6 +563,8 @@ factcmp(const void *a, const void *b)
const struct factent *x = a, *y = b;
int r = strcmp(x->mod, y->mod);
if (r != 0) return r;
if (x->d->sourceid != y->d->sourceid)
return x->d->sourceid - y->d->sourceid;
int xr = x->d->kind == N_TYPEDECL ? 0 : 1;
int yr = y->d->kind == N_TYPEDECL ? 0 : 1;
if (xr != yr) return xr - yr;
@@ -607,7 +609,7 @@ wwi_fact_grow(struct factent **v, int *cap, int need)
}
static Sym *
wwi_valuesym(Checker *c, const char *owner, const char *name)
wwi_valuesym(Checker *c, const char *owner, int source, const char *name)
{
for (Scope *p = c->top; p; p = p->parent)
for (Sym *s = p->first; s; s = s->next)
@@ -617,25 +619,26 @@ wwi_valuesym(Checker *c, const char *owner, const char *name)
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, s->mod))
return s;
&& wwi_direct_mod_visible(c, owner, source, s->mod))
return s;
return NULL;
}
static void wwi_collect_decl(struct factset*, const char*, Node*);
static void wwi_collect_type(struct factset*, const char*, Node*);
static void wwi_collect_type(struct factset*, const char*, int, Node*);
static void
wwi_collect_expr(struct factset *fs, const char *owner, Node *e)
wwi_collect_expr(struct factset *fs, const char *owner, int source, Node *e)
{
if (e == NULL) return;
Sym *s = NULL;
if (e->kind == N_IDENT) {
s = wwi_valuesym(fs->c, owner, e->str);
s = wwi_valuesym(fs->c, owner, source, e->str);
} else if (e->kind == N_DOT && e->lhs
&& e->lhs->kind == N_IDENT) {
const char *mod = wwi_use_path(fs->c, owner, e->lhs->str);
if (mod) s = wwi_valuesym(fs->c, mod, e->str);
const char *mod = wwi_use_path(fs->c, owner, source,
e->lhs->str);
if (mod) s = wwi_valuesym(fs->c, mod, source, e->str);
}
if (s && s->decl && s->decl->kind == N_DEF) {
if (!s->decl->export) {
@@ -648,23 +651,23 @@ wwi_collect_expr(struct factset *fs, const char *owner, Node *e)
return;
}
if (e->kind == N_BIN) {
wwi_collect_expr(fs, owner, e->lhs);
wwi_collect_expr(fs, owner, e->rhs);
wwi_collect_expr(fs, owner, source, e->lhs);
wwi_collect_expr(fs, owner, source, e->rhs);
} else if (e->kind == N_UN) {
wwi_collect_expr(fs, owner, e->lhs);
wwi_collect_expr(fs, owner, source, e->lhs);
} else if (e->kind == N_CAST) {
wwi_collect_expr(fs, owner, e->lhs);
wwi_collect_type(fs, owner, e->rhs);
wwi_collect_expr(fs, owner, source, e->lhs);
wwi_collect_type(fs, owner, source, e->rhs);
}
}
static void
wwi_collect_type(struct factset *fs, const char *owner, Node *t)
wwi_collect_type(struct factset *fs, const char *owner, int source, Node *t)
{
if (t == NULL) return;
switch (t->kind) {
case N_TNAME: {
Sym *s = wwi_typesym(fs->c, owner, t->str);
Sym *s = wwi_typesym(fs->c, owner, source, t->str);
if (s && s->decl && s->decl->kind == N_TYPEDECL)
wwi_collect_decl(fs, s->mod, s->decl);
break;
@@ -673,7 +676,7 @@ wwi_collect_type(struct factset *fs, const char *owner, Node *t)
case N_TSLICE:
case N_TBANG:
case N_TCHAN:
wwi_collect_type(fs, owner, t->lhs);
wwi_collect_type(fs, owner, source, t->lhs);
break;
case N_TARRAY:
/* Array length is part of the resolved type identity, not a source
@@ -681,7 +684,7 @@ wwi_collect_type(struct factset *fs, const char *owner, Node *t)
* and a consumer never needs an implementation def to size the type. */
if (t->rhs != NULL && t->rhs->kind != N_INTLIT) {
u64 len;
if (!check_eval_const(fs->c, t->rhs, owner, &len)) {
if (!check_eval_const(fs->c, t->rhs, owner, source, &len)) {
errorf(t->pos, "cannot encode array dimension");
fs->bad = 1;
} else {
@@ -692,24 +695,24 @@ wwi_collect_type(struct factset *fs, const char *owner, Node *t)
e->lhs = e->rhs = e->list = NULL;
}
}
wwi_collect_type(fs, owner, t->lhs);
wwi_collect_type(fs, owner, source, t->lhs);
break;
case N_TFN:
for (Node *p = t->list; p; p = p->next)
wwi_collect_type(fs, owner, p->lhs);
wwi_collect_type(fs, owner, t->lhs);
wwi_collect_type(fs, owner, source, p->lhs);
wwi_collect_type(fs, owner, source, t->lhs);
break;
case N_TSTRUCT:
for (Node *f = t->list; f; f = f->next)
wwi_collect_type(fs, owner, f->lhs);
wwi_collect_type(fs, owner, source, f->lhs);
break;
case N_TTAGGED:
case N_TTUPLE:
for (Node *e = t->list; e; e = e->next)
wwi_collect_type(fs, owner, e);
wwi_collect_type(fs, owner, source, e);
break;
case N_TENUM:
wwi_collect_type(fs, owner, t->lhs);
wwi_collect_type(fs, owner, source, t->lhs);
/* Member identifiers are enum-local prior-sibling references, not
* package defs; the complete member list already carries their facts. */
break;
@@ -756,18 +759,18 @@ wwi_collect_decl(struct factset *fs, const char *owner, Node *d)
switch (d->kind) {
case N_FNDECL:
for (Node *p = d->list; p; p = p->next)
wwi_collect_type(fs, owner, p->lhs);
wwi_collect_type(fs, owner, d->lhs);
wwi_collect_type(fs, owner, d->sourceid, p->lhs);
wwi_collect_type(fs, owner, d->sourceid, d->lhs);
break;
case N_TYPEDECL:
wwi_collect_type(fs, owner, d->lhs);
wwi_collect_type(fs, owner, d->sourceid, d->lhs);
break;
case N_DEF:
wwi_collect_type(fs, owner, d->lhs);
wwi_collect_expr(fs, owner, d->rhs);
wwi_collect_type(fs, owner, d->sourceid, d->lhs);
wwi_collect_expr(fs, owner, d->sourceid, d->rhs);
break;
case N_LET:
wwi_collect_type(fs, owner, d->lhs);
wwi_collect_type(fs, owner, d->sourceid, d->lhs);
break;
default:
break;
@@ -775,24 +778,32 @@ wwi_collect_decl(struct factset *fs, const char *owner, Node *d)
}
static int
wwi_use_owned(Node *u, const char *owner)
wwi_use_owned(Node *u, const char *owner, int source)
{
return u->kind == N_USE && u->imported != 0
&& wwi_mod_eq(u->module, owner);
&& u->sourceid == source && wwi_mod_eq(u->module, owner);
}
static void
wwi_emit_fact_imports(FILE *of, Node *file, const char *owner)
wwi_emit_imports(FILE *of, Node *file, const char *owner, int source,
int imported)
{
int nuse = 0;
for (Node *u = file->list; u; u = u->next)
if (wwi_use_owned(u, owner)) nuse++;
for (Node *u = file->list; u; u = u->next) {
int owned = imported ? wwi_use_owned(u, owner, source)
: u->kind == N_USE && u->imported == 0
&& u->sourceid == source;
if (owned) nuse++;
}
if (nuse == 0) return;
struct useent *us = malloc((size_t)nuse * sizeof *us);
if (us == NULL) fatal("wwi: out of memory");
int k = 0;
for (Node *u = file->list; u; u = u->next) {
if (!wwi_use_owned(u, owner)) continue;
int owned = imported ? wwi_use_owned(u, owner, source)
: u->kind == N_USE && u->imported == 0
&& u->sourceid == source;
if (!owned) continue;
us[k].path = u->usepath ? u->usepath : u->str;
us[k].idx = k;
k++;
@@ -807,6 +818,65 @@ wwi_emit_fact_imports(FILE *of, Node *file, const char *owner)
free(us);
}
static int
wwi_primary_section_has(Checker *c, Node *file, struct factset *fs,
struct declent *exports, int nexports, int source)
{
for (Node *u = file->list; u; u = u->next)
if (u->kind == N_USE && u->imported == 0
&& u->sourceid == source)
return 1;
for (int i = 0; i < fs->nprivate; i++)
if (fs->privatefacts[i].d->sourceid == source) return 1;
for (int i = 0; i < nexports; i++)
if (exports[i].d->sourceid == source) return 1;
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"))
return 1;
return 0;
}
static int
wwi_first_primary_source(Node *file)
{
int found = 0;
int source = 0;
for (Node *d = file->list; d; d = d->next) {
if (!wwi_primary(d)) continue;
if (!found || d->sourceid < source) {
source = d->sourceid;
found = 1;
}
}
return found ? source : file->sourceid;
}
static void
wwi_emit_primary_section(Checker *c, FILE *of, Node *file,
struct factset *fs, struct declent *exports, int nexports,
const char *owner, const char *pkg, int source)
{
if (owner != NULL && owner[0] != '\0')
fprintf(of, "//ww:module %s\n", owner);
fprintf(of, "package %s;\n", pkg && pkg[0] ? pkg : "main");
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);
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);
for (int i = 0; i < nexports; i++)
if (exports[i].d->sourceid == source)
wwi_decl(of, exports[i].d);
}
int
wwi_emit(Checker *c, FILE *of, Node *file)
{
@@ -841,83 +911,15 @@ wwi_emit(Checker *c, FILE *of, Node *file)
if (fs.nfacts > 1)
qsort(fs.facts, (size_t)fs.nfacts, sizeof *fs.facts, factcmp);
/* package line: leaf of the first primary decl's module tag. */
const char *pkg = "main";
int found = 0;
for (Node *d = file->list; d; d = d->next) {
if (wwi_primary(d) && d->module && d->module[0]) {
const char *dot = strrchr(d->module, '.');
pkg = dot ? dot + 1 : d->module;
found = 1;
break;
}
}
/* #11: a decl-less / export-less primary body carries no
* module-tagged decl, so the scan above finds nothing; fall back to
* the primary module identity stamped on the N_FILE node at parse
* time. A raw single-file `package main` root arrives via a bare reset
* and leaves file->module NULL, so it stays "main". The detector is
* scan-miss (`!found`), NOT pkg=="main": a body whose first tagged
* decl legitimately leafs to "main" must keep that, and must match
* selfhost's `found` flag byte-for-byte (rule 10). */
if (!found && file->module && file->module[0]) {
const char *dot = strrchr(file->module, '.');
pkg = dot ? dot + 1 : file->module;
}
if (file->module != NULL && file->module[0] != '\0')
fprintf(of, "//ww:module %s\n", file->module);
fprintf(of, "package %s;\n", pkg);
/* imports — primary N_USE, byte-sorted by import path. */
int nuse = 0;
for (Node *u = file->list; u; u = u->next)
if (u->kind == N_USE && wwi_primary(u))
nuse++;
if (nuse > 0) {
struct useent *us = malloc((size_t)nuse * sizeof *us);
int k = 0;
for (Node *u = file->list; u; u = u->next) {
if (u->kind != N_USE || !wwi_primary(u))
continue;
us[k].path = u->usepath ? u->usepath : u->str;
us[k].idx = k;
k++;
}
qsort(us, (size_t)nuse, sizeof *us, usecmp);
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);
previous = us[i].path;
}
free(us);
}
/* Compiler-private primary nominal facts precede the public declarations.
* They are parseable within this package export but lack `export`, so a
* source qualifier cannot name them. */
for (int i = 0; i < fs.nprivate; i++)
wwi_decl(of, fs.privatefacts[i].d);
/* Package-test metadata is private, deterministic, and owned by the test
* variant. A distinct generated-main compile consumes it through --import. */
if (c->is_test_package) {
/* Unit source paths and declarations are already deterministically
* ordered; preserve that order as the test runner's user-facing order. */
for (Node *d = file->list; d; d = d->next)
if (wwi_primary(d) && d->kind == N_FNDECL
&& !d->export && wwi_has_attr(d, "test"))
wwi_decl(of, d);
}
/* decls — exported primary, byte-sorted by symbol name. */
/* Exported declarations are sorted within their source-file sections. */
int ndecl = 0;
for (Node *d = file->list; d; d = d->next)
if (wwi_primary(d) && d->export && wwi_is_decl(d))
ndecl++;
struct declent *ds = NULL;
if (ndecl > 0) {
struct declent *ds = malloc((size_t)ndecl * sizeof *ds);
ds = malloc((size_t)ndecl * sizeof *ds);
if (ds == NULL) fatal("wwi: out of memory");
int k = 0;
for (Node *d = file->list; d; d = d->next) {
if (!wwi_primary(d) || !d->export || !wwi_is_decl(d))
@@ -927,24 +929,49 @@ wwi_emit(Checker *c, FILE *of, Node *file)
k++;
}
qsort(ds, (size_t)ndecl, sizeof *ds, declcmp);
for (int i = 0; i < ndecl; i++)
wwi_decl(of, ds[i].d);
free(ds);
}
/* Canonical ownership, declared name, and lexical source scope are three
* independent export facts. Repeated owner sections preserve the file that
* owns each binding while keeping every symbol/action keyed by OWNER. */
int nsection = 0;
for (Node *p = file->body; p; p = p->next) {
if (p->imported || !wwi_primary_section_has(c, file, &fs, ds,
ndecl, p->sourceid))
continue;
const char *owner = p->module && p->module[0]
? p->module : file->module;
const char *pkg = p->pkgname && p->pkgname[0]
? p->pkgname : file->pkgname;
wwi_emit_primary_section(c, of, file, &fs, ds, ndecl, owner,
pkg, p->sourceid);
nsection++;
}
if (nsection == 0) {
const char *pkg = file->pkgname && file->pkgname[0]
? file->pkgname : "main";
wwi_emit_primary_section(c, of, file, &fs, ds, ndecl,
file->module, pkg, wwi_first_primary_source(file));
}
free(ds);
/* Compiler-owned public fact closure. A module marker changes semantic
* ownership without making the namespace a source import of the eventual
* consumer; direct visibility continues to come solely from its own N_USE. */
const char *lastmod = NULL;
int lastsource = -1;
for (int i = 0; i < fs.nfacts; i++) {
struct factent *f = &fs.facts[i];
if (lastmod == NULL || strcmp(lastmod, f->mod) != 0) {
if (lastmod == NULL || strcmp(lastmod, f->mod) != 0
|| lastsource != f->d->sourceid) {
const char *dot = strrchr(f->mod, '.');
const char *leaf = dot ? dot + 1 : 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", leaf);
wwi_emit_fact_imports(of, file, f->mod);
fprintf(of, "package %s;\n", factpkg);
wwi_emit_imports(of, file, f->mod, f->d->sourceid, 1);
lastmod = f->mod;
lastsource = f->d->sourceid;
}
wwi_decl(of, f->d);
}