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

@@ -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);
}