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

View File

@@ -58,8 +58,10 @@ lookup_builtin(const char *name)
}
static const char *decl_mod(Node *file, Node *d);
static const char *use_path(Node *file, const char *curmod, const char *alias);
static int src_imports(Node *file, const char *modtag, const char *name);
static const char *use_path(Node *file, const char *curmod, int source,
const char *alias);
static int src_imports(Node *file, const char *modtag, int source,
const char *name);
static Sym *lookup_visible(Checker *c, const char *name);
static Sym *lookup_visible_type(Checker *c, const char *name);
static void resolve_typedecl(Checker *c, Node *d);
@@ -89,6 +91,7 @@ resolve_typename(Checker *c, Node *n)
/* M1 #22: map the qualifier alias to its dotted
* import path (symbols are path-keyed). */
const char *mk = use_path(c->file, c->cur_mod,
c->cur_source,
head);
if (mk != NULL)
s = scope_lookup_in_module(c->cur, mk,
@@ -622,7 +625,8 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
case N_DOT: {
if (n->lhs == NULL || n->lhs->kind != N_IDENT) return 0;
/* M1 #22: map the qualifier alias to its dotted import path. */
const char *mk = use_path(c->file, c->cur_mod, n->lhs->str);
const char *mk = use_path(c->file, c->cur_mod, c->cur_source,
n->lhs->str);
if (mk == NULL) return 0;
Sym *s = scope_lookup_in_module(c->cur, mk, n->str);
if (s == NULL || s->kind != SK_DEF ||
@@ -636,12 +640,15 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
}
int
check_eval_const(Checker *c, Node *n, const char *owner, u64 *out)
check_eval_const(Checker *c, Node *n, const char *owner, int source, u64 *out)
{
const char *saved = c->cur_mod;
int savesource = c->cur_source;
c->cur_mod = owner;
c->cur_source = source;
int ok = eval_def_const(c, n, out, 0);
c->cur_mod = saved;
c->cur_source = savesource;
return ok;
}
@@ -1475,6 +1482,7 @@ cexpr(Checker *c, Node *n)
* import path; map the alias the user wrote to
* that path before looking up the leaf. */
const char *mk = use_path(c->file, c->cur_mod,
c->cur_source,
n->lhs->str);
if (mk == NULL) {
if (ms->kind == SK_USE)
@@ -2854,6 +2862,7 @@ check_init(Checker *c, Arena *a)
c->is_test = 0; /* #15: caller (w6c main) sets it after init */
c->is_test_package = 0;
c->test_module = "test";
c->test_target = NULL;
typesinit(a);
c->top = newscope(a, NULL);
c->cur = c->top;
@@ -2873,6 +2882,10 @@ static const char *
decl_mod(Node *file, Node *d)
{
if (d == NULL || d->module == NULL || file == NULL) return NULL;
/* Separate-compilation interfaces already carry their canonical owner.
* Visibility is checked independently against the referencing source's
* import binding; never erase semantic ownership for a transitive fact. */
if (d->imported) return d->module;
for (Node *u = file->list; u; u = u->next) {
/* M1 #22: a decl is imported iff some `use` directive's full
* dotted import path equals the decl's module (now the path,
@@ -2887,41 +2900,40 @@ decl_mod(Node *file, Node *d)
}
/*
* use_path — map a `use` alias (leaf bareword the user writes, `utf8`)
* to the full dotted import path it binds (`encoding.utf8`), for the
* module-qualified resolution and the codegen hint (M1 #22, §2.4). For
* single-level packages usepath == alias so the result is unchanged.
* use_path — map a source-file default qualifier (the imported package's
* declared name) to the full canonical import path it binds, for
* module-qualified resolution and the codegen hint (M1 #22, §2.4).
*
* The alias→path map is NOT file-global: two modules in the same
* concatenated unit may bind the same leaf alias to different paths
* (sha256's `import crypto.math` and strconv's `import math` both bind
* alias `math`). The import declared in the SAME module as the
* reference (`curmod`) is the authoritative one; requiring it closes both
* cross-module mis-resolution and accidental transitive-import visibility.
* The qualifier→path map is source-file local: two files in the same
* concatenated unit may bind the same declared name to different paths. The
* import with the reference's source ID and owner is authoritative, closing
* both cross-file mis-resolution and accidental transitive visibility.
* Returns NULL if the referencing package has no such `use`.
*/
static const char *
use_path(Node *file, const char *curmod, const char *alias)
use_path(Node *file, const char *curmod, int source, const char *alias)
{
if (file == NULL || alias == NULL) return NULL;
/* Legacy inline multi-package units may spell a package's own
* declarations as `pkg.member`. That is self-qualification, not an
* imported namespace; preserve it without reopening transitive lookup. */
if (curmod != NULL) {
if (source == 0 && curmod != NULL) {
const char *dot = strrchr(curmod, '.');
const char *leaf = dot ? dot + 1 : curmod;
if (strcmp(alias, leaf) == 0) return curmod;
}
for (Node *u = 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;
const char *p = u->usepath ? u->usepath : u->str;
const char *um = decl_mod(file, u);
int same = (um == NULL) ? (curmod == NULL)
: (curmod != NULL && strcmp(um, curmod) == 0);
if (same)
if (same) {
u->used = 1;
return p;
}
}
return NULL;
}
@@ -2947,9 +2959,12 @@ resolve_typedecl(Checker *c, Node *d)
if (t == NULL || t->under != NULL || t->resolving) return;
t->resolving = 1;
const char *save = c->cur_mod;
int savesource = c->cur_source;
c->cur_mod = decl_mod(c->file, d);
c->cur_source = d->sourceid;
Type *under = resolve_type(c, d->lhs);
c->cur_mod = save;
c->cur_source = savesource;
/* Alias-root cycle (`type a = b; type b = a` / `type a = a`):
* checked BEFORE clearing the flag so self-aliases trip on their
* own in-progress mark. ty_err instead of the cyclic under keeps
@@ -2978,11 +2993,11 @@ resolve_typedecl(Checker *c, Node *d)
* (N_USE nodes with module == NULL).
*/
static int
src_imports(Node *file, const char *modtag, const char *name)
src_imports(Node *file, const char *modtag, int source, const char *name)
{
if (file == NULL || name == NULL || name[0] == '\0') return 0;
for (Node *u = file->list; u; u = u->next) {
if (u->kind != N_USE) continue;
if (u->kind != N_USE || u->sourceid != source) continue;
/* Skip self-imports: lib/fmt/fmt_test.ww carries `use fmt;`
* even though its module tag is also "fmt"; that directive
* doesn't introduce a foreign module bareword and lib/fmt's
@@ -3013,10 +3028,18 @@ static int
direct_module_visible(Checker *c, const char *mod)
{
if (c == NULL || mod == NULL || mod[0] == '\0') return 0;
const char *dot = strrchr(mod, '.');
const char *alias = dot ? dot + 1 : mod;
const char *path = use_path(c->file, c->cur_mod, 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 != 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 *
@@ -3035,7 +3058,7 @@ lookup_visible(Checker *c, const char *name)
* 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, name) != NULL) {
if (use_path(c->file, c->cur_mod, c->cur_source, name) != 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
@@ -3101,7 +3124,7 @@ check_module_shadow(Checker *c, const char *name, Pos pos,
}
}
if (!seen_use) return;
if (!src_imports(c->file, c->cur_mod, name)) return;
if (!src_imports(c->file, c->cur_mod, c->cur_source, name)) return;
err(c, pos, "%s '%s' shadows imported module '%s'",
kindstr, name, name);
}
@@ -3132,6 +3155,80 @@ same_import_fact(Checker *c, Node *d, const char *mod, Skind kind)
return s;
}
static int
top_decl_kind(Node *d)
{
return d != NULL && (d->kind == N_TYPEDECL || d->kind == N_DEF
|| d->kind == N_FNDECL || d->kind == N_LET);
}
static void
check_import_alt(Node *d, const char *name)
{
FILE *f = errout ? errout : stderr;
fprintf(f, "\t%s:%d:%d: other declaration of %s\n",
d->pos.file ? d->pos.file : "?", d->pos.line, d->pos.col, name);
}
/* Go's default import binding lives in the importing file's scope. Reject
* only another binding in that same source section; equal names in sibling
* files are independent even though their canonical edges are package-wide. */
static void
check_import_redeclarations(Checker *c, Node *file)
{
if (!c->sep_mode) return;
for (Node *u = file->list; u; u = u->next) {
if (u->kind != N_USE || u->imported || u->str == NULL)
continue;
for (Node *v = file->list; v != u; v = v->next) {
if (v->kind != N_USE || v->imported || v->str == NULL
|| v->sourceid != u->sourceid)
continue;
if (strcmp(v->str, u->str) == 0) {
err(c, u->pos, "%s redeclared in this block", u->str);
check_import_alt(v, u->str);
break;
}
}
}
}
/* Report file-local unused bindings before package-declaration collisions,
* matching the pinned Go resolver's stable ordering. The package declarations
* themselves are package-scoped, so they collide with an equal import name in
* any contributing source file. */
static void
check_import_usage_and_collisions(Checker *c, Node *file)
{
if (!c->sep_mode) return;
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 *dot = strrchr(path, '.');
const char *leaf = dot ? dot + 1 : path;
if (strcmp(u->str, leaf) == 0)
err(c, u->pos, "\"%s\" imported and not used", path);
else
err(c, u->pos, "\"%s\" imported as %s and not used",
path, u->str);
}
for (Node *d = file->list; d; d = d->next) {
if (d->imported || !top_decl_kind(d) || d->str == NULL)
continue;
for (Node *u = file->list; u; u = u->next) {
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;
err(c, d->pos,
"%s already declared through import of package %s (\"%s\")",
d->str, u->str, path);
check_import_alt(u, d->str);
}
}
}
void
check_file(Checker *c, Node *file)
{
@@ -3148,21 +3245,30 @@ check_file(Checker *c, Node *file)
* already emits the qualified call. wwstage twin in check.ww. */
if (c->is_test) {
int present = 0;
for (Node *u = file->list; u; u = u->next)
for (Node *u = file->list; u; u = u->next) {
if (c->test_target != NULL && u->kind == N_USE
&& !u->imported && u->usepath
&& (strcmp(u->usepath, c->test_target) == 0
|| strcmp(u->usepath, c->test_module) == 0))
u->used = 1;
if (u->kind == N_USE && !u->imported
&& u->usepath && strcmp(u->usepath, c->test_module) == 0) {
present = 1;
break;
}
}
if (!present) {
Node *usenode = newnode(c->a, N_USE, file->pos);
usenode->str = c->test_module;
usenode->strlen = strlen(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->next = file->list;
file->list = usenode;
}
}
check_import_redeclarations(c, file);
/* pass 1: install names (types first, then defs/fns).
* For self-referential types we install the named-type placeholder
@@ -3172,13 +3278,10 @@ check_file(Checker *c, Node *file)
* references (`strconv.invalid`) resolve when typedecl bodies are
* walked in the next pass. */
for (Node *d = file->list; d; d = d->next) {
c->cur_source = d->sourceid;
if (d->kind == N_USE) {
/* check-(c) self-import: a package may not import
* itself. Pure owner==leaf string compare, package-
* model-independent — sound under ww's filename-keyed
* file-inclusion imports. check-(a) unused and
* (b)/(d) membership DEFERRED to task #8 (filename-
* keyed pulls lack import->file->symbol provenance). */
/* A package may not import its own canonical owner. Import
* usage and membership are checked with source-file provenance. */
const char *owner = decl_mod(file, d);
/* M1 #22: self-import ⟺ the imported path equals the
* use's own (owning) module path. Compares paths, not
@@ -3233,9 +3336,11 @@ check_file(Checker *c, Node *file)
* here. A foldable stub carries type NULL until then. A duplicate
* (prev already bound non-USE) is left for that loop to diagnose. */
c->cur_mod = NULL;
c->cur_source = 0;
for (Node *d = file->list; d; d = d->next) {
if (d->kind != N_DEF) continue;
c->cur_mod = decl_mod(file, d);
c->cur_source = d->sourceid;
const char *mod = decl_mod(file, d);
if (same_import_fact(c, d, mod, SK_DEF) != NULL)
continue;
@@ -3250,13 +3355,16 @@ check_file(Checker *c, Node *file)
}
}
c->cur_mod = NULL;
c->cur_source = 0;
for (Node *d = file->list; d; d = d->next) {
if (d->kind != N_TYPEDECL) continue;
resolve_typedecl(c, d);
}
c->cur_mod = NULL;
c->cur_source = 0;
for (Node *d = file->list; d; d = d->next) {
c->cur_mod = decl_mod(file, d);
c->cur_source = d->sourceid;
switch (d->kind) {
case N_USE:
/* already installed in pass 1; no-op here so the
@@ -3360,6 +3468,7 @@ check_file(Checker *c, Node *file)
}
}
c->cur_mod = NULL;
c->cur_source = 0;
/* Program-global uniqueness on the ENTRY `main`. M1 #32: the entry
* is the ROOT-unit main (imported==0) — it alone lowers to the bare
@@ -3487,8 +3596,15 @@ check_file(Checker *c, Node *file)
nm->strlen = strlen(d->str);
Node *id;
if (d->imported && d->module && d->module[0]) {
const char *dotp = strrchr(d->module, '.');
const char *alias = dotp ? dotp + 1 : d->module;
const char *alias = d->pkgname && d->pkgname[0]
? d->pkgname : d->module;
/* A generated dispatcher cannot bind a command test
* package as `main`: that would collide with its own entry.
* This is a compiler-owned binding, never source alias syntax;
* use the canonical target path as its private qualifier. */
if (c->test_target != NULL
&& strcmp(d->module, c->test_target) == 0)
alias = c->test_target;
id = newnode(c->a, N_DOT, fp);
id->lhs = newnode(c->a, N_IDENT, fp);
id->lhs->str = alias;
@@ -3542,6 +3658,8 @@ check_file(Checker *c, Node *file)
tab = newnode(c->a, N_LET, fp);
tab->op = TK_CONST;
tab->str = "__wwtests";
tab->pkgname = file->pkgname;
tab->sourceid = file->sourceid;
tab->lhs = tsl;
tab->rhs = arr;
/* pass 1 already ran, so install the table's name now —
@@ -3581,10 +3699,15 @@ check_file(Checker *c, Node *file)
Node *m = newnode(c->a, N_FNDECL, fp);
m->str = "main";
m->export = 1;
m->pkgname = file->pkgname;
m->sourceid = file->sourceid;
m->lhs = newnode(c->a, N_TNAME, fp);
m->lhs->str = "i32";
m->body = body;
int savesource = c->cur_source;
c->cur_source = m->sourceid;
m->type = build_fn_type(c, m);
c->cur_source = savesource;
/* pass 1 already ran, so the install loop never stamped m's
* type; set it explicitly (pass 2 below reads d->type).
* Append the table const (if any) then main to file->list. */
@@ -3603,6 +3726,7 @@ check_file(Checker *c, Node *file)
/* pass 2: check def initialisers and fn bodies */
for (Node *d = file->list; d; d = d->next) {
c->cur_mod = decl_mod(file, d);
c->cur_source = d->sourceid;
switch (d->kind) {
case N_DEF: {
if (d->rhs) {
@@ -3786,6 +3910,8 @@ 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

View File

@@ -1327,9 +1327,9 @@ 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 (n->module) so the
* checker can match decl identity on the path, while n->str stays the
* leaf alias the user writes (`utf8.x`). */
/* 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 *path = leaf;
while (accept(p, TK_DOT)) {
@@ -1358,19 +1358,23 @@ parseimports(Parser *p)
* the following package clause and imports. */
if (p->cur.kind == TK_MODPATH) {
sawpackage = 0;
p->sourceid++;
p->pathmod = p->cur.text;
p->curmod = p->cur.text;
p->resetmod = NULL;
p->curpkg = NULL;
advance(p);
continue;
}
if (p->cur.kind == TK_MODRESET) {
const char *rp = p->cur.text;
sawpackage = 0;
p->sourceid++;
advance(p);
p->pathmod = NULL;
p->curmod = rp;
p->resetmod = rp;
p->curpkg = NULL;
continue;
}
if (p->cur.kind == TK_MODULE) {
@@ -1385,9 +1389,13 @@ parseimports(Parser *p)
}
const char *name = expectident(p);
expect(p, TK_SEMI);
p->curmod = name;
p->curpkg = name;
if (p->pathmod == NULL && p->resetmod == NULL)
p->curmod = name;
Node *package = newnode(p->a, N_FILE, pp);
package->module = name;
package->module = p->curmod;
package->pkgname = name;
package->sourceid = p->sourceid;
if (packages == NULL)
packages = package;
else
@@ -1395,6 +1403,8 @@ parseimports(Parser *p)
packagetail = package;
if (!sawpackage) {
file->module = name;
file->pkgname = name;
file->sourceid = p->sourceid;
file->pos = pp;
sawpackage = 1;
}
@@ -1408,6 +1418,8 @@ parseimports(Parser *p)
if (p->cur.kind == TK_USE) {
Node *d = parseuse(p);
d->module = p->curmod;
d->pkgname = p->curpkg;
d->sourceid = p->sourceid;
if (head == NULL)
head = d;
else
@@ -1424,6 +1436,8 @@ parseimports(Parser *p)
p->errs++;
Node *d = parseuse(p);
d->module = p->curmod;
d->pkgname = p->curpkg;
d->sourceid = p->sourceid;
if (head == NULL)
head = d;
else
@@ -1441,6 +1455,8 @@ parseimports(Parser *p)
p->errs++;
Node *d = parseuse(p);
d->module = p->curmod;
d->pkgname = p->curpkg;
d->sourceid = p->sourceid;
if (head == NULL)
head = d;
else
@@ -1546,6 +1562,7 @@ parsefile(Parser *p)
Pos pp = { p->l->file, 1, 1 };
Node *file = newnode(p->a, N_FILE, pp);
Node *head = NULL, *tail = NULL;
Node *packages = NULL, *packagetail = NULL;
int sawpackage = 0;
while (p->cur.kind != TK_EOF) {
/* `package foo;` — directory-as-module declaration. Every
@@ -1555,58 +1572,41 @@ parsefile(Parser *p)
* sep primary-reset (resetmod) regions carry identity
* out-of-band and are exempt. */
if (p->cur.kind == TK_MODULE) {
Pos packagepos = p->cur.pos;
sawpackage = 1;
advance(p);
const char *name = expectident(p);
expect(p, TK_SEMI);
if (p->pathmod != NULL || p->resetmod != NULL) {
/* M1 #22: while an import path is active the
* in-file `package` clause is an ASSERTION — its
* leaf must equal the path's last component; it
* does NOT overwrite the path-derived module.
* #57 extends this to the sep primary-reset path
* (resetmod): the dotted reset path is the
* authoritative identity, the clause asserts. */
const char *active =
p->pathmod ? p->pathmod : p->resetmod;
const char *dot = strrchr(active, '.');
const char *last = dot ? dot + 1 : active;
int testsupport = p->testmodule != NULL
&& strcmp(p->testmodule, "__wwtest") == 0
&& strcmp(active, "__wwtest") == 0
&& strcmp(name, "test") == 0;
int commandpackage = p->commandpackage
&& p->pathmod == NULL && p->resetmod != NULL
&& (strcmp(name, "main") == 0
|| strcmp(name, "main_test") == 0);
if (strcmp(name, last) != 0 && !testsupport
&& !commandpackage) {
errorf(p->cur.pos,
"package %s does not match import path %s",
name, active);
p->errs++;
}
} else {
p->curpkg = name;
if (p->pathmod == NULL && p->resetmod == NULL) {
p->curmod = name;
/* #11: stamp the primary module identity on the
* N_FILE node so wwi_emit can derive the
* `package` leaf even when the body carries zero
* module-tagged decls. Primary identity only;
* never the imported boundary (TK_MODPATH). */
if (file->module == NULL)
file->module = name;
}
Node *package = newnode(p->a, N_FILE, packagepos);
package->module = p->curmod;
package->pkgname = name;
package->sourceid = p->sourceid;
package->imported = p->pathmod != NULL;
if (packages == NULL) packages = package;
else packagetail->next = package;
packagetail = package;
if (file->pkgname == NULL) {
file->pkgname = name;
file->sourceid = p->sourceid;
}
continue;
}
/* `//ww:module <path>` — M1 #22 import boundary. The following
* file's decls mangle on the full dotted import path, not the
* leaf `package` clause, and are flagged imported (gates the
* file's decls mangle on the full dotted import path independently
* of its `package` clause, and are flagged imported (gates the
* root-only bare-`main` rule, #32). */
if (p->cur.kind == TK_MODPATH) {
sawpackage = 0;
p->sourceid++;
p->pathmod = p->cur.text;
p->curmod = p->cur.text;
p->resetmod = NULL;
p->curpkg = NULL;
if (file->module == NULL) file->module = p->cur.text;
advance(p);
continue;
}
@@ -1620,6 +1620,7 @@ parsefile(Parser *p)
* decls to bare — that usage is deliberate-only. */
if (p->cur.kind == TK_MODRESET) {
sawpackage = 0;
p->sourceid++;
/* #57: a path-carrying reset (sep primary body) mangles
* decls on the dotted path so definer == importer, but
* leaves imported==0 (curmod set, pathmod NULL) so -c
@@ -1630,6 +1631,7 @@ parsefile(Parser *p)
const char *rp = p->cur.text;
advance(p);
p->pathmod = NULL;
p->curpkg = NULL;
if (rp != NULL) {
p->curmod = rp;
p->resetmod = rp;
@@ -1679,14 +1681,17 @@ parsefile(Parser *p)
advance(p);
continue;
}
if (d != NULL) {
d->module = p->curmod;
d->imported = (p->pathmod != NULL);
if (d != NULL) {
d->module = p->curmod;
d->pkgname = p->curpkg;
d->sourceid = p->sourceid;
d->imported = (p->pathmod != NULL);
}
if (head == NULL) head = d;
else tail->next = d;
tail = d;
}
file->list = head;
file->body = packages;
return file;
}

View File

@@ -173,7 +173,7 @@ typedef enum {
* that replaces the withdrawn `package main` inject). */
TK_MODPATH, /* `//ww:module <dotted-path>` — driver import boundary:
* the following file's decls mangle on the full import
* path, not the leaf `package` clause (M1 #22). Token
* path independently of the `package` clause (M1 #22). Token
* text carries the dotted path. */
TK_LAST /* sentinel for tables */
@@ -344,10 +344,16 @@ struct Node {
* this is the importing (owning)
* module. */
const char *usepath; /* M1 #22: on an N_USE node, the full
* dotted IMPORT path (`encoding.utf8`)
* vs the leaf alias in `str`. Drives
* canonical import path (`encoding.utf8`);
* `str` is the declared default qualifier. Drives
* the path-keyed decl_mod match and the
* qualified-ref codegen hint. */
* qualified-ref codegen hint. */
const char *pkgname; /* declared package name for this source/export
* section; independent of canonical `module`. */
int sourceid; /* lexical source-file scope within the parsed
* owner unit; module-reset/module boundaries
* advance it deterministically. */
int used; /* N_USE: checker observed this file-local binding. */
int imported; /* M1 #22: decl reached through an
* `//ww:module <path>` import boundary
* (vs root/primary). Gates the root-only
@@ -373,12 +379,12 @@ struct Parser {
* import path; while set, decls stamp
* module=pathmod and imported=1, and the
* in-file `package` clause is an assertion. */
const char *resetmod; /* #57: active `//ww:module-reset <path>` dotted
* path; mangles decls on the path WITHOUT
* imported=1 (primary-ness for -c and the #32
* bare-main rule stay intact), and the in-file
* `package` clause asserts (leaf == last
* component) instead of overwriting curmod. */
const char *resetmod; /* #57: active `//ww:module-reset <path>` canonical
* identity; decls mangle on it without becoming
* imported. The package clause independently
* supplies the declared name. */
const char *curpkg; /* declared name of the active source section. */
int sourceid; /* deterministic lexical source-section ordinal. */
const char *testmodule; /* hidden package-driver alias for toolchain
* `package test`; NULL outside that compile */
int commandpackage; /* selected command family: package main/main_test
@@ -581,8 +587,10 @@ struct Checker {
* currently being checked; NULL for primary
* compilation unit. Drives same-module
* preference in bare-leaf lookups so a bare
* `read` inside lib/os resolves to os.read
* rather than colliding io.read. */
* `read` inside lib/os resolves to os.read
* rather than colliding io.read. */
int cur_source; /* lexical source-file scope of the declaration
* currently being checked. */
Node *file; /* current N_FILE root; used by check_module_shadow
* to consult the declaring source file's own `use`
* directives when refusing param/let names that
@@ -596,6 +604,8 @@ struct Checker {
* bodies and export compiler-private metadata,
* but do not synthesize an entry. */
const char *test_module; /* generated dispatcher support qualifier */
const char *test_target; /* canonical target path used only as the
* compiler-owned generated-main qualifier */
int sep_mode; /* -c package compilation: imported interfaces are
* present, so absent members are hard export errors. */
Node *synth_test_run; /* exact compiler-generated support.run DOT;
@@ -620,6 +630,6 @@ int fold_int_literal(Node*, u64*);
/* Re-evaluate a checked integer constant under the declaration owner's
* import scope. The compiler export writer uses this to canonicalize array
* dimensions without serializing source-level constant dependencies. */
int check_eval_const(Checker*, Node*, const char *owner, u64*);
int check_eval_const(Checker*, Node*, const char *owner, int source, u64*);
#endif /* WW_H */

View File

@@ -717,6 +717,9 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package,
struct sepbind {
char kind;
char *name;
char *source;
int line;
int col;
int dep; /* stable canonical action index; -1 for inline */
};
@@ -753,6 +756,7 @@ struct seppkg {
int root; /* requested usage; never package-action identity */
int link_entry; /* package supplies the executable's bare main */
int generated_main; /* compiler-owned generated test-main package */
int generated_target; /* target variant used by generated test main */
int failed; /* discovery/compile failure reaches this action */
int test_support; /* compiler-generated -T support package */
int loaded; /* directory membership/name loaded exactly once */
@@ -1094,18 +1098,20 @@ sep_root_is_command(const struct seppkg *p)
return strcmp(p->name, "main") == 0;
}
static int sep_external_production_edge(const struct sepgraph *, int, int);
static int sep_external_name_matches_production(const struct sepgraph *,
int, int);
static int
sep_forbidden_command_import(const struct sepgraph *g, int importer, int dep)
{
const struct seppkg *from = &g->pkg[importer];
const struct seppkg *to = &g->pkg[dep];
if (strcmp(to->name, "main") != 0 || to->role != SEP_ROLE_NORMAL)
return 0;
/* An external test's exact import of its colocated command production is
* test-variant wiring, not a general source-importable command edge. */
return !(from->variant == SEP_VARIANT_EXTERNAL
&& sep_command_declared_name(from)
&& strcmp(from->canon, to->canon) == 0);
return !(sep_external_production_edge(g, importer, dep)
&& sep_external_name_matches_production(g, importer, dep));
}
static int
@@ -1550,7 +1556,10 @@ sep_pkg_free_fields(struct seppkg *p)
{
for (int j = 0; j < p->nsources; j++) free(p->sources[j]);
free(p->sources);
for (int j = 0; j < p->bindings.n; j++) free(p->bindings.v[j].name);
for (int j = 0; j < p->bindings.n; j++) {
free(p->bindings.v[j].name);
free(p->bindings.v[j].source);
}
free(p->bindings.v);
free(p->context_state);
free(p->deps);
@@ -2071,28 +2080,46 @@ use_node_cmp(const void *a, const void *b)
const char *yp = y->usepath ? y->usepath : y->str;
int r = strcmp(xp, yp);
if (r != 0) return r;
r = strcmp(x->pos.file ? x->pos.file : "",
y->pos.file ? y->pos.file : "");
if (r != 0) return r;
if (x->pos.line != y->pos.line) return x->pos.line - y->pos.line;
return x->pos.col - y->pos.col;
}
static int
sep_external_production_name(const struct seppkg *pkg, const char *path,
int leaf_only)
sep_external_production_name(const struct seppkg *pkg, const char *name)
{
if (pkg->variant != SEP_VARIANT_EXTERNAL
|| pkg->test_package == NULL || pkg->test_package[0] == '\0')
return 0;
const char *name = path;
if (leaf_only) {
const char *dot = strrchr(path, '.');
if (dot != NULL) name = dot + 1;
}
size_t n = strlen(name);
size_t tn = strlen(pkg->test_package);
return tn == n + 5 && strncmp(pkg->test_package, name, n) == 0
&& strcmp(pkg->test_package + n, "_test") == 0;
}
static int
sep_external_production_edge(const struct sepgraph *g, int importer, int dep)
{
const struct seppkg *from = &g->pkg[importer];
const struct seppkg *to = &g->pkg[dep];
return from->variant == SEP_VARIANT_EXTERNAL
&& to->variant == SEP_VARIANT_PRODUCTION
&& to->role == SEP_ROLE_NORMAL
&& strcmp(from->canon, to->canon) == 0;
}
static int
sep_external_name_matches_production(const struct sepgraph *g, int importer,
int dep)
{
const struct seppkg *from = &g->pkg[importer];
const struct seppkg *to = &g->pkg[dep];
return from->name != NULL && to->name != NULL
&& sep_external_production_name(from, to->name);
}
static char *sep_local_import_base(const struct seppkg *);
/* Canonical source bindings make a shared action independent of the request
@@ -2101,20 +2128,21 @@ static char *sep_local_import_base(const struct seppkg *);
* route/root legality deliberately does not enter action identity. */
static int
sep_binding_add(struct sepbindset *bindings, char kind, const char *name,
int dep)
int dep, Pos pos)
{
for (int i = 0; i < bindings->n; i++)
if (bindings->v[i].kind == kind
&& bindings->v[i].dep == dep
&& strcmp(bindings->v[i].name, name) == 0)
return 0;
if (bindings->n == INT_MAX) return sep_fail_size();
if (sep_reserve((void **)&bindings->v, &bindings->cap,
bindings->n + 1, sizeof *bindings->v) < 0)
return -1;
char *copy = strdup(name);
if (copy == NULL) return sep_fail_nomem();
bindings->v[bindings->n++] = (struct sepbind){ kind, copy, dep };
char *source = strdup(pos.file ? pos.file : "");
if (source == NULL) {
free(copy);
return sep_fail_nomem();
}
bindings->v[bindings->n++] = (struct sepbind){
kind, copy, source, pos.line, pos.col, dep };
return 0;
}
@@ -2126,13 +2154,83 @@ sep_binding_cmp(const void *a, const void *b)
if (r != 0) return r;
if (x->kind != y->kind) return (unsigned char)x->kind
- (unsigned char)y->kind;
return x->dep < y->dep ? -1 : x->dep > y->dep;
if (x->dep != y->dep) return x->dep < y->dep ? -1 : 1;
r = strcmp(x->source, y->source);
if (r != 0) return r;
if (x->line != y->line) return x->line - y->line;
return x->col - y->col;
}
static int
sep_binding_semantic_same(const struct sepbind *a, const struct sepbind *b)
{
return a->kind == b->kind && a->dep == b->dep
&& strcmp(a->name, b->name) == 0;
}
static int
sep_bindsets_semantically_same(const struct sepbindset *a,
const struct sepbindset *b)
{
int ai = 0, bi = 0;
while (ai < a->n && bi < b->n) {
if (!sep_binding_semantic_same(&a->v[ai], &b->v[bi])) return 0;
struct sepbind *av = &a->v[ai];
struct sepbind *bv = &b->v[bi];
do ai++; while (ai < a->n
&& sep_binding_semantic_same(av, &a->v[ai]));
do bi++; while (bi < b->n
&& sep_binding_semantic_same(bv, &b->v[bi]));
}
return ai == a->n && bi == b->n;
}
static int
sep_validate_bindings(const struct sepgraph *g,
const struct sepbindset *bindings)
{
const char *name = NULL;
int dep = -1;
for (int i = 0; i < bindings->n; i++) {
const struct sepbind *b = &bindings->v[i];
if (b->kind != 'D') continue;
if (name != NULL && strcmp(name, b->name) == 0 && dep != b->dep) {
Pos pos = { b->source, b->line, b->col };
errorf(pos, "package path %s resolves to both %s and %s",
b->name, g->pkg[dep].path, g->pkg[b->dep].path);
return -1;
}
name = b->name;
dep = b->dep;
}
return 0;
}
static int
sep_binding_needs_map(const struct sepgraph *g, const struct sepbind *b)
{
return b->kind == 'D' && b->dep >= 0 && b->dep < g->n
&& strcmp(b->name, g->pkg[b->dep].path) != 0;
}
static int
sep_binding_first_map(const struct sepgraph *g,
const struct sepbindset *bindings, int i)
{
if (!sep_binding_needs_map(g, &bindings->v[i])) return 0;
for (int j = i - 1; j >= 0
&& strcmp(bindings->v[j].name, bindings->v[i].name) == 0; j--)
if (sep_binding_needs_map(g, &bindings->v[j])) return 0;
return 1;
}
static void
sep_bindset_free(struct sepbindset *bindings)
{
for (int i = 0; i < bindings->n; i++) free(bindings->v[i].name);
for (int i = 0; i < bindings->n; i++) {
free(bindings->v[i].name);
free(bindings->v[i].source);
}
free(bindings->v);
bindings->v = NULL;
bindings->n = bindings->cap = 0;
@@ -2403,12 +2501,9 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
if (nuse > 1) qsort(uses, (size_t)nuse, sizeof *uses, use_node_cmp);
int rc = 0;
const char *previous = NULL;
for (int i = 0; i < nuse && rc == 0; i++) {
Node *u = uses[i];
const char *name = u->usepath ? u->usepath : u->str;
if (previous && strcmp(previous, name) == 0) continue;
previous = name;
if (reserved_import_path(name)) {
errorf(u->pos, "package path %s is reserved", name);
rc = -1;
@@ -2442,9 +2537,8 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
g->context[context].route);
int literal_self = route_suffix != NULL
? strcmp(path_form, route_suffix) == 0
&& sep_external_production_name(&g->pkg[pi], name, 1)
: strchr(name, '.') == NULL
&& sep_external_production_name(&g->pkg[pi], name, 0);
&& sep_external_production_name(&g->pkg[pi], name);
if (literal_self) {
if (g->pkg[pi].import_base != NULL)
resolved.identity = strdup(g->pkg[pi].import_base);
@@ -2471,18 +2565,17 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
break;
}
if (!located) {
const char *dot = strrchr(name, '.');
const char *leaf = dot ? dot + 1 : name;
int inline_package = 0;
if (!g->pkg[pi].is_dir)
for (Node *package = imports->body; package;
package = package->next)
if (strcmp(package->module, leaf) == 0) {
if (strcmp(package->module, name) == 0) {
inline_package = 1;
break;
}
if (inline_package) {
if (sep_binding_add(bindings, 'I', name, -1) < 0)
if (sep_binding_add(bindings, 'I', name, -1,
u->pos) < 0)
rc = -1;
continue;
}
@@ -2506,10 +2599,7 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
break;
}
int self = strcmp(canon, g->pkg[pi].canon) == 0;
if (self
&& (sep_external_production_name(&g->pkg[pi], name, 1)
|| (g->pkg[pi].variant == SEP_VARIANT_EXTERNAL
&& sep_command_declared_name(&g->pkg[pi]))))
if (self && g->pkg[pi].variant == SEP_VARIANT_EXTERNAL)
external_production = 1;
if (self && !external_production) {
const char *owner = g->pkg[pi].path[0]
@@ -2574,7 +2664,7 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
int child_context = sep_child_context_for(g, context,
resolved.entry, resolved.source_root);
if (child_context < 0
|| sep_binding_add(bindings, 'D', name, di) < 0
|| sep_binding_add(bindings, 'D', name, di, u->pos) < 0
|| sep_add_dep(g, pi, di) < 0
|| sep_children_add(children, di, child_context) < 0) {
free(canon);
@@ -2688,6 +2778,7 @@ sep_add_generated_main(struct sepgraph *g, struct sepproduct *product,
p->root = 1;
p->link_entry = 1;
p->generated_main = 1;
p->generated_target = variant;
p->loaded = 1;
p->emit_context = product->context;
if (sep_set_context_state(p, product->context, 2) < 0
@@ -2754,18 +2845,6 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context,
for (int i = 0; i < g->pkg[pi].nsources && rc == 0; i++)
rc = sep_scan_file(g, pi, g->pkg[pi].sources[i],
context, &fv, &bindings, children, 1);
if (rc == 0 && g->pkg[pi].path[0] != '\0'
&& !g->pkg[pi].test_support) {
const char *dot = strrchr(g->pkg[pi].path, '.');
const char *leaf = dot ? dot + 1 : g->pkg[pi].path;
if (strcmp(g->pkg[pi].name, leaf) != 0
&& !sep_command_declared_name(&g->pkg[pi])) {
fprintf(stderr,
"ww: package %s does not match import path %s\n",
g->pkg[pi].name, g->pkg[pi].path);
rc = -1;
}
}
} else if (rc == 0) {
rc = sep_scan_file(g, pi, g->pkg[pi].entry, context,
&fv, &bindings, children, 0);
@@ -2774,6 +2853,7 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context,
if (bindings.n > 1)
qsort(bindings.v, (size_t)bindings.n,
sizeof *bindings.v, sep_binding_cmp);
if (rc == 0 && sep_validate_bindings(g, &bindings) < 0) rc = -1;
if (rc == 0 && g->pkg[pi].emit_context < 0) {
g->pkg[pi].bindings = bindings;
bindings.v = NULL;
@@ -2781,12 +2861,7 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context,
g->pkg[pi].emit_context = context;
} else if (rc == 0) {
struct sepbindset *want = &g->pkg[pi].bindings;
if (want->n != bindings.n) rc = -1;
for (int i = 0; i < want->n && rc == 0; i++)
if (want->v[i].kind != bindings.v[i].kind
|| want->v[i].dep != bindings.v[i].dep
|| strcmp(want->v[i].name, bindings.v[i].name) != 0)
rc = -1;
if (!sep_bindsets_semantically_same(want, &bindings)) rc = -1;
if (rc < 0) {
const char *first =
g->context[g->pkg[pi].emit_context].root;
@@ -2875,6 +2950,14 @@ sep_load_pkg(struct sepgraph *g, int pi, int context)
if (f->pending_dep >= 0) {
int dep = f->pending_dep;
f->pending_dep = -1;
if (dep != f->pkg
&& sep_external_production_edge(g, f->pkg, dep)
&& !sep_external_name_matches_production(g, f->pkg, dep)) {
fprintf(stderr,
"ww: external test package %s does not match production package %s\n",
g->pkg[f->pkg].name, g->pkg[dep].name);
goto failed;
}
if (dep != f->pkg
&& sep_forbidden_command_import(g, f->pkg, dep)) {
fprintf(stderr,
@@ -3055,54 +3138,27 @@ sep_reverse_import_base(const struct sepgraph *g, const struct seppkg *pkg,
return 0;
}
static char *
sep_ordinary_declared_name(const struct seppkg *p)
{
if (p->name == NULL || p->name[0] == '\0') return NULL;
size_t n = strlen(p->name);
if (p->variant == SEP_VARIANT_EXTERNAL) {
if (n <= 5 || strcmp(p->name + n - 5, "_test") != 0) {
fprintf(stderr,
"ww: package-test selector does not name an external package\n");
return NULL;
}
n -= 5;
}
char *out = malloc(n + 1);
if (out == NULL) {
sep_fail_nomem();
return NULL;
}
memcpy(out, p->name, n);
out[n] = '\0';
return out;
}
/* The reserved local namespace is reversible, so filesystem identity never
* depends on a hash, request order, output name, or another selected package. */
* depends on a hash, request order, output name, declared package name, or
* another selected package. */
static char *
sep_local_import_base(const struct seppkg *p)
{
char *leaf = sep_ordinary_declared_name(p);
if (leaf == NULL) return NULL;
size_t prefix = strlen(SEP_LOCAL_IMPORT_PREFIX);
size_t canon_len = strlen(p->canon), leaf_len = strlen(leaf);
if (leaf_len > (size_t)-1 - prefix - 4
|| canon_len > ((size_t)-1 - prefix - 4 - leaf_len) / 4) {
size_t canon_len = strlen(p->canon);
if (canon_len > ((size_t)-1 - prefix - 3) / 4) {
sep_fail_size();
free(leaf);
return NULL;
}
size_t outsz = prefix + 3 + 4 * canon_len + leaf_len + 1;
size_t outsz = prefix + 2 + 4 * canon_len + 1;
char *out = malloc(outsz);
if (out == NULL) {
sep_fail_nomem();
free(leaf);
return NULL;
}
size_t off = 0;
int n = snprintf(out, outsz, "%s.p", SEP_LOCAL_IMPORT_PREFIX);
if (n < 0 || (size_t)n >= outsz) { free(leaf); free(out); return NULL; }
if (n < 0 || (size_t)n >= outsz) { free(out); return NULL; }
off = (size_t)n;
static const char hex[] = "0123456789abcdef";
for (const unsigned char *s = (const unsigned char *)p->canon;
@@ -3110,25 +3166,21 @@ sep_local_import_base(const struct seppkg *p)
unsigned char c = *s;
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')) {
if (off + 1 >= outsz) { free(leaf); free(out); return NULL; }
if (off + 1 >= outsz) { free(out); return NULL; }
out[off++] = (char)c;
} else if (c == '_' || c == '/') {
if (off + 2 >= outsz) { free(leaf); free(out); return NULL; }
if (off + 2 >= outsz) { free(out); return NULL; }
out[off++] = '_';
out[off++] = c == '_' ? 'u' : 's';
} else {
if (off + 4 >= outsz) { free(leaf); free(out); return NULL; }
if (off + 4 >= outsz) { free(out); return NULL; }
out[off++] = '_';
out[off++] = 'x';
out[off++] = hex[c >> 4];
out[off++] = hex[c & 15];
}
}
size_t ln = leaf_len;
if (off + 1 + ln + 1 > outsz) { free(leaf); free(out); return NULL; }
out[off++] = '.';
memcpy(out + off, leaf, ln + 1);
free(leaf);
out[off] = '\0';
return out;
}
@@ -3193,17 +3245,6 @@ sep_finalize_directory_identities(struct sepgraph *g)
struct seppkg *p = &g->pkg[pi];
if (!p->is_dir || p->generated_main || p->failed || !p->loaded)
continue;
const char *dot = strrchr(p->path, '.');
const char *leaf = dot ? dot + 1 : p->path;
int support_alias = p->role == SEP_ROLE_TEST_SUPPORT
&& strcmp(p->path, SEP_TEST_SUPPORT_MODULE) == 0
&& strcmp(p->name, "test") == 0;
if (!support_alias && strcmp(p->name, leaf) != 0
&& !sep_command_declared_name(p)) {
fprintf(stderr, "ww: package %s does not match import path %s\n",
p->name, p->path);
return -1;
}
free(p->artifact);
p->artifact = NULL;
if (p->variant == SEP_VARIANT_SAME_TEST)
@@ -3418,9 +3459,7 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *unitf)
* retarget cannot reuse stale assembly even when export bytes are equal. */
for (int i = 0; i < g->pkg[pi].bindings.n && bodyrc == 0; i++) {
struct sepbind *b = &g->pkg[pi].bindings.v[i];
if (b->kind != 'D' || b->dep < 0 || b->dep >= g->n
|| strcmp(b->name, g->pkg[b->dep].path) == 0)
continue;
if (!sep_binding_first_map(g, &g->pkg[pi].bindings, i)) continue;
if (fprintf(u, "//ww:import-map %s %s ",
b->name, g->pkg[b->dep].path) < 0
|| sep_emit_hex(u, g->pkg[b->dep].canon) < 0
@@ -3624,7 +3663,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 ? 12 : 13, is_test ? "test" : "build", emit_asm);
is_test ? 13 : 14, is_test ? "test" : "build", emit_asm);
}
/* A stale global builder identity invalidates every committed unit voucher in
@@ -3658,6 +3697,20 @@ invalidate_workdir_units(const char *scratch)
return rc;
}
static int
sep_discard_action_staging(int warm, const char *unit, const char *wwi,
const char *assembly, const char *object, const char *archive)
{
if (!warm) return 0;
const char *paths[] = { unit, wwi, assembly, object, archive };
for (size_t i = 0; i < sizeof paths / sizeof paths[0]; i++) {
if (unlink(paths[i]) == 0 || errno == ENOENT) continue;
fprintf(stderr, "ww: cannot remove staged package artifacts\n");
return -1;
}
return 0;
}
struct sep_created_dirs {
char path[PATH_MAX];
unsigned short offset[(PATH_MAX + 1) / 2];
@@ -4200,7 +4253,15 @@ build_one_sep_impl(const char *src, int entry_is_dir,
const char *cs = warm ? asmnew : asmf;
const char *co = warm ? objnew : obj;
const char *ca = warm ? anew : apath;
if (sep_discard_action_staging(warm, unitnew, wwinew, asmnew,
objnew, anew) < 0) {
g->pkg[pi].failed = 1;
any_failed = 1;
continue;
}
if (sep_compose_unit(g, pi, cu) < 0) {
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
g->pkg[pi].failed = 1;
any_failed = 1;
continue;
@@ -4225,26 +4286,30 @@ build_one_sep_impl(const char *src, int entry_is_dir,
}
int nmaps = 0;
for (int k = 0; k < g->pkg[pi].bindings.n; k++) {
struct sepbind *b = &g->pkg[pi].bindings.v[k];
if (b->kind == 'D' && b->dep >= 0 && b->dep < g->n
&& strcmp(b->name, g->pkg[b->dep].path) != 0) {
if (nmaps == INT_MAX) {
sep_fail_size();
free(order);
return 1;
}
nmaps++;
if (!sep_binding_first_map(g, &g->pkg[pi].bindings, k))
continue;
if (nmaps == INT_MAX) {
sep_fail_size();
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
free(order);
return 1;
}
nmaps++;
}
size_t cargvcap = 12;
size_t cargvcap = 14;
if ((size_t)g->pkg[pi].ndeps > ((size_t)-1 - cargvcap) / 3) {
fprintf(stderr, "ww: package graph is too large\n");
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
free(order);
return 1;
}
cargvcap += 3 * (size_t)g->pkg[pi].ndeps;
if ((size_t)nmaps > ((size_t)-1 - cargvcap) / 3) {
fprintf(stderr, "ww: package graph is too large\n");
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
free(order);
return 1;
}
@@ -4257,6 +4322,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
if (cargv == NULL || (g->pkg[pi].ndeps > 0
&& importfiles == NULL)) {
fprintf(stderr, "ww: out of memory\n");
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
free(importfiles);
free(cargv);
free(order);
@@ -4270,6 +4337,10 @@ build_one_sep_impl(const char *src, int entry_is_dir,
cargv[cpos++] = "--entry";
cargv[cpos++] = "--test-support-module";
cargv[cpos++] = (char *)test_support_module;
if (g->pkg[pi].generated_main) {
cargv[cpos++] = "--test-target-package";
cargv[cpos++] = g->pkg[g->pkg[pi].generated_target].path;
}
} else {
if (g->pkg[pi].variant == SEP_VARIANT_SAME_TEST
|| g->pkg[pi].variant == SEP_VARIANT_EXTERNAL)
@@ -4294,8 +4365,7 @@ build_one_sep_impl(const char *src, int entry_is_dir,
}
for (int k = 0; k < g->pkg[pi].bindings.n; k++) {
struct sepbind *b = &g->pkg[pi].bindings.v[k];
if (b->kind != 'D' || b->dep < 0 || b->dep >= g->n
|| strcmp(b->name, g->pkg[b->dep].path) == 0)
if (!sep_binding_first_map(g, &g->pkg[pi].bindings, k))
continue;
cargv[cpos++] = "--import-map";
cargv[cpos++] = b->name;
@@ -4315,6 +4385,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
g->pkg[pi].failed = 1;
any_failed = 1;
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
continue;
}
g->pkg[pi].export_changed = !warm
@@ -4327,6 +4399,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
g->pkg[pi].failed = 1;
any_failed = 1;
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
continue;
}
}
@@ -4338,6 +4412,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
g->pkg[pi].failed = 1;
any_failed = 1;
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
continue;
}
}
@@ -4353,6 +4429,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
g->pkg[pi].failed = 1;
any_failed = 1;
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
continue;
}
}