ww: separate package identity from declared name
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
143
cmd/w6c/main.c
143
cmd/w6c/main.c
@@ -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;
|
||||
|
||||
287
cmd/w6c/wwi.c
287
cmd/w6c/wwi.c
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user