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

View File

@@ -38,6 +38,8 @@ struct Cg {
* lib/foo binds to `foo.frob` regardless * lib/foo binds to `foo.frob` regardless
* of which other modules also export `frob`. * of which other modules also export `frob`.
* Set by cgfn before walking the body. */ * 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 framesize; /* bytes of locals; 16-byte aligned */
int curoff; /* current top of locals */ int curoff; /* current top of locals */
Scope *locals; /* (name → offset) tracked via Sym */ Scope *locals; /* (name → offset) tracked via Sym */

View File

@@ -39,6 +39,7 @@ struct importin {
const char *file; const char *file;
char *buf; char *buf;
u64 len; u64 len;
Node *ast;
}; };
struct importmap { struct importmap {
@@ -47,13 +48,6 @@ struct importmap {
int seen; int seen;
}; };
static const char *
importleaf(const char *path)
{
const char *dot = strrchr(path, '.');
return dot != NULL ? dot + 1 : path;
}
static Node * static Node *
parseinput(Arena *a, const char *file, char *buf, u64 len, parseinput(Arena *a, const char *file, char *buf, u64 len,
const char *mod, const char *testsupport, int commandpackage, int *bad) 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; 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 static void
appendnodes(Node **head, Node **tail, Node *list) appendnodes(Node **head, Node **tail, Node *list)
{ {
@@ -90,6 +157,7 @@ main(int argc, char **argv)
const char *out = NULL; const char *out = NULL;
const char *wwiout = NULL; /* -I <out.wwi>: M2 export-data producer */ const char *wwiout = NULL; /* -I <out.wwi>: M2 export-data producer */
const char *testsupport = NULL; const char *testsupport = NULL;
const char *testtarget = NULL;
int testmode = 0; int testmode = 0;
int testpackage = 0; int testpackage = 0;
int commandpackage = 0; int commandpackage = 0;
@@ -127,6 +195,12 @@ main(int argc, char **argv)
return 2; return 2;
} }
testsupport = argv[++i]; 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) { } else if (strcmp(a, "-c") == 0) {
sepmode = 1; sepmode = 1;
} else if (strcmp(a, "--import") == 0) { } else if (strcmp(a, "--import") == 0) {
@@ -156,7 +230,7 @@ main(int argc, char **argv)
} }
} }
if (src == NULL) { 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); "[--import path dep.wwi]... [--import-map source path]... [-o out.s] file.ww\n", stderr);
return 2; return 2;
} }
@@ -196,11 +270,6 @@ main(int argc, char **argv)
stderr); stderr);
return 2; 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; int direct = 0;
for (int j = 0; j < nimports; j++) for (int j = 0; j < nimports; j++)
if (strcmp(maps[i].path, imports[j].path) == 0) { 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); fputs("w6c: invalid --test-support-module\n", stderr);
return 2; 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(); Arena *a = newarena();
Checker c; Checker c;
@@ -243,6 +328,10 @@ main(int argc, char **argv)
Node *f = parseinput(a, imports[i].file, imports[i].buf, Node *f = parseinput(a, imports[i].file, imports[i].buf,
imports[i].len, imports[i].path, testsupport, 0, &bad); imports[i].len, imports[i].path, testsupport, 0, &bad);
if (bad) return 1; 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); appendnodes(&head, &tail, f->list);
} }
@@ -259,7 +348,7 @@ main(int argc, char **argv)
Node *file = parseinput(a, src, buf, len, NULL, testsupport, Node *file = parseinput(a, src, buf, len, NULL, testsupport,
commandpackage || entrymode, &bad); commandpackage || entrymode, &bad);
if (bad) return 1; 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 * resolution supplies the expanded canonical owner. Rewrite only the
* primary import key before imported interface nodes are prepended. */ * primary import key before imported interface nodes are prepended. */
for (Node *u = file->list; u; u = u->next) { 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); fputs("w6c: --import-map source is not in primary input\n", stderr);
return 2; 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) { if (head != NULL) {
tail->next = file->list; tail->next = file->list;
file->list = head; file->list = head;
@@ -285,6 +399,7 @@ main(int argc, char **argv)
c.is_test = testmode; c.is_test = testmode;
c.is_test_package = testpackage; c.is_test_package = testpackage;
if (testsupport != NULL) c.test_module = testsupport; if (testsupport != NULL) c.test_module = testsupport;
c.test_target = testtarget;
c.sep_mode = sepmode; c.sep_mode = sepmode;
check_file(&c, file); check_file(&c, file);
if (c.errs) return 1; 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 * flat in the parser, so consulting every N_USE without this owner filter
* would let one dependency accidentally resolve another dependency's alias. */ * would let one dependency accidentally resolve another dependency's alias. */
static const char * 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) { for (Node *u = c->file->list; u; u = u->next) {
if (u->kind != N_USE || u->str == NULL if (u->kind != N_USE || u->str == NULL
|| strcmp(u->str, alias) != 0) || u->sourceid != source || strcmp(u->str, alias) != 0)
continue; continue;
int same = owner == NULL ? u->imported == 0 int same = owner == NULL ? u->imported == 0
: u->imported != 0 && wwi_mod_eq(u->module, owner); : 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 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; if (mod == NULL || mod[0] == '\0') return 0;
const char *dot = strrchr(mod, '.'); for (Node *u = c->file->list; u; u = u->next) {
const char *alias = dot ? dot + 1 : mod; if (u->kind != N_USE || u->sourceid != source) continue;
const char *path = wwi_use_path(c, owner, alias); int same = owner == NULL ? u->imported == 0
return path != NULL && strcmp(path, mod) == 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 * 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; if (nm == NULL) return NULL;
const char *dot = strrchr(nm, '.'); 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"); if (alias == NULL) fatal("wwi: out of memory");
memcpy(alias, nm, n); memcpy(alias, nm, n);
alias[n] = '\0'; alias[n] = '\0';
const char *mod = wwi_use_path(c, owner, alias); const char *mod = wwi_use_path(c, owner, source, alias);
if (mod) if (mod)
s = scope_lookup_in_module(c->cur, mod, dot + 1); s = scope_lookup_in_module(c->cur, mod, dot + 1);
free(alias); 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) { for (Sym *b = p->first; b; b = b->next) {
if (b->kind == SK_TYPE if (b->kind == SK_TYPE
&& strcmp(b->name, nm) == 0 && strcmp(b->name, nm) == 0
&& wwi_direct_mod_visible(c, owner, b->mod)) { && wwi_direct_mod_visible(c, owner, source,
b->mod)) {
s = b; s = b;
break; break;
} }
@@ -563,6 +563,8 @@ factcmp(const void *a, const void *b)
const struct factent *x = a, *y = b; const struct factent *x = a, *y = b;
int r = strcmp(x->mod, y->mod); int r = strcmp(x->mod, y->mod);
if (r != 0) return r; 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 xr = x->d->kind == N_TYPEDECL ? 0 : 1;
int yr = y->d->kind == N_TYPEDECL ? 0 : 1; int yr = y->d->kind == N_TYPEDECL ? 0 : 1;
if (xr != yr) return xr - yr; if (xr != yr) return xr - yr;
@@ -607,7 +609,7 @@ wwi_fact_grow(struct factent **v, int *cap, int need)
} }
static Sym * 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 (Scope *p = c->top; p; p = p->parent)
for (Sym *s = p->first; s; s = s->next) 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 (Scope *p = c->top; p; p = p->parent)
for (Sym *s = p->first; s; s = s->next) for (Sym *s = p->first; s; s = s->next)
if (s->kind == SK_DEF && strcmp(s->name, name) == 0 if (s->kind == SK_DEF && strcmp(s->name, name) == 0
&& wwi_direct_mod_visible(c, owner, s->mod)) && wwi_direct_mod_visible(c, owner, source, s->mod))
return s; return s;
return NULL; return NULL;
} }
static void wwi_collect_decl(struct factset*, const char*, Node*); 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 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; if (e == NULL) return;
Sym *s = NULL; Sym *s = NULL;
if (e->kind == N_IDENT) { 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 } else if (e->kind == N_DOT && e->lhs
&& e->lhs->kind == N_IDENT) { && e->lhs->kind == N_IDENT) {
const char *mod = wwi_use_path(fs->c, owner, e->lhs->str); const char *mod = wwi_use_path(fs->c, owner, source,
if (mod) s = wwi_valuesym(fs->c, mod, e->str); 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 && s->decl && s->decl->kind == N_DEF) {
if (!s->decl->export) { if (!s->decl->export) {
@@ -648,23 +651,23 @@ wwi_collect_expr(struct factset *fs, const char *owner, Node *e)
return; return;
} }
if (e->kind == N_BIN) { if (e->kind == N_BIN) {
wwi_collect_expr(fs, owner, e->lhs); wwi_collect_expr(fs, owner, source, e->lhs);
wwi_collect_expr(fs, owner, e->rhs); wwi_collect_expr(fs, owner, source, e->rhs);
} else if (e->kind == N_UN) { } 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) { } else if (e->kind == N_CAST) {
wwi_collect_expr(fs, owner, e->lhs); wwi_collect_expr(fs, owner, source, e->lhs);
wwi_collect_type(fs, owner, e->rhs); wwi_collect_type(fs, owner, source, e->rhs);
} }
} }
static void 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; if (t == NULL) return;
switch (t->kind) { switch (t->kind) {
case N_TNAME: { 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) if (s && s->decl && s->decl->kind == N_TYPEDECL)
wwi_collect_decl(fs, s->mod, s->decl); wwi_collect_decl(fs, s->mod, s->decl);
break; break;
@@ -673,7 +676,7 @@ wwi_collect_type(struct factset *fs, const char *owner, Node *t)
case N_TSLICE: case N_TSLICE:
case N_TBANG: case N_TBANG:
case N_TCHAN: case N_TCHAN:
wwi_collect_type(fs, owner, t->lhs); wwi_collect_type(fs, owner, source, t->lhs);
break; break;
case N_TARRAY: case N_TARRAY:
/* Array length is part of the resolved type identity, not a source /* 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. */ * and a consumer never needs an implementation def to size the type. */
if (t->rhs != NULL && t->rhs->kind != N_INTLIT) { if (t->rhs != NULL && t->rhs->kind != N_INTLIT) {
u64 len; 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"); errorf(t->pos, "cannot encode array dimension");
fs->bad = 1; fs->bad = 1;
} else { } else {
@@ -692,24 +695,24 @@ wwi_collect_type(struct factset *fs, const char *owner, Node *t)
e->lhs = e->rhs = e->list = NULL; e->lhs = e->rhs = e->list = NULL;
} }
} }
wwi_collect_type(fs, owner, t->lhs); wwi_collect_type(fs, owner, source, t->lhs);
break; break;
case N_TFN: case N_TFN:
for (Node *p = t->list; p; p = p->next) for (Node *p = t->list; p; p = p->next)
wwi_collect_type(fs, owner, p->lhs); wwi_collect_type(fs, owner, source, p->lhs);
wwi_collect_type(fs, owner, t->lhs); wwi_collect_type(fs, owner, source, t->lhs);
break; break;
case N_TSTRUCT: case N_TSTRUCT:
for (Node *f = t->list; f; f = f->next) 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; break;
case N_TTAGGED: case N_TTAGGED:
case N_TTUPLE: case N_TTUPLE:
for (Node *e = t->list; e; e = e->next) for (Node *e = t->list; e; e = e->next)
wwi_collect_type(fs, owner, e); wwi_collect_type(fs, owner, source, e);
break; break;
case N_TENUM: 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 /* Member identifiers are enum-local prior-sibling references, not
* package defs; the complete member list already carries their facts. */ * package defs; the complete member list already carries their facts. */
break; break;
@@ -756,18 +759,18 @@ wwi_collect_decl(struct factset *fs, const char *owner, Node *d)
switch (d->kind) { switch (d->kind) {
case N_FNDECL: case N_FNDECL:
for (Node *p = d->list; p; p = p->next) for (Node *p = d->list; p; p = p->next)
wwi_collect_type(fs, owner, p->lhs); wwi_collect_type(fs, owner, d->sourceid, p->lhs);
wwi_collect_type(fs, owner, d->lhs); wwi_collect_type(fs, owner, d->sourceid, d->lhs);
break; break;
case N_TYPEDECL: case N_TYPEDECL:
wwi_collect_type(fs, owner, d->lhs); wwi_collect_type(fs, owner, d->sourceid, d->lhs);
break; break;
case N_DEF: case N_DEF:
wwi_collect_type(fs, owner, d->lhs); wwi_collect_type(fs, owner, d->sourceid, d->lhs);
wwi_collect_expr(fs, owner, d->rhs); wwi_collect_expr(fs, owner, d->sourceid, d->rhs);
break; break;
case N_LET: case N_LET:
wwi_collect_type(fs, owner, d->lhs); wwi_collect_type(fs, owner, d->sourceid, d->lhs);
break; break;
default: default:
break; break;
@@ -775,24 +778,32 @@ wwi_collect_decl(struct factset *fs, const char *owner, Node *d)
} }
static int 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 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 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; int nuse = 0;
for (Node *u = file->list; u; u = u->next) for (Node *u = file->list; u; u = u->next) {
if (wwi_use_owned(u, owner)) nuse++; 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; if (nuse == 0) return;
struct useent *us = malloc((size_t)nuse * sizeof *us); struct useent *us = malloc((size_t)nuse * sizeof *us);
if (us == NULL) fatal("wwi: out of memory"); if (us == NULL) fatal("wwi: out of memory");
int k = 0; int k = 0;
for (Node *u = file->list; u; u = u->next) { 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].path = u->usepath ? u->usepath : u->str;
us[k].idx = k; us[k].idx = k;
k++; k++;
@@ -807,6 +818,65 @@ wwi_emit_fact_imports(FILE *of, Node *file, const char *owner)
free(us); 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 int
wwi_emit(Checker *c, FILE *of, Node *file) wwi_emit(Checker *c, FILE *of, Node *file)
{ {
@@ -841,83 +911,15 @@ wwi_emit(Checker *c, FILE *of, Node *file)
if (fs.nfacts > 1) if (fs.nfacts > 1)
qsort(fs.facts, (size_t)fs.nfacts, sizeof *fs.facts, factcmp); qsort(fs.facts, (size_t)fs.nfacts, sizeof *fs.facts, factcmp);
/* package line: leaf of the first primary decl's module tag. */ /* Exported declarations are sorted within their source-file sections. */
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. */
int ndecl = 0; int ndecl = 0;
for (Node *d = file->list; d; d = d->next) for (Node *d = file->list; d; d = d->next)
if (wwi_primary(d) && d->export && wwi_is_decl(d)) if (wwi_primary(d) && d->export && wwi_is_decl(d))
ndecl++; ndecl++;
struct declent *ds = NULL;
if (ndecl > 0) { 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; int k = 0;
for (Node *d = file->list; d; d = d->next) { for (Node *d = file->list; d; d = d->next) {
if (!wwi_primary(d) || !d->export || !wwi_is_decl(d)) if (!wwi_primary(d) || !d->export || !wwi_is_decl(d))
@@ -927,24 +929,49 @@ wwi_emit(Checker *c, FILE *of, Node *file)
k++; k++;
} }
qsort(ds, (size_t)ndecl, sizeof *ds, declcmp); 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 /* Compiler-owned public fact closure. A module marker changes semantic
* ownership without making the namespace a source import of the eventual * ownership without making the namespace a source import of the eventual
* consumer; direct visibility continues to come solely from its own N_USE. */ * consumer; direct visibility continues to come solely from its own N_USE. */
const char *lastmod = NULL; const char *lastmod = NULL;
int lastsource = -1;
for (int i = 0; i < fs.nfacts; i++) { for (int i = 0; i < fs.nfacts; i++) {
struct factent *f = &fs.facts[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 *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, "//ww:module %s\n", f->mod);
fprintf(of, "package %s;\n", leaf); fprintf(of, "package %s;\n", factpkg);
wwi_emit_fact_imports(of, file, f->mod); wwi_emit_imports(of, file, f->mod, f->d->sourceid, 1);
lastmod = f->mod; lastmod = f->mod;
lastsource = f->d->sourceid;
} }
wwi_decl(of, f->d); 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 *decl_mod(Node *file, Node *d);
static const char *use_path(Node *file, const char *curmod, const char *alias); static const char *use_path(Node *file, const char *curmod, int source,
static int src_imports(Node *file, const char *modtag, const char *name); 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(Checker *c, const char *name);
static Sym *lookup_visible_type(Checker *c, const char *name); static Sym *lookup_visible_type(Checker *c, const char *name);
static void resolve_typedecl(Checker *c, Node *d); 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 /* M1 #22: map the qualifier alias to its dotted
* import path (symbols are path-keyed). */ * import path (symbols are path-keyed). */
const char *mk = use_path(c->file, c->cur_mod, const char *mk = use_path(c->file, c->cur_mod,
c->cur_source,
head); head);
if (mk != NULL) if (mk != NULL)
s = scope_lookup_in_module(c->cur, mk, 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: { case N_DOT: {
if (n->lhs == NULL || n->lhs->kind != N_IDENT) return 0; if (n->lhs == NULL || n->lhs->kind != N_IDENT) return 0;
/* M1 #22: map the qualifier alias to its dotted import path. */ /* 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; if (mk == NULL) return 0;
Sym *s = scope_lookup_in_module(c->cur, mk, n->str); Sym *s = scope_lookup_in_module(c->cur, mk, n->str);
if (s == NULL || s->kind != SK_DEF || if (s == NULL || s->kind != SK_DEF ||
@@ -636,12 +640,15 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
} }
int 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; const char *saved = c->cur_mod;
int savesource = c->cur_source;
c->cur_mod = owner; c->cur_mod = owner;
c->cur_source = source;
int ok = eval_def_const(c, n, out, 0); int ok = eval_def_const(c, n, out, 0);
c->cur_mod = saved; c->cur_mod = saved;
c->cur_source = savesource;
return ok; return ok;
} }
@@ -1475,6 +1482,7 @@ cexpr(Checker *c, Node *n)
* import path; map the alias the user wrote to * import path; map the alias the user wrote to
* that path before looking up the leaf. */ * that path before looking up the leaf. */
const char *mk = use_path(c->file, c->cur_mod, const char *mk = use_path(c->file, c->cur_mod,
c->cur_source,
n->lhs->str); n->lhs->str);
if (mk == NULL) { if (mk == NULL) {
if (ms->kind == SK_USE) 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 = 0; /* #15: caller (w6c main) sets it after init */
c->is_test_package = 0; c->is_test_package = 0;
c->test_module = "test"; c->test_module = "test";
c->test_target = NULL;
typesinit(a); typesinit(a);
c->top = newscope(a, NULL); c->top = newscope(a, NULL);
c->cur = c->top; c->cur = c->top;
@@ -2873,6 +2882,10 @@ static const char *
decl_mod(Node *file, Node *d) decl_mod(Node *file, Node *d)
{ {
if (d == NULL || d->module == NULL || file == NULL) return NULL; 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) { for (Node *u = file->list; u; u = u->next) {
/* M1 #22: a decl is imported iff some `use` directive's full /* M1 #22: a decl is imported iff some `use` directive's full
* dotted import path equals the decl's module (now the path, * 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`) * use_path — map a source-file default qualifier (the imported package's
* to the full dotted import path it binds (`encoding.utf8`), for the * declared name) to the full canonical import path it binds, for
* module-qualified resolution and the codegen hint (M1 #22, §2.4). For * module-qualified resolution and the codegen hint (M1 #22, §2.4).
* single-level packages usepath == alias so the result is unchanged.
* *
* The alias→path map is NOT file-global: two modules in the same * The qualifier→path map is source-file local: two files in the same
* concatenated unit may bind the same leaf alias to different paths * concatenated unit may bind the same declared name to different paths. The
* (sha256's `import crypto.math` and strconv's `import math` both bind * import with the reference's source ID and owner is authoritative, closing
* alias `math`). The import declared in the SAME module as the * both cross-file mis-resolution and accidental transitive visibility.
* reference (`curmod`) is the authoritative one; requiring it closes both
* cross-module mis-resolution and accidental transitive-import visibility.
* Returns NULL if the referencing package has no such `use`. * Returns NULL if the referencing package has no such `use`.
*/ */
static const char * 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; if (file == NULL || alias == NULL) return NULL;
/* Legacy inline multi-package units may spell a package's own /* Legacy inline multi-package units may spell a package's own
* declarations as `pkg.member`. That is self-qualification, not an * declarations as `pkg.member`. That is self-qualification, not an
* imported namespace; preserve it without reopening transitive lookup. */ * imported namespace; preserve it without reopening transitive lookup. */
if (curmod != NULL) { if (source == 0 && curmod != NULL) {
const char *dot = strrchr(curmod, '.'); const char *dot = strrchr(curmod, '.');
const char *leaf = dot ? dot + 1 : curmod; const char *leaf = dot ? dot + 1 : curmod;
if (strcmp(alias, leaf) == 0) return curmod; if (strcmp(alias, leaf) == 0) return curmod;
} }
for (Node *u = file->list; u; u = u->next) { for (Node *u = file->list; u; u = u->next) {
if (u->kind != N_USE || u->str == NULL if (u->kind != N_USE || u->str == NULL
|| strcmp(u->str, alias) != 0) || u->sourceid != source || strcmp(u->str, alias) != 0)
continue; continue;
const char *p = u->usepath ? u->usepath : u->str; const char *p = u->usepath ? u->usepath : u->str;
const char *um = decl_mod(file, u); const char *um = decl_mod(file, u);
int same = (um == NULL) ? (curmod == NULL) int same = (um == NULL) ? (curmod == NULL)
: (curmod != NULL && strcmp(um, curmod) == 0); : (curmod != NULL && strcmp(um, curmod) == 0);
if (same) if (same) {
u->used = 1;
return p; return p;
}
} }
return NULL; return NULL;
} }
@@ -2947,9 +2959,12 @@ resolve_typedecl(Checker *c, Node *d)
if (t == NULL || t->under != NULL || t->resolving) return; if (t == NULL || t->under != NULL || t->resolving) return;
t->resolving = 1; t->resolving = 1;
const char *save = c->cur_mod; const char *save = c->cur_mod;
int savesource = c->cur_source;
c->cur_mod = decl_mod(c->file, d); c->cur_mod = decl_mod(c->file, d);
c->cur_source = d->sourceid;
Type *under = resolve_type(c, d->lhs); Type *under = resolve_type(c, d->lhs);
c->cur_mod = save; c->cur_mod = save;
c->cur_source = savesource;
/* Alias-root cycle (`type a = b; type b = a` / `type a = a`): /* Alias-root cycle (`type a = b; type b = a` / `type a = a`):
* checked BEFORE clearing the flag so self-aliases trip on their * checked BEFORE clearing the flag so self-aliases trip on their
* own in-progress mark. ty_err instead of the cyclic under keeps * 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). * (N_USE nodes with module == NULL).
*/ */
static int 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; if (file == NULL || name == NULL || name[0] == '\0') return 0;
for (Node *u = file->list; u; u = u->next) { 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;` /* Skip self-imports: lib/fmt/fmt_test.ww carries `use fmt;`
* even though its module tag is also "fmt"; that directive * even though its module tag is also "fmt"; that directive
* doesn't introduce a foreign module bareword and lib/fmt's * 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) direct_module_visible(Checker *c, const char *mod)
{ {
if (c == NULL || mod == NULL || mod[0] == '\0') return 0; if (c == NULL || mod == NULL || mod[0] == '\0') return 0;
const char *dot = strrchr(mod, '.'); for (Node *u = c->file->list; u; u = u->next) {
const char *alias = dot ? dot + 1 : mod; if (u->kind != N_USE || u->sourceid != c->cur_source) continue;
const char *path = use_path(c->file, c->cur_mod, alias); const char *um = decl_mod(c->file, u);
return path != NULL && strcmp(path, mod) == 0; 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 * static Sym *
@@ -3035,7 +3058,7 @@ lookup_visible(Checker *c, const char *name)
* source-owned alias map is authoritative: if this package directly * source-owned alias map is authoritative: if this package directly
* imports NAME, return the coalesced module marker only as a marker; the * imports NAME, return the coalesced module marker only as a marker; the
* N_DOT path maps the alias to the correct full path again. */ * 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 (Scope *p = c->cur; p; p = p->parent)
for (Sym *b = p->first; b; b = b->next) for (Sym *b = p->first; b; b = b->next)
if (strcmp(b->name, name) == 0 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 (!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'", err(c, pos, "%s '%s' shadows imported module '%s'",
kindstr, name, name); kindstr, name, name);
} }
@@ -3132,6 +3155,80 @@ same_import_fact(Checker *c, Node *d, const char *mod, Skind kind)
return s; 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 void
check_file(Checker *c, Node *file) 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. */ * already emits the qualified call. wwstage twin in check.ww. */
if (c->is_test) { if (c->is_test) {
int present = 0; 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 if (u->kind == N_USE && !u->imported
&& u->usepath && strcmp(u->usepath, c->test_module) == 0) { && u->usepath && strcmp(u->usepath, c->test_module) == 0) {
present = 1; present = 1;
break;
} }
}
if (!present) { if (!present) {
Node *usenode = newnode(c->a, N_USE, file->pos); Node *usenode = newnode(c->a, N_USE, file->pos);
usenode->str = c->test_module; usenode->str = c->test_module;
usenode->strlen = strlen(c->test_module); usenode->strlen = strlen(c->test_module);
usenode->usepath = 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; usenode->next = file->list;
file->list = usenode; file->list = usenode;
} }
} }
check_import_redeclarations(c, file);
/* pass 1: install names (types first, then defs/fns). /* pass 1: install names (types first, then defs/fns).
* For self-referential types we install the named-type placeholder * 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 * references (`strconv.invalid`) resolve when typedecl bodies are
* walked in the next pass. */ * walked in the next pass. */
for (Node *d = file->list; d; d = d->next) { for (Node *d = file->list; d; d = d->next) {
c->cur_source = d->sourceid;
if (d->kind == N_USE) { if (d->kind == N_USE) {
/* check-(c) self-import: a package may not import /* A package may not import its own canonical owner. Import
* itself. Pure owner==leaf string compare, package- * usage and membership are checked with source-file provenance. */
* 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). */
const char *owner = decl_mod(file, d); const char *owner = decl_mod(file, d);
/* M1 #22: self-import ⟺ the imported path equals the /* M1 #22: self-import ⟺ the imported path equals the
* use's own (owning) module path. Compares paths, not * 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 * here. A foldable stub carries type NULL until then. A duplicate
* (prev already bound non-USE) is left for that loop to diagnose. */ * (prev already bound non-USE) is left for that loop to diagnose. */
c->cur_mod = NULL; c->cur_mod = NULL;
c->cur_source = 0;
for (Node *d = file->list; d; d = d->next) { for (Node *d = file->list; d; d = d->next) {
if (d->kind != N_DEF) continue; if (d->kind != N_DEF) continue;
c->cur_mod = decl_mod(file, d); c->cur_mod = decl_mod(file, d);
c->cur_source = d->sourceid;
const char *mod = decl_mod(file, d); const char *mod = decl_mod(file, d);
if (same_import_fact(c, d, mod, SK_DEF) != NULL) if (same_import_fact(c, d, mod, SK_DEF) != NULL)
continue; continue;
@@ -3250,13 +3355,16 @@ check_file(Checker *c, Node *file)
} }
} }
c->cur_mod = NULL; c->cur_mod = NULL;
c->cur_source = 0;
for (Node *d = file->list; d; d = d->next) { for (Node *d = file->list; d; d = d->next) {
if (d->kind != N_TYPEDECL) continue; if (d->kind != N_TYPEDECL) continue;
resolve_typedecl(c, d); resolve_typedecl(c, d);
} }
c->cur_mod = NULL; c->cur_mod = NULL;
c->cur_source = 0;
for (Node *d = file->list; d; d = d->next) { for (Node *d = file->list; d; d = d->next) {
c->cur_mod = decl_mod(file, d); c->cur_mod = decl_mod(file, d);
c->cur_source = d->sourceid;
switch (d->kind) { switch (d->kind) {
case N_USE: case N_USE:
/* already installed in pass 1; no-op here so the /* 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_mod = NULL;
c->cur_source = 0;
/* Program-global uniqueness on the ENTRY `main`. M1 #32: the entry /* Program-global uniqueness on the ENTRY `main`. M1 #32: the entry
* is the ROOT-unit main (imported==0) — it alone lowers to the bare * 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); nm->strlen = strlen(d->str);
Node *id; Node *id;
if (d->imported && d->module && d->module[0]) { if (d->imported && d->module && d->module[0]) {
const char *dotp = strrchr(d->module, '.'); const char *alias = d->pkgname && d->pkgname[0]
const char *alias = dotp ? dotp + 1 : d->module; ? 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 = newnode(c->a, N_DOT, fp);
id->lhs = newnode(c->a, N_IDENT, fp); id->lhs = newnode(c->a, N_IDENT, fp);
id->lhs->str = alias; id->lhs->str = alias;
@@ -3542,6 +3658,8 @@ check_file(Checker *c, Node *file)
tab = newnode(c->a, N_LET, fp); tab = newnode(c->a, N_LET, fp);
tab->op = TK_CONST; tab->op = TK_CONST;
tab->str = "__wwtests"; tab->str = "__wwtests";
tab->pkgname = file->pkgname;
tab->sourceid = file->sourceid;
tab->lhs = tsl; tab->lhs = tsl;
tab->rhs = arr; tab->rhs = arr;
/* pass 1 already ran, so install the table's name now — /* 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); Node *m = newnode(c->a, N_FNDECL, fp);
m->str = "main"; m->str = "main";
m->export = 1; m->export = 1;
m->pkgname = file->pkgname;
m->sourceid = file->sourceid;
m->lhs = newnode(c->a, N_TNAME, fp); m->lhs = newnode(c->a, N_TNAME, fp);
m->lhs->str = "i32"; m->lhs->str = "i32";
m->body = body; m->body = body;
int savesource = c->cur_source;
c->cur_source = m->sourceid;
m->type = build_fn_type(c, m); m->type = build_fn_type(c, m);
c->cur_source = savesource;
/* pass 1 already ran, so the install loop never stamped m's /* pass 1 already ran, so the install loop never stamped m's
* type; set it explicitly (pass 2 below reads d->type). * type; set it explicitly (pass 2 below reads d->type).
* Append the table const (if any) then main to file->list. */ * 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 */ /* pass 2: check def initialisers and fn bodies */
for (Node *d = file->list; d; d = d->next) { for (Node *d = file->list; d; d = d->next) {
c->cur_mod = decl_mod(file, d); c->cur_mod = decl_mod(file, d);
c->cur_source = d->sourceid;
switch (d->kind) { switch (d->kind) {
case N_DEF: { case N_DEF: {
if (d->rhs) { if (d->rhs) {
@@ -3786,6 +3910,8 @@ check_file(Checker *c, Node *file)
} }
} }
c->cur_mod = NULL; 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 * #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; Pos pp = p->cur.pos;
expect(p, TK_USE); expect(p, TK_USE);
Node *n = newnode(p->a, N_USE, pp); Node *n = newnode(p->a, N_USE, pp);
/* M1 #22: accumulate the full dotted import path (n->module) so the /* M1 #22: accumulate the full dotted import path in usepath. `str`
* checker can match decl identity on the path, while n->str stays the * starts as its leaf; direct export metadata later installs the imported
* leaf alias the user writes (`utf8.x`). */ * declaration's default name without changing canonical identity. */
const char *leaf = expectident(p); const char *leaf = expectident(p);
const char *path = leaf; const char *path = leaf;
while (accept(p, TK_DOT)) { while (accept(p, TK_DOT)) {
@@ -1358,19 +1358,23 @@ parseimports(Parser *p)
* the following package clause and imports. */ * the following package clause and imports. */
if (p->cur.kind == TK_MODPATH) { if (p->cur.kind == TK_MODPATH) {
sawpackage = 0; sawpackage = 0;
p->sourceid++;
p->pathmod = p->cur.text; p->pathmod = p->cur.text;
p->curmod = p->cur.text; p->curmod = p->cur.text;
p->resetmod = NULL; p->resetmod = NULL;
p->curpkg = NULL;
advance(p); advance(p);
continue; continue;
} }
if (p->cur.kind == TK_MODRESET) { if (p->cur.kind == TK_MODRESET) {
const char *rp = p->cur.text; const char *rp = p->cur.text;
sawpackage = 0; sawpackage = 0;
p->sourceid++;
advance(p); advance(p);
p->pathmod = NULL; p->pathmod = NULL;
p->curmod = rp; p->curmod = rp;
p->resetmod = rp; p->resetmod = rp;
p->curpkg = NULL;
continue; continue;
} }
if (p->cur.kind == TK_MODULE) { if (p->cur.kind == TK_MODULE) {
@@ -1385,9 +1389,13 @@ parseimports(Parser *p)
} }
const char *name = expectident(p); const char *name = expectident(p);
expect(p, TK_SEMI); 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); 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) if (packages == NULL)
packages = package; packages = package;
else else
@@ -1395,6 +1403,8 @@ parseimports(Parser *p)
packagetail = package; packagetail = package;
if (!sawpackage) { if (!sawpackage) {
file->module = name; file->module = name;
file->pkgname = name;
file->sourceid = p->sourceid;
file->pos = pp; file->pos = pp;
sawpackage = 1; sawpackage = 1;
} }
@@ -1408,6 +1418,8 @@ parseimports(Parser *p)
if (p->cur.kind == TK_USE) { if (p->cur.kind == TK_USE) {
Node *d = parseuse(p); Node *d = parseuse(p);
d->module = p->curmod; d->module = p->curmod;
d->pkgname = p->curpkg;
d->sourceid = p->sourceid;
if (head == NULL) if (head == NULL)
head = d; head = d;
else else
@@ -1424,6 +1436,8 @@ parseimports(Parser *p)
p->errs++; p->errs++;
Node *d = parseuse(p); Node *d = parseuse(p);
d->module = p->curmod; d->module = p->curmod;
d->pkgname = p->curpkg;
d->sourceid = p->sourceid;
if (head == NULL) if (head == NULL)
head = d; head = d;
else else
@@ -1441,6 +1455,8 @@ parseimports(Parser *p)
p->errs++; p->errs++;
Node *d = parseuse(p); Node *d = parseuse(p);
d->module = p->curmod; d->module = p->curmod;
d->pkgname = p->curpkg;
d->sourceid = p->sourceid;
if (head == NULL) if (head == NULL)
head = d; head = d;
else else
@@ -1546,6 +1562,7 @@ parsefile(Parser *p)
Pos pp = { p->l->file, 1, 1 }; Pos pp = { p->l->file, 1, 1 };
Node *file = newnode(p->a, N_FILE, pp); Node *file = newnode(p->a, N_FILE, pp);
Node *head = NULL, *tail = NULL; Node *head = NULL, *tail = NULL;
Node *packages = NULL, *packagetail = NULL;
int sawpackage = 0; int sawpackage = 0;
while (p->cur.kind != TK_EOF) { while (p->cur.kind != TK_EOF) {
/* `package foo;` — directory-as-module declaration. Every /* `package foo;` — directory-as-module declaration. Every
@@ -1555,58 +1572,41 @@ parsefile(Parser *p)
* sep primary-reset (resetmod) regions carry identity * sep primary-reset (resetmod) regions carry identity
* out-of-band and are exempt. */ * out-of-band and are exempt. */
if (p->cur.kind == TK_MODULE) { if (p->cur.kind == TK_MODULE) {
Pos packagepos = p->cur.pos;
sawpackage = 1; sawpackage = 1;
advance(p); advance(p);
const char *name = expectident(p); const char *name = expectident(p);
expect(p, TK_SEMI); expect(p, TK_SEMI);
if (p->pathmod != NULL || p->resetmod != NULL) { p->curpkg = name;
/* M1 #22: while an import path is active the if (p->pathmod == NULL && p->resetmod == NULL) {
* 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->curmod = name; p->curmod = name;
/* #11: stamp the primary module identity on the }
* N_FILE node so wwi_emit can derive the Node *package = newnode(p->a, N_FILE, packagepos);
* `package` leaf even when the body carries zero package->module = p->curmod;
* module-tagged decls. Primary identity only; package->pkgname = name;
* never the imported boundary (TK_MODPATH). */ package->sourceid = p->sourceid;
if (file->module == NULL) package->imported = p->pathmod != NULL;
file->module = name; if (packages == NULL) packages = package;
else packagetail->next = package;
packagetail = package;
if (file->pkgname == NULL) {
file->pkgname = name;
file->sourceid = p->sourceid;
} }
continue; continue;
} }
/* `//ww:module <path>` — M1 #22 import boundary. The following /* `//ww:module <path>` — M1 #22 import boundary. The following
* file's decls mangle on the full dotted import path, not the * file's decls mangle on the full dotted import path independently
* leaf `package` clause, and are flagged imported (gates the * of its `package` clause, and are flagged imported (gates the
* root-only bare-`main` rule, #32). */ * root-only bare-`main` rule, #32). */
if (p->cur.kind == TK_MODPATH) { if (p->cur.kind == TK_MODPATH) {
sawpackage = 0; sawpackage = 0;
p->sourceid++;
p->pathmod = p->cur.text; p->pathmod = p->cur.text;
p->curmod = p->cur.text; p->curmod = p->cur.text;
p->resetmod = NULL; p->resetmod = NULL;
p->curpkg = NULL;
if (file->module == NULL) file->module = p->cur.text;
advance(p); advance(p);
continue; continue;
} }
@@ -1620,6 +1620,7 @@ parsefile(Parser *p)
* decls to bare — that usage is deliberate-only. */ * decls to bare — that usage is deliberate-only. */
if (p->cur.kind == TK_MODRESET) { if (p->cur.kind == TK_MODRESET) {
sawpackage = 0; sawpackage = 0;
p->sourceid++;
/* #57: a path-carrying reset (sep primary body) mangles /* #57: a path-carrying reset (sep primary body) mangles
* decls on the dotted path so definer == importer, but * decls on the dotted path so definer == importer, but
* leaves imported==0 (curmod set, pathmod NULL) so -c * leaves imported==0 (curmod set, pathmod NULL) so -c
@@ -1630,6 +1631,7 @@ parsefile(Parser *p)
const char *rp = p->cur.text; const char *rp = p->cur.text;
advance(p); advance(p);
p->pathmod = NULL; p->pathmod = NULL;
p->curpkg = NULL;
if (rp != NULL) { if (rp != NULL) {
p->curmod = rp; p->curmod = rp;
p->resetmod = rp; p->resetmod = rp;
@@ -1679,14 +1681,17 @@ parsefile(Parser *p)
advance(p); advance(p);
continue; continue;
} }
if (d != NULL) { if (d != NULL) {
d->module = p->curmod; d->module = p->curmod;
d->imported = (p->pathmod != NULL); d->pkgname = p->curpkg;
d->sourceid = p->sourceid;
d->imported = (p->pathmod != NULL);
} }
if (head == NULL) head = d; if (head == NULL) head = d;
else tail->next = d; else tail->next = d;
tail = d; tail = d;
} }
file->list = head; file->list = head;
file->body = packages;
return file; return file;
} }

View File

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

View File

@@ -717,6 +717,9 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package,
struct sepbind { struct sepbind {
char kind; char kind;
char *name; char *name;
char *source;
int line;
int col;
int dep; /* stable canonical action index; -1 for inline */ int dep; /* stable canonical action index; -1 for inline */
}; };
@@ -753,6 +756,7 @@ struct seppkg {
int root; /* requested usage; never package-action identity */ int root; /* requested usage; never package-action identity */
int link_entry; /* package supplies the executable's bare main */ int link_entry; /* package supplies the executable's bare main */
int generated_main; /* compiler-owned generated test-main package */ 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 failed; /* discovery/compile failure reaches this action */
int test_support; /* compiler-generated -T support package */ int test_support; /* compiler-generated -T support package */
int loaded; /* directory membership/name loaded exactly once */ 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; 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 static int
sep_forbidden_command_import(const struct sepgraph *g, int importer, int dep) 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]; const struct seppkg *to = &g->pkg[dep];
if (strcmp(to->name, "main") != 0 || to->role != SEP_ROLE_NORMAL) if (strcmp(to->name, "main") != 0 || to->role != SEP_ROLE_NORMAL)
return 0; return 0;
/* An external test's exact import of its colocated command production is /* An external test's exact import of its colocated command production is
* test-variant wiring, not a general source-importable command edge. */ * test-variant wiring, not a general source-importable command edge. */
return !(from->variant == SEP_VARIANT_EXTERNAL return !(sep_external_production_edge(g, importer, dep)
&& sep_command_declared_name(from) && sep_external_name_matches_production(g, importer, dep));
&& strcmp(from->canon, to->canon) == 0);
} }
static int 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]); for (int j = 0; j < p->nsources; j++) free(p->sources[j]);
free(p->sources); 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->bindings.v);
free(p->context_state); free(p->context_state);
free(p->deps); 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; const char *yp = y->usepath ? y->usepath : y->str;
int r = strcmp(xp, yp); int r = strcmp(xp, yp);
if (r != 0) return r; 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; if (x->pos.line != y->pos.line) return x->pos.line - y->pos.line;
return x->pos.col - y->pos.col; return x->pos.col - y->pos.col;
} }
static int static int
sep_external_production_name(const struct seppkg *pkg, const char *path, sep_external_production_name(const struct seppkg *pkg, const char *name)
int leaf_only)
{ {
if (pkg->variant != SEP_VARIANT_EXTERNAL if (pkg->variant != SEP_VARIANT_EXTERNAL
|| pkg->test_package == NULL || pkg->test_package[0] == '\0') || pkg->test_package == NULL || pkg->test_package[0] == '\0')
return 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 n = strlen(name);
size_t tn = strlen(pkg->test_package); size_t tn = strlen(pkg->test_package);
return tn == n + 5 && strncmp(pkg->test_package, name, n) == 0 return tn == n + 5 && strncmp(pkg->test_package, name, n) == 0
&& strcmp(pkg->test_package + n, "_test") == 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 *); static char *sep_local_import_base(const struct seppkg *);
/* Canonical source bindings make a shared action independent of the request /* 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. */ * route/root legality deliberately does not enter action identity. */
static int static int
sep_binding_add(struct sepbindset *bindings, char kind, const char *name, 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 (bindings->n == INT_MAX) return sep_fail_size();
if (sep_reserve((void **)&bindings->v, &bindings->cap, if (sep_reserve((void **)&bindings->v, &bindings->cap,
bindings->n + 1, sizeof *bindings->v) < 0) bindings->n + 1, sizeof *bindings->v) < 0)
return -1; return -1;
char *copy = strdup(name); char *copy = strdup(name);
if (copy == NULL) return sep_fail_nomem(); 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; return 0;
} }
@@ -2126,13 +2154,83 @@ sep_binding_cmp(const void *a, const void *b)
if (r != 0) return r; if (r != 0) return r;
if (x->kind != y->kind) return (unsigned char)x->kind if (x->kind != y->kind) return (unsigned char)x->kind
- (unsigned char)y->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 static void
sep_bindset_free(struct sepbindset *bindings) 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); free(bindings->v);
bindings->v = NULL; bindings->v = NULL;
bindings->n = bindings->cap = 0; 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); if (nuse > 1) qsort(uses, (size_t)nuse, sizeof *uses, use_node_cmp);
int rc = 0; int rc = 0;
const char *previous = NULL;
for (int i = 0; i < nuse && rc == 0; i++) { for (int i = 0; i < nuse && rc == 0; i++) {
Node *u = uses[i]; Node *u = uses[i];
const char *name = u->usepath ? u->usepath : u->str; const char *name = u->usepath ? u->usepath : u->str;
if (previous && strcmp(previous, name) == 0) continue;
previous = name;
if (reserved_import_path(name)) { if (reserved_import_path(name)) {
errorf(u->pos, "package path %s is reserved", name); errorf(u->pos, "package path %s is reserved", name);
rc = -1; rc = -1;
@@ -2442,9 +2537,8 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
g->context[context].route); g->context[context].route);
int literal_self = route_suffix != NULL int literal_self = route_suffix != NULL
? strcmp(path_form, route_suffix) == 0 ? strcmp(path_form, route_suffix) == 0
&& sep_external_production_name(&g->pkg[pi], name, 1)
: strchr(name, '.') == NULL : strchr(name, '.') == NULL
&& sep_external_production_name(&g->pkg[pi], name, 0); && sep_external_production_name(&g->pkg[pi], name);
if (literal_self) { if (literal_self) {
if (g->pkg[pi].import_base != NULL) if (g->pkg[pi].import_base != NULL)
resolved.identity = strdup(g->pkg[pi].import_base); resolved.identity = strdup(g->pkg[pi].import_base);
@@ -2471,18 +2565,17 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
break; break;
} }
if (!located) { if (!located) {
const char *dot = strrchr(name, '.');
const char *leaf = dot ? dot + 1 : name;
int inline_package = 0; int inline_package = 0;
if (!g->pkg[pi].is_dir) if (!g->pkg[pi].is_dir)
for (Node *package = imports->body; package; for (Node *package = imports->body; package;
package = package->next) package = package->next)
if (strcmp(package->module, leaf) == 0) { if (strcmp(package->module, name) == 0) {
inline_package = 1; inline_package = 1;
break; break;
} }
if (inline_package) { 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; rc = -1;
continue; continue;
} }
@@ -2506,10 +2599,7 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
break; break;
} }
int self = strcmp(canon, g->pkg[pi].canon) == 0; int self = strcmp(canon, g->pkg[pi].canon) == 0;
if (self if (self && g->pkg[pi].variant == SEP_VARIANT_EXTERNAL)
&& (sep_external_production_name(&g->pkg[pi], name, 1)
|| (g->pkg[pi].variant == SEP_VARIANT_EXTERNAL
&& sep_command_declared_name(&g->pkg[pi]))))
external_production = 1; external_production = 1;
if (self && !external_production) { if (self && !external_production) {
const char *owner = g->pkg[pi].path[0] 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, int child_context = sep_child_context_for(g, context,
resolved.entry, resolved.source_root); resolved.entry, resolved.source_root);
if (child_context < 0 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_add_dep(g, pi, di) < 0
|| sep_children_add(children, di, child_context) < 0) { || sep_children_add(children, di, child_context) < 0) {
free(canon); free(canon);
@@ -2688,6 +2778,7 @@ sep_add_generated_main(struct sepgraph *g, struct sepproduct *product,
p->root = 1; p->root = 1;
p->link_entry = 1; p->link_entry = 1;
p->generated_main = 1; p->generated_main = 1;
p->generated_target = variant;
p->loaded = 1; p->loaded = 1;
p->emit_context = product->context; p->emit_context = product->context;
if (sep_set_context_state(p, product->context, 2) < 0 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++) for (int i = 0; i < g->pkg[pi].nsources && rc == 0; i++)
rc = sep_scan_file(g, pi, g->pkg[pi].sources[i], rc = sep_scan_file(g, pi, g->pkg[pi].sources[i],
context, &fv, &bindings, children, 1); 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) { } else if (rc == 0) {
rc = sep_scan_file(g, pi, g->pkg[pi].entry, context, rc = sep_scan_file(g, pi, g->pkg[pi].entry, context,
&fv, &bindings, children, 0); &fv, &bindings, children, 0);
@@ -2774,6 +2853,7 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context,
if (bindings.n > 1) if (bindings.n > 1)
qsort(bindings.v, (size_t)bindings.n, qsort(bindings.v, (size_t)bindings.n,
sizeof *bindings.v, sep_binding_cmp); 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) { if (rc == 0 && g->pkg[pi].emit_context < 0) {
g->pkg[pi].bindings = bindings; g->pkg[pi].bindings = bindings;
bindings.v = NULL; bindings.v = NULL;
@@ -2781,12 +2861,7 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context,
g->pkg[pi].emit_context = context; g->pkg[pi].emit_context = context;
} else if (rc == 0) { } else if (rc == 0) {
struct sepbindset *want = &g->pkg[pi].bindings; struct sepbindset *want = &g->pkg[pi].bindings;
if (want->n != bindings.n) rc = -1; if (!sep_bindsets_semantically_same(want, &bindings)) 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 (rc < 0) { if (rc < 0) {
const char *first = const char *first =
g->context[g->pkg[pi].emit_context].root; 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) { if (f->pending_dep >= 0) {
int dep = f->pending_dep; int dep = f->pending_dep;
f->pending_dep = -1; 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 if (dep != f->pkg
&& sep_forbidden_command_import(g, f->pkg, dep)) { && sep_forbidden_command_import(g, f->pkg, dep)) {
fprintf(stderr, fprintf(stderr,
@@ -3055,54 +3138,27 @@ sep_reverse_import_base(const struct sepgraph *g, const struct seppkg *pkg,
return 0; 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 /* 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 * static char *
sep_local_import_base(const struct seppkg *p) 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 prefix = strlen(SEP_LOCAL_IMPORT_PREFIX);
size_t canon_len = strlen(p->canon), leaf_len = strlen(leaf); size_t canon_len = strlen(p->canon);
if (leaf_len > (size_t)-1 - prefix - 4 if (canon_len > ((size_t)-1 - prefix - 3) / 4) {
|| canon_len > ((size_t)-1 - prefix - 4 - leaf_len) / 4) {
sep_fail_size(); sep_fail_size();
free(leaf);
return NULL; 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); char *out = malloc(outsz);
if (out == NULL) { if (out == NULL) {
sep_fail_nomem(); sep_fail_nomem();
free(leaf);
return NULL; return NULL;
} }
size_t off = 0; size_t off = 0;
int n = snprintf(out, outsz, "%s.p", SEP_LOCAL_IMPORT_PREFIX); 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; off = (size_t)n;
static const char hex[] = "0123456789abcdef"; static const char hex[] = "0123456789abcdef";
for (const unsigned char *s = (const unsigned char *)p->canon; 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; unsigned char c = *s;
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')) { || (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; out[off++] = (char)c;
} else if (c == '_' || 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++] = '_';
out[off++] = c == '_' ? 'u' : 's'; out[off++] = c == '_' ? 'u' : 's';
} else { } else {
if (off + 4 >= outsz) { free(leaf); free(out); return NULL; } if (off + 4 >= outsz) { free(out); return NULL; }
out[off++] = '_'; out[off++] = '_';
out[off++] = 'x'; out[off++] = 'x';
out[off++] = hex[c >> 4]; out[off++] = hex[c >> 4];
out[off++] = hex[c & 15]; out[off++] = hex[c & 15];
} }
} }
size_t ln = leaf_len; out[off] = '\0';
if (off + 1 + ln + 1 > outsz) { free(leaf); free(out); return NULL; }
out[off++] = '.';
memcpy(out + off, leaf, ln + 1);
free(leaf);
return out; return out;
} }
@@ -3193,17 +3245,6 @@ sep_finalize_directory_identities(struct sepgraph *g)
struct seppkg *p = &g->pkg[pi]; struct seppkg *p = &g->pkg[pi];
if (!p->is_dir || p->generated_main || p->failed || !p->loaded) if (!p->is_dir || p->generated_main || p->failed || !p->loaded)
continue; 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); free(p->artifact);
p->artifact = NULL; p->artifact = NULL;
if (p->variant == SEP_VARIANT_SAME_TEST) 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. */ * retarget cannot reuse stale assembly even when export bytes are equal. */
for (int i = 0; i < g->pkg[pi].bindings.n && bodyrc == 0; i++) { for (int i = 0; i < g->pkg[pi].bindings.n && bodyrc == 0; i++) {
struct sepbind *b = &g->pkg[pi].bindings.v[i]; struct sepbind *b = &g->pkg[pi].bindings.v[i];
if (b->kind != 'D' || b->dep < 0 || b->dep >= g->n if (!sep_binding_first_map(g, &g->pkg[pi].bindings, i)) continue;
|| strcmp(b->name, g->pkg[b->dep].path) == 0)
continue;
if (fprintf(u, "//ww:import-map %s %s ", if (fprintf(u, "//ww:import-map %s %s ",
b->name, g->pkg[b->dep].path) < 0 b->name, g->pkg[b->dep].path) < 0
|| sep_emit_hex(u, g->pkg[b->dep].canon) < 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) 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", 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 /* A stale global builder identity invalidates every committed unit voucher in
@@ -3658,6 +3697,20 @@ invalidate_workdir_units(const char *scratch)
return rc; 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 { struct sep_created_dirs {
char path[PATH_MAX]; char path[PATH_MAX];
unsigned short offset[(PATH_MAX + 1) / 2]; 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 *cs = warm ? asmnew : asmf;
const char *co = warm ? objnew : obj; const char *co = warm ? objnew : obj;
const char *ca = warm ? anew : apath; 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) { if (sep_compose_unit(g, pi, cu) < 0) {
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
g->pkg[pi].failed = 1; g->pkg[pi].failed = 1;
any_failed = 1; any_failed = 1;
continue; continue;
@@ -4225,26 +4286,30 @@ build_one_sep_impl(const char *src, int entry_is_dir,
} }
int nmaps = 0; int nmaps = 0;
for (int k = 0; k < g->pkg[pi].bindings.n; k++) { for (int k = 0; k < g->pkg[pi].bindings.n; k++) {
struct sepbind *b = &g->pkg[pi].bindings.v[k]; if (!sep_binding_first_map(g, &g->pkg[pi].bindings, k))
if (b->kind == 'D' && b->dep >= 0 && b->dep < g->n continue;
&& strcmp(b->name, g->pkg[b->dep].path) != 0) { if (nmaps == INT_MAX) {
if (nmaps == INT_MAX) { sep_fail_size();
sep_fail_size(); (void)sep_discard_action_staging(warm, unitnew, wwinew,
free(order); asmnew, objnew, anew);
return 1; free(order);
} return 1;
nmaps++;
} }
nmaps++;
} }
size_t cargvcap = 12; size_t cargvcap = 14;
if ((size_t)g->pkg[pi].ndeps > ((size_t)-1 - cargvcap) / 3) { if ((size_t)g->pkg[pi].ndeps > ((size_t)-1 - cargvcap) / 3) {
fprintf(stderr, "ww: package graph is too large\n"); fprintf(stderr, "ww: package graph is too large\n");
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
free(order); free(order);
return 1; return 1;
} }
cargvcap += 3 * (size_t)g->pkg[pi].ndeps; cargvcap += 3 * (size_t)g->pkg[pi].ndeps;
if ((size_t)nmaps > ((size_t)-1 - cargvcap) / 3) { if ((size_t)nmaps > ((size_t)-1 - cargvcap) / 3) {
fprintf(stderr, "ww: package graph is too large\n"); fprintf(stderr, "ww: package graph is too large\n");
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
free(order); free(order);
return 1; 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 if (cargv == NULL || (g->pkg[pi].ndeps > 0
&& importfiles == NULL)) { && importfiles == NULL)) {
fprintf(stderr, "ww: out of memory\n"); fprintf(stderr, "ww: out of memory\n");
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
free(importfiles); free(importfiles);
free(cargv); free(cargv);
free(order); free(order);
@@ -4270,6 +4337,10 @@ build_one_sep_impl(const char *src, int entry_is_dir,
cargv[cpos++] = "--entry"; cargv[cpos++] = "--entry";
cargv[cpos++] = "--test-support-module"; cargv[cpos++] = "--test-support-module";
cargv[cpos++] = (char *)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 { } else {
if (g->pkg[pi].variant == SEP_VARIANT_SAME_TEST if (g->pkg[pi].variant == SEP_VARIANT_SAME_TEST
|| g->pkg[pi].variant == SEP_VARIANT_EXTERNAL) || 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++) { for (int k = 0; k < g->pkg[pi].bindings.n; k++) {
struct sepbind *b = &g->pkg[pi].bindings.v[k]; struct sepbind *b = &g->pkg[pi].bindings.v[k];
if (b->kind != 'D' || b->dep < 0 || b->dep >= g->n if (!sep_binding_first_map(g, &g->pkg[pi].bindings, k))
|| strcmp(b->name, g->pkg[b->dep].path) == 0)
continue; continue;
cargv[cpos++] = "--import-map"; cargv[cpos++] = "--import-map";
cargv[cpos++] = b->name; 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].path[0] ? g->pkg[pi].path : "(root)");
g->pkg[pi].failed = 1; g->pkg[pi].failed = 1;
any_failed = 1; any_failed = 1;
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
continue; continue;
} }
g->pkg[pi].export_changed = !warm 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].path[0] ? g->pkg[pi].path : "(root)");
g->pkg[pi].failed = 1; g->pkg[pi].failed = 1;
any_failed = 1; any_failed = 1;
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
continue; 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].path[0] ? g->pkg[pi].path : "(root)");
g->pkg[pi].failed = 1; g->pkg[pi].failed = 1;
any_failed = 1; any_failed = 1;
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
continue; 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].path[0] ? g->pkg[pi].path : "(root)");
g->pkg[pi].failed = 1; g->pkg[pi].failed = 1;
any_failed = 1; any_failed = 1;
(void)sep_discard_action_staging(warm, unitnew, wwinew,
asmnew, objnew, anew);
continue; continue;
} }
} }

View File

@@ -37,7 +37,6 @@ import crypto.math;
import endian; import endian;
import hash; import hash;
import io; import io;
import os;
// ref/hare/crypto/sha256/sha256.ha:11. // ref/hare/crypto/sha256/sha256.ha:11.
export def SZ: size = 32; export def SZ: size = 32;

View File

@@ -21,7 +21,6 @@
package hash; package hash;
import io; import io;
import os;
// ref/hare/hash/hash.ha:8-25. `vt` (inline io.vtable) replaces Hare's // ref/hare/hash/hash.ha:8-25. `vt` (inline io.vtable) replaces Hare's
// `stream: io::stream` per the header divergence — it MUST stay the // `stream: io::stream` per the header divergence — it MUST stay the

View File

@@ -31,6 +31,8 @@
package io; package io;
import errors;
// Q2 layering (ratified): the file-arm of the handle dispatchers routes // Q2 layering (ratified): the file-arm of the handle dispatchers routes
// to os.{read,write,close,lseek}; ww `os` plays Hare's `sys` role, so // to os.{read,write,close,lseek}; ww `os` plays Hare's `sys` role, so
// `lib/io import os` is the correct direction (os is the import floor). // `lib/io import os` is the correct direction (os is the import floor).

View File

@@ -4,12 +4,9 @@
package os; package os;
import time; import time;
// [[args]] allocates the []str view via the `alloc` builtin, whose malloc // [[args]] allocates the []str view via the `alloc` builtin. Package-mode
// lowers to rt_malloc only when the rt binding is in the bundle (mirror // lowering targets the runtime ABI directly; no source import is needed merely
// lib/strings/strings.ww:30 — every alloc-using module imports rt). os is // to trigger a linker choice, so the dependency graph remains source-semantic.
// bundled by ~every program, so without this a plain `ww build` of any
// os-importing program links bare libc `malloc` (undefined). Task #17.
import rt;
@symbol("rt_syscall") fn syscall0(num: nr) i64; @symbol("rt_syscall") fn syscall0(num: nr) i64;
@symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64; @symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64;

View File

@@ -20,7 +20,6 @@
package os_test; package os_test;
import os; import os;
import rt;
import test; import test;

View File

@@ -127,8 +127,11 @@ export type node = struct {
type_: *void, // filled in by checker; type.ww treats it as *tinfo type_: *void, // filled in by checker; type.ww treats it as *tinfo
tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...) tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...)
nmod: str, // originating module from `// MODULE: foo`; "" if none nmod: str, // originating module from `// MODULE: foo`; "" if none
usepath: str, // on an N_USE: full dotted IMPORT path vs leaf alias usepath: str, // on N_USE: full canonical import path; `str` becomes
// in `str` (M1 #22); "" otherwise // the file-local declared default qualifier; "" otherwise
pkgname: str, // declared package name; independent of canonical nmod
sourceid: i32, // lexical source-file scope in an owner/export unit
used: i32, // N_USE: checker observed this file-local binding
imported: i32, // M1 #22: decl reached via `//ww:module <path>` import imported: i32, // M1 #22: decl reached via `//ww:module <path>` import
// boundary (vs root/primary); gates root-only bare main // boundary (vs root/primary); gates root-only bare main
}; };
@@ -137,7 +140,7 @@ export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
// fval cast-init: 990's wwdump TK_FLOAT diff requires this file // fval cast-init: 990's wwdump TK_FLOAT diff requires this file
// to tokenise identically through C and ww (lex.ww:382 has the // to tokenise identically through C and ww (lex.ww:382 has the
// same workaround for the cstage %g-formats vs ww-skips divergence). // same workaround for the cstage %g-formats vs ww-skips divergence).
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, packed=0, type_=nil, tsuffix="", nmod="", usepath="", imported=0})!; let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, packed=0, type_=nil, tsuffix="", nmod="", usepath="", pkgname="", sourceid=0, used=0, imported=0})!;
return n; return n;
}; };

View File

@@ -2,7 +2,6 @@
package syntax; package syntax;
import os;
import strings; import strings;
// `import encoding.utf8;` — the driver resolves the dotted path to // `import encoding.utf8;` — the driver resolves the dotted path to
@@ -18,8 +17,8 @@ fn parseuse(p: *parser) *node = {
let n: *node = newnode(nkind.N_USE, pf, pl, pc); let n: *node = newnode(nkind.N_USE, pf, pl, pc);
n.nmod = p.curmod; n.nmod = p.curmod;
// M1 #22: accumulate the full dotted import path (n.usepath) for the // M1 #22: accumulate the full dotted import path (n.usepath) for the
// checker's path-keyed module match; n.str stays the leaf alias the // checker's path-keyed module match. n.str is the provisional path leaf;
// user writes (`utf8.x`). // direct export metadata later replaces it with the declared default name.
let leaf: str; let leaf: str;
expectident(p, &leaf); expectident(p, &leaf);
let path: str = leaf; let path: str = leaf;

View File

@@ -2,8 +2,6 @@
package syntax; package syntax;
import os;
// Inlined to avoid a cross-module `use sym;` for one call site. // Inlined to avoid a cross-module `use sym;` for one call site.
fn streqlocal(a: str, b: str) bool = { fn streqlocal(a: str, b: str) bool = {
if (a.len != b.len) { return false; }; if (a.len != b.len) { return false; };

View File

@@ -41,11 +41,14 @@ export type parser = struct {
// decls stamp nmod=pathmod and imported=1, and the in-file `package` // decls stamp nmod=pathmod and imported=1, and the in-file `package`
// clause is an assertion. "" means inactive (root/primary). // clause is an assertion. "" means inactive (root/primary).
pathmod: str, pathmod: str,
// #57: active `//ww:module-reset <path>` dotted path. Mangles decls on // #57: active `//ww:module-reset <path>` canonical identity. Decls
// the path WITHOUT imported=1 (primary-ness for -c and the #32 bare-main // mangle on it without imported=1; the package clause independently
// rule stay intact), and the in-file `package` clause asserts (leaf == // supplies the declared name. "" means inactive.
// last component) instead of overwriting curmod. "" means inactive.
resetmod: str, resetmod: str,
// Declared package name and file scope are deliberately orthogonal to
// curmod/pathmod. Every driver module boundary advances sourceid.
curpkg: str,
sourceid: i32,
// Hidden package-driver alias for the toolchain `package test` source. // Hidden package-driver alias for the toolchain `package test` source.
// Empty outside that one separate-compilation edge. // Empty outside that one separate-compilation edge.
testmodule: str, testmodule: str,
@@ -73,6 +76,8 @@ export fn parserinit(p: *parser, l: *lex) void = {
p.nocast = 0; p.nocast = 0;
p.pathmod = ""; p.pathmod = "";
p.resetmod = ""; p.resetmod = "";
p.curpkg = "";
p.sourceid = 0;
p.testmodule = ""; p.testmodule = "";
p.commandpackage = false; p.commandpackage = false;
refill(p); refill(p);
@@ -498,19 +503,23 @@ export fn parseimports(p: *parser) *node = {
// the following package clause and imports. // the following package clause and imports.
if (p.curkind == tkind.TK_MODPATH) { if (p.curkind == tkind.TK_MODPATH) {
sawpackage = false; sawpackage = false;
p.sourceid += 1;
p.pathmod = p.curtext; p.pathmod = p.curtext;
p.curmod = p.curtext; p.curmod = p.curtext;
p.resetmod = ""; p.resetmod = "";
p.curpkg = "";
advance(p); advance(p);
continue; continue;
}; };
if (p.curkind == tkind.TK_MODRESET) { if (p.curkind == tkind.TK_MODRESET) {
let rp: str = p.curtext; let rp: str = p.curtext;
sawpackage = false; sawpackage = false;
p.sourceid += 1;
advance(p); advance(p);
p.pathmod = ""; p.pathmod = "";
p.curmod = rp; p.curmod = rp;
p.resetmod = rp; p.resetmod = rp;
p.curpkg = "";
continue; continue;
}; };
if (p.curkind == tkind.TK_MODULE) { if (p.curkind == tkind.TK_MODULE) {
@@ -527,14 +536,21 @@ export fn parseimports(p: *parser) *node = {
let name: str; let name: str;
expectident(p, &name); expectident(p, &name);
expecttok(p, tkind.TK_SEMI, "expected ';' after module name"); expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
p.curmod = name; p.curpkg = name;
if (p.pathmod.len == 0 && p.resetmod.len == 0) {
p.curmod = name;
};
let pm = newnode(nkind.N_FILE, pf, pl, pc); let pm = newnode(nkind.N_FILE, pf, pl, pc);
pm.nmod = name; pm.nmod = p.curmod;
pm.pkgname = name;
pm.sourceid = p.sourceid;
if (packages == nil) { packages = pm; } if (packages == nil) { packages = pm; }
else { packagetail.next = pm; }; else { packagetail.next = pm; };
packagetail = pm; packagetail = pm;
if (!sawpackage) { if (!sawpackage) {
f.nmod = name; f.nmod = name;
f.pkgname = name;
f.sourceid = p.sourceid;
f.file = pf; f.file = pf;
f.line = pl; f.line = pl;
f.col = pc; f.col = pc;
@@ -549,6 +565,8 @@ export fn parseimports(p: *parser) *node = {
if (p.curkind == tkind.TK_USE) { if (p.curkind == tkind.TK_USE) {
let d: *node = parseuse(p); let d: *node = parseuse(p);
d.nmod = p.curmod; d.nmod = p.curmod;
d.pkgname = p.curpkg;
d.sourceid = p.sourceid;
if (head == nil) { head = d; } else { tail.next = d; }; if (head == nil) { head = d; } else { tail.next = d; };
tail = d; tail = d;
continue; continue;
@@ -560,6 +578,8 @@ export fn parseimports(p: *parser) *node = {
errmsg(p, "import cannot be exported or attributed"); errmsg(p, "import cannot be exported or attributed");
let d: *node = parseuse(p); let d: *node = parseuse(p);
d.nmod = p.curmod; d.nmod = p.curmod;
d.pkgname = p.curpkg;
d.sourceid = p.sourceid;
if (head == nil) { head = d; } else { tail.next = d; }; if (head == nil) { head = d; } else { tail.next = d; };
tail = d; tail = d;
continue; continue;
@@ -573,6 +593,8 @@ export fn parseimports(p: *parser) *node = {
errmsg(p, "import cannot be exported or attributed"); errmsg(p, "import cannot be exported or attributed");
let d: *node = parseuse(p); let d: *node = parseuse(p);
d.nmod = p.curmod; d.nmod = p.curmod;
d.pkgname = p.curpkg;
d.sourceid = p.sourceid;
if (head == nil) { head = d; } else { tail.next = d; }; if (head == nil) { head = d; } else { tail.next = d; };
tail = d; tail = d;
continue; continue;
@@ -593,6 +615,8 @@ export fn parsefile(p: *parser) *node = {
let f = newnode(nkind.N_FILE, p.curfile, p.curline, p.curcol); let f = newnode(nkind.N_FILE, p.curfile, p.curline, p.curcol);
let head: *node = nil; let head: *node = nil;
let tail: *node = nil; let tail: *node = nil;
let packages: *node = nil;
let packagetail: *node = nil;
let sawpackage: i32 = 0; let sawpackage: i32 = 0;
for (p.curkind != tkind.TK_EOF) { for (p.curkind != tkind.TK_EOF) {
// `package foo;` — directory-as-module declaration. Every // `package foo;` — directory-as-module declaration. Every
@@ -602,57 +626,44 @@ export fn parsefile(p: *parser) *node = {
// sep primary-reset (resetmod) regions carry identity // sep primary-reset (resetmod) regions carry identity
// out-of-band and are exempt. // out-of-band and are exempt.
if (p.curkind == tkind.TK_MODULE) { if (p.curkind == tkind.TK_MODULE) {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
sawpackage = 1; sawpackage = 1;
advance(p); advance(p);
let name: str; let name: str;
expectident(p, &name); expectident(p, &name);
expecttok(p, tkind.TK_SEMI, "expected ';' after module name"); expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
if (p.pathmod.len != 0 || p.resetmod.len != 0) { p.curpkg = name;
// M1 #22: while an import path is active the in-file if (p.pathmod.len == 0 && p.resetmod.len == 0) {
// `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 authoritative, the clause asserts.
let active: str = p.pathmod;
if (p.pathmod.len == 0) { active = p.resetmod; };
let (pre, post) = strings.rcut(active, ".");
let last: str = post;
if (post.len == 0) { last = active; };
let testsupport: bool = strings.compare(
p.testmodule, "__wwtest") == 0
&& strings.compare(active, "__wwtest") == 0
&& strings.compare(name, "test") == 0;
let commandpackage: bool = p.commandpackage
&& p.pathmod.len == 0 && p.resetmod.len != 0
&& (strings.compare(name, "main") == 0
|| strings.compare(name, "main_test") == 0);
if (strings.compare(name, last) != 0 && !testsupport
&& !commandpackage) {
errmsg(p, strings.concat(strings.concat(strings.concat(
"package ", name),
" does not match import path "), active));
};
} else {
p.curmod = name; p.curmod = name;
// #11: stamp the primary module identity on the };
// N_FILE node so wwi_emit can derive the `package` let pm: *node = newnode(nkind.N_FILE, pf, pl, pc);
// leaf even when the body carries zero module-tagged pm.nmod = p.curmod;
// decls. Primary identity only; never the imported pm.pkgname = name;
// boundary (TK_MODPATH). pm.sourceid = p.sourceid;
if (f.nmod.len == 0) { f.nmod = name; }; if (p.pathmod.len != 0) { pm.imported = 1; };
if (packages == nil) { packages = pm; }
else { packagetail.next = pm; };
packagetail = pm;
if (f.pkgname.len == 0) {
f.pkgname = name;
f.sourceid = p.sourceid;
}; };
continue; continue;
}; };
// `//ww:module <path>` — M1 #22 import boundary. The following // `//ww:module <path>` — M1 #22 import boundary. The following
// file's decls mangle on the full dotted import path, not the // file's decls mangle on the full dotted import path independently
// leaf `package` clause, and are flagged imported (gates the // of its `package` clause, and are flagged imported (gates the
// root-only bare-`main` rule, #32). // root-only bare-`main` rule, #32).
if (p.curkind == tkind.TK_MODPATH) { if (p.curkind == tkind.TK_MODPATH) {
sawpackage = 0; sawpackage = 0;
p.sourceid += 1;
p.pathmod = p.curtext; p.pathmod = p.curtext;
p.curmod = p.curtext; p.curmod = p.curtext;
p.resetmod = ""; p.resetmod = "";
p.curpkg = "";
if (f.nmod.len == 0) { f.nmod = p.curtext; };
advance(p); advance(p);
continue; continue;
}; };
@@ -666,6 +677,7 @@ export fn parsefile(p: *parser) *node = {
// decls to bare — that usage is deliberate-only. // decls to bare — that usage is deliberate-only.
if (p.curkind == tkind.TK_MODRESET) { if (p.curkind == tkind.TK_MODRESET) {
sawpackage = 0; sawpackage = 0;
p.sourceid += 1;
// #57: a path-carrying reset (sep primary body) mangles decls // #57: a path-carrying reset (sep primary body) mangles decls
// on the dotted path so definer == importer, but leaves // on the dotted path so definer == importer, but leaves
// imported==0 (curmod set, pathmod "") so -c primary-ness and // imported==0 (curmod set, pathmod "") so -c primary-ness and
@@ -675,6 +687,7 @@ export fn parsefile(p: *parser) *node = {
let rp: str = p.curtext; let rp: str = p.curtext;
advance(p); advance(p);
p.pathmod = ""; p.pathmod = "";
p.curpkg = "";
if (rp.len != 0) { if (rp.len != 0) {
p.curmod = rp; p.curmod = rp;
p.resetmod = rp; p.resetmod = rp;
@@ -756,6 +769,9 @@ export fn parsefile(p: *parser) *node = {
// M1 #22: flag decls reached via an import-path boundary // M1 #22: flag decls reached via an import-path boundary
// (gates the root-only bare-`main` rule, #32). // (gates the root-only bare-`main` rule, #32).
if (p.pathmod.len != 0) { d.imported = 1; }; if (p.pathmod.len != 0) { d.imported = 1; };
d.nmod = p.curmod;
d.pkgname = p.curpkg;
d.sourceid = p.sourceid;
if (head == nil) { if (head == nil) {
head = d; head = d;
tail = d; tail = d;
@@ -766,5 +782,6 @@ export fn parsefile(p: *parser) *node = {
}; };
}; };
f.list = head; f.list = head;
f.body = packages;
return f; return f;
}; };

View File

@@ -2,8 +2,6 @@
package syntax; package syntax;
import os;
fn parseletlocal(p: *parser) *node = { fn parseletlocal(p: *parser) *node = {
let pf = p.curfile; let pf = p.curfile;
let pl = p.curline; let pl = p.curline;

View File

@@ -115,8 +115,8 @@ export type tkind = enum i32 {
// reset curmod to "" before a package-less file // reset curmod to "" before a package-less file
// (#16 option-B; cstage TK_MODRESET twin) // (#16 option-B; cstage TK_MODRESET twin)
TK_MODPATH = 88, // `//ww:module <dotted-path>` driver import TK_MODPATH = 88, // `//ww:module <dotted-path>` driver import
// boundary; decls mangle on the path, not the // boundary; decls mangle on the path independently
// leaf `package` clause (M1 #22; cstage twin) // of the `package` clause (M1 #22; cstage twin)
TK_LAST = 89, TK_LAST = 89,
}; };

View File

@@ -8,8 +8,6 @@
package syntax; package syntax;
import os;
// Mirror of the C `TypeKind` enum in cmd/wcc/ww.h. Numeric values // Mirror of the C `TypeKind` enum in cmd/wcc/ww.h. Numeric values
// are explicit and must stay in sync — the selfhost selfcheck and // are explicit and must stay in sync — the selfhost selfcheck and
// typed-AST printers depend on matching numeric layout. // typed-AST printers depend on matching numeric layout.

View File

@@ -4,7 +4,6 @@
package main; package main;
import os; import os;
import rt;
import strings; import strings;
export fn emitbyte(a: *asm_, b: u8) void = { export fn emitbyte(a: *asm_, b: u8) void = {

View File

@@ -5,7 +5,6 @@
package main; package main;
import os; import os;
import rt;
import strings; import strings;
fn cstreq(a: *u8, lit: str) bool = { fn cstreq(a: *u8, lit: str) bool = {

View File

@@ -3,7 +3,6 @@
package main; package main;
import os; import os;
import rt;
import strings; import strings;
import syntax; import syntax;
import wcc; import wcc;
@@ -95,13 +94,93 @@ fn allocimportptrs(count: i32) ([]*u8 | nomem) = {
return value; return value;
}; };
fn importleaf(path: *u8) str = { fn allocnodeptrs(count: i32) ([]*syntax.node | nomem) = {
let whole: str = pathstr(path); let value: []*syntax.node = alloc([], count: u64)?;
let (prefix, suffix) = strings.rcut(whole, "."); return value;
// rcut returns (whole, empty) when absent and (prefix, empty) for a };
// trailing delimiter. Preserve that distinction to match strrchr.
if (suffix.len != 0 || prefix.len != whole.len) { return suffix; }; // Export data carries canonical owner and declared name independently. Each
return whole; // direct interface's package-clause markers also describe its reachable fact
// closure, so search all parsed interfaces for a canonical owner.
fn importpkgname(asts: []*syntax.node, nasts: i32, path: str,
primary: *syntax.node, conflict: *bool) str = {
let name: str;
let i: i32 = 0;
for (i < nasts) {
let f: *syntax.node = asts[i];
let p: *syntax.node = nil;
if (f != nil) { p = f.body; };
for (p != nil) {
if (p.nmod.len > 0 && p.pkgname.len > 0
&& syntax.streq(p.nmod, path)) {
if (name.len > 0 && !syntax.streq(name, p.pkgname)) {
*conflict = true;
let empty: str;
return empty;
};
name = p.pkgname;
};
p = p.next;
};
i += 1;
};
let p: *syntax.node = nil;
if (primary != nil) { p = primary.body; };
for (p != nil) {
if (p.nmod.len > 0 && p.pkgname.len > 0
&& syntax.streq(p.nmod, path)) {
if (name.len > 0 && !syntax.streq(name, p.pkgname)) {
*conflict = true;
let empty: str;
return empty;
};
name = p.pkgname;
};
p = p.next;
};
return name;
};
fn bindimportnames(list: *syntax.node, asts: []*syntax.node, nasts: i32,
primary: *syntax.node, testsupport: *u8) bool = {
let u: *syntax.node = list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.usepath.len > 0) {
let reserved: bool = testsupport != nil
&& cstreq(testsupport, "__wwtest")
&& syntax.streq(u.usepath, "__wwtest");
if (!reserved) {
let conflict: bool = false;
let name: str = importpkgname(asts, nasts, u.usepath,
primary, &conflict);
if (conflict) {
let pre: str = "w6c: package ";
let post: str = " has conflicting declared names in export data\n";
os.write(2, pre.ptr, pre.len: u64);
os.write(2, u.usepath.ptr, u.usepath.len: u64);
os.write(2, post.ptr, post.len: u64);
return false;
};
if (name.len > 0) {
u.str = name;
} else { if (u.imported == 0) {
let pre: str = "w6c: import ";
let post: str = " has no declared package name in direct export data\n";
os.write(2, pre.ptr, pre.len: u64);
os.write(2, u.usepath.ptr, u.usepath.len: u64);
os.write(2, post.ptr, post.len: u64);
return false;
} else {
// A closure-only import may have no declarations in this
// interface. Its canonical path must never become a leaf
// qualifier by fallback.
u.str = u.usepath;
}; };
};
};
u = u.next;
};
return true;
}; };
export fn main(argc: i32, argv: **u8) i32 = { export fn main(argc: i32, argv: **u8) i32 = {
@@ -109,6 +188,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
let out: *u8 = nil; let out: *u8 = nil;
let wwiout: *u8 = nil; // -I <out.wwi>: M2 export-data producer let wwiout: *u8 = nil; // -I <out.wwi>: M2 export-data producer
let testsupport: *u8 = nil; let testsupport: *u8 = nil;
let testtarget: *u8 = nil;
let testmode: i32 = 0i32; // #15: `-T` test-mode let testmode: i32 = 0i32; // #15: `-T` test-mode
let testpackage: i32 = 0i32; let testpackage: i32 = 0i32;
let commandpackage: i32 = 0i32; let commandpackage: i32 = 0i32;
@@ -120,6 +200,8 @@ export fn main(argc: i32, argv: **u8) i32 = {
let fileallocation: ([]*u8 | nomem) = allocimportptrs(argc); let fileallocation: ([]*u8 | nomem) = allocimportptrs(argc);
let importpaths: []*u8; let importpaths: []*u8;
let importfiles: []*u8; let importfiles: []*u8;
let astallocation: ([]*syntax.node | nomem) = allocnodeptrs(argc);
let importasts: []*syntax.node;
match (pathallocation) { match (pathallocation) {
case let value: []*u8 => importpaths = value; case let value: []*u8 => importpaths = value;
case nomem => { case nomem => {
@@ -138,6 +220,15 @@ export fn main(argc: i32, argv: **u8) i32 = {
}; };
importpaths.len = argc; importpaths.len = argc;
importfiles.len = argc; importfiles.len = argc;
match (astallocation) {
case let value: []*syntax.node => importasts = value;
case nomem => {
let m: str = "w6c: out of memory\n";
os.write(2, m.ptr, m.len: u64);
return 1;
};
};
importasts.len = argc;
let nimports: i32 = 0; let nimports: i32 = 0;
let mapallocation: ([]importmap | nomem) = allocimportmaps(argc); let mapallocation: ([]importmap | nomem) = allocimportmaps(argc);
let importmaps: []importmap; let importmaps: []importmap;
@@ -187,6 +278,14 @@ export fn main(argc: i32, argv: **u8) i32 = {
return 2; return 2;
}; };
testsupport = argv[i]; testsupport = argv[i];
} else { if (cstreq(a, "--test-target-package")) {
i += 1;
if (i >= argc) {
let m: str = "w6c: --test-target-package requires arg\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
testtarget = argv[i];
} else { if (cstreq(a, "-c")) { } else { if (cstreq(a, "-c")) {
sepmode = 1i32; sepmode = 1i32;
} else { if (cstreq(a, "--import")) { } else { if (cstreq(a, "--import")) {
@@ -221,12 +320,12 @@ export fn main(argc: i32, argv: **u8) i32 = {
return 2; return 2;
}; };
src = a; src = a;
}; }; }; }; }; }; }; }; }; }; }; }; }; }; }; }; }; }; }; }; }; }; };
i += 1; i += 1;
}; };
if (src == nil) { if (src == nil) {
let m: str = "usage: w6c_ww [-T|--test-package] [--command-package] [--entry] [-c] [-I out.wwi] [--import path dep.wwi]... [--import-map source path]... [-o out.s] file.ww\n"; let m: str = "usage: w6c_ww [-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";
os.write(2, m.ptr, m.len: u64); os.write(2, m.ptr, m.len: u64);
return 2; return 2;
}; };
@@ -280,12 +379,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
os.write(2, m.ptr, m.len: u64); os.write(2, m.ptr, m.len: u64);
return 2; return 2;
}; };
if (!syntax.streq(importleaf(importmaps[mapi].source),
importleaf(importmaps[mapi].path))) {
let m: str = "w6c: --import-map must preserve import leaf\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
let direct: bool = false; let direct: bool = false;
let directi: i32 = 0; let directi: i32 = 0;
for (directi < nimports) { for (directi < nimports) {
@@ -310,6 +403,27 @@ export fn main(argc: i32, argv: **u8) i32 = {
os.write(2, m.ptr, m.len: u64); os.write(2, m.ptr, m.len: u64);
return 2; return 2;
}; };
if (testtarget != nil && (sepmode == 0 || testmode == 0
|| testtarget[0u64] == 0u8)) {
let m: str = "w6c: invalid --test-target-package\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
if (testtarget != nil) {
let direct: bool = false;
importi = 0;
for (importi < nimports) {
if (cstreq(importpaths[importi], pathstr(testtarget))) {
direct = true;
};
importi += 1;
};
if (!direct) {
let m: str = "w6c: --test-target-package is not a direct import\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
};
let importhead: *node = nil; let importhead: *node = nil;
let importtail: *node = nil; let importtail: *node = nil;
@@ -352,6 +466,9 @@ export fn main(argc: i32, argv: **u8) i32 = {
if (testsupport != nil) { ips.testmodule = pathstr(testsupport); }; if (testsupport != nil) { ips.testmodule = pathstr(testsupport); };
let imported: *node = parsefile(&ips); let imported: *node = parsefile(&ips);
if (il.errs > 0 || ips.errs > 0) { return 1; }; if (il.errs > 0 || ips.errs > 0) { return 1; };
importasts[importi] = imported;
if (!bindimportnames(imported.list, importasts, importi + 1,
nil, testsupport)) { return 1; };
let d: *node = imported.list; let d: *node = imported.list;
if (d != nil) { if (d != nil) {
if (importhead == nil) { importhead = d; } if (importhead == nil) { importhead = d; }
@@ -408,6 +525,35 @@ export fn main(argc: i32, argv: **u8) i32 = {
}; };
mapi += 1; mapi += 1;
}; };
// A later direct interface may supply metadata for an origin section used
// by an earlier interface, so bind once more against the complete set.
importi = 0;
for (importi < nimports) {
if (!bindimportnames(importasts[importi].list, importasts, nimports,
nil, testsupport)) { return 1; };
importi += 1;
};
if (!bindimportnames(f.list, importasts, nimports, f, testsupport)) {
return 1;
};
if (testtarget != nil) {
let seen: i32 = 0;
let targetuse: *syntax.node = f.list;
for (targetuse != nil) {
if (targetuse.kind == syntax.nkind.N_USE
&& targetuse.imported == 0
&& syntax.streq(targetuse.usepath, pathstr(testtarget))) {
targetuse.str = targetuse.usepath;
seen += 1;
};
targetuse = targetuse.next;
};
if (seen != 1) {
let m: str = "w6c: generated test target import is not unique\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
};
if (importhead != nil) { if (importhead != nil) {
importtail.next = f.list; importtail.next = f.list;
f.list = importhead; f.list = importhead;
@@ -433,8 +579,10 @@ export fn main(argc: i32, argv: **u8) i32 = {
let testmodule: str; let testmodule: str;
if (testsupport != nil) { testmodule = pathstr(testsupport); }; if (testsupport != nil) { testmodule = pathstr(testsupport); };
let testtargetmodule: str;
if (testtarget != nil) { testtargetmodule = pathstr(testtarget); };
let interfaceout: str; let interfaceout: str;
if (wwiout != nil) { interfaceout = pathstr(wwiout); }; if (wwiout != nil) { interfaceout = pathstr(wwiout); };
return wcc.compilefile(f, testmode, testpackage, testmodule, sepmode, return wcc.compilefile(f, testmode, testpackage, testmodule,
interfaceout, entrymode); testtargetmodule, sepmode, interfaceout, entrymode);
}; };

View File

@@ -7,7 +7,6 @@
package main; package main;
import os; import os;
import rt;
import strings; import strings;
def ET_DYN_SO: u16 = 3u16; def ET_DYN_SO: u16 = 3u16;

View File

@@ -25,7 +25,6 @@
package main; package main;
import os; import os;
import rt;
import strings; import strings;
def ET_EXEC_D: u16 = 2u16; def ET_EXEC_D: u16 = 2u16;

View File

@@ -5,7 +5,6 @@
package main; package main;
import os; import os;
import rt;
import strings; import strings;
def BASE: u64 = 4194304u64; // 0x400000 def BASE: u64 = 4194304u64; // 0x400000

View File

@@ -3,7 +3,6 @@
package main; package main;
import os; import os;
import rt;
import strings; import strings;
def ET_REL: i32 = 1; def ET_REL: i32 = 1;

View File

@@ -3,7 +3,6 @@
package main; package main;
import os; import os;
import rt;
def ET_EXEC: u16 = 2u16; def ET_EXEC: u16 = 2u16;
def EM_X86_64_W: u16 = 62u16; def EM_X86_64_W: u16 = 62u16;

View File

@@ -3,7 +3,8 @@ package wcc;
import syntax; import syntax;
export fn compilefile(file: *syntax.node, testmode: i32, testpackage: i32, export fn compilefile(file: *syntax.node, testmode: i32, testpackage: i32,
testmodule: str, sepmode: i32, wwiout: str, entrymode: i32) i32 = { testmodule: str, testtarget: str, sepmode: i32, wwiout: str,
entrymode: i32) i32 = {
let tc: syntax.tctx; let tc: syntax.tctx;
syntax.typesinit(&tc); syntax.typesinit(&tc);
let ck: checker; let ck: checker;
@@ -11,6 +12,7 @@ export fn compilefile(file: *syntax.node, testmode: i32, testpackage: i32,
ck.istest = testmode; ck.istest = testmode;
ck.istestpackage = testpackage; ck.istestpackage = testpackage;
if (testmodule.len > 0) { ck.testmodule = testmodule; }; if (testmodule.len > 0) { ck.testmodule = testmodule; };
ck.testtarget = testtarget;
ck.sepmode = sepmode; ck.sepmode = sepmode;
checkfile(&ck, file); checkfile(&ck, file);
if (ck.errs > 0) { return 1; }; if (ck.errs > 0) { return 1; };

View File

@@ -455,7 +455,9 @@ type cgen = struct {
// bare-IDENT call mangling — `frob()` from // bare-IDENT call mangling — `frob()` from
// inside lib/foo binds to `foo.frob` even when // inside lib/foo binds to `foo.frob` even when
// other modules also export `frob`. Set in cgfn // other modules also export `frob`. Set in cgfn
// before walking the body. // before walking the body.
cursource: i32, // lexical source-file scope of the current decl;
// selects its own import bindings.
fnret: *syntax.node, // declared return type of current fn (or nil) fnret: *syntax.node, // declared return type of current fn (or nil)
looptop: i32, looptop: i32,
loopendbuf: []str, // stack of end labels for break loopendbuf: []str, // stack of end labels for break
@@ -1420,9 +1422,11 @@ fn letpreintern(c: *cgen, file: *syntax.node) void = {
// fn-ptr relocs — see the same value they did before; letpreintern // fn-ptr relocs — see the same value they did before; letpreintern
// itself only interns, so driving curmod here has no other effect. // itself only interns, so driving curmod here has no other effect.
let savedmod: str = c.curmod; let savedmod: str = c.curmod;
let savedsource: i32 = c.cursource;
let d: *syntax.node = file.list; let d: *syntax.node = file.list;
for (d != nil) { for (d != nil) {
c.curmod = d.nmod; c.curmod = d.nmod;
c.cursource = d.sourceid;
// #22 M3: skip imported deps so the strlit table (and its _S_ // #22 M3: skip imported deps so the strlit table (and its _S_
// sequence) is a pure function of THIS package's own decls. A // sequence) is a pure function of THIS package's own decls. A
// dep's body initializer would intern here, but its `.wwi` (init // dep's body initializer would intern here, but its `.wwi` (init
@@ -1596,6 +1600,7 @@ fn letpreintern(c: *cgen, file: *syntax.node) void = {
d = d.next; d = d.next;
}; };
c.curmod = savedmod; c.curmod = savedmod;
c.cursource = savedsource;
}; };
// emitletdataw — DATAW directive per top-level `let` global. // emitletdataw — DATAW directive per top-level `let` global.
@@ -2890,8 +2895,12 @@ fn emittupledata(c: *cgen, name: str, module: str, tt: *syntax.node, rhs: *synta
}; };
fn emitletdataw(c: *cgen, file: *syntax.node) void = { fn emitletdataw(c: *cgen, file: *syntax.node) void = {
let savedmod: str = c.curmod;
let savedsource: i32 = c.cursource;
let d: *syntax.node = file.list; let d: *syntax.node = file.list;
for (d != nil) { for (d != nil) {
c.curmod = d.nmod;
c.cursource = d.sourceid;
// #22 M3 THE ONE REAL GUARD: a `.wwi` dep value-global is // #22 M3 THE ONE REAL GUARD: a `.wwi` dep value-global is
// initializer-less; emitting a DATAW for it would DUPLICATE the // initializer-less; emitting a DATAW for it would DUPLICATE the
// definition that lives in the dep's own .o → link collision. // definition that lives in the dep's own .o → link collision.
@@ -3226,6 +3235,8 @@ fn emitletdataw(c: *cgen, file: *syntax.node) void = {
}; };
d = d.next; d = d.next;
}; };
c.curmod = savedmod;
c.cursource = savedsource;
}; };
// emitdefconstants — DATA directive per top-level fold-to-literal // emitdefconstants — DATA directive per top-level fold-to-literal
@@ -3235,8 +3246,12 @@ fn emitletdataw(c: *cgen, file: *syntax.node) void = {
// N_UN(TK_MINUS, N_INTLIT) — the unary peel is exactly what the // N_UN(TK_MINUS, N_INTLIT) — the unary peel is exactly what the
// gate is for. // gate is for.
fn emitdefconstants(c: *cgen, file: *syntax.node) void = { fn emitdefconstants(c: *cgen, file: *syntax.node) void = {
let savedmod: str = c.curmod;
let savedsource: i32 = c.cursource;
let d: *syntax.node = file.list; let d: *syntax.node = file.list;
for (d != nil) { for (d != nil) {
c.curmod = d.nmod;
c.cursource = d.sourceid;
// #22 M3: a `.wwi` dep def with DATA storage (int-fold / float / // #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 // struct / array) must NOT re-emit — the dep's own .o owns the
// symbol. Str defs are inline-spliced (never emitted here), so // symbol. Str defs are inline-spliced (never emitted here), so
@@ -3369,6 +3384,8 @@ fn emitdefconstants(c: *cgen, file: *syntax.node) void = {
}; };
d = d.next; d = d.next;
}; };
c.curmod = savedmod;
c.cursource = savedsource;
}; };
// emitdatasection — DATA directives for every interned strlit. // emitdatasection — DATA directives for every interned strlit.
@@ -3760,6 +3777,7 @@ type modent = struct {
mname: str, // the bare ident as it appears in source mname: str, // the bare ident as it appears in source
nmod: str, // the originating module (`// MODULE: foo`) nmod: str, // the originating module (`// MODULE: foo`)
omod: str, // owning module of a `use` decl (#40); unused for mods omod: str, // owning module of a `use` decl (#40); unused for mods
sourceid: i32, // owning lexical source-file scope for N_USE entries
mnext: *modent, mnext: *modent,
}; };
@@ -3772,7 +3790,7 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
// M1 #22: record alias→path for the qualified-ref hint. // M1 #22: record alias→path for the qualified-ref hint.
if (d.kind == syntax.nkind.N_USE) { if (d.kind == syntax.nkind.N_USE) {
if (d.usepath.len > 0) { if (d.usepath.len > 0) {
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, omod=d.nmod, mnext=c.uses})!; let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, omod=d.nmod, sourceid=d.sourceid, mnext=c.uses})!;
c.uses = um; c.uses = um;
}; };
}; };
@@ -3811,7 +3829,7 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
// Explicit entry mode clears sepisdep independently of export // Explicit entry mode clears sepisdep independently of export
// production; every other package's main remains mangled. // production; every other package's main remains mangled.
if (!syntax.streq(d.str, "main") || d.imported != 0 || c.sepisdep != 0) { if (!syntax.streq(d.str, "main") || d.imported != 0 || c.sepisdep != 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!; let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, sourceid=d.sourceid, mnext=c.mods})!;
c.mods = m; c.mods = m;
}; };
}; };
@@ -3827,19 +3845,19 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
// retained until then (rule-11). // retained until then (rule-11).
if (d.kind == syntax.nkind.N_DEF) { if (d.kind == syntax.nkind.N_DEF) {
if (d.nmod.len > 0) { if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!; let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, sourceid=d.sourceid, mnext=c.mods})!;
c.mods = m; c.mods = m;
}; };
}; };
if (d.kind == syntax.nkind.N_TYPEDECL) { if (d.kind == syntax.nkind.N_TYPEDECL) {
if (d.nmod.len > 0) { if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!; let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, sourceid=d.sourceid, mnext=c.mods})!;
c.mods = m; c.mods = m;
}; };
}; };
if (d.kind == syntax.nkind.N_LET) { if (d.kind == syntax.nkind.N_LET) {
if (d.nmod.len > 0) { if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!; let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, sourceid=d.sourceid, mnext=c.mods})!;
c.mods = m; c.mods = m;
}; };
}; };
@@ -3859,18 +3877,10 @@ fn modlookup(c: *cgen, name: str) str = {
return empty; return empty;
}; };
// usehint — M1 #22: map a qualified-ref alias (`utf8`) to its dotted // usehint — map a declared default qualifier to its canonical import path so
// import path (`encoding.utf8`) so the codegen hint keys the path-keyed // codegen mangles on package identity. It is source-file local: separate files
// mods map. For single-level packages alias == path (no-op). Returns the // may bind the same declared name to different paths. Raw non-package
// alias unchanged when no matching `use` exists. // compilation retains its historical single-occurrence fallback.
//
// NOT file-global (#40): two modules in one unit may bind the same leaf
// alias to different paths (module one's `import a.math` vs module two's
// `import b.math`, both alias `math`). The `use` declared in the SAME
// module as the reference (c.curmod) is authoritative; preferring it
// routes each `math.pick()` to its own package. Falls back to any
// matching alias when c.curmod has no own import. Mirrors the checker's
// use_path curmod-preference (cstage check.c, M1 55f54fb).
fn usehint(c: *cgen, alias: str) str = { fn usehint(c: *cgen, alias: str) str = {
let m: *modent = c.uses; let m: *modent = c.uses;
let any: str; let any: str;
@@ -3878,8 +3888,12 @@ fn usehint(c: *cgen, alias: str) str = {
any.len = 0; any.len = 0;
for (m != nil) { for (m != nil) {
if (syntax.streq(m.mname, alias)) { if (syntax.streq(m.mname, alias)) {
if (syntax.streq(m.omod, c.curmod)) { return m.nmod; }; if (m.sourceid == c.cursource && syntax.streq(m.omod, c.curmod)) {
if (any.ptr == nil && any.len == 0) { any = m.nmod; }; return m.nmod;
};
if (c.sepmode == 0 && any.ptr == nil && any.len == 0) {
any = m.nmod;
};
}; };
m = m.mnext; m = m.mnext;
}; };

View File

@@ -2,7 +2,6 @@ package wcc;
import os; import os;
import syntax; import syntax;
import strconv;
fn cgfnparams(c: *cgen, params: *syntax.node) void = { fn cgfnparams(c: *cgen, params: *syntax.node) void = {
let p: *syntax.node = params; let p: *syntax.node = params;
@@ -518,6 +517,7 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
cgeninit(c); cgeninit(c);
c.fnname = fn_.str; c.fnname = fn_.str;
c.curmod = fn_.nmod; c.curmod = fn_.nmod;
c.cursource = fn_.sourceid;
c.fnret = fn_.lhs; c.fnret = fn_.lhs;
// sret callee (#23): return type is plain TY_STRUCT > 24B. // sret callee (#23): return type is plain TY_STRUCT > 24B.

View File

@@ -11,7 +11,6 @@ package wcc;
import os; import os;
import syntax; import syntax;
import strconv;
// cgfloatbits — materialise a float constant in X0: MOVQ the IEEE bits // cgfloatbits — materialise a float constant in X0: MOVQ the IEEE bits
// into AX, PUSH, MOVSD off the stack into X0. Shared by N_FLOATLIT (bits // into AX, PUSH, MOVSD off the stack into X0. Shared by N_FLOATLIT (bits

View File

@@ -5,7 +5,6 @@ package wcc;
import os; import os;
import syntax; import syntax;
import strconv;
// slicewrap — synthesise an N_TSLICE node wrapping the given element // slicewrap — synthesise an N_TSLICE node wrapping the given element
// type AST. Used by the Hare-style variadic path so the local entry // type AST. Used by the Hare-style variadic path so the local entry

View File

@@ -23,6 +23,8 @@ type checker = struct {
istestpackage: i32, // validate/retain package-owned @test bodies and istestpackage: i32, // validate/retain package-owned @test bodies and
// export compiler-private metadata; no entry synth // export compiler-private metadata; no entry synth
testmodule: str, // generated dispatcher support qualifier testmodule: str, // generated dispatcher support qualifier
testtarget: str, // canonical target path used only as the
// compiler-owned generated-main qualifier
sepmode: i32, // -c package compilation: imported interfaces are sepmode: i32, // -c package compilation: imported interfaces are
// present, so absent members are hard export errors. // present, so absent members are hard export errors.
synthtestrun: *syntax.node, // exact generated support.run DOT; its synthtestrun: *syntax.node, // exact generated support.run DOT; its
@@ -32,7 +34,9 @@ type checker = struct {
curmod: str, // importing-module bareword for the decl curmod: str, // importing-module bareword for the decl
// currently being walked; "" for primary // currently being walked; "" for primary
// compilation unit. Drives same-module // compilation unit. Drives same-module
// preference in bare-leaf lookups. // preference in bare-leaf lookups.
cursource: i32, // lexical source-file scope of the current decl;
// selects only that file's import bindings.
file: *syntax.node, // N_FILE root; used by checkmoduleshadow file: *syntax.node, // N_FILE root; used by checkmoduleshadow
// to consult the declaring source's own // to consult the declaring source's own
// `use` directives. // `use` directives.
@@ -154,6 +158,7 @@ fn declmod(file: *syntax.node, d: *syntax.node) str = {
if (d == nil) { return empty; }; if (d == nil) { return empty; };
if (d.nmod.len == 0) { return empty; }; if (d.nmod.len == 0) { return empty; };
if (file == nil) { return empty; }; if (file == nil) { return empty; };
if (d.imported != 0) { return d.nmod; };
let u: *syntax.node = file.list; let u: *syntax.node = file.list;
for (u != nil) { for (u != nil) {
// M1 #22: a decl is imported iff some `use` directive's full // M1 #22: a decl is imported iff some `use` directive's full
@@ -168,17 +173,16 @@ fn declmod(file: *syntax.node, d: *syntax.node) str = {
return empty; return empty;
}; };
// usepath — map a `use` alias (leaf bareword the user writes, `utf8`) // usepath — map a source-file default qualifier (the imported package's
// to the full dotted import path it binds (`encoding.utf8`), for the // declared name) to the full canonical import path it binds, for
// module-qualified resolution and codegen hint (M1 #22). Single-level // module-qualified resolution and codegen hint (M1 #22). Only an import owned
// packages have usepath == alias so the result is unchanged. The // by the referencing file is visible; a matching
// Only an import owned by the referencing package is visible; a matching
// alias carried by a transitive interface is deliberately ignored. // alias carried by a transitive interface is deliberately ignored.
fn usepathfor(file: *syntax.node, modtag: str, alias: str) str = { fn usepathfor(file: *syntax.node, modtag: str, source: i32, alias: str) str = {
let empty: str; let empty: str;
if (file == nil) { return empty; }; if (file == nil) { return empty; };
if (alias.len == 0) { return empty; }; if (alias.len == 0) { return empty; };
if (modtag.len != 0) { if (source == 0 && modtag.len != 0) {
let (prefix, suffix) = strings.rcut(modtag, "."); let (prefix, suffix) = strings.rcut(modtag, ".");
let leaf: str = suffix; let leaf: str = suffix;
if (leaf.len == 0) { leaf = modtag; }; if (leaf.len == 0) { leaf = modtag; };
@@ -186,14 +190,15 @@ fn usepathfor(file: *syntax.node, modtag: str, alias: str) str = {
}; };
let u: *syntax.node = file.list; let u: *syntax.node = file.list;
for (u != nil) { for (u != nil) {
if (u.kind == syntax.nkind.N_USE) { if (u.kind == syntax.nkind.N_USE && u.sourceid == source) {
if (syntax.streq(u.str, alias)) { if (syntax.streq(u.str, alias)) {
let um: str = declmod(file, u); let um: str = declmod(file, u);
let same: bool = false; let same: bool = false;
if (modtag.len == 0) { if (modtag.len == 0) {
if (um.len == 0) { same = true; }; if (um.len == 0) { same = true; };
} else { if (syntax.streq(um, modtag)) { same = true; }; }; } else { if (syntax.streq(um, modtag)) { same = true; }; };
if (same) { if (same) {
u.used = 1;
if (u.usepath.len != 0) { return u.usepath; }; if (u.usepath.len != 0) { return u.usepath; };
return u.str; return u.str;
}; };
@@ -207,19 +212,19 @@ fn usepathfor(file: *syntax.node, modtag: str, alias: str) str = {
// modkeyfor — the module key for a directly imported alias. Empty means // modkeyfor — the module key for a directly imported alias. Empty means
// the referencing package did not itself declare that import. // the referencing package did not itself declare that import.
fn modkeyfor(c: *checker, alias: str) str = { fn modkeyfor(c: *checker, alias: str) str = {
return usepathfor(c.file, c.curmod, alias); return usepathfor(c.file, c.curmod, c.cursource, alias);
}; };
// srcimports — does the source file that contributed decl-module // srcimports — does the source file that contributed decl-module
// `modtag` carry `use <name>;`? Mirrors cstage's src_imports — // `modtag` carry `use <name>;`? Mirrors cstage's src_imports —
// `modtag.len == 0` means primary, matching declmod's empty-str // `modtag.len == 0` means primary, matching declmod's empty-str
// return for primary-source decls. // return for primary-source decls.
fn srcimports(file: *syntax.node, modtag: str, name: str) bool = { fn srcimports(file: *syntax.node, modtag: str, source: i32, name: str) bool = {
if (file == nil) { return false; }; if (file == nil) { return false; };
if (name.len == 0) { return false; }; if (name.len == 0) { return false; };
let u: *syntax.node = file.list; let u: *syntax.node = file.list;
for (u != nil) { for (u != nil) {
if (u.kind == syntax.nkind.N_USE) { if (u.kind == syntax.nkind.N_USE && u.sourceid == source) {
// Skip self-imports: lib/fmt/fmt_test.ww carries // Skip self-imports: lib/fmt/fmt_test.ww carries
// `use fmt;` while its module tag is also "fmt". // `use fmt;` while its module tag is also "fmt".
// That directive doesn't introduce a foreign // That directive doesn't introduce a foreign
@@ -251,11 +256,23 @@ fn srcimports(file: *syntax.node, modtag: str, name: str) bool = {
// symbol iff the referencing source package itself imported its module path. // symbol iff the referencing source package itself imported its module path.
fn directmodvisible(c: *checker, mod: str) bool = { fn directmodvisible(c: *checker, mod: str) bool = {
if (mod.len == 0) { return false; }; if (mod.len == 0) { return false; };
let (prefix, suffix) = strings.rcut(mod, "."); let u: *syntax.node = c.file.list;
let alias: str = suffix; for (u != nil) {
if (alias.len == 0) { alias = mod; }; if (u.kind == syntax.nkind.N_USE && u.sourceid == c.cursource) {
let path: str = usepathfor(c.file, c.curmod, alias); let um: str = declmod(c.file, u);
return path.len != 0 && syntax.streq(path, mod); let same: bool = false;
if (c.curmod.len == 0) { same = um.len == 0; }
else { same = syntax.streq(um, c.curmod); };
let path: str = u.usepath;
if (path.len == 0) { path = u.str; };
if (same && syntax.streq(path, mod)) {
u.used = 1;
return true;
};
};
u = u.next;
};
return false;
}; };
fn lookupvisible(c: *checker, name: str) *syntax.sym = { fn lookupvisible(c: *checker, name: str) *syntax.sym = {
@@ -268,7 +285,7 @@ fn lookupvisible(c: *checker, name: str) *syntax.sym = {
// Flat scope installation coalesces same-leaf N_USE entries. The // Flat scope installation coalesces same-leaf N_USE entries. The
// source-owned alias map, not the retained marker's mod field, decides // source-owned alias map, not the retained marker's mod field, decides
// whether this package can use the qualifier. // whether this package can use the qualifier.
if (usepathfor(c.file, c.curmod, name).len != 0) { if (usepathfor(c.file, c.curmod, c.cursource, name).len != 0) {
let q: *syntax.scope = c.cur; let q: *syntax.scope = c.cur;
for (q != nil) { for (q != nil) {
let u: *syntax.sym = q.first; let u: *syntax.sym = q.first;
@@ -354,9 +371,11 @@ fn builtintypename(name: str) bool = {
fn packageaccesserr(c: *checker, e: *syntax.node, pkg: str, member: str, fn packageaccesserr(c: *checker, e: *syntax.node, pkg: str, member: str,
missing: bool) void = { missing: bool) void = {
cerr(e.file); cerr(":"); let at: *syntax.node = e;
cerr(strconv.i32tos(e.line, strconv.base.DEC)); cerr(":"); for (at.kind == syntax.nkind.N_DOT && at.lhs != nil) { at = at.lhs; };
cerr(strconv.i32tos(e.col, strconv.base.DEC)); cerr(at.file); cerr(":");
cerr(strconv.i32tos(at.line, strconv.base.DEC)); cerr(":");
cerr(strconv.i32tos(at.col, strconv.base.DEC));
cerr(": error: package '"); cerr(pkg); cerr(": error: package '"); cerr(pkg);
if (missing) { if (missing) {
cerr("' has no exported declaration '"); cerr(member); cerr("'\n"); cerr("' has no exported declaration '"); cerr(member); cerr("'\n");
@@ -396,7 +415,7 @@ fn checkmoduleshadow(c: *checker, name: str, kindstr: str) void = {
if (s != nil) { s = s.parent; }; if (s != nil) { s = s.parent; };
}; };
if (!seen) { return; }; if (!seen) { return; };
if (!srcimports(c.file, c.curmod, name)) { return; }; if (!srcimports(c.file, c.curmod, c.cursource, name)) { return; };
cerr(kindstr); cerr(kindstr);
cerr(" '"); cerr(" '");
cerr(name); cerr(name);
@@ -437,10 +456,8 @@ fn installdecl(c: *checker, file: *syntax.node, d: *syntax.node) void = {
let k: syntax.nkind = d.kind; let k: syntax.nkind = d.kind;
let nm: str = d.str; let nm: str = d.str;
let mod: str = declmod(file, d); let mod: str = declmod(file, d);
// check-(c) self-import: a package may not import itself. Pure // A package may not import its own canonical owner. Import usage and
// owner==leaf string compare, package-model-independent. check-(a) // membership are checked with source-file provenance. Message byte-identical
// unused + (b)/(d) membership DEFERRED to task #8 (filename-keyed
// pulls lack import->file->symbol provenance). Message byte-identical
// to cstage check.c. // to cstage check.c.
if (k == syntax.nkind.N_USE) { if (k == syntax.nkind.N_USE) {
// M1 #22: self-import ⟺ the imported path equals the use's own // M1 #22: self-import ⟺ the imported path equals the use's own
@@ -2696,10 +2713,13 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
// owns it. A direct dependency's fact may be demanded while // owns it. A direct dependency's fact may be demanded while
// walking a consumer-owned type; keeping the consumer module // walking a consumer-owned type; keeping the consumer module
// here can bind bare names in the fact to the wrong package. // here can bind bare names in the fact to the wrong package.
let savedmod: str = c.curmod; let savedmod: str = c.curmod;
c.curmod = s.mod; let savedsource: i32 = c.cursource;
let under: *syntax.tinfo = tinfofornode(c, body); c.curmod = s.mod;
c.curmod = savedmod; if (s.decl != nil) { c.cursource = s.decl.sourceid; };
let under: *syntax.tinfo = tinfofornode(c, body);
c.curmod = savedmod;
c.cursource = savedsource;
// #62/#69: alias-root cycle (`type a = b; // #62/#69: alias-root cycle (`type a = b;
// type b = a` / `type a = a`) — checked // type b = a` / `type a = a`) — checked
// BEFORE clearing the flag so self-aliases // BEFORE clearing the flag so self-aliases
@@ -3821,6 +3841,9 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
return tn; return tn;
}; };
if (k == syntax.nkind.N_IDENT) { if (k == syntax.nkind.N_IDENT) {
// resolvewalk visits the dot receiver before its parent. Preserve the
// first causal undefined error just as cstage's cached cexpr does.
if (e.type_ == c.tc.tyerr: *void) { return nil; };
// #55: bare-leaf value-ident must prefer curmod. Flat-scope // #55: bare-leaf value-ident must prefer curmod. Flat-scope
// scopelookup bucket-walks and can bind a same-leaf symbol from // scopelookup bucket-walks and can bind a same-leaf symbol from
// the wrong module under a foreign curmod, dragging its decl's // the wrong module under a foreign curmod, dragging its decl's
@@ -4406,7 +4429,12 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
// name path stays primary because a fn-NAME callee node in wwstage // name path stays primary because a fn-NAME callee node in wwstage
// already carries its RETURN type (fn-decl.lhs), not its fn-type — // already carries its RETURN type (fn-decl.lhs), not its fn-type —
// so a `fn make() fn() void` callee would otherwise mis-yield void. // so a `fn make() fn() void` callee would otherwise mis-yield void.
let ct: *syntax.node = resolvealias(c, unwrapbang(exprtype(c, callee, nil))); let calleetn: *syntax.node = exprtype(c, callee, nil);
if (callee.type_ == c.tc.tyerr: *void) {
e.type_ = c.tc.tyerr: *void;
return nil;
};
let ct: *syntax.node = resolvealias(c, unwrapbang(calleetn));
// #14/#181-cgen: ONE pointer-peel only, mirroring cstage // #14/#181-cgen: ONE pointer-peel only, mirroring cstage
// cmd/wcc/check.c:1947. #181-cgen lowers an indirect call by using the // cmd/wcc/check.c:1947. #181-cgen lowers an indirect call by using the
// callee VALUE as the target, the fn address for a single `*fn` but only // callee VALUE as the target, the fn address for a single `*fn` but only
@@ -4554,6 +4582,10 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
// check.c:805-832. Peel one TPTR for `(*EnumT).MEMBER` (rare // check.c:805-832. Peel one TPTR for `(*EnumT).MEMBER` (rare
// but cstage handles it at L808). // but cstage handles it at L808).
let basetn: *syntax.node = exprtype(c, lhsn, nil); let basetn: *syntax.node = exprtype(c, lhsn, nil);
if (lhsn != nil && lhsn.type_ == c.tc.tyerr: *void) {
e.type_ = c.tc.tyerr: *void;
return nil;
};
if (basetn != nil) { if (basetn != nil) {
let bu: *syntax.node = resolvealias(c, unwrapbang(basetn)); let bu: *syntax.node = resolvealias(c, unwrapbang(basetn));
if (bu != nil) { if (bu.kind == syntax.nkind.N_TPTR) { if (bu != nil) { if (bu.kind == syntax.nkind.N_TPTR) {
@@ -7499,6 +7531,90 @@ fn asserttyped(c: *checker, n: *syntax.node, indot: bool) void = {
}; };
}; };
fn importdiagprefix(n: *syntax.node) void = {
cerr(n.file); cerr(":");
cerr(strconv.i32tos(n.line, strconv.base.DEC)); cerr(":");
cerr(strconv.i32tos(n.col, strconv.base.DEC)); cerr(": error: ");
};
fn importdiagalt(n: *syntax.node, name: str) void = {
cerr("\t"); cerr(n.file); cerr(":");
cerr(strconv.i32tos(n.line, strconv.base.DEC)); cerr(":");
cerr(strconv.i32tos(n.col, strconv.base.DEC));
cerr(": other declaration of "); cerr(name); cerr("\n");
};
fn topdeclkind(d: *syntax.node) bool = {
return d != nil && (d.kind == syntax.nkind.N_TYPEDECL
|| d.kind == syntax.nkind.N_DEF || d.kind == syntax.nkind.N_FNDECL
|| d.kind == syntax.nkind.N_LET);
};
fn checkimportredeclarations(c: *checker, file: *syntax.node) void = {
if (c.sepmode == 0) { return; };
let u: *syntax.node = file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.imported == 0) {
let v: *syntax.node = file.list;
for (v != u) {
if (v.kind == syntax.nkind.N_USE && v.imported == 0
&& v.sourceid == u.sourceid && syntax.streq(v.str, u.str)) {
importdiagprefix(u); cerr(u.str);
cerr(" redeclared in this block\n");
c.errs += 1;
importdiagalt(v, u.str);
break;
};
v = v.next;
};
};
u = u.next;
};
};
fn checkimportusageandcollisions(c: *checker, file: *syntax.node) void = {
if (c.sepmode == 0) { return; };
let u: *syntax.node = file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.imported == 0 && u.used == 0) {
let path: str = u.usepath;
if (path.len == 0) { path = u.str; };
let (prefix, suffix) = strings.rcut(path, ".");
let leaf: str = suffix;
if (leaf.len == 0) { leaf = path; };
importdiagprefix(u); cerr("\""); cerr(path);
if (syntax.streq(u.str, leaf)) {
cerr("\" imported and not used\n");
} else {
cerr("\" imported as "); cerr(u.str);
cerr(" and not used\n");
};
c.errs += 1;
};
u = u.next;
};
let d: *syntax.node = file.list;
for (d != nil) {
if (d.imported == 0 && topdeclkind(d)) {
u = file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.imported == 0
&& syntax.streq(d.str, u.str)) {
let path: str = u.usepath;
if (path.len == 0) { path = u.str; };
importdiagprefix(d); cerr(d.str);
cerr(" already declared through import of package ");
cerr(u.str); cerr(" (\""); cerr(path); cerr("\")\n");
c.errs += 1;
importdiagalt(u, d.str);
};
u = u.next;
};
};
d = d.next;
};
};
fn checkinit(c: *checker, tc: *syntax.tctx) void = { fn checkinit(c: *checker, tc: *syntax.tctx) void = {
c.tc = tc; c.tc = tc;
c.top = syntax.newscope(nil); c.top = syntax.newscope(nil);
@@ -7509,12 +7625,15 @@ fn checkinit(c: *checker, tc: *syntax.tctx) void = {
c.istest = 0i32; // #15: caller (w6c main) sets it after init c.istest = 0i32; // #15: caller (w6c main) sets it after init
c.istestpackage = 0i32; c.istestpackage = 0i32;
c.testmodule = "test"; c.testmodule = "test";
let emptytesttarget: str;
c.testtarget = emptytesttarget;
c.sepmode = 0i32; // caller (w6c main) sets it from -c c.sepmode = 0i32; // caller (w6c main) sets it from -c
c.synthtestrun = nil; c.synthtestrun = nil;
c.verbose = 0; c.verbose = 0;
c.fnret = nil; c.fnret = nil;
let empty: str; let empty: str;
c.curmod = empty; c.curmod = empty;
c.cursource = 0;
c.file = nil; c.file = nil;
c.allococtx = nil; c.allococtx = nil;
seedprimitives(c); seedprimitives(c);
@@ -7538,25 +7657,35 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
let present: bool = false; let present: bool = false;
let su: *syntax.node = file.list; let su: *syntax.node = file.list;
for (su != nil) { for (su != nil) {
if (c.testtarget.len > 0 && su.kind == syntax.nkind.N_USE
&& su.imported == 0
&& (syntax.streq(su.usepath, c.testtarget)
|| syntax.streq(su.usepath, c.testmodule))) {
su.used = 1i32;
};
if (su.kind == syntax.nkind.N_USE && su.imported == 0 if (su.kind == syntax.nkind.N_USE && su.imported == 0
&& syntax.streq(su.usepath, c.testmodule)) { && syntax.streq(su.usepath, c.testmodule)) {
present = true; present = true;
break;
}; };
su = su.next; su = su.next;
}; };
if (!present) { if (!present) {
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, file.file, file.line, file.col); let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, file.file, file.line, file.col);
usenode.str = c.testmodule; usenode.str = c.testmodule;
usenode.usepath = c.testmodule; usenode.usepath = c.testmodule;
usenode.next = file.list; usenode.pkgname = file.pkgname;
usenode.sourceid = file.sourceid;
if (c.testtarget.len > 0) { usenode.used = 1i32; };
usenode.next = file.list;
file.list = usenode; file.list = usenode;
}; };
}; };
checkimportredeclarations(c, file);
// Pass 1: install all top-level names. // Pass 1: install all top-level names.
let d: *syntax.node = file.list; let d: *syntax.node = file.list;
for (d != nil) { for (d != nil) {
c.cursource = d.sourceid;
installdecl(c, file, d); installdecl(c, file, d);
d = d.next; d = d.next;
}; };
@@ -7693,11 +7822,14 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
}; };
let nm: *syntax.node = syntax.newnode(syntax.nkind.N_STRLIT, pf, pl, pc); let nm: *syntax.node = syntax.newnode(syntax.nkind.N_STRLIT, pf, pl, pc);
nm.str = t.str; nm.str = t.str;
let id: *syntax.node; let id: *syntax.node;
if (t.imported != 0 && t.nmod.len > 0) { if (t.imported != 0 && t.nmod.len > 0) {
let (prefix, suffix) = strings.rcut(t.nmod, "."); let alias: str = t.pkgname;
let alias: str = suffix;
if (alias.len == 0) { alias = t.nmod; }; if (alias.len == 0) { alias = t.nmod; };
if (c.testtarget.len > 0
&& syntax.streq(t.nmod, c.testtarget)) {
alias = c.testtarget;
};
id = syntax.newnode(syntax.nkind.N_DOT, pf, pl, pc); id = syntax.newnode(syntax.nkind.N_DOT, pf, pl, pc);
id.lhs = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc); id.lhs = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
id.lhs.str = alias; id.lhs.str = alias;
@@ -7758,8 +7890,10 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
let arr: *syntax.node = syntax.newnode(syntax.nkind.N_ARRLIT, pf, pl, pc); let arr: *syntax.node = syntax.newnode(syntax.nkind.N_ARRLIT, pf, pl, pc);
arr.list = rhead; arr.list = rhead;
tab = syntax.newnode(syntax.nkind.N_LET, pf, pl, pc); tab = syntax.newnode(syntax.nkind.N_LET, pf, pl, pc);
tab.op = syntax.tkind.TK_CONST; tab.op = syntax.tkind.TK_CONST;
tab.str = "__wwtests"; tab.str = "__wwtests";
tab.pkgname = file.pkgname;
tab.sourceid = file.sourceid;
tab.lhs = tsl; tab.lhs = tsl;
tab.rhs = arr; tab.rhs = arr;
// pass 1 already ran; install the table name now so main // pass 1 already ran; install the table name now so main
@@ -7795,8 +7929,10 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
// the synthesized call type-resolves. Pass 1 installs the use. // the synthesized call type-resolves. Pass 1 installs the use.
}; };
let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc); let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc);
m.str = "main"; m.str = "main";
m.exported = 1i32; m.exported = 1i32;
m.pkgname = file.pkgname;
m.sourceid = file.sourceid;
let rety: *syntax.node = syntax.newnode(syntax.nkind.N_TNAME, pf, pl, pc); let rety: *syntax.node = syntax.newnode(syntax.nkind.N_TNAME, pf, pl, pc);
rety.str = "i32"; rety.str = "i32";
m.lhs = rety; m.lhs = rety;
@@ -7824,6 +7960,7 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
d = file.list; d = file.list;
for (d != nil) { for (d != nil) {
c.curmod = declmod(file, d); c.curmod = declmod(file, d);
c.cursource = d.sourceid;
// A.6.2.1-pre — attr-subtree gap: top-level dispatch below walks // A.6.2.1-pre — attr-subtree gap: top-level dispatch below walks
// d.lhs / d.body per kind but never d.attr, leaving `@symbol("…")` // d.lhs / d.body per kind but never d.attr, leaving `@symbol("…")`
// arg literals (N_STRLIT) outside the post-order exprtype // arg literals (N_STRLIT) outside the post-order exprtype
@@ -7918,6 +8055,7 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
}; };
d = d.next; d = d.next;
}; };
checkimportusageandcollisions(c, file);
// Pass 3 (#15, A.6.2.1e): post-checker invariant gate. Walks each // Pass 3 (#15, A.6.2.1e): post-checker invariant gate. Walks each
// decl with its curmod set so asserttyped's gate lookups resolve // decl with its curmod set so asserttyped's gate lookups resolve
@@ -7925,6 +8063,7 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
d = file.list; d = file.list;
for (d != nil) { for (d != nil) {
c.curmod = declmod(file, d); c.curmod = declmod(file, d);
c.cursource = d.sourceid;
asserttyped(c, d, false); asserttyped(c, d, false);
d = d.next; d = d.next;
}; };
@@ -7967,5 +8106,6 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
let empty: str; let empty: str;
c.curmod = empty; c.curmod = empty;
c.cursource = 0;
}; };

View File

@@ -82,24 +82,11 @@ fn wwimodeq(a: str, b: str) bool = {
// Map an import alias in the source package that owns the reference. The // Map an import alias in the source package that owns the reference. The
// flattened parser file contains every imported interface's N_USE nodes, so // flattened parser file contains every imported interface's N_USE nodes, so
// this owner filter is what prevents cross-package alias capture. // this owner filter is what prevents cross-package alias capture.
fn wwiusepath(c: *checker, owner: str, alias: str) str = { fn wwiusepath(c: *checker, owner: str, source: i32, alias: str) str = {
if (owner.len > 0) {
let dotidx: i32 = -1;
let i: i32 = 0;
for (i < owner.len) {
if (owner[i] == 46u8) { dotidx = i; };
i += 1;
};
let leaf: str = owner;
if (dotidx >= 0) {
leaf.ptr = owner.ptr + ((dotidx + 1): u64);
leaf.len = owner.len - dotidx - 1;
};
if (syntax.streq(alias, leaf)) { return owner; };
};
let u: *syntax.node = c.file.list; let u: *syntax.node = c.file.list;
for (u != nil) { for (u != nil) {
if (u.kind == syntax.nkind.N_USE && syntax.streq(u.str, alias)) { if (u.kind == syntax.nkind.N_USE && u.sourceid == source
&& syntax.streq(u.str, alias)) {
let same: bool = false; let same: bool = false;
if (owner.len == 0) { if (owner.len == 0) {
same = u.imported == 0; same = u.imported == 0;
@@ -117,24 +104,27 @@ fn wwiusepath(c: *checker, owner: str, alias: str) str = {
return empty; return empty;
}; };
fn wwidirectmodvisible(c: *checker, owner: str, mod: str) bool = { fn wwidirectmodvisible(c: *checker, owner: str, source: i32, mod: str) bool = {
if (mod.len == 0) { return false; }; if (mod.len == 0) { return false; };
let dotidx: i32 = -1; let u: *syntax.node = c.file.list;
let i: i32 = 0; for (u != nil) {
for (i < mod.len) { if (u.kind == syntax.nkind.N_USE && u.sourceid == source) {
if (mod[i] == 46u8) { dotidx = i; }; let same: bool = false;
i += 1; if (owner.len == 0) {
same = u.imported == 0;
} else {
same = u.imported != 0 && wwimodeq(u.nmod, owner);
};
let path: str = u.str;
if (u.usepath.len > 0) { path = u.usepath; };
if (same && syntax.streq(path, mod)) { return true; };
};
u = u.next;
}; };
let alias: str = mod; return false;
if (dotidx >= 0) {
alias.ptr = mod.ptr + ((dotidx + 1): u64);
alias.len = mod.len - dotidx - 1;
};
let path: str = wwiusepath(c, owner, alias);
return path.len > 0 && syntax.streq(path, mod);
}; };
fn wwitypesym(c: *checker, owner: str, nm: str) *syntax.sym = { fn wwitypesym(c: *checker, owner: str, source: i32, nm: str) *syntax.sym = {
let dotidx: i32 = -1; let dotidx: i32 = -1;
let i: i32 = 0; let i: i32 = 0;
for (i < nm.len) { for (i < nm.len) {
@@ -149,7 +139,7 @@ fn wwitypesym(c: *checker, owner: str, nm: str) *syntax.sym = {
let leaf: str; let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64); leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1; leaf.len = nm.len - dotidx - 1;
let mod: str = wwiusepath(c, owner, head); let mod: str = wwiusepath(c, owner, source, head);
if (mod.len > 0) { if (mod.len > 0) {
s = syntax.scopelookupinmodule(c.cur, mod, leaf); s = syntax.scopelookupinmodule(c.cur, mod, leaf);
}; };
@@ -162,7 +152,7 @@ fn wwitypesym(c: *checker, owner: str, nm: str) *syntax.sym = {
for (b != nil) { for (b != nil) {
if (b.skind == syntax.skind.SK_TYPE if (b.skind == syntax.skind.SK_TYPE
&& syntax.streq(b.name, nm) && syntax.streq(b.name, nm)
&& wwidirectmodvisible(c, owner, b.mod)) { && wwidirectmodvisible(c, owner, source, b.mod)) {
s = b; s = b;
break; break;
}; };
@@ -669,7 +659,8 @@ fn wwifactsame(mod: str, rank: i32, d: *syntax.node,
return rank == srank && wwimodeq(mod, smod) && syntax.streq(d.str, sd.str); return rank == srank && wwimodeq(mod, smod) && syntax.streq(d.str, sd.str);
}; };
fn wwifactvaluesym(c: *checker, owner: str, name: str) *syntax.sym = { fn wwifactvaluesym(c: *checker, owner: str, source: i32,
name: str) *syntax.sym = {
let p: *syntax.scope = c.top; let p: *syntax.scope = c.top;
for (p != nil) { for (p != nil) {
let s: *syntax.sym = p.first; let s: *syntax.sym = p.first;
@@ -685,7 +676,7 @@ fn wwifactvaluesym(c: *checker, owner: str, name: str) *syntax.sym = {
let s: *syntax.sym = p.first; let s: *syntax.sym = p.first;
for (s != nil) { for (s != nil) {
if (s.skind == syntax.skind.SK_DEF && syntax.streq(s.name, name) if (s.skind == syntax.skind.SK_DEF && syntax.streq(s.name, name)
&& wwidirectmodvisible(c, owner, s.mod)) { return s; }; && wwidirectmodvisible(c, owner, source, s.mod)) { return s; };
s = s.snext; s = s.snext;
}; };
p = p.parent; p = p.parent;
@@ -757,27 +748,30 @@ fn wwicollectdecl(fs: *wwifactset, owner: str, d: *syntax.node) void = {
if (d.kind == syntax.nkind.N_FNDECL) { if (d.kind == syntax.nkind.N_FNDECL) {
let p: *syntax.node = d.list; let p: *syntax.node = d.list;
for (p != nil) { wwicollecttype(fs, owner, p.lhs); p = p.next; }; for (p != nil) { wwicollecttype(fs, owner, d.sourceid, p.lhs); p = p.next; };
wwicollecttype(fs, owner, d.lhs); wwicollecttype(fs, owner, d.sourceid, d.lhs);
} else { if (d.kind == syntax.nkind.N_TYPEDECL) { } else { if (d.kind == syntax.nkind.N_TYPEDECL) {
wwicollecttype(fs, owner, d.lhs); wwicollecttype(fs, owner, d.sourceid, d.lhs);
} else { if (d.kind == syntax.nkind.N_DEF) { } else { if (d.kind == syntax.nkind.N_DEF) {
wwicollecttype(fs, owner, d.lhs); wwicollecttype(fs, owner, d.sourceid, d.lhs);
wwicollectexpr(fs, owner, d.rhs); wwicollectexpr(fs, owner, d.sourceid, d.rhs);
} else { if (d.kind == syntax.nkind.N_LET) { } else { if (d.kind == syntax.nkind.N_LET) {
wwicollecttype(fs, owner, d.lhs); wwicollecttype(fs, owner, d.sourceid, d.lhs);
};};};}; };};};};
}; };
fn wwicollectexpr(fs: *wwifactset, owner: str, e: *syntax.node) void = { fn wwicollectexpr(fs: *wwifactset, owner: str, source: i32,
e: *syntax.node) void = {
if (e == nil) { return; }; if (e == nil) { return; };
let s: *syntax.sym = nil; let s: *syntax.sym = nil;
if (e.kind == syntax.nkind.N_IDENT) { if (e.kind == syntax.nkind.N_IDENT) {
s = wwifactvaluesym(fs.c, owner, e.str); s = wwifactvaluesym(fs.c, owner, source, e.str);
} else { if (e.kind == syntax.nkind.N_DOT && e.lhs != nil) { } else { if (e.kind == syntax.nkind.N_DOT && e.lhs != nil) {
if (e.lhs.kind == syntax.nkind.N_IDENT) { if (e.lhs.kind == syntax.nkind.N_IDENT) {
let mod: str = wwiusepath(fs.c, owner, e.lhs.str); let mod: str = wwiusepath(fs.c, owner, source, e.lhs.str);
if (mod.len > 0) { s = wwifactvaluesym(fs.c, mod, e.str); }; if (mod.len > 0) {
s = wwifactvaluesym(fs.c, mod, source, e.str);
};
}; };
}; }; }; };
if (s != nil && s.decl != nil) { if (s != nil && s.decl != nil) {
@@ -790,30 +784,37 @@ fn wwicollectexpr(fs: *wwifactset, owner: str, e: *syntax.node) void = {
return; return;
}; };
if (e.kind == syntax.nkind.N_BIN) { if (e.kind == syntax.nkind.N_BIN) {
wwicollectexpr(fs, owner, e.lhs); wwicollectexpr(fs, owner, source, e.lhs);
wwicollectexpr(fs, owner, e.rhs); wwicollectexpr(fs, owner, source, e.rhs);
} else { if (e.kind == syntax.nkind.N_UN) { } else { if (e.kind == syntax.nkind.N_UN) {
wwicollectexpr(fs, owner, e.lhs); wwicollectexpr(fs, owner, source, e.lhs);
} else { if (e.kind == syntax.nkind.N_CAST) { } else { if (e.kind == syntax.nkind.N_CAST) {
wwicollectexpr(fs, owner, e.lhs); wwicollectexpr(fs, owner, source, e.lhs);
wwicollecttype(fs, owner, e.rhs); wwicollecttype(fs, owner, source, e.rhs);
}; }; }; }; }; };
}; };
fn wwievalconst(fs: *wwifactset, owner: str, e: *syntax.node, fn wwievalconst(fs: *wwifactset, owner: str, source: i32, e: *syntax.node,
out: *u64) bool = { out: *u64) bool = {
let saved: str = fs.c.curmod; let saved: str = fs.c.curmod;
let savesource: i32 = fs.c.cursource;
fs.c.curmod = owner; fs.c.curmod = owner;
fs.c.cursource = source;
let ok: bool = evaldefconst(fs.c, e, out, 0); let ok: bool = evaldefconst(fs.c, e, out, 0);
fs.c.curmod = saved; fs.c.curmod = saved;
fs.c.cursource = savesource;
return ok; return ok;
}; };
fn wwicollecttype(fs: *wwifactset, owner: str, t: *syntax.node) void = { fn wwicollecttype(fs: *wwifactset, owner: str, source: i32,
t: *syntax.node) void = {
if (t == nil) { return; }; if (t == nil) { return; };
if (t.kind == syntax.nkind.N_TPARAM) { wwicollecttype(fs, owner, t.lhs); return; }; if (t.kind == syntax.nkind.N_TPARAM) {
wwicollecttype(fs, owner, source, t.lhs);
return;
};
if (t.kind == syntax.nkind.N_TNAME) { if (t.kind == syntax.nkind.N_TNAME) {
let s: *syntax.sym = wwitypesym(fs.c, owner, t.str); let s: *syntax.sym = wwitypesym(fs.c, owner, source, t.str);
if (s != nil && s.decl != nil) { if (s != nil && s.decl != nil) {
if (s.decl.kind == syntax.nkind.N_TYPEDECL) { if (s.decl.kind == syntax.nkind.N_TYPEDECL) {
wwicollectdecl(fs, s.mod, s.decl); wwicollectdecl(fs, s.mod, s.decl);
@@ -823,14 +824,14 @@ fn wwicollecttype(fs: *wwifactset, owner: str, t: *syntax.node) void = {
t.kind == syntax.nkind.N_TPTR || t.kind == syntax.nkind.N_TSLICE || t.kind == syntax.nkind.N_TPTR || t.kind == syntax.nkind.N_TSLICE ||
t.kind == syntax.nkind.N_TBANG || t.kind == syntax.nkind.N_TCHAN t.kind == syntax.nkind.N_TBANG || t.kind == syntax.nkind.N_TCHAN
) { ) {
wwicollecttype(fs, owner, t.lhs); wwicollecttype(fs, owner, source, t.lhs);
} else { if (t.kind == syntax.nkind.N_TARRAY) { } else { if (t.kind == syntax.nkind.N_TARRAY) {
// Array length is resolved type identity, not a source name // Array length is resolved type identity, not a source name
// dependency. Canonicalize it so private constants stay private // dependency. Canonicalize it so private constants stay private
// and consumers never need implementation defs merely for layout. // and consumers never need implementation defs merely for layout.
if (t.rhs != nil && t.rhs.kind != syntax.nkind.N_INTLIT) { if (t.rhs != nil && t.rhs.kind != syntax.nkind.N_INTLIT) {
let len: u64 = 0u64; let len: u64 = 0u64;
if (!wwievalconst(fs, owner, t.rhs, &len)) { if (!wwievalconst(fs, owner, source, t.rhs, &len)) {
wwiencodearrayreject(t); wwiencodearrayreject(t);
fs.bad = 1; fs.bad = 1;
} else { } else {
@@ -841,19 +842,19 @@ fn wwicollecttype(fs: *wwifactset, owner: str, t: *syntax.node) void = {
let empty: str; e.tsuffix = empty; let empty: str; e.tsuffix = empty;
}; };
}; };
wwicollecttype(fs, owner, t.lhs); wwicollecttype(fs, owner, source, t.lhs);
} else { if (t.kind == syntax.nkind.N_TFN) { } else { if (t.kind == syntax.nkind.N_TFN) {
let p: *syntax.node = t.list; let p: *syntax.node = t.list;
for (p != nil) { wwicollecttype(fs, owner, p.lhs); p = p.next; }; for (p != nil) { wwicollecttype(fs, owner, source, p.lhs); p = p.next; };
wwicollecttype(fs, owner, t.lhs); wwicollecttype(fs, owner, source, t.lhs);
} else { if (t.kind == syntax.nkind.N_TSTRUCT) { } else { if (t.kind == syntax.nkind.N_TSTRUCT) {
let f: *syntax.node = t.list; let f: *syntax.node = t.list;
for (f != nil) { wwicollecttype(fs, owner, f.lhs); f = f.next; }; for (f != nil) { wwicollecttype(fs, owner, source, f.lhs); f = f.next; };
} else { if (t.kind == syntax.nkind.N_TTAGGED || t.kind == syntax.nkind.N_TTUPLE) { } else { if (t.kind == syntax.nkind.N_TTAGGED || t.kind == syntax.nkind.N_TTUPLE) {
let e: *syntax.node = t.list; let e: *syntax.node = t.list;
for (e != nil) { wwicollecttype(fs, owner, e); e = e.next; }; for (e != nil) { wwicollecttype(fs, owner, source, e); e = e.next; };
} else { if (t.kind == syntax.nkind.N_TENUM) { } else { if (t.kind == syntax.nkind.N_TENUM) {
wwicollecttype(fs, owner, t.lhs); wwicollecttype(fs, owner, source, t.lhs);
// Member identifiers refer to prior siblings in this enum, not // Member identifiers refer to prior siblings in this enum, not
// package defs; the declaration already carries the whole list. // package defs; the declaration already carries the whole list.
};};};};};};}; };};};};};};};
@@ -866,6 +867,9 @@ fn wwisortfacts(fs: *wwifactset) void = {
let j: i32 = i + 1; let j: i32 = i + 1;
for (j < fs.nfacts) { for (j < fs.nfacts) {
let r: i32 = wwistrcmp(fs.factmods[j], fs.factmods[best]); let r: i32 = wwistrcmp(fs.factmods[j], fs.factmods[best]);
if (r == 0) {
r = fs.factnodes[j].sourceid - fs.factnodes[best].sourceid;
};
if (r == 0) { r = fs.factranks[j] - fs.factranks[best]; }; if (r == 0) { r = fs.factranks[j] - fs.factranks[best]; };
if (r == 0) { r = wwistrcmp(fs.factnodes[j].str, fs.factnodes[best].str); }; if (r == 0) { r = wwistrcmp(fs.factnodes[j].str, fs.factnodes[best].str); };
if (r < 0) { best = j; }; if (r < 0) { best = j; };
@@ -880,22 +884,40 @@ fn wwisortfacts(fs: *wwifactset) void = {
}; };
}; };
fn wwiowneduse(u: *syntax.node, owner: str) bool = { fn wwiowneduse(u: *syntax.node, owner: str, source: i32) bool = {
return u.kind == syntax.nkind.N_USE && u.imported != 0 return u.kind == syntax.nkind.N_USE && u.imported != 0
&& syntax.streq(u.nmod, owner); && u.sourceid == source && wwimodeq(u.nmod, owner);
}; };
fn wwiemitfactimports(fd: i32, file: *syntax.node, owner: str) void = { fn wwiemitimports(fd: i32, file: *syntax.node, owner: str, source: i32,
imported: bool) void = {
let nuse: i32 = 0; let nuse: i32 = 0;
let u: *syntax.node = file.list; let u: *syntax.node = file.list;
for (u != nil) { if (wwiowneduse(u, owner)) { nuse += 1; }; u = u.next; }; for (u != nil) {
let owned: bool = false;
if (imported) {
owned = wwiowneduse(u, owner, source);
} else {
owned = u.kind == syntax.nkind.N_USE && u.imported == 0
&& u.sourceid == source;
};
if (owned) { nuse += 1; };
u = u.next;
};
if (nuse == 0) { return; }; if (nuse == 0) { return; };
let paths: []str = alloc([], nuse: u64)!; paths.len = nuse; let paths: []str = alloc([], nuse: u64)!; paths.len = nuse;
let nodes: []*syntax.node = alloc([], nuse: u64)!; nodes.len = nuse; let nodes: []*syntax.node = alloc([], nuse: u64)!; nodes.len = nuse;
let k: i32 = 0; let k: i32 = 0;
u = file.list; u = file.list;
for (u != nil) { for (u != nil) {
if (wwiowneduse(u, owner)) { let owned: bool = false;
if (imported) {
owned = wwiowneduse(u, owner, source);
} else {
owned = u.kind == syntax.nkind.N_USE && u.imported == 0
&& u.sourceid == source;
};
if (owned) {
if (u.usepath.len > 0) { paths[k] = u.usepath; } else { paths[k] = u.str; }; if (u.usepath.len > 0) { paths[k] = u.usepath; } else { paths[k] = u.str; };
nodes[k] = u; nodes[k] = u;
k += 1; k += 1;
@@ -914,6 +936,85 @@ fn wwiemitfactimports(fd: i32, file: *syntax.node, owner: str) void = {
}; };
}; };
fn wwiprimarysectionhas(c: *checker, file: *syntax.node,
fs: *wwifactset, exports: []*syntax.node, nexports: i32,
source: i32) bool = {
let u: *syntax.node = file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.imported == 0
&& u.sourceid == source) { return true; };
u = u.next;
};
let i: i32 = 0;
for (i < fs.nprivate) {
if (fs.privatenodes[i].sourceid == source) { return true; };
i += 1;
};
i = 0;
for (i < nexports) {
if (exports[i].sourceid == source) { return true; };
i += 1;
};
if (c.istestpackage != 0) {
let d: *syntax.node = file.list;
for (d != nil) {
if (wwiprimary(d) && d.sourceid == source
&& d.kind == syntax.nkind.N_FNDECL && d.exported == 0
&& wwihasattr(d, "test")) { return true; };
d = d.next;
};
};
return false;
};
fn wwifirstprimarysource(file: *syntax.node) i32 = {
let found: bool = false;
let source: i32 = 0;
let d: *syntax.node = file.list;
for (d != nil) {
if (wwiprimary(d) && (!found || d.sourceid < source)) {
source = d.sourceid;
found = true;
};
d = d.next;
};
if (found) { return source; };
return file.sourceid;
};
fn wwiemitprimarysection(c: *checker, fd: i32, file: *syntax.node,
fs: *wwifactset, exports: []*syntax.node, nexports: i32,
owner: str, pkgname: str, source: i32) void = {
if (owner.len > 0) {
wputs(fd, "//ww:module "); wputs(fd, owner); wputs(fd, "\n");
};
let pkg: str = pkgname;
if (pkg.len == 0) { pkg = "main"; };
wputs(fd, "package "); wputs(fd, pkg); wputs(fd, ";\n");
wwiemitimports(fd, file, owner, source, false);
let i: i32 = 0;
for (i < fs.nprivate) {
if (fs.privatenodes[i].sourceid == source) {
wwidecl(fd, fs.privatenodes[i]);
};
i += 1;
};
if (c.istestpackage != 0) {
let d: *syntax.node = file.list;
for (d != nil) {
if (wwiprimary(d) && d.sourceid == source
&& d.kind == syntax.nkind.N_FNDECL && d.exported == 0
&& wwihasattr(d, "test")) { wwidecl(fd, d); };
d = d.next;
};
};
i = 0;
for (i < nexports) {
if (exports[i].sourceid == source) { wwidecl(fd, exports[i]); };
i += 1;
};
};
fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = { fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
// §5: check_exported_type FIRST, before any byte — a producer // §5: check_exported_type FIRST, before any byte — a producer
// without it can emit a dangling `.wwi`. // without it can emit a dangling `.wwi`.
@@ -959,140 +1060,24 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
wwisortdecls(fs.privatekeys, fs.privatenodes, fs.nprivate); wwisortdecls(fs.privatekeys, fs.privatenodes, fs.nprivate);
wwisortfacts(&fs); wwisortfacts(&fs);
let fd: i32 = os.open(path, // Exported declarations are sorted within their source-file sections.
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
if (fd < 0) {
wputs(2, "w6c: cannot open ");
wputs(2, path);
wputs(2, "\n");
return 1i32;
};
// package line: leaf of the first primary decl's module tag.
let pkg: str = "main";
let found: i32 = 0;
let pd: *syntax.node = file.list;
for (pd != nil) {
if (wwiprimary(pd) && pd.nmod.len > 0) {
let dotidx: i32 = -1;
let i: i32 = 0;
for (i < pd.nmod.len) {
if (pd.nmod[i] == 46u8) { dotidx = i; };
i += 1;
};
if (dotidx >= 0) {
let leaf: str;
leaf.ptr = pd.nmod.ptr + ((dotidx + 1): u64);
leaf.len = pd.nmod.len - dotidx - 1;
pkg = leaf;
} else {
pkg = pd.nmod;
};
found = 1;
pd = nil;
} else {
pd = pd.next;
};
};
// #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.nmod empty, so it stays "main". The detector is
// scan-miss (found==0), NOT pkg=="main", to match cstage byte-for-
// byte (rule 10) when a tagged decl legitimately leafs to "main".
if (found == 0 && file.nmod.len > 0) {
let dotidx: i32 = -1;
let i: i32 = 0;
for (i < file.nmod.len) {
if (file.nmod[i] == 46u8) { dotidx = i; };
i += 1;
};
if (dotidx >= 0) {
let leaf: str;
leaf.ptr = file.nmod.ptr + ((dotidx + 1): u64);
leaf.len = file.nmod.len - dotidx - 1;
pkg = leaf;
} else {
pkg = file.nmod;
};
};
if (file.nmod.len > 0) {
wputs(fd, "//ww:module "); wputs(fd, file.nmod); wputs(fd, "\n");
};
wputs(fd, "package ");
wputs(fd, pkg);
wputs(fd, ";\n");
// imports — primary N_USE, byte-sorted by import path.
let nuse: i32 = 0;
let u: *syntax.node = file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && wwiprimary(u)) { nuse += 1; };
u = u.next;
};
if (nuse > 0) {
let upaths: []str = alloc([], nuse: u64)!;
upaths.len = nuse;
let unodes: []*syntax.node = alloc([], nuse: u64)!;
unodes.len = nuse;
let k: i32 = 0;
u = file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && wwiprimary(u)) {
if (u.usepath.len > 0) { upaths[k] = u.usepath; } else { upaths[k] = u.str; };
unodes[k] = u;
k += 1;
};
u = u.next;
};
wwisortdecls(upaths, unodes, nuse);
let i: i32 = 0;
let previous: str = "";
for (i < nuse) {
if (previous.len == 0 || !syntax.streq(previous, upaths[i])) {
wputs(fd, "import ");
wputs(fd, upaths[i]);
wputs(fd, ";\n");
previous = upaths[i];
};
i += 1;
};
};
// Compiler-private owner nominals precede public declarations. They are
// available to export decoding but remain absent from source visibility.
let pri: i32 = 0;
for (pri < fs.nprivate) {
wwidecl(fd, fs.privatenodes[pri]);
pri += 1;
};
// Package-test metadata is private and deterministic. Only the distinct
// generated-main package consumes these declarations through --import.
if (c.istestpackage != 0) {
d = file.list;
for (d != nil) {
if (wwiprimary(d) && d.kind == syntax.nkind.N_FNDECL
&& d.exported == 0 && wwihasattr(d, "test")) {
wwidecl(fd, d);
};
d = d.next;
};
};
// decls — exported primary, byte-sorted by symbol name.
let ndecl: i32 = 0; let ndecl: i32 = 0;
d = file.list; d = file.list;
for (d != nil) { for (d != nil) {
if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) { ndecl += 1; }; if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) {
ndecl += 1;
};
d = d.next; d = d.next;
}; };
let dkeys: []str;
let dnodes: []*syntax.node;
if (ndecl > 0) { if (ndecl > 0) {
let dkeys: []str = alloc([], ndecl: u64)!; let keys: []str = alloc([], ndecl: u64)!;
dkeys.len = ndecl; keys.len = ndecl;
let dnodes: []*syntax.node = alloc([], ndecl: u64)!; dkeys = keys;
dnodes.len = ndecl; let nodes: []*syntax.node = alloc([], ndecl: u64)!;
nodes.len = ndecl;
dnodes = nodes;
let k: i32 = 0; let k: i32 = 0;
d = file.list; d = file.list;
for (d != nil) { for (d != nil) {
@@ -1104,32 +1089,66 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
d = d.next; d = d.next;
}; };
wwisortdecls(dkeys, dnodes, ndecl); wwisortdecls(dkeys, dnodes, ndecl);
let i: i32 = 0; };
for (i < ndecl) {
wwidecl(fd, dnodes[i]); let fd: i32 = os.open(path,
i += 1; os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
if (fd < 0) {
wputs(2, "w6c: cannot open ");
wputs(2, path);
wputs(2, "\n");
return 1i32;
};
// Canonical ownership, declared name, and lexical source scope are
// independent export facts. Repeated owner sections preserve the file
// that owns each binding while every symbol/action remains keyed by owner.
let nsection: i32 = 0;
let p: *syntax.node = file.body;
for (p != nil) {
if (p.imported == 0 && wwiprimarysectionhas(c, file, &fs,
dnodes, ndecl, p.sourceid)) {
let owner: str = p.nmod;
if (owner.len == 0) { owner = file.nmod; };
let pkg: str = p.pkgname;
if (pkg.len == 0) { pkg = file.pkgname; };
wwiemitprimarysection(c, fd, file, &fs, dnodes, ndecl,
owner, pkg, p.sourceid);
nsection += 1;
}; };
p = p.next;
};
if (nsection == 0) {
wwiemitprimarysection(c, fd, file, &fs, dnodes, ndecl,
file.nmod, file.pkgname, wwifirstprimarysource(file));
}; };
// Compiler-owned public fact closure. Origin markers preserve nominal // Compiler-owned public fact closure. Origin markers preserve nominal
// ownership but do not create source imports in the eventual consumer. // ownership but do not create source imports in the eventual consumer.
let lastmod: str; let lastmod: str;
let lastsource: i32 = -1;
let fi: i32 = 0; let fi: i32 = 0;
for (fi < fs.nfacts) { for (fi < fs.nfacts) {
let mod: str = fs.factmods[fi]; let mod: str = fs.factmods[fi];
if (lastmod.len == 0 || !syntax.streq(lastmod, mod)) { let source: i32 = fs.factnodes[fi].sourceid;
if (lastmod.len == 0 || !syntax.streq(lastmod, mod)
|| lastsource != source) {
wputs(fd, "//ww:module "); wputs(fd, mod); wputs(fd, "\n"); wputs(fd, "//ww:module "); wputs(fd, mod); wputs(fd, "\n");
let dotidx: i32 = -1; let factpkg: str = fs.factnodes[fi].pkgname;
let mi: i32 = 0; if (factpkg.len == 0) {
for (mi < mod.len) { if (mod[mi] == 46u8) { dotidx = mi; }; mi += 1; }; let dotidx: i32 = -1;
let leaf: str = mod; let mi: i32 = 0;
if (dotidx >= 0) { for (mi < mod.len) { if (mod[mi] == 46u8) { dotidx = mi; }; mi += 1; };
leaf.ptr = mod.ptr + ((dotidx + 1): u64); factpkg = mod;
leaf.len = mod.len - dotidx - 1; if (dotidx >= 0) {
factpkg.ptr = mod.ptr + ((dotidx + 1): u64);
factpkg.len = mod.len - dotidx - 1;
};
}; };
wputs(fd, "package "); wputs(fd, leaf); wputs(fd, ";\n"); wputs(fd, "package "); wputs(fd, factpkg); wputs(fd, ";\n");
wwiemitfactimports(fd, file, mod); wwiemitimports(fd, file, mod, source, true);
lastmod = mod; lastmod = mod;
lastsource = source;
}; };
wwidecl(fd, fs.factnodes[fi]); wwidecl(fd, fs.factnodes[fi]);
fi += 1; fi += 1;

View File

@@ -17,7 +17,6 @@ import crypto.sha256;
import hash; import hash;
import os; import os;
import os.exec; import os.exec;
import rt;
import strings; import strings;
import syntax; import syntax;
@@ -893,6 +892,9 @@ type lflags = struct {
type sepbind = struct { type sepbind = struct {
kind: u8, kind: u8,
name: str, name: str,
source: str,
line: i32,
col: i32,
dep: i32, dep: i32,
}; };
@@ -919,6 +921,7 @@ type seppkg = struct {
root: bool, // requested usage; never package-action identity root: bool, // requested usage; never package-action identity
linkentry: bool, linkentry: bool,
generatedmain: bool, generatedmain: bool,
generatedtarget: i32,
failed: bool, failed: bool,
testsupport: bool, testsupport: bool,
loaded: bool, loaded: bool,
@@ -1609,15 +1612,13 @@ fn seprootiscommand(p: *seppkg) bool = {
}; };
fn sepforbiddencommandimport(g: *sepgraph, importer: i32, dep: i32) bool = { fn sepforbiddencommandimport(g: *sepgraph, importer: i32, dep: i32) bool = {
let from: *seppkg = &g.pkg[importer];
let to: *seppkg = &g.pkg[dep]; let to: *seppkg = &g.pkg[dep];
if (to.name == nil || !cstreqlit(to.name, "main") if (to.name == nil || !cstreqlit(to.name, "main")
|| to.role != SEP_ROLE_NORMAL) { return false; }; || to.role != SEP_ROLE_NORMAL) { return false; };
// An external test's exact colocated production edge is variant wiring, // An external test's exact colocated production edge is variant wiring,
// not a general source-importable command-package alias. // not a general source-importable command-package alias.
return !(from.variant == SEP_VARIANT_EXTERNAL return !(sepexternalproductionedge(g, importer, dep)
&& sepcommanddeclaredname(from) && sepexternalnamematchesproduction(g, importer, dep));
&& cstreq(from.canon, to.canon));
}; };
fn sepinternalparentcount(path: *u8, parents: *u64) bool = { fn sepinternalparentcount(path: *u8, parents: *u64) bool = {
@@ -2085,6 +2086,10 @@ fn sepgraphfree(g: *sepgraph) void = {
os.free(g.pkg[i].bindings[bi].name.ptr: *void, os.free(g.pkg[i].bindings[bi].name.ptr: *void,
g.pkg[i].bindings[bi].name.cap: u64); g.pkg[i].bindings[bi].name.cap: u64);
}; };
if (g.pkg[i].bindings[bi].source.ptr != nil) {
os.free(g.pkg[i].bindings[bi].source.ptr: *void,
g.pkg[i].bindings[bi].source.cap: u64);
};
bi += 1; bi += 1;
}; };
if (g.pkg[i].bindings.ptr != nil) { if (g.pkg[i].bindings.ptr != nil) {
@@ -2556,41 +2561,41 @@ fn sepvalidateartifactpaths(g: *sepgraph, scratch: *u8) i32 = {
return 0; return 0;
}; };
fn sepexternalname(pkg: *seppkg, path: *u8, n: u64, fn sepexternalname(pkg: *seppkg, path: *u8, n: u64) bool = {
leafonly: bool) bool = {
if (pkg.variant != SEP_VARIANT_EXTERNAL || pkg.testpackage == nil) { if (pkg.variant != SEP_VARIANT_EXTERNAL || pkg.testpackage == nil) {
return false; return false;
}; };
let begin: u64 = 0u64;
if (leafonly) {
let i: u64 = 0u64;
for (i < n) {
if (path[i] == '.') { begin = i + 1u64; };
i += 1u64;
};
};
let leafn: u64 = n - begin;
let tn: u64 = cstrlen(pkg.testpackage); let tn: u64 = cstrlen(pkg.testpackage);
if (tn != leafn + 5u64) { return false; }; if (tn != n + 5u64) { return false; };
if (bytecmp(pkg.testpackage, leafn, path + begin, leafn) != 0) { if (bytecmp(pkg.testpackage, n, path, n) != 0) {
return false; return false;
}; };
return pkg.testpackage[leafn] == '_' return pkg.testpackage[n] == '_'
&& pkg.testpackage[leafn + 1u64] == 't' && pkg.testpackage[n + 1u64] == 't'
&& pkg.testpackage[leafn + 2u64] == 'e' && pkg.testpackage[n + 2u64] == 'e'
&& pkg.testpackage[leafn + 3u64] == 's' && pkg.testpackage[n + 3u64] == 's'
&& pkg.testpackage[leafn + 4u64] == 't'; && pkg.testpackage[n + 4u64] == 't';
};
fn sepexternalproductionedge(g: *sepgraph, importer: i32, dep: i32) bool = {
let from: *seppkg = &g.pkg[importer];
let to: *seppkg = &g.pkg[dep];
return from.variant == SEP_VARIANT_EXTERNAL
&& to.variant == SEP_VARIANT_PRODUCTION
&& to.role == SEP_ROLE_NORMAL
&& cstreq(from.canon, to.canon);
};
fn sepexternalnamematchesproduction(g: *sepgraph, importer: i32,
dep: i32) bool = {
let from: *seppkg = &g.pkg[importer];
let to: *seppkg = &g.pkg[dep];
if (from.name == nil || to.name == nil) { return false; };
return sepexternalname(from, to.name, cstrlen(to.name));
}; };
fn sepbindadd(bindings: *[]sepbind, kind: u8, name: str, fn sepbindadd(bindings: *[]sepbind, kind: u8, name: str,
dep: i32) bool = { dep: i32, source: str, line: i32, col: i32) bool = {
let i: i32 = 0;
for (i < len(*bindings)) {
let b: sepbind = (*bindings)[i];
if (b.kind == kind && b.dep == dep
&& syntax.streq(b.name, name)) { return true; };
i += 1;
};
if (bindings.len == SEP_COUNT_MAX) { sepfailsize(); return false; }; if (bindings.len == SEP_COUNT_MAX) { sepfailsize(); return false; };
if (!sepreservebinds(bindings, bindings.len + 1)) { if (!sepreservebinds(bindings, bindings.len + 1)) {
return false; return false;
@@ -2601,9 +2606,24 @@ fn sepbindadd(bindings: *[]sepbind, kind: u8, name: str,
case let value: str => copied = value; case let value: str => copied = value;
case nomem => { sepfailnomem(); return false; }; case nomem => { sepfailnomem(); return false; };
}; };
let sourceallocation: (str | nomem) = sepdupstr(source);
let sourcecopy: str;
match (sourceallocation) {
case let value: str => sourcecopy = value;
case nomem => {
if (copied.ptr != nil) {
os.free(copied.ptr: *void, copied.cap: u64);
};
sepfailnomem();
return false;
};
};
append(*bindings, sepbind { append(*bindings, sepbind {
kind = kind, kind = kind,
name = copied, name = copied,
source = sourcecopy,
line = line,
col = col,
dep = dep, dep = dep,
}); });
return true; return true;
@@ -2616,6 +2636,12 @@ fn sepbindcmp(a: sepbind, b: sepbind) i32 = {
if (a.kind > b.kind) { return 1; }; if (a.kind > b.kind) { return 1; };
if (a.dep < b.dep) { return -1; }; if (a.dep < b.dep) { return -1; };
if (a.dep > b.dep) { return 1; }; if (a.dep > b.dep) { return 1; };
r = strings.compare(a.source, b.source): i32;
if (r != 0) { return r; };
if (a.line < b.line) { return -1; };
if (a.line > b.line) { return 1; };
if (a.col < b.col) { return -1; };
if (a.col > b.col) { return 1; };
return 0; return 0;
}; };
@@ -2633,17 +2659,63 @@ fn sepbindsort(bindings: *[]sepbind) void = {
}; };
}; };
fn sepbindsemanticsame(a: sepbind, b: sepbind) bool = {
return a.kind == b.kind && a.dep == b.dep
&& syntax.streq(a.name, b.name);
};
fn sepbindsame(a: []sepbind, b: []sepbind) bool = { fn sepbindsame(a: []sepbind, b: []sepbind) bool = {
if (len(a) != len(b)) { return false; }; let ai: i32 = 0;
let bi: i32 = 0;
for (ai < len(a) && bi < len(b)) {
if (!sepbindsemanticsame(a[ai], b[bi])) { return false; };
let av: sepbind = a[ai];
let bv: sepbind = b[bi];
ai += 1;
for (ai < len(a) && sepbindsemanticsame(av, a[ai])) { ai += 1; };
bi += 1;
for (bi < len(b) && sepbindsemanticsame(bv, b[bi])) { bi += 1; };
};
return ai == len(a) && bi == len(b);
};
fn sepvalidatebindings(g: *sepgraph, bindings: []sepbind) bool = {
let name: str;
let dep: i32 = -1;
let i: i32 = 0; let i: i32 = 0;
for (i < len(a)) { for (i < bindings.len) {
if (a[i].kind != b[i].kind || a[i].dep != b[i].dep let b: sepbind = bindings[i];
|| !syntax.streq(a[i].name, b[i].name)) { return false; }; if (b.kind == 'D': u8) {
if (name.len > 0 && syntax.streq(name, b.name) && dep != b.dep) {
cerrpos(b.source, b.line, b.col);
cerr(": error: package path "); cerr(b.name);
cerr(" resolves to both "); cerr(pathstr(g.pkg[dep].path));
cerr(" and "); cerr(pathstr(g.pkg[b.dep].path)); cerr("\n");
return false;
};
name = b.name;
dep = b.dep;
};
i += 1; i += 1;
}; };
return true; return true;
}; };
fn sepbindneedsmap(g: *sepgraph, b: sepbind) bool = {
return b.kind == 'D': u8 && b.dep >= 0 && b.dep < g.n
&& !syntax.streq(b.name, pathstr(g.pkg[b.dep].path));
};
fn sepbindfirstmap(g: *sepgraph, bindings: []sepbind, i: i32) bool = {
if (!sepbindneedsmap(g, bindings[i])) { return false; };
let j: i32 = i - 1;
for (j >= 0 && syntax.streq(bindings[j].name, bindings[i].name)) {
if (sepbindneedsmap(g, bindings[j])) { return false; };
j -= 1;
};
return true;
};
fn sepchildrenadd(children: *[]sepchild, pkg: i32, context: i32) bool = { fn sepchildrenadd(children: *[]sepchild, pkg: i32, context: i32) bool = {
let i: i32 = 0; let i: i32 = 0;
for (i < children.len) { for (i < children.len) {
@@ -2780,6 +2852,18 @@ fn sepresolvesourceimport(g: *sepgraph, context: i32, name: *u8,
return 1; return 1;
}; };
fn sepusecmp(a: *syntax.node, b: *syntax.node) i32 = {
let r: i32 = strings.compare(a.usepath, b.usepath): i32;
if (r != 0) { return r; };
r = strings.compare(a.file, b.file): i32;
if (r != 0) { return r; };
if (a.line < b.line) { return -1; };
if (a.line > b.line) { return 1; };
if (a.col < b.col) { return -1; };
if (a.col > b.col) { return 1; };
return 0;
};
// Scan one already-selected source file for its leading package clause // Scan one already-selected source file for its leading package clause
// (when it is an owned directory source) and top-level imports. A DIRECTORY // (when it is an owned directory source) and top-level imports. A DIRECTORY
// import is a package boundary: add as a direct dep of pi. A FILE import is an // import is a package boundary: add as a direct dep of pi. A FILE import is an
@@ -2880,8 +2964,7 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32,
for (si < nuse) { for (si < nuse) {
let sj: i32 = si; let sj: i32 = si;
for (sj > 0) { for (sj > 0) {
if (strings.compare(uses[sj - 1].usepath, if (sepusecmp(uses[sj - 1], uses[sj]) <= 0) { sj = 0; }
uses[sj].usepath) <= 0) { sj = 0; }
else { else {
let t: *syntax.node = uses[sj]; let t: *syntax.node = uses[sj];
uses[sj] = uses[sj - 1]; uses[sj] = uses[sj - 1];
@@ -2891,14 +2974,9 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32,
}; };
si += 1; si += 1;
}; };
let previous: str = "";
ui = 0; ui = 0;
for (ui < nuse) { for (ui < nuse) {
u = uses[ui]; u = uses[ui];
let duplicate: bool = previous.len > 0
&& syntax.streq(previous, u.usepath);
if (!duplicate) {
previous = u.usepath;
let idp: *u8 = u.usepath.ptr; let idp: *u8 = u.usepath.ptr;
let idn: u64 = u.usepath.len: u64; let idn: u64 = u.usepath.len: u64;
if (reservedimport(u.usepath)) { if (reservedimport(u.usepath)) {
@@ -2933,11 +3011,10 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32,
oi += 1u64; oi += 1u64;
}; };
let literalself: bool = routesuffix != nil let literalself: bool = routesuffix != nil
&& cstreq(pathform, routesuffix) && cstreq(pathform, routesuffix);
&& sepexternalname(&g.pkg[pi], idp, idn, true);
if (routesuffix == nil) { if (routesuffix == nil) {
literalself = ordinaryleaf literalself = ordinaryleaf
&& sepexternalname(&g.pkg[pi], idp, idn, false); && sepexternalname(&g.pkg[pi], idp, idn);
}; };
if (literalself) { if (literalself) {
if (g.pkg[pi].importbase != nil) { if (g.pkg[pi].importbase != nil) {
@@ -2961,9 +3038,7 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32,
let externalproduction: bool = false; let externalproduction: bool = false;
let self: bool = os.samefile(pathstr(resolved.entry), let self: bool = os.samefile(pathstr(resolved.entry),
pathstr(g.pkg[pi].entry)); pathstr(g.pkg[pi].entry));
if (self && (sepexternalname(&g.pkg[pi], idp, idn, true) if (self && g.pkg[pi].variant == SEP_VARIANT_EXTERNAL) {
|| (g.pkg[pi].variant == SEP_VARIANT_EXTERNAL
&& sepcommanddeclaredname(&g.pkg[pi])))) {
externalproduction = true; externalproduction = true;
}; };
if (self && !externalproduction) { if (self && !externalproduction) {
@@ -3010,26 +3085,19 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32,
let childcontext: i32 = sepchildcontextfor(g, context, let childcontext: i32 = sepchildcontextfor(g, context,
resolved.entry, resolved.sourceroot); resolved.entry, resolved.sourceroot);
if (childcontext < 0 if (childcontext < 0
|| !sepbindadd(bindings, 'D': u8, u.usepath, di) || !sepbindadd(bindings, 'D': u8, u.usepath, di,
u.file, u.line, u.col)
|| !sepadddep(g, pi, di) || !sepadddep(g, pi, di)
|| !sepchildrenadd(children, di, childcontext)) { || !sepchildrenadd(children, di, childcontext)) {
return -1; return -1;
}; };
} else { } else {
let lstart: u64 = 0u64;
let lk: u64 = 0u64;
for (lk < idn) {
if (idp[lk] == 46u8) { lstart = lk + 1u64; }; // '.'
lk += 1u64;
};
let leafp: *u8 = idp + lstart;
let leafn: u64 = idn - lstart;
let inlinepackage: bool = false; let inlinepackage: bool = false;
if (g.pkg[pi].isdir == 0) { if (g.pkg[pi].isdir == 0) {
let pm: *syntax.node = imports.body; let pm: *syntax.node = imports.body;
for (pm != nil) { for (pm != nil) {
if (bytecmp(pm.nmod.ptr, pm.nmod.len: u64, if (bytecmp(pm.nmod.ptr, pm.nmod.len: u64,
leafp, leafn) == 0) { inlinepackage = true; }; idp, idn) == 0) { inlinepackage = true; };
pm = pm.next; pm = pm.next;
}; };
}; };
@@ -3040,13 +3108,13 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32,
cerr("\n"); cerr("\n");
return -1; return -1;
} else { } else {
if (!sepbindadd(bindings, 'I': u8, u.usepath, -1)) { if (!sepbindadd(bindings, 'I': u8, u.usepath, -1,
u.file, u.line, u.col)) {
return -1; return -1;
}; };
}; };
}; };
}; ui += 1;
ui += 1;
}; };
return 0; return 0;
}; };
@@ -3160,6 +3228,7 @@ fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32,
p.root = true; p.root = true;
p.linkentry = true; p.linkentry = true;
p.generatedmain = true; p.generatedmain = true;
p.generatedtarget = variant;
p.failed = false; p.failed = false;
p.testsupport = false; p.testsupport = false;
p.loaded = true; p.loaded = true;
@@ -3239,30 +3308,12 @@ fn seppreparepkgcontext(g: *sepgraph, pi: i32, context: i32,
}; };
i += 1; i += 1;
}; };
if (rc == 0 && g.pkg[pi].path[0u64] != 0u8
&& !g.pkg[pi].testsupport) {
let plen: u64 = cstrlen(g.pkg[pi].path);
let leaf: *u8 = g.pkg[pi].path;
let j: u64 = 0u64;
for (j < plen) {
if (g.pkg[pi].path[j] == '.') { leaf = g.pkg[pi].path + j + 1u64; };
j += 1u64;
};
if (!cstreq(g.pkg[pi].name, leaf)
&& !sepcommanddeclaredname(&g.pkg[pi])) {
cerr("ww: package ");
cerr(pathstr(g.pkg[pi].name));
cerr(" does not match import path ");
cerr(pathstr(g.pkg[pi].path));
cerr("\n");
rc = -1;
};
};
} else { if (rc == 0) { } else { if (rc == 0) {
rc = sepscanfile(g, pi, g.pkg[pi].entry, context, rc = sepscanfile(g, pi, g.pkg[pi].entry, context,
&fv, &bindings, children, 0); &fv, &bindings, children, 0);
}; }; }; };
sepbindsort(&bindings); sepbindsort(&bindings);
if (rc == 0 && !sepvalidatebindings(g, bindings)) { rc = -1; };
if (rc == 0 && g.pkg[pi].emitcontext < 0) { if (rc == 0 && g.pkg[pi].emitcontext < 0) {
g.pkg[pi].bindings = bindings; g.pkg[pi].bindings = bindings;
g.pkg[pi].emitcontext = context; g.pkg[pi].emitcontext = context;
@@ -3415,6 +3466,20 @@ fn seploadpkg(g: *sepgraph, pi: i32, context: i32) i32 = {
if (f.pendingdep >= 0) { if (f.pendingdep >= 0) {
let dep: i32 = f.pendingdep; let dep: i32 = f.pendingdep;
f.pendingdep = -1; f.pendingdep = -1;
if (dep != f.pkg && sepexternalproductionedge(g, f.pkg, dep)
&& !sepexternalnamematchesproduction(g, f.pkg, dep)) {
cerr("ww: external test package ");
cerr(pathstr(g.pkg[f.pkg].name));
cerr(" does not match production package ");
cerr(pathstr(g.pkg[dep].name)); cerr("\n");
let fi: i32 = 0;
for (fi < nframe) {
g.pkg[frames[fi].pkg].failed = true;
sepclearchildren(&frames[fi].children);
fi += 1;
};
return sepfinishloadframes(frames, -1);
};
if (dep != f.pkg && sepforbiddencommandimport(g, f.pkg, dep)) { if (dep != f.pkg && sepforbiddencommandimport(g, f.pkg, dep)) {
cerr("ww: package "); cerr("ww: package ");
if (g.pkg[dep].path[0u64] != 0u8) { if (g.pkg[dep].path[0u64] != 0u8) {
@@ -3603,30 +3668,14 @@ fn sepreverseimportbase(g: *sepgraph, p: *seppkg, context: i32,
return 0; return 0;
}; };
fn sepordinarydeclaredname(p: *seppkg) *u8 = {
if (p.name == nil || p.name[0u64] == 0u8) { return nil; };
let n: u64 = cstrlen(p.name);
if (p.variant == SEP_VARIANT_EXTERNAL) {
if (n <= 5u64 || !cstrendswithlit(p.name, "_test")) {
cerr("ww: package-test selector does not name an external package\n");
return nil;
};
n -= 5u64;
};
return sepdupcstr(p.name, n);
};
// The reserved local namespace is reversible, so filesystem identity never // 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.
fn seplocalimportbase(p: *seppkg) *u8 = { fn seplocalimportbase(p: *seppkg) *u8 = {
let leaf: *u8 = sepordinarydeclaredname(p);
if (leaf == nil) { return nil; };
let need: u64 = 0u64; let need: u64 = 0u64;
if (!sepaddbytes(&need, SEP_LOCAL_IMPORT_PREFIX.len: u64) if (!sepaddbytes(&need, SEP_LOCAL_IMPORT_PREFIX.len: u64)
|| !sepaddbytes(&need, 2u64) || !sepaddbytes(&need, 2u64)
|| !sepmuladdbytes(&need, cstrlen(p.canon), 4u64) || !sepmuladdbytes(&need, cstrlen(p.canon), 4u64)
|| !sepaddbytes(&need, 1u64)
|| !sepaddbytes(&need, cstrlen(leaf))
|| !sepaddbytes(&need, 1u64)) { return nil; }; || !sepaddbytes(&need, 1u64)) { return nil; };
let out: []u8; let out: []u8;
if (!sepmakebytes(need, &out)) { return nil; }; if (!sepmakebytes(need, &out)) { return nil; };
@@ -3656,8 +3705,6 @@ fn seplocalimportbase(p: *seppkg) *u8 = {
}; }; }; };
i += 1u64; i += 1u64;
}; };
off = byteinto(out.ptr, off, '.': u8);
off = cstrinto(out.ptr, off, leaf);
cstrseal(out.ptr, off); cstrseal(out.ptr, off);
return out.ptr; return out.ptr;
}; };
@@ -3725,23 +3772,6 @@ fn sepfinalizedirectoryidentities(g: *sepgraph) i32 = {
for (pi < g.n) { for (pi < g.n) {
let p: *seppkg = &g.pkg[pi]; let p: *seppkg = &g.pkg[pi];
if (p.isdir != 0 && !p.generatedmain && !p.failed && p.loaded) { if (p.isdir != 0 && !p.generatedmain && !p.failed && p.loaded) {
let plen: u64 = cstrlen(p.path);
let leaf: *u8 = p.path;
let j: u64 = 0u64;
for (j < plen) {
if (p.path[j] == '.') { leaf = p.path + j + 1u64; };
j += 1u64;
};
let supportalias: bool = p.role == SEP_ROLE_TEST_SUPPORT
&& cstreqlit(p.path, SEP_TEST_SUPPORT_MODULE)
&& cstreqlit(p.name, "test");
if (!supportalias && !cstreq(p.name, leaf)
&& !sepcommanddeclaredname(p)) {
cerr("ww: package "); cerr(pathstr(p.name));
cerr(" does not match import path "); cerr(pathstr(p.path));
cerr("\n");
return -1;
};
p.artifact = nil; p.artifact = nil;
if (p.variant == SEP_VARIANT_SAME_TEST) { if (p.variant == SEP_VARIANT_SAME_TEST) {
p.artifact = sepappendlit(p.path, "-internal-test"); p.artifact = sepappendlit(p.path, "-internal-test");
@@ -4014,8 +4044,7 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, unitf: *u8) i32 = {
let bi: i32 = 0; let bi: i32 = 0;
for (bi < g.pkg[pi].bindings.len && bodyrc == 0) { for (bi < g.pkg[pi].bindings.len && bodyrc == 0) {
let b: sepbind = g.pkg[pi].bindings[bi]; let b: sepbind = g.pkg[pi].bindings[bi];
if (b.kind == 'D': u8 && b.dep >= 0 && b.dep < g.n if (sepbindfirstmap(g, g.pkg[pi].bindings, bi)) {
&& !syntax.streq(b.name, pathstr(g.pkg[b.dep].path))) {
let pre: str = "//ww:import-map "; let pre: str = "//ww:import-map ";
let space: str = " "; let space: str = " ";
let newline: str = "\n"; let newline: str = "\n";
@@ -4331,14 +4360,14 @@ fn validatecommandoutputpath(out: *u8) i32 = {
fn workdirstamptext(istest: i32, emitasm: i32) str = { fn workdirstamptext(istest: i32, emitasm: i32) str = {
if (istest != 0) { if (istest != 0) {
if (emitasm != 0) { if (emitasm != 0) {
return "ww workdir fmt 12 mode test asm 1\n"; return "ww workdir fmt 13 mode test asm 1\n";
}; };
return "ww workdir fmt 12 mode test asm 0\n"; return "ww workdir fmt 13 mode test asm 0\n";
}; };
if (emitasm != 0) { if (emitasm != 0) {
return "ww workdir fmt 13 mode build asm 1\n"; return "ww workdir fmt 14 mode build asm 1\n";
}; };
return "ww workdir fmt 13 mode build asm 0\n"; return "ww workdir fmt 14 mode build asm 0\n";
}; };
fn stampmatches(path: *u8, want: str) bool = { fn stampmatches(path: *u8, want: str) bool = {
@@ -4434,6 +4463,22 @@ fn invalidateworkdirunits(scratch: *u8) i32 = {
return rc; return rc;
}; };
fn sepdiscardactionstaging(warm: bool, unit: *u8, wwi: *u8,
assembly: *u8, object: *u8, archive: *u8) i32 = {
if (!warm) { return 0; };
let paths: []*u8 = [unit, wwi, assembly, object, archive];
let i: i32 = 0;
for (i < paths.len) {
let rr: i32 = os.remove(pathstr(paths[i]));
if (rr != 0 && rr != -2) {
cerr("ww: cannot remove staged package artifacts\n");
return -1;
};
i += 1;
};
return 0;
};
type sepcreateddirs = struct { type sepcreateddirs = struct {
path: [4096]u8, path: [4096]u8,
offset: [2048]u16, offset: [2048]u16,
@@ -5092,7 +5137,16 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
cu = unitnew; cw = wwinew; cs = asmnew; cu = unitnew; cw = wwinew; cs = asmnew;
co = objnew; ca = anew; co = objnew; ca = anew;
}; };
if (sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew) < 0) {
g.pkg[pi].failed = true;
anyfailed = true;
oi += 1;
continue;
};
if (sepcomposeunit(g, pi, cu) < 0) { if (sepcomposeunit(g, pi, cu) < 0) {
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
g.pkg[pi].failed = true; g.pkg[pi].failed = true;
anyfailed = true; anyfailed = true;
oi += 1; oi += 1;
@@ -5122,7 +5176,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
}; };
}; };
}; };
if (sepfatalallocation) { return 1; }; if (sepfatalallocation) {
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
return 1;
};
if (fresh) { if (fresh) {
if (os.remove(pathstr(unitnew)) != 0) { if (os.remove(pathstr(unitnew)) != 0) {
cerrpath("ww: cannot remove ", unitnew, "\n"); cerrpath("ww: cannot remove ", unitnew, "\n");
@@ -5144,17 +5202,19 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
let nmaps: i32 = 0; let nmaps: i32 = 0;
let mapk: i32 = 0; let mapk: i32 = 0;
for (mapk < g.pkg[pi].bindings.len) { for (mapk < g.pkg[pi].bindings.len) {
let b: sepbind = g.pkg[pi].bindings[mapk]; if (sepbindfirstmap(g, g.pkg[pi].bindings, mapk)) {
if (b.kind == 'D': u8 && b.dep >= 0 && b.dep < g.n if (nmaps == SEP_COUNT_MAX) {
&& !syntax.streq(b.name, sepfailsize();
pathstr(g.pkg[b.dep].path))) { sepdiscardactionstaging(warm, unitnew, wwinew,
if (nmaps == SEP_COUNT_MAX) { sepfailsize(); return 1; }; asmnew, objnew, anew);
return 1;
};
nmaps += 1; nmaps += 1;
}; };
mapk += 1; mapk += 1;
}; };
let alen: i32 = 8; let alen: i32 = 8;
if (gent) { alen += 4; } if (gent) { alen += 4; if (g.pkg[pi].generatedmain) { alen += 2; }; }
else { else {
if (testpkg) { alen += 1; }; if (testpkg) { alen += 1; };
if (commandpkg) { alen += 1; }; if (commandpkg) { alen += 1; };
@@ -5163,11 +5223,15 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
}; };
if (g.pkg[pi].ndeps > (SEP_COUNT_MAX - alen) / 3) { if (g.pkg[pi].ndeps > (SEP_COUNT_MAX - alen) / 3) {
sepfailsize(); sepfailsize();
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
return 1; return 1;
}; };
alen += g.pkg[pi].ndeps * 3; alen += g.pkg[pi].ndeps * 3;
if (nmaps > (SEP_COUNT_MAX - alen) / 3) { if (nmaps > (SEP_COUNT_MAX - alen) / 3) {
sepfailsize(); sepfailsize();
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
return 1; return 1;
}; };
alen += nmaps * 3; alen += nmaps * 3;
@@ -5175,7 +5239,12 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
let argv: []str; let argv: []str;
match (allocation) { match (allocation) {
case let value: []str => argv = value; case let value: []str => argv = value;
case nomem => { sepfailnomem(); return 1; }; case nomem => {
sepfailnomem();
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
return 1;
};
}; };
append(argv, "w6c"); append(argv, "w6c");
if (gent) { if (gent) {
@@ -5183,6 +5252,10 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
append(argv, "--entry"); append(argv, "--entry");
append(argv, "--test-support-module"); append(argv, "--test-support-module");
append(argv, testsupportmodule); append(argv, testsupportmodule);
if (g.pkg[pi].generatedmain) {
append(argv, "--test-target-package");
append(argv, pathstr(g.pkg[g.pkg[pi].generatedtarget].path));
};
} else { } else {
if (testpkg) { append(argv, "--test-package"); }; if (testpkg) { append(argv, "--test-package"); };
if (commandpkg) { append(argv, "--command-package"); }; if (commandpkg) { append(argv, "--command-package"); };
@@ -5199,16 +5272,18 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
append(argv, "--import"); append(argv, "--import");
append(argv, pathstr(g.pkg[dj].path)); append(argv, pathstr(g.pkg[dj].path));
let depinterface: *u8 = sepfname(g, dj, scratch, ".wwi"); let depinterface: *u8 = sepfname(g, dj, scratch, ".wwi");
if (depinterface == nil) { return 1; }; if (depinterface == nil) {
sepdiscardactionstaging(warm, unitnew, wwinew,
asmnew, objnew, anew);
return 1;
};
append(argv, pathstr(depinterface)); append(argv, pathstr(depinterface));
importk += 1; importk += 1;
}; };
mapk = 0; mapk = 0;
for (mapk < g.pkg[pi].bindings.len) { for (mapk < g.pkg[pi].bindings.len) {
let b: sepbind = g.pkg[pi].bindings[mapk]; let b: sepbind = g.pkg[pi].bindings[mapk];
if (b.kind == 'D': u8 && b.dep >= 0 && b.dep < g.n if (sepbindfirstmap(g, g.pkg[pi].bindings, mapk)) {
&& !syntax.streq(b.name,
pathstr(g.pkg[b.dep].path))) {
append(argv, "--import-map"); append(argv, "--import-map");
append(argv, b.name); append(argv, b.name);
append(argv, pathstr(g.pkg[b.dep].path)); append(argv, pathstr(g.pkg[b.dep].path));
@@ -5237,6 +5312,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
}; };
g.pkg[pi].failed = true; g.pkg[pi].failed = true;
anyfailed = true; anyfailed = true;
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
oi += 1; oi += 1;
continue; continue;
}; };
@@ -5244,13 +5321,22 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
if (!warm || !fileequal(wwinew, wwi)) { if (!warm || !fileequal(wwinew, wwi)) {
g.pkg[pi].exportchanged = true; g.pkg[pi].exportchanged = true;
}; };
if (sepfatalallocation) { return 1; }; if (sepfatalallocation) {
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
return 1;
};
if (emitasm == 0) { if (emitasm == 0) {
let argallocation: ([]str | nomem) = sepallocstrs(4); let argallocation: ([]str | nomem) = sepallocstrs(4);
let argv: []str; let argv: []str;
match (argallocation) { match (argallocation) {
case let value: []str => argv = value; case let value: []str => argv = value;
case nomem => { sepfailnomem(); return 1; }; case nomem => {
sepfailnomem();
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
return 1;
};
}; };
append(argv, "w6a"); append(argv, "w6a");
append(argv, "-o"); append(argv, "-o");
@@ -5273,6 +5359,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
}; };
g.pkg[pi].failed = true; g.pkg[pi].failed = true;
anyfailed = true; anyfailed = true;
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
oi += 1; oi += 1;
continue; continue;
}; };
@@ -5284,6 +5372,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
cerr("ww: archive failed\n"); cerr("ww: archive failed\n");
g.pkg[pi].failed = true; g.pkg[pi].failed = true;
anyfailed = true; anyfailed = true;
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
oi += 1; oi += 1;
continue; continue;
}; };
@@ -5328,6 +5418,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
}; };
g.pkg[pi].failed = true; g.pkg[pi].failed = true;
anyfailed = true; anyfailed = true;
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
oi += 1; oi += 1;
continue; continue;
}; };

View File

@@ -148,7 +148,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
// silently). Mirrors w6c main.ww:162 / cmd/w6c/main.c. // silently). Mirrors w6c main.ww:162 / cmd/w6c/main.c.
if (l.errs > 0 || ps.errs > 0) { return 1; }; if (l.errs > 0 || ps.errs > 0) { return 1; };
let empty: str; let empty: str;
if (wcc.compilefile(f, 0, 0, empty, 0, empty, 0) != 0) { return 1; }; if (wcc.compilefile(f, 0, 0, empty, empty, 0, empty, 0) != 0) { return 1; };
};};};}; };};};};
if (l.errs > 0) { return 1; }; if (l.errs > 0) { return 1; };