From 6efe9b70d40f39ce4d263696821476bdab0a16d6 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 14 Aug 2026 03:07:58 +0900 Subject: [PATCH] ww: separate package identity from declared name --- cmd/w6c/cgen.c | 60 +++-- cmd/w6c/gc.h | 2 + cmd/w6c/main.c | 143 ++++++++++-- cmd/w6c/wwi.c | 287 ++++++++++++----------- cmd/wcc/check.c | 198 +++++++++++++--- cmd/wcc/parse.c | 95 ++++---- cmd/wcc/ww.h | 36 +-- cmd/ww/main.c | 320 ++++++++++++++++---------- lib/crypto/sha256/sha256.ww | 1 - lib/hash/hash.ww | 1 - lib/io/stream.ww | 2 + lib/os/os.ww | 9 +- lib/os/os_test.ww | 1 - lib/ww/syntax/ast.ww | 9 +- lib/ww/syntax/decl.ww | 5 +- lib/ww/syntax/expr.ww | 2 - lib/ww/syntax/parse.ww | 99 ++++---- lib/ww/syntax/stmt.ww | 2 - lib/ww/syntax/tok.ww | 4 +- lib/ww/syntax/typ.ww | 2 - selfhost/cmd/w6a/asm.ww | 1 - selfhost/cmd/w6a/main.ww | 1 - selfhost/cmd/w6c/main.ww | 184 +++++++++++++-- selfhost/cmd/w6l/dyn.ww | 1 - selfhost/cmd/w6l/dynout.ww | 1 - selfhost/cmd/w6l/main.ww | 1 - selfhost/cmd/w6l/obj.ww | 1 - selfhost/cmd/w6l/out.ww | 1 - selfhost/cmd/wcc/api.ww | 4 +- selfhost/cmd/wcc/cgen.ww | 54 +++-- selfhost/cmd/wcc/cgendecl.ww | 2 +- selfhost/cmd/wcc/cgenexpr.ww | 1 - selfhost/cmd/wcc/cgenutil.ww | 1 - selfhost/cmd/wcc/check.ww | 230 ++++++++++++++---- selfhost/cmd/wcc/wwi.ww | 435 ++++++++++++++++++----------------- selfhost/cmd/ww/main.ww | 366 ++++++++++++++++++----------- selfhost/cmd/wwdump/main.ww | 2 +- 37 files changed, 1678 insertions(+), 886 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 78345a59..7198025b 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -1226,33 +1226,31 @@ struct Use { const char *alias; const char *path; const char *module; /* owning module of the `use` decl (#40) */ + int sourceid; /* owning lexical source-file scope */ Use *next; }; static Use *use_map; /* - * use_hint — map a `use` alias to its dotted import path for the - * qualified-ref mangle hint. NOT file-global: two modules in one unit - * may bind the same leaf alias to different paths (#40 — module one's - * `import a.math` and module two's `import b.math` both alias `math`). - * The import declared in the SAME module as the reference (curmod) is - * authoritative; preferring it routes each `math.pick()` to its own - * package. Falls back to any matching alias when curmod has no own - * import (single-occurrence case). Mirrors the checker's use_path - * curmod-preference (check.c, M1 55f54fb). Returns the alias unchanged - * when no `use` matches. + * use_hint — map a declared default qualifier to its canonical import path + * for qualified-ref mangling. It is source-file local: separate files may + * bind the same declared name to different paths. Raw non-package compilation + * retains its historical single-occurrence fallback. Returns the qualifier + * unchanged when no `use` matches. */ static const char * -use_hint(const char *curmod, const char *alias) +use_hint(Cg *c, const char *alias) { const char *any = NULL; if (alias == NULL) return alias; for (Use *u = use_map; u; u = u->next) { if (strcmp(u->alias, alias) != 0) continue; - int same = (u->module == NULL) ? (curmod == NULL) - : (curmod != NULL && strcmp(u->module, curmod) == 0); + int same = u->sourceid == c->cur_source + && ((u->module == NULL) ? (c->cur_mod == NULL) + : (c->cur_mod != NULL + && strcmp(u->module, c->cur_mod) == 0)); if (same) return u->path; - if (any == NULL) any = u->path; + if (!c->sep_mode && any == NULL) any = u->path; } return any ? any : alias; } @@ -1484,6 +1482,7 @@ mod_collect(Cg *c, Node *file) u->alias = d->str; u->path = d->usepath; u->module = d->module; + u->sourceid = d->sourceid; u->next = use_map; use_map = u; continue; @@ -4564,7 +4563,7 @@ cgexpr(Cg *c, Node *n, Local *locals) if (lu && lu->kind == TY_FN) ins2(c, A_LEAQ, mafn(c, opnd->str, - use_hint(c->cur_mod, opnd->lhs->str)), + use_hint(c, opnd->lhs->str)), areg(D_AX)); else /* #229: dotted-module value @@ -4573,7 +4572,7 @@ cgexpr(Cg *c, Node *n, Local *locals) * same-leaf collision. */ ins2(c, A_LEAQ, mafn(c, opnd->str, - use_hint(c->cur_mod, opnd->lhs->str)), + use_hint(c, opnd->lhs->str)), areg(D_AX)); break; } @@ -10976,7 +10975,7 @@ cgexpr(Cg *c, Node *n, Local *locals) * same-leaf exports resolve correctly. */ ins1(c, A_CALL, mafn(c, n->lhs->str, - use_hint(c->cur_mod, n->lhs->lhs->str))); + use_hint(c, n->lhs->lhs->str))); } else { cgexpr(c, n->lhs, locals); /* AX = fn ptr */ ins1(c, A_CALL, areg(D_AX)); @@ -11906,7 +11905,7 @@ cgexpr(Cg *c, Node *n, Local *locals) /* `mod.fn` address-of via N_DOT — pass the * module bareword as the disambiguation hint. */ ins2(c, A_LEAQ, - mafn(c, n->str, use_hint(c->cur_mod, n->lhs->str)), + mafn(c, n->str, use_hint(c, n->lhs->str)), areg(D_AX)); break; } @@ -11923,7 +11922,7 @@ cgexpr(Cg *c, Node *n, Local *locals) for (s = sdefs; s; s = s->next) { if (strcmp(s->name, n->str) != 0) continue; - if (sdef_mod_match_hint(s, use_hint(c->cur_mod, n->lhs->str))) + if (sdef_mod_match_hint(s, use_hint(c, n->lhs->str))) break; } if (s == NULL) { @@ -11955,11 +11954,11 @@ cgexpr(Cg *c, Node *n, Local *locals) * branch above already uses n->lhs->str via mafn. */ if (mqop == A_MOVQ) { ins2(c, A_MOVQ, - mafn(c, n->str, use_hint(c->cur_mod, n->lhs->str)), + mafn(c, n->str, use_hint(c, n->lhs->str)), areg(D_AX)); } else { ins2(c, A_LEAQ, - mafn(c, n->str, use_hint(c->cur_mod, n->lhs->str)), + mafn(c, n->str, use_hint(c, n->lhs->str)), areg(D_CX)); ins2(c, mqop, amem(D_CX, 0), areg(D_AX)); } @@ -15800,6 +15799,7 @@ cgfn(Cg *c, FILE *out, Node *fn) c->head = c->tail = NULL; c->fnname = fn->str; c->cur_mod = (fn->module && fn->module[0]) ? fn->module : NULL; + c->cur_source = fn->sourceid; c->labelseq = 0; cg_stack_arg_cursor = 0; ndefers = 0; @@ -16913,7 +16913,7 @@ node_fnptr_sym(Cg *c, Node *ev) return NULL; Type *du = type_chase_named(opnd->type); if (du == NULL || du->kind != TY_FN) return NULL; - return mod_mangle_fn(c, opnd->str, use_hint(c->cur_mod, opnd->lhs->str)); + return mod_mangle_fn(c, opnd->str, use_hint(c, opnd->lhs->str)); } if (opnd == NULL || opnd->kind != N_IDENT) return NULL; Type *ou = type_chase_named(opnd->type); @@ -17205,8 +17205,12 @@ emit_slice_data(FILE *out, Cg *c, const char *directive, const char *name, static void emit_lets(Cg *c, FILE *out, Node *file) { + const char *save_mod = c->cur_mod; + int save_source = c->cur_source; for (Node *d = file->list; d; d = d->next) { if (d->kind != N_LET) continue; + c->cur_mod = (d->module && d->module[0]) ? d->module : NULL; + c->cur_source = d->sourceid; if (d->str == NULL || d->str[0] == '\0') continue; /* #22 M3 THE ONE REAL GUARD: a `.wwi` dep value-global is * initializer-less; emitting a DATAW for it would DUPLICATE the @@ -17377,6 +17381,8 @@ emit_lets(Cg *c, FILE *out, Node *file) emit_data_row_zero(out, "DATAW", mod_mangle_fn(c, d->str, d->module), sz); } + c->cur_mod = save_mod; + c->cur_source = save_source; } /* Emit DATA directives for top-level `def` constants whose value @@ -17392,8 +17398,12 @@ emit_lets(Cg *c, FILE *out, Node *file) static void emit_defs(Cg *c, FILE *out, Node *file) { + const char *save_mod = c->cur_mod; + int save_source = c->cur_source; for (Node *d = file->list; d; d = d->next) { if (d->kind != N_DEF || d->rhs == NULL) continue; + c->cur_mod = (d->module && d->module[0]) ? d->module : NULL; + c->cur_source = d->sourceid; /* #22 M3: a `.wwi` dep def with DATA storage (int-fold / float / * struct / array) must NOT re-emit — the dep's own .o owns the * symbol. Str defs are inline-spliced (sdef_collect), never @@ -17455,7 +17465,8 @@ emit_defs(Cg *c, FILE *out, Node *file) "asm.c:362); read-only `def` unsupported (#10, " "rule 7)"); } - (void)c; + c->cur_mod = save_mod; + c->cur_source = save_source; } /* Collect str-typed `def`s so cgexpr N_IDENT can splice them inline. @@ -17537,8 +17548,10 @@ let_pre_intern(Cg *c, Node *file) * they did before. let_pre_intern itself only interns, so driving * cur_mod here has no other effect. */ const char *save_mod = c->cur_mod; + int save_source = c->cur_source; for (Node *d = file->list; d; d = d->next) { c->cur_mod = (d->module && d->module[0]) ? d->module : NULL; + c->cur_source = d->sourceid; /* #22 M3: skip imported deps so the strlit table (and its _S_ * sequence) is a pure function of THIS package's own decls. A * dep's body initializer would intern here, but its `.wwi` @@ -17658,6 +17671,7 @@ let_pre_intern(Cg *c, Node *file) (void)intern_strlit(c, r->str, r->strlen); } c->cur_mod = save_mod; + c->cur_source = save_source; } void diff --git a/cmd/w6c/gc.h b/cmd/w6c/gc.h index c28061e3..d603108b 100644 --- a/cmd/w6c/gc.h +++ b/cmd/w6c/gc.h @@ -38,6 +38,8 @@ struct Cg { * lib/foo binds to `foo.frob` regardless * of which other modules also export `frob`. * Set by cgfn before walking the body. */ + int cur_source; /* lexical source-file scope of the current + * declaration; selects its own import bindings. */ int framesize; /* bytes of locals; 16-byte aligned */ int curoff; /* current top of locals */ Scope *locals; /* (name → offset) tracked via Sym */ diff --git a/cmd/w6c/main.c b/cmd/w6c/main.c index 53ec7c45..d659983e 100644 --- a/cmd/w6c/main.c +++ b/cmd/w6c/main.c @@ -39,6 +39,7 @@ struct importin { const char *file; char *buf; u64 len; + Node *ast; }; struct importmap { @@ -47,13 +48,6 @@ struct importmap { int seen; }; -static const char * -importleaf(const char *path) -{ - const char *dot = strrchr(path, '.'); - return dot != NULL ? dot + 1 : path; -} - static Node * parseinput(Arena *a, const char *file, char *buf, u64 len, const char *mod, const char *testsupport, int commandpackage, int *bad) @@ -73,6 +67,79 @@ parseinput(Arena *a, const char *file, char *buf, u64 len, return f; } +/* Export data carries canonical owner and declared package name separately. + * Search every direct interface's package-clause markers because its + * self-contained fact closure can also name a transitive owner. */ +static const char * +import_pkgname(struct importin *imports, int nimports, const char *path, + Node *primary, int *conflict) +{ + const char *name = NULL; + for (int i = 0; i < nimports; i++) { + Node *file = imports[i].ast; + for (Node *p = file ? file->body : NULL; p; p = p->next) { + if (p->module == NULL || p->pkgname == NULL + || strcmp(p->module, path) != 0) + continue; + if (name != NULL && strcmp(name, p->pkgname) != 0) { + *conflict = 1; + return NULL; + } + name = p->pkgname; + } + } + for (Node *p = primary ? primary->body : NULL; p; p = p->next) { + if (p->module == NULL || p->pkgname == NULL + || strcmp(p->module, path) != 0) + continue; + if (name != NULL && strcmp(name, p->pkgname) != 0) { + *conflict = 1; + return NULL; + } + name = p->pkgname; + } + return name; +} + +static int +bind_import_names(Node *list, struct importin *imports, int nimports, + Node *primary, const char *testsupport) +{ + for (Node *u = list; u; u = u->next) { + if (u->kind != N_USE || u->usepath == NULL) continue; + /* The reserved test-support spelling is a compiler-owned alias, not + * source default-import syntax. */ + if (testsupport != NULL && strcmp(testsupport, "__wwtest") == 0 + && strcmp(u->usepath, testsupport) == 0) + continue; + int conflict = 0; + const char *name = import_pkgname(imports, nimports, u->usepath, + primary, &conflict); + if (conflict) { + fprintf(stderr, + "w6c: package %s has conflicting declared names in export data\n", + u->usepath); + return -1; + } + if (name != NULL) { + u->str = name; + u->strlen = strlen(name); + } else if (!u->imported) { + fprintf(stderr, + "w6c: import %s has no declared package name in direct export data\n", + u->usepath); + return -1; + } else { + /* A closure-only import need not contribute declarations to this + * interface. Keep it canonical-path keyed without reinstalling + * the historical path-leaf qualifier. */ + u->str = u->usepath; + u->strlen = strlen(u->usepath); + } + } + return 0; +} + static void appendnodes(Node **head, Node **tail, Node *list) { @@ -90,6 +157,7 @@ main(int argc, char **argv) const char *out = NULL; const char *wwiout = NULL; /* -I : M2 export-data producer */ const char *testsupport = NULL; + const char *testtarget = NULL; int testmode = 0; int testpackage = 0; int commandpackage = 0; @@ -127,6 +195,12 @@ main(int argc, char **argv) return 2; } testsupport = argv[++i]; + } else if (strcmp(a, "--test-target-package") == 0) { + if (i + 1 >= argc) { + fputs("w6c: --test-target-package requires arg\n", stderr); + return 2; + } + testtarget = argv[++i]; } else if (strcmp(a, "-c") == 0) { sepmode = 1; } else if (strcmp(a, "--import") == 0) { @@ -156,7 +230,7 @@ main(int argc, char **argv) } } if (src == NULL) { - fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [-c] [-I out.wwi] " + fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [--test-target-package path] [-c] [-I out.wwi] " "[--import path dep.wwi]... [--import-map source path]... [-o out.s] file.ww\n", stderr); return 2; } @@ -196,11 +270,6 @@ main(int argc, char **argv) stderr); return 2; } - if (strcmp(importleaf(maps[i].source), - importleaf(maps[i].path)) != 0) { - fputs("w6c: --import-map must preserve import leaf\n", stderr); - return 2; - } int direct = 0; for (int j = 0; j < nimports; j++) if (strcmp(maps[i].path, imports[j].path) == 0) { @@ -219,6 +288,22 @@ main(int argc, char **argv) fputs("w6c: invalid --test-support-module\n", stderr); return 2; } + if (testtarget != NULL && (!sepmode || !testmode + || testtarget[0] == '\0')) { + fputs("w6c: invalid --test-target-package\n", stderr); + return 2; + } + if (testtarget != NULL) { + int direct = 0; + for (int i = 0; i < nimports; i++) + if (strcmp(imports[i].path, testtarget) == 0) + direct = 1; + if (!direct) { + fputs("w6c: --test-target-package is not a direct import\n", + stderr); + return 2; + } + } Arena *a = newarena(); Checker c; @@ -243,6 +328,10 @@ main(int argc, char **argv) Node *f = parseinput(a, imports[i].file, imports[i].buf, imports[i].len, imports[i].path, testsupport, 0, &bad); if (bad) return 1; + imports[i].ast = f; + if (bind_import_names(f->list, imports, i + 1, NULL, + testsupport) < 0) + return 1; appendnodes(&head, &tail, f->list); } @@ -259,7 +348,7 @@ main(int argc, char **argv) Node *file = parseinput(a, src, buf, len, NULL, testsupport, commandpackage || entrymode, &bad); if (bad) return 1; - /* Source keeps its effective spelling and leaf alias, while package + /* Source keeps its effective spelling and position, while package * resolution supplies the expanded canonical owner. Rewrite only the * primary import key before imported interface nodes are prepended. */ for (Node *u = file->list; u; u = u->next) { @@ -276,6 +365,31 @@ main(int argc, char **argv) fputs("w6c: --import-map source is not in primary input\n", stderr); return 2; } + /* Later direct interfaces may supply names for origin sections referenced + * by an earlier interface, so perform one complete metadata pass now. */ + for (int i = 0; i < nimports; i++) + if (bind_import_names(imports[i].ast->list, imports, nimports, + NULL, testsupport) < 0) + return 1; + if (bind_import_names(file->list, imports, nimports, file, + testsupport) < 0) + return 1; + if (testtarget != NULL) { + int seen = 0; + for (Node *u = file->list; u; u = u->next) { + if (u->kind != N_USE || u->imported || u->usepath == NULL + || strcmp(u->usepath, testtarget) != 0) + continue; + u->str = u->usepath; + u->strlen = strlen(u->usepath); + seen++; + } + if (seen != 1) { + fputs("w6c: generated test target import is not unique\n", + stderr); + return 2; + } + } if (head != NULL) { tail->next = file->list; file->list = head; @@ -285,6 +399,7 @@ main(int argc, char **argv) c.is_test = testmode; c.is_test_package = testpackage; if (testsupport != NULL) c.test_module = testsupport; + c.test_target = testtarget; c.sep_mode = sepmode; check_file(&c, file); if (c.errs) return 1; diff --git a/cmd/w6c/wwi.c b/cmd/w6c/wwi.c index 5121e94d..48921970 100644 --- a/cmd/w6c/wwi.c +++ b/cmd/w6c/wwi.c @@ -54,17 +54,11 @@ wwi_mod_eq(const char *a, const char *b) * flat in the parser, so consulting every N_USE without this owner filter * would let one dependency accidentally resolve another dependency's alias. */ static const char * -wwi_use_path(Checker *c, const char *owner, const char *alias) +wwi_use_path(Checker *c, const char *owner, int source, const char *alias) { - if (owner && alias) { - const char *dot = strrchr(owner, '.'); - const char *leaf = dot ? dot + 1 : owner; - if (strcmp(alias, leaf) == 0) - return owner; - } for (Node *u = c->file->list; u; u = u->next) { if (u->kind != N_USE || u->str == NULL - || strcmp(u->str, alias) != 0) + || u->sourceid != source || strcmp(u->str, alias) != 0) continue; int same = owner == NULL ? u->imported == 0 : u->imported != 0 && wwi_mod_eq(u->module, owner); @@ -75,17 +69,22 @@ wwi_use_path(Checker *c, const char *owner, const char *alias) } static int -wwi_direct_mod_visible(Checker *c, const char *owner, const char *mod) +wwi_direct_mod_visible(Checker *c, const char *owner, int source, + const char *mod) { if (mod == NULL || mod[0] == '\0') return 0; - const char *dot = strrchr(mod, '.'); - const char *alias = dot ? dot + 1 : mod; - const char *path = wwi_use_path(c, owner, alias); - return path != NULL && strcmp(path, mod) == 0; + for (Node *u = c->file->list; u; u = u->next) { + if (u->kind != N_USE || u->sourceid != source) continue; + int same = owner == NULL ? u->imported == 0 + : u->imported != 0 && wwi_mod_eq(u->module, owner); + const char *path = u->usepath ? u->usepath : u->str; + if (same && path != NULL && strcmp(path, mod) == 0) return 1; + } + return 0; } static Sym * -wwi_typesym(Checker *c, const char *owner, const char *nm) +wwi_typesym(Checker *c, const char *owner, int source, const char *nm) { if (nm == NULL) return NULL; const char *dot = strrchr(nm, '.'); @@ -96,7 +95,7 @@ wwi_typesym(Checker *c, const char *owner, const char *nm) if (alias == NULL) fatal("wwi: out of memory"); memcpy(alias, nm, n); alias[n] = '\0'; - const char *mod = wwi_use_path(c, owner, alias); + const char *mod = wwi_use_path(c, owner, source, alias); if (mod) s = scope_lookup_in_module(c->cur, mod, dot + 1); free(alias); @@ -107,7 +106,8 @@ wwi_typesym(Checker *c, const char *owner, const char *nm) for (Sym *b = p->first; b; b = b->next) { if (b->kind == SK_TYPE && strcmp(b->name, nm) == 0 - && wwi_direct_mod_visible(c, owner, b->mod)) { + && wwi_direct_mod_visible(c, owner, source, + b->mod)) { s = b; break; } @@ -563,6 +563,8 @@ factcmp(const void *a, const void *b) const struct factent *x = a, *y = b; int r = strcmp(x->mod, y->mod); if (r != 0) return r; + if (x->d->sourceid != y->d->sourceid) + return x->d->sourceid - y->d->sourceid; int xr = x->d->kind == N_TYPEDECL ? 0 : 1; int yr = y->d->kind == N_TYPEDECL ? 0 : 1; if (xr != yr) return xr - yr; @@ -607,7 +609,7 @@ wwi_fact_grow(struct factent **v, int *cap, int need) } static Sym * -wwi_valuesym(Checker *c, const char *owner, const char *name) +wwi_valuesym(Checker *c, const char *owner, int source, const char *name) { for (Scope *p = c->top; p; p = p->parent) for (Sym *s = p->first; s; s = s->next) @@ -617,25 +619,26 @@ wwi_valuesym(Checker *c, const char *owner, const char *name) for (Scope *p = c->top; p; p = p->parent) for (Sym *s = p->first; s; s = s->next) if (s->kind == SK_DEF && strcmp(s->name, name) == 0 - && wwi_direct_mod_visible(c, owner, s->mod)) - return s; + && wwi_direct_mod_visible(c, owner, source, s->mod)) + return s; return NULL; } static void wwi_collect_decl(struct factset*, const char*, Node*); -static void wwi_collect_type(struct factset*, const char*, Node*); +static void wwi_collect_type(struct factset*, const char*, int, Node*); static void -wwi_collect_expr(struct factset *fs, const char *owner, Node *e) +wwi_collect_expr(struct factset *fs, const char *owner, int source, Node *e) { if (e == NULL) return; Sym *s = NULL; if (e->kind == N_IDENT) { - s = wwi_valuesym(fs->c, owner, e->str); + s = wwi_valuesym(fs->c, owner, source, e->str); } else if (e->kind == N_DOT && e->lhs && e->lhs->kind == N_IDENT) { - const char *mod = wwi_use_path(fs->c, owner, e->lhs->str); - if (mod) s = wwi_valuesym(fs->c, mod, e->str); + const char *mod = wwi_use_path(fs->c, owner, source, + e->lhs->str); + if (mod) s = wwi_valuesym(fs->c, mod, source, e->str); } if (s && s->decl && s->decl->kind == N_DEF) { if (!s->decl->export) { @@ -648,23 +651,23 @@ wwi_collect_expr(struct factset *fs, const char *owner, Node *e) return; } if (e->kind == N_BIN) { - wwi_collect_expr(fs, owner, e->lhs); - wwi_collect_expr(fs, owner, e->rhs); + wwi_collect_expr(fs, owner, source, e->lhs); + wwi_collect_expr(fs, owner, source, e->rhs); } else if (e->kind == N_UN) { - wwi_collect_expr(fs, owner, e->lhs); + wwi_collect_expr(fs, owner, source, e->lhs); } else if (e->kind == N_CAST) { - wwi_collect_expr(fs, owner, e->lhs); - wwi_collect_type(fs, owner, e->rhs); + wwi_collect_expr(fs, owner, source, e->lhs); + wwi_collect_type(fs, owner, source, e->rhs); } } static void -wwi_collect_type(struct factset *fs, const char *owner, Node *t) +wwi_collect_type(struct factset *fs, const char *owner, int source, Node *t) { if (t == NULL) return; switch (t->kind) { case N_TNAME: { - Sym *s = wwi_typesym(fs->c, owner, t->str); + Sym *s = wwi_typesym(fs->c, owner, source, t->str); if (s && s->decl && s->decl->kind == N_TYPEDECL) wwi_collect_decl(fs, s->mod, s->decl); break; @@ -673,7 +676,7 @@ wwi_collect_type(struct factset *fs, const char *owner, Node *t) case N_TSLICE: case N_TBANG: case N_TCHAN: - wwi_collect_type(fs, owner, t->lhs); + wwi_collect_type(fs, owner, source, t->lhs); break; case N_TARRAY: /* Array length is part of the resolved type identity, not a source @@ -681,7 +684,7 @@ wwi_collect_type(struct factset *fs, const char *owner, Node *t) * and a consumer never needs an implementation def to size the type. */ if (t->rhs != NULL && t->rhs->kind != N_INTLIT) { u64 len; - if (!check_eval_const(fs->c, t->rhs, owner, &len)) { + if (!check_eval_const(fs->c, t->rhs, owner, source, &len)) { errorf(t->pos, "cannot encode array dimension"); fs->bad = 1; } else { @@ -692,24 +695,24 @@ wwi_collect_type(struct factset *fs, const char *owner, Node *t) e->lhs = e->rhs = e->list = NULL; } } - wwi_collect_type(fs, owner, t->lhs); + wwi_collect_type(fs, owner, source, t->lhs); break; case N_TFN: for (Node *p = t->list; p; p = p->next) - wwi_collect_type(fs, owner, p->lhs); - wwi_collect_type(fs, owner, t->lhs); + wwi_collect_type(fs, owner, source, p->lhs); + wwi_collect_type(fs, owner, source, t->lhs); break; case N_TSTRUCT: for (Node *f = t->list; f; f = f->next) - wwi_collect_type(fs, owner, f->lhs); + wwi_collect_type(fs, owner, source, f->lhs); break; case N_TTAGGED: case N_TTUPLE: for (Node *e = t->list; e; e = e->next) - wwi_collect_type(fs, owner, e); + wwi_collect_type(fs, owner, source, e); break; case N_TENUM: - wwi_collect_type(fs, owner, t->lhs); + wwi_collect_type(fs, owner, source, t->lhs); /* Member identifiers are enum-local prior-sibling references, not * package defs; the complete member list already carries their facts. */ break; @@ -756,18 +759,18 @@ wwi_collect_decl(struct factset *fs, const char *owner, Node *d) switch (d->kind) { case N_FNDECL: for (Node *p = d->list; p; p = p->next) - wwi_collect_type(fs, owner, p->lhs); - wwi_collect_type(fs, owner, d->lhs); + wwi_collect_type(fs, owner, d->sourceid, p->lhs); + wwi_collect_type(fs, owner, d->sourceid, d->lhs); break; case N_TYPEDECL: - wwi_collect_type(fs, owner, d->lhs); + wwi_collect_type(fs, owner, d->sourceid, d->lhs); break; case N_DEF: - wwi_collect_type(fs, owner, d->lhs); - wwi_collect_expr(fs, owner, d->rhs); + wwi_collect_type(fs, owner, d->sourceid, d->lhs); + wwi_collect_expr(fs, owner, d->sourceid, d->rhs); break; case N_LET: - wwi_collect_type(fs, owner, d->lhs); + wwi_collect_type(fs, owner, d->sourceid, d->lhs); break; default: break; @@ -775,24 +778,32 @@ wwi_collect_decl(struct factset *fs, const char *owner, Node *d) } static int -wwi_use_owned(Node *u, const char *owner) +wwi_use_owned(Node *u, const char *owner, int source) { return u->kind == N_USE && u->imported != 0 - && wwi_mod_eq(u->module, owner); + && u->sourceid == source && wwi_mod_eq(u->module, owner); } static void -wwi_emit_fact_imports(FILE *of, Node *file, const char *owner) +wwi_emit_imports(FILE *of, Node *file, const char *owner, int source, + int imported) { int nuse = 0; - for (Node *u = file->list; u; u = u->next) - if (wwi_use_owned(u, owner)) nuse++; + for (Node *u = file->list; u; u = u->next) { + int owned = imported ? wwi_use_owned(u, owner, source) + : u->kind == N_USE && u->imported == 0 + && u->sourceid == source; + if (owned) nuse++; + } if (nuse == 0) return; struct useent *us = malloc((size_t)nuse * sizeof *us); if (us == NULL) fatal("wwi: out of memory"); int k = 0; for (Node *u = file->list; u; u = u->next) { - if (!wwi_use_owned(u, owner)) continue; + int owned = imported ? wwi_use_owned(u, owner, source) + : u->kind == N_USE && u->imported == 0 + && u->sourceid == source; + if (!owned) continue; us[k].path = u->usepath ? u->usepath : u->str; us[k].idx = k; k++; @@ -807,6 +818,65 @@ wwi_emit_fact_imports(FILE *of, Node *file, const char *owner) free(us); } +static int +wwi_primary_section_has(Checker *c, Node *file, struct factset *fs, + struct declent *exports, int nexports, int source) +{ + for (Node *u = file->list; u; u = u->next) + if (u->kind == N_USE && u->imported == 0 + && u->sourceid == source) + return 1; + for (int i = 0; i < fs->nprivate; i++) + if (fs->privatefacts[i].d->sourceid == source) return 1; + for (int i = 0; i < nexports; i++) + if (exports[i].d->sourceid == source) return 1; + if (c->is_test_package) + for (Node *d = file->list; d; d = d->next) + if (wwi_primary(d) && d->sourceid == source + && d->kind == N_FNDECL && !d->export + && wwi_has_attr(d, "test")) + return 1; + return 0; +} + +static int +wwi_first_primary_source(Node *file) +{ + int found = 0; + int source = 0; + for (Node *d = file->list; d; d = d->next) { + if (!wwi_primary(d)) continue; + if (!found || d->sourceid < source) { + source = d->sourceid; + found = 1; + } + } + return found ? source : file->sourceid; +} + +static void +wwi_emit_primary_section(Checker *c, FILE *of, Node *file, + struct factset *fs, struct declent *exports, int nexports, + const char *owner, const char *pkg, int source) +{ + if (owner != NULL && owner[0] != '\0') + fprintf(of, "//ww:module %s\n", owner); + fprintf(of, "package %s;\n", pkg && pkg[0] ? pkg : "main"); + wwi_emit_imports(of, file, owner, source, 0); + for (int i = 0; i < fs->nprivate; i++) + if (fs->privatefacts[i].d->sourceid == source) + wwi_decl(of, fs->privatefacts[i].d); + if (c->is_test_package) + for (Node *d = file->list; d; d = d->next) + if (wwi_primary(d) && d->sourceid == source + && d->kind == N_FNDECL && !d->export + && wwi_has_attr(d, "test")) + wwi_decl(of, d); + for (int i = 0; i < nexports; i++) + if (exports[i].d->sourceid == source) + wwi_decl(of, exports[i].d); +} + int wwi_emit(Checker *c, FILE *of, Node *file) { @@ -841,83 +911,15 @@ wwi_emit(Checker *c, FILE *of, Node *file) if (fs.nfacts > 1) qsort(fs.facts, (size_t)fs.nfacts, sizeof *fs.facts, factcmp); - /* package line: leaf of the first primary decl's module tag. */ - const char *pkg = "main"; - int found = 0; - for (Node *d = file->list; d; d = d->next) { - if (wwi_primary(d) && d->module && d->module[0]) { - const char *dot = strrchr(d->module, '.'); - pkg = dot ? dot + 1 : d->module; - found = 1; - break; - } - } - /* #11: a decl-less / export-less primary body carries no - * module-tagged decl, so the scan above finds nothing; fall back to - * the primary module identity stamped on the N_FILE node at parse - * time. A raw single-file `package main` root arrives via a bare reset - * and leaves file->module NULL, so it stays "main". The detector is - * scan-miss (`!found`), NOT pkg=="main": a body whose first tagged - * decl legitimately leafs to "main" must keep that, and must match - * selfhost's `found` flag byte-for-byte (rule 10). */ - if (!found && file->module && file->module[0]) { - const char *dot = strrchr(file->module, '.'); - pkg = dot ? dot + 1 : file->module; - } - if (file->module != NULL && file->module[0] != '\0') - fprintf(of, "//ww:module %s\n", file->module); - fprintf(of, "package %s;\n", pkg); - - /* imports — primary N_USE, byte-sorted by import path. */ - int nuse = 0; - for (Node *u = file->list; u; u = u->next) - if (u->kind == N_USE && wwi_primary(u)) - nuse++; - if (nuse > 0) { - struct useent *us = malloc((size_t)nuse * sizeof *us); - int k = 0; - for (Node *u = file->list; u; u = u->next) { - if (u->kind != N_USE || !wwi_primary(u)) - continue; - us[k].path = u->usepath ? u->usepath : u->str; - us[k].idx = k; - k++; - } - qsort(us, (size_t)nuse, sizeof *us, usecmp); - const char *previous = NULL; - for (int i = 0; i < nuse; i++) { - if (previous && strcmp(previous, us[i].path) == 0) - continue; - fprintf(of, "import %s;\n", us[i].path); - previous = us[i].path; - } - free(us); - } - - /* Compiler-private primary nominal facts precede the public declarations. - * They are parseable within this package export but lack `export`, so a - * source qualifier cannot name them. */ - for (int i = 0; i < fs.nprivate; i++) - wwi_decl(of, fs.privatefacts[i].d); - - /* Package-test metadata is private, deterministic, and owned by the test - * variant. A distinct generated-main compile consumes it through --import. */ - if (c->is_test_package) { - /* Unit source paths and declarations are already deterministically - * ordered; preserve that order as the test runner's user-facing order. */ - for (Node *d = file->list; d; d = d->next) - if (wwi_primary(d) && d->kind == N_FNDECL - && !d->export && wwi_has_attr(d, "test")) - wwi_decl(of, d); - } - - /* decls — exported primary, byte-sorted by symbol name. */ + /* Exported declarations are sorted within their source-file sections. */ int ndecl = 0; for (Node *d = file->list; d; d = d->next) if (wwi_primary(d) && d->export && wwi_is_decl(d)) ndecl++; + struct declent *ds = NULL; if (ndecl > 0) { - struct declent *ds = malloc((size_t)ndecl * sizeof *ds); + ds = malloc((size_t)ndecl * sizeof *ds); + if (ds == NULL) fatal("wwi: out of memory"); int k = 0; for (Node *d = file->list; d; d = d->next) { if (!wwi_primary(d) || !d->export || !wwi_is_decl(d)) @@ -927,24 +929,49 @@ wwi_emit(Checker *c, FILE *of, Node *file) k++; } qsort(ds, (size_t)ndecl, sizeof *ds, declcmp); - for (int i = 0; i < ndecl; i++) - wwi_decl(of, ds[i].d); - free(ds); } + /* Canonical ownership, declared name, and lexical source scope are three + * independent export facts. Repeated owner sections preserve the file that + * owns each binding while keeping every symbol/action keyed by OWNER. */ + int nsection = 0; + for (Node *p = file->body; p; p = p->next) { + if (p->imported || !wwi_primary_section_has(c, file, &fs, ds, + ndecl, p->sourceid)) + continue; + const char *owner = p->module && p->module[0] + ? p->module : file->module; + const char *pkg = p->pkgname && p->pkgname[0] + ? p->pkgname : file->pkgname; + wwi_emit_primary_section(c, of, file, &fs, ds, ndecl, owner, + pkg, p->sourceid); + nsection++; + } + if (nsection == 0) { + const char *pkg = file->pkgname && file->pkgname[0] + ? file->pkgname : "main"; + wwi_emit_primary_section(c, of, file, &fs, ds, ndecl, + file->module, pkg, wwi_first_primary_source(file)); + } + free(ds); + /* Compiler-owned public fact closure. A module marker changes semantic * ownership without making the namespace a source import of the eventual * consumer; direct visibility continues to come solely from its own N_USE. */ const char *lastmod = NULL; + int lastsource = -1; for (int i = 0; i < fs.nfacts; i++) { struct factent *f = &fs.facts[i]; - if (lastmod == NULL || strcmp(lastmod, f->mod) != 0) { + if (lastmod == NULL || strcmp(lastmod, f->mod) != 0 + || lastsource != f->d->sourceid) { const char *dot = strrchr(f->mod, '.'); - const char *leaf = dot ? dot + 1 : f->mod; + const char *factpkg = f->d->pkgname && f->d->pkgname[0] + ? f->d->pkgname : (dot ? dot + 1 : f->mod); fprintf(of, "//ww:module %s\n", f->mod); - fprintf(of, "package %s;\n", leaf); - wwi_emit_fact_imports(of, file, f->mod); + fprintf(of, "package %s;\n", factpkg); + wwi_emit_imports(of, file, f->mod, f->d->sourceid, 1); lastmod = f->mod; + lastsource = f->d->sourceid; } wwi_decl(of, f->d); } diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index da7eb396..70cc9ed4 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -58,8 +58,10 @@ lookup_builtin(const char *name) } static const char *decl_mod(Node *file, Node *d); -static const char *use_path(Node *file, const char *curmod, const char *alias); -static int src_imports(Node *file, const char *modtag, const char *name); +static const char *use_path(Node *file, const char *curmod, int source, + const char *alias); +static int src_imports(Node *file, const char *modtag, int source, + const char *name); static Sym *lookup_visible(Checker *c, const char *name); static Sym *lookup_visible_type(Checker *c, const char *name); static void resolve_typedecl(Checker *c, Node *d); @@ -89,6 +91,7 @@ resolve_typename(Checker *c, Node *n) /* M1 #22: map the qualifier alias to its dotted * import path (symbols are path-keyed). */ const char *mk = use_path(c->file, c->cur_mod, + c->cur_source, head); if (mk != NULL) s = scope_lookup_in_module(c->cur, mk, @@ -622,7 +625,8 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth) case N_DOT: { if (n->lhs == NULL || n->lhs->kind != N_IDENT) return 0; /* M1 #22: map the qualifier alias to its dotted import path. */ - const char *mk = use_path(c->file, c->cur_mod, n->lhs->str); + const char *mk = use_path(c->file, c->cur_mod, c->cur_source, + n->lhs->str); if (mk == NULL) return 0; Sym *s = scope_lookup_in_module(c->cur, mk, n->str); if (s == NULL || s->kind != SK_DEF || @@ -636,12 +640,15 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth) } int -check_eval_const(Checker *c, Node *n, const char *owner, u64 *out) +check_eval_const(Checker *c, Node *n, const char *owner, int source, u64 *out) { const char *saved = c->cur_mod; + int savesource = c->cur_source; c->cur_mod = owner; + c->cur_source = source; int ok = eval_def_const(c, n, out, 0); c->cur_mod = saved; + c->cur_source = savesource; return ok; } @@ -1475,6 +1482,7 @@ cexpr(Checker *c, Node *n) * import path; map the alias the user wrote to * that path before looking up the leaf. */ const char *mk = use_path(c->file, c->cur_mod, + c->cur_source, n->lhs->str); if (mk == NULL) { if (ms->kind == SK_USE) @@ -2854,6 +2862,7 @@ check_init(Checker *c, Arena *a) c->is_test = 0; /* #15: caller (w6c main) sets it after init */ c->is_test_package = 0; c->test_module = "test"; + c->test_target = NULL; typesinit(a); c->top = newscope(a, NULL); c->cur = c->top; @@ -2873,6 +2882,10 @@ static const char * decl_mod(Node *file, Node *d) { if (d == NULL || d->module == NULL || file == NULL) return NULL; + /* Separate-compilation interfaces already carry their canonical owner. + * Visibility is checked independently against the referencing source's + * import binding; never erase semantic ownership for a transitive fact. */ + if (d->imported) return d->module; for (Node *u = file->list; u; u = u->next) { /* M1 #22: a decl is imported iff some `use` directive's full * dotted import path equals the decl's module (now the path, @@ -2887,41 +2900,40 @@ decl_mod(Node *file, Node *d) } /* - * use_path — map a `use` alias (leaf bareword the user writes, `utf8`) - * to the full dotted import path it binds (`encoding.utf8`), for the - * module-qualified resolution and the codegen hint (M1 #22, §2.4). For - * single-level packages usepath == alias so the result is unchanged. + * use_path — map a source-file default qualifier (the imported package's + * declared name) to the full canonical import path it binds, for + * module-qualified resolution and the codegen hint (M1 #22, §2.4). * - * The alias→path map is NOT file-global: two modules in the same - * concatenated unit may bind the same leaf alias to different paths - * (sha256's `import crypto.math` and strconv's `import math` both bind - * alias `math`). The import declared in the SAME module as the - * reference (`curmod`) is the authoritative one; requiring it closes both - * cross-module mis-resolution and accidental transitive-import visibility. + * The qualifier→path map is source-file local: two files in the same + * concatenated unit may bind the same declared name to different paths. The + * import with the reference's source ID and owner is authoritative, closing + * both cross-file mis-resolution and accidental transitive visibility. * Returns NULL if the referencing package has no such `use`. */ static const char * -use_path(Node *file, const char *curmod, const char *alias) +use_path(Node *file, const char *curmod, int source, const char *alias) { if (file == NULL || alias == NULL) return NULL; /* Legacy inline multi-package units may spell a package's own * declarations as `pkg.member`. That is self-qualification, not an * imported namespace; preserve it without reopening transitive lookup. */ - if (curmod != NULL) { + if (source == 0 && curmod != NULL) { const char *dot = strrchr(curmod, '.'); const char *leaf = dot ? dot + 1 : curmod; if (strcmp(alias, leaf) == 0) return curmod; } for (Node *u = file->list; u; u = u->next) { if (u->kind != N_USE || u->str == NULL - || strcmp(u->str, alias) != 0) + || u->sourceid != source || strcmp(u->str, alias) != 0) continue; const char *p = u->usepath ? u->usepath : u->str; const char *um = decl_mod(file, u); int same = (um == NULL) ? (curmod == NULL) : (curmod != NULL && strcmp(um, curmod) == 0); - if (same) + if (same) { + u->used = 1; return p; + } } return NULL; } @@ -2947,9 +2959,12 @@ resolve_typedecl(Checker *c, Node *d) if (t == NULL || t->under != NULL || t->resolving) return; t->resolving = 1; const char *save = c->cur_mod; + int savesource = c->cur_source; c->cur_mod = decl_mod(c->file, d); + c->cur_source = d->sourceid; Type *under = resolve_type(c, d->lhs); c->cur_mod = save; + c->cur_source = savesource; /* Alias-root cycle (`type a = b; type b = a` / `type a = a`): * checked BEFORE clearing the flag so self-aliases trip on their * own in-progress mark. ty_err instead of the cyclic under keeps @@ -2978,11 +2993,11 @@ resolve_typedecl(Checker *c, Node *d) * (N_USE nodes with module == NULL). */ static int -src_imports(Node *file, const char *modtag, const char *name) +src_imports(Node *file, const char *modtag, int source, const char *name) { if (file == NULL || name == NULL || name[0] == '\0') return 0; for (Node *u = file->list; u; u = u->next) { - if (u->kind != N_USE) continue; + if (u->kind != N_USE || u->sourceid != source) continue; /* Skip self-imports: lib/fmt/fmt_test.ww carries `use fmt;` * even though its module tag is also "fmt"; that directive * doesn't introduce a foreign module bareword and lib/fmt's @@ -3013,10 +3028,18 @@ static int direct_module_visible(Checker *c, const char *mod) { if (c == NULL || mod == NULL || mod[0] == '\0') return 0; - const char *dot = strrchr(mod, '.'); - const char *alias = dot ? dot + 1 : mod; - const char *path = use_path(c->file, c->cur_mod, alias); - return path != NULL && strcmp(path, mod) == 0; + for (Node *u = c->file->list; u; u = u->next) { + if (u->kind != N_USE || u->sourceid != c->cur_source) continue; + const char *um = decl_mod(c->file, u); + int same = c->cur_mod == NULL ? um == NULL + : um != NULL && strcmp(um, c->cur_mod) == 0; + const char *path = u->usepath ? u->usepath : u->str; + if (same && path != NULL && strcmp(path, mod) == 0) { + u->used = 1; + return 1; + } + } + return 0; } static Sym * @@ -3035,7 +3058,7 @@ lookup_visible(Checker *c, const char *name) * source-owned alias map is authoritative: if this package directly * imports NAME, return the coalesced module marker only as a marker; the * N_DOT path maps the alias to the correct full path again. */ - if (use_path(c->file, c->cur_mod, name) != NULL) { + if (use_path(c->file, c->cur_mod, c->cur_source, name) != NULL) { for (Scope *p = c->cur; p; p = p->parent) for (Sym *b = p->first; b; b = b->next) if (strcmp(b->name, name) == 0 @@ -3101,7 +3124,7 @@ check_module_shadow(Checker *c, const char *name, Pos pos, } } if (!seen_use) return; - if (!src_imports(c->file, c->cur_mod, name)) return; + if (!src_imports(c->file, c->cur_mod, c->cur_source, name)) return; err(c, pos, "%s '%s' shadows imported module '%s'", kindstr, name, name); } @@ -3132,6 +3155,80 @@ same_import_fact(Checker *c, Node *d, const char *mod, Skind kind) return s; } +static int +top_decl_kind(Node *d) +{ + return d != NULL && (d->kind == N_TYPEDECL || d->kind == N_DEF + || d->kind == N_FNDECL || d->kind == N_LET); +} + +static void +check_import_alt(Node *d, const char *name) +{ + FILE *f = errout ? errout : stderr; + fprintf(f, "\t%s:%d:%d: other declaration of %s\n", + d->pos.file ? d->pos.file : "?", d->pos.line, d->pos.col, name); +} + +/* Go's default import binding lives in the importing file's scope. Reject + * only another binding in that same source section; equal names in sibling + * files are independent even though their canonical edges are package-wide. */ +static void +check_import_redeclarations(Checker *c, Node *file) +{ + if (!c->sep_mode) return; + for (Node *u = file->list; u; u = u->next) { + if (u->kind != N_USE || u->imported || u->str == NULL) + continue; + for (Node *v = file->list; v != u; v = v->next) { + if (v->kind != N_USE || v->imported || v->str == NULL + || v->sourceid != u->sourceid) + continue; + if (strcmp(v->str, u->str) == 0) { + err(c, u->pos, "%s redeclared in this block", u->str); + check_import_alt(v, u->str); + break; + } + } + } +} + +/* Report file-local unused bindings before package-declaration collisions, + * matching the pinned Go resolver's stable ordering. The package declarations + * themselves are package-scoped, so they collide with an equal import name in + * any contributing source file. */ +static void +check_import_usage_and_collisions(Checker *c, Node *file) +{ + if (!c->sep_mode) return; + for (Node *u = file->list; u; u = u->next) { + if (u->kind != N_USE || u->imported || u->used || u->str == NULL) + continue; + const char *path = u->usepath ? u->usepath : u->str; + const char *dot = strrchr(path, '.'); + const char *leaf = dot ? dot + 1 : path; + if (strcmp(u->str, leaf) == 0) + err(c, u->pos, "\"%s\" imported and not used", path); + else + err(c, u->pos, "\"%s\" imported as %s and not used", + path, u->str); + } + for (Node *d = file->list; d; d = d->next) { + if (d->imported || !top_decl_kind(d) || d->str == NULL) + continue; + for (Node *u = file->list; u; u = u->next) { + if (u->kind != N_USE || u->imported || u->str == NULL + || strcmp(d->str, u->str) != 0) + continue; + const char *path = u->usepath ? u->usepath : u->str; + err(c, d->pos, + "%s already declared through import of package %s (\"%s\")", + d->str, u->str, path); + check_import_alt(u, d->str); + } + } +} + void check_file(Checker *c, Node *file) { @@ -3148,21 +3245,30 @@ check_file(Checker *c, Node *file) * already emits the qualified call. wwstage twin in check.ww. */ if (c->is_test) { int present = 0; - for (Node *u = file->list; u; u = u->next) + for (Node *u = file->list; u; u = u->next) { + if (c->test_target != NULL && u->kind == N_USE + && !u->imported && u->usepath + && (strcmp(u->usepath, c->test_target) == 0 + || strcmp(u->usepath, c->test_module) == 0)) + u->used = 1; if (u->kind == N_USE && !u->imported && u->usepath && strcmp(u->usepath, c->test_module) == 0) { present = 1; - break; } + } if (!present) { Node *usenode = newnode(c->a, N_USE, file->pos); usenode->str = c->test_module; usenode->strlen = strlen(c->test_module); usenode->usepath = c->test_module; + usenode->pkgname = file->pkgname; + usenode->sourceid = file->sourceid; + if (c->test_target != NULL) usenode->used = 1; usenode->next = file->list; file->list = usenode; } } + check_import_redeclarations(c, file); /* pass 1: install names (types first, then defs/fns). * For self-referential types we install the named-type placeholder @@ -3172,13 +3278,10 @@ check_file(Checker *c, Node *file) * references (`strconv.invalid`) resolve when typedecl bodies are * walked in the next pass. */ for (Node *d = file->list; d; d = d->next) { + c->cur_source = d->sourceid; if (d->kind == N_USE) { - /* check-(c) self-import: a package may not import - * itself. Pure owner==leaf string compare, package- - * model-independent — sound under ww's filename-keyed - * file-inclusion imports. check-(a) unused and - * (b)/(d) membership DEFERRED to task #8 (filename- - * keyed pulls lack import->file->symbol provenance). */ + /* A package may not import its own canonical owner. Import + * usage and membership are checked with source-file provenance. */ const char *owner = decl_mod(file, d); /* M1 #22: self-import ⟺ the imported path equals the * use's own (owning) module path. Compares paths, not @@ -3233,9 +3336,11 @@ check_file(Checker *c, Node *file) * here. A foldable stub carries type NULL until then. A duplicate * (prev already bound non-USE) is left for that loop to diagnose. */ c->cur_mod = NULL; + c->cur_source = 0; for (Node *d = file->list; d; d = d->next) { if (d->kind != N_DEF) continue; c->cur_mod = decl_mod(file, d); + c->cur_source = d->sourceid; const char *mod = decl_mod(file, d); if (same_import_fact(c, d, mod, SK_DEF) != NULL) continue; @@ -3250,13 +3355,16 @@ check_file(Checker *c, Node *file) } } c->cur_mod = NULL; + c->cur_source = 0; for (Node *d = file->list; d; d = d->next) { if (d->kind != N_TYPEDECL) continue; resolve_typedecl(c, d); } c->cur_mod = NULL; + c->cur_source = 0; for (Node *d = file->list; d; d = d->next) { c->cur_mod = decl_mod(file, d); + c->cur_source = d->sourceid; switch (d->kind) { case N_USE: /* already installed in pass 1; no-op here so the @@ -3360,6 +3468,7 @@ check_file(Checker *c, Node *file) } } c->cur_mod = NULL; + c->cur_source = 0; /* Program-global uniqueness on the ENTRY `main`. M1 #32: the entry * is the ROOT-unit main (imported==0) — it alone lowers to the bare @@ -3487,8 +3596,15 @@ check_file(Checker *c, Node *file) nm->strlen = strlen(d->str); Node *id; if (d->imported && d->module && d->module[0]) { - const char *dotp = strrchr(d->module, '.'); - const char *alias = dotp ? dotp + 1 : d->module; + const char *alias = d->pkgname && d->pkgname[0] + ? d->pkgname : d->module; + /* A generated dispatcher cannot bind a command test + * package as `main`: that would collide with its own entry. + * This is a compiler-owned binding, never source alias syntax; + * use the canonical target path as its private qualifier. */ + if (c->test_target != NULL + && strcmp(d->module, c->test_target) == 0) + alias = c->test_target; id = newnode(c->a, N_DOT, fp); id->lhs = newnode(c->a, N_IDENT, fp); id->lhs->str = alias; @@ -3542,6 +3658,8 @@ check_file(Checker *c, Node *file) tab = newnode(c->a, N_LET, fp); tab->op = TK_CONST; tab->str = "__wwtests"; + tab->pkgname = file->pkgname; + tab->sourceid = file->sourceid; tab->lhs = tsl; tab->rhs = arr; /* pass 1 already ran, so install the table's name now — @@ -3581,10 +3699,15 @@ check_file(Checker *c, Node *file) Node *m = newnode(c->a, N_FNDECL, fp); m->str = "main"; m->export = 1; + m->pkgname = file->pkgname; + m->sourceid = file->sourceid; m->lhs = newnode(c->a, N_TNAME, fp); m->lhs->str = "i32"; m->body = body; + int savesource = c->cur_source; + c->cur_source = m->sourceid; m->type = build_fn_type(c, m); + c->cur_source = savesource; /* pass 1 already ran, so the install loop never stamped m's * type; set it explicitly (pass 2 below reads d->type). * Append the table const (if any) then main to file->list. */ @@ -3603,6 +3726,7 @@ check_file(Checker *c, Node *file) /* pass 2: check def initialisers and fn bodies */ for (Node *d = file->list; d; d = d->next) { c->cur_mod = decl_mod(file, d); + c->cur_source = d->sourceid; switch (d->kind) { case N_DEF: { if (d->rhs) { @@ -3786,6 +3910,8 @@ check_file(Checker *c, Node *file) } } c->cur_mod = NULL; + c->cur_source = 0; + check_import_usage_and_collisions(c, file); /* * #6 harec-fidelity (ref/harec/src/check.c:3941): a @test fn is diff --git a/cmd/wcc/parse.c b/cmd/wcc/parse.c index fd0d0cdc..f21757c7 100644 --- a/cmd/wcc/parse.c +++ b/cmd/wcc/parse.c @@ -1327,9 +1327,9 @@ parseuse(Parser *p) Pos pp = p->cur.pos; expect(p, TK_USE); Node *n = newnode(p->a, N_USE, pp); - /* M1 #22: accumulate the full dotted import path (n->module) so the - * checker can match decl identity on the path, while n->str stays the - * leaf alias the user writes (`utf8.x`). */ + /* M1 #22: accumulate the full dotted import path in usepath. `str` + * starts as its leaf; direct export metadata later installs the imported + * declaration's default name without changing canonical identity. */ const char *leaf = expectident(p); const char *path = leaf; while (accept(p, TK_DOT)) { @@ -1358,19 +1358,23 @@ parseimports(Parser *p) * the following package clause and imports. */ if (p->cur.kind == TK_MODPATH) { sawpackage = 0; + p->sourceid++; p->pathmod = p->cur.text; p->curmod = p->cur.text; p->resetmod = NULL; + p->curpkg = NULL; advance(p); continue; } if (p->cur.kind == TK_MODRESET) { const char *rp = p->cur.text; sawpackage = 0; + p->sourceid++; advance(p); p->pathmod = NULL; p->curmod = rp; p->resetmod = rp; + p->curpkg = NULL; continue; } if (p->cur.kind == TK_MODULE) { @@ -1385,9 +1389,13 @@ parseimports(Parser *p) } const char *name = expectident(p); expect(p, TK_SEMI); - p->curmod = name; + p->curpkg = name; + if (p->pathmod == NULL && p->resetmod == NULL) + p->curmod = name; Node *package = newnode(p->a, N_FILE, pp); - package->module = name; + package->module = p->curmod; + package->pkgname = name; + package->sourceid = p->sourceid; if (packages == NULL) packages = package; else @@ -1395,6 +1403,8 @@ parseimports(Parser *p) packagetail = package; if (!sawpackage) { file->module = name; + file->pkgname = name; + file->sourceid = p->sourceid; file->pos = pp; sawpackage = 1; } @@ -1408,6 +1418,8 @@ parseimports(Parser *p) if (p->cur.kind == TK_USE) { Node *d = parseuse(p); d->module = p->curmod; + d->pkgname = p->curpkg; + d->sourceid = p->sourceid; if (head == NULL) head = d; else @@ -1424,6 +1436,8 @@ parseimports(Parser *p) p->errs++; Node *d = parseuse(p); d->module = p->curmod; + d->pkgname = p->curpkg; + d->sourceid = p->sourceid; if (head == NULL) head = d; else @@ -1441,6 +1455,8 @@ parseimports(Parser *p) p->errs++; Node *d = parseuse(p); d->module = p->curmod; + d->pkgname = p->curpkg; + d->sourceid = p->sourceid; if (head == NULL) head = d; else @@ -1546,6 +1562,7 @@ parsefile(Parser *p) Pos pp = { p->l->file, 1, 1 }; Node *file = newnode(p->a, N_FILE, pp); Node *head = NULL, *tail = NULL; + Node *packages = NULL, *packagetail = NULL; int sawpackage = 0; while (p->cur.kind != TK_EOF) { /* `package foo;` — directory-as-module declaration. Every @@ -1555,58 +1572,41 @@ parsefile(Parser *p) * sep primary-reset (resetmod) regions carry identity * out-of-band and are exempt. */ if (p->cur.kind == TK_MODULE) { + Pos packagepos = p->cur.pos; sawpackage = 1; advance(p); const char *name = expectident(p); expect(p, TK_SEMI); - if (p->pathmod != NULL || p->resetmod != NULL) { - /* M1 #22: while an import path is active the - * in-file `package` clause is an ASSERTION — its - * leaf must equal the path's last component; it - * does NOT overwrite the path-derived module. - * #57 extends this to the sep primary-reset path - * (resetmod): the dotted reset path is the - * authoritative identity, the clause asserts. */ - const char *active = - p->pathmod ? p->pathmod : p->resetmod; - const char *dot = strrchr(active, '.'); - const char *last = dot ? dot + 1 : active; - int testsupport = p->testmodule != NULL - && strcmp(p->testmodule, "__wwtest") == 0 - && strcmp(active, "__wwtest") == 0 - && strcmp(name, "test") == 0; - int commandpackage = p->commandpackage - && p->pathmod == NULL && p->resetmod != NULL - && (strcmp(name, "main") == 0 - || strcmp(name, "main_test") == 0); - if (strcmp(name, last) != 0 && !testsupport - && !commandpackage) { - errorf(p->cur.pos, - "package %s does not match import path %s", - name, active); - p->errs++; - } - } else { + p->curpkg = name; + if (p->pathmod == NULL && p->resetmod == NULL) { p->curmod = name; - /* #11: stamp the primary module identity on the - * N_FILE node so wwi_emit can derive the - * `package` leaf even when the body carries zero - * module-tagged decls. Primary identity only; - * never the imported boundary (TK_MODPATH). */ - if (file->module == NULL) - file->module = name; + } + Node *package = newnode(p->a, N_FILE, packagepos); + package->module = p->curmod; + package->pkgname = name; + package->sourceid = p->sourceid; + package->imported = p->pathmod != NULL; + if (packages == NULL) packages = package; + else packagetail->next = package; + packagetail = package; + if (file->pkgname == NULL) { + file->pkgname = name; + file->sourceid = p->sourceid; } continue; } /* `//ww:module ` — M1 #22 import boundary. The following - * file's decls mangle on the full dotted import path, not the - * leaf `package` clause, and are flagged imported (gates the + * file's decls mangle on the full dotted import path independently + * of its `package` clause, and are flagged imported (gates the * root-only bare-`main` rule, #32). */ if (p->cur.kind == TK_MODPATH) { sawpackage = 0; + p->sourceid++; p->pathmod = p->cur.text; p->curmod = p->cur.text; p->resetmod = NULL; + p->curpkg = NULL; + if (file->module == NULL) file->module = p->cur.text; advance(p); continue; } @@ -1620,6 +1620,7 @@ parsefile(Parser *p) * decls to bare — that usage is deliberate-only. */ if (p->cur.kind == TK_MODRESET) { sawpackage = 0; + p->sourceid++; /* #57: a path-carrying reset (sep primary body) mangles * decls on the dotted path so definer == importer, but * leaves imported==0 (curmod set, pathmod NULL) so -c @@ -1630,6 +1631,7 @@ parsefile(Parser *p) const char *rp = p->cur.text; advance(p); p->pathmod = NULL; + p->curpkg = NULL; if (rp != NULL) { p->curmod = rp; p->resetmod = rp; @@ -1679,14 +1681,17 @@ parsefile(Parser *p) advance(p); continue; } - if (d != NULL) { - d->module = p->curmod; - d->imported = (p->pathmod != NULL); + if (d != NULL) { + d->module = p->curmod; + d->pkgname = p->curpkg; + d->sourceid = p->sourceid; + d->imported = (p->pathmod != NULL); } if (head == NULL) head = d; else tail->next = d; tail = d; } file->list = head; + file->body = packages; return file; } diff --git a/cmd/wcc/ww.h b/cmd/wcc/ww.h index f538480e..9f3b36c5 100644 --- a/cmd/wcc/ww.h +++ b/cmd/wcc/ww.h @@ -173,7 +173,7 @@ typedef enum { * that replaces the withdrawn `package main` inject). */ TK_MODPATH, /* `//ww:module ` — driver import boundary: * the following file's decls mangle on the full import - * path, not the leaf `package` clause (M1 #22). Token + * path independently of the `package` clause (M1 #22). Token * text carries the dotted path. */ TK_LAST /* sentinel for tables */ @@ -344,10 +344,16 @@ struct Node { * this is the importing (owning) * module. */ const char *usepath; /* M1 #22: on an N_USE node, the full - * dotted IMPORT path (`encoding.utf8`) - * vs the leaf alias in `str`. Drives + * canonical import path (`encoding.utf8`); + * `str` is the declared default qualifier. Drives * the path-keyed decl_mod match and the - * qualified-ref codegen hint. */ + * qualified-ref codegen hint. */ + const char *pkgname; /* declared package name for this source/export + * section; independent of canonical `module`. */ + int sourceid; /* lexical source-file scope within the parsed + * owner unit; module-reset/module boundaries + * advance it deterministically. */ + int used; /* N_USE: checker observed this file-local binding. */ int imported; /* M1 #22: decl reached through an * `//ww:module ` import boundary * (vs root/primary). Gates the root-only @@ -373,12 +379,12 @@ struct Parser { * import path; while set, decls stamp * module=pathmod and imported=1, and the * in-file `package` clause is an assertion. */ - const char *resetmod; /* #57: active `//ww:module-reset ` dotted - * path; mangles decls on the path WITHOUT - * imported=1 (primary-ness for -c and the #32 - * bare-main rule stay intact), and the in-file - * `package` clause asserts (leaf == last - * component) instead of overwriting curmod. */ + const char *resetmod; /* #57: active `//ww:module-reset ` canonical + * identity; decls mangle on it without becoming + * imported. The package clause independently + * supplies the declared name. */ + const char *curpkg; /* declared name of the active source section. */ + int sourceid; /* deterministic lexical source-section ordinal. */ const char *testmodule; /* hidden package-driver alias for toolchain * `package test`; NULL outside that compile */ int commandpackage; /* selected command family: package main/main_test @@ -581,8 +587,10 @@ struct Checker { * currently being checked; NULL for primary * compilation unit. Drives same-module * preference in bare-leaf lookups so a bare - * `read` inside lib/os resolves to os.read - * rather than colliding io.read. */ + * `read` inside lib/os resolves to os.read + * rather than colliding io.read. */ + int cur_source; /* lexical source-file scope of the declaration + * currently being checked. */ Node *file; /* current N_FILE root; used by check_module_shadow * to consult the declaring source file's own `use` * directives when refusing param/let names that @@ -596,6 +604,8 @@ struct Checker { * bodies and export compiler-private metadata, * but do not synthesize an entry. */ const char *test_module; /* generated dispatcher support qualifier */ + const char *test_target; /* canonical target path used only as the + * compiler-owned generated-main qualifier */ int sep_mode; /* -c package compilation: imported interfaces are * present, so absent members are hard export errors. */ Node *synth_test_run; /* exact compiler-generated support.run DOT; @@ -620,6 +630,6 @@ int fold_int_literal(Node*, u64*); /* Re-evaluate a checked integer constant under the declaration owner's * import scope. The compiler export writer uses this to canonicalize array * dimensions without serializing source-level constant dependencies. */ -int check_eval_const(Checker*, Node*, const char *owner, u64*); +int check_eval_const(Checker*, Node*, const char *owner, int source, u64*); #endif /* WW_H */ diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 99858899..22e4ac4e 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -717,6 +717,9 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package, struct sepbind { char kind; char *name; + char *source; + int line; + int col; int dep; /* stable canonical action index; -1 for inline */ }; @@ -753,6 +756,7 @@ struct seppkg { int root; /* requested usage; never package-action identity */ int link_entry; /* package supplies the executable's bare main */ int generated_main; /* compiler-owned generated test-main package */ + int generated_target; /* target variant used by generated test main */ int failed; /* discovery/compile failure reaches this action */ int test_support; /* compiler-generated -T support package */ int loaded; /* directory membership/name loaded exactly once */ @@ -1094,18 +1098,20 @@ sep_root_is_command(const struct seppkg *p) return strcmp(p->name, "main") == 0; } +static int sep_external_production_edge(const struct sepgraph *, int, int); +static int sep_external_name_matches_production(const struct sepgraph *, + int, int); + static int sep_forbidden_command_import(const struct sepgraph *g, int importer, int dep) { - const struct seppkg *from = &g->pkg[importer]; const struct seppkg *to = &g->pkg[dep]; if (strcmp(to->name, "main") != 0 || to->role != SEP_ROLE_NORMAL) return 0; /* An external test's exact import of its colocated command production is * test-variant wiring, not a general source-importable command edge. */ - return !(from->variant == SEP_VARIANT_EXTERNAL - && sep_command_declared_name(from) - && strcmp(from->canon, to->canon) == 0); + return !(sep_external_production_edge(g, importer, dep) + && sep_external_name_matches_production(g, importer, dep)); } static int @@ -1550,7 +1556,10 @@ sep_pkg_free_fields(struct seppkg *p) { for (int j = 0; j < p->nsources; j++) free(p->sources[j]); free(p->sources); - for (int j = 0; j < p->bindings.n; j++) free(p->bindings.v[j].name); + for (int j = 0; j < p->bindings.n; j++) { + free(p->bindings.v[j].name); + free(p->bindings.v[j].source); + } free(p->bindings.v); free(p->context_state); free(p->deps); @@ -2071,28 +2080,46 @@ use_node_cmp(const void *a, const void *b) const char *yp = y->usepath ? y->usepath : y->str; int r = strcmp(xp, yp); if (r != 0) return r; + r = strcmp(x->pos.file ? x->pos.file : "", + y->pos.file ? y->pos.file : ""); + if (r != 0) return r; if (x->pos.line != y->pos.line) return x->pos.line - y->pos.line; return x->pos.col - y->pos.col; } static int -sep_external_production_name(const struct seppkg *pkg, const char *path, - int leaf_only) +sep_external_production_name(const struct seppkg *pkg, const char *name) { if (pkg->variant != SEP_VARIANT_EXTERNAL || pkg->test_package == NULL || pkg->test_package[0] == '\0') return 0; - const char *name = path; - if (leaf_only) { - const char *dot = strrchr(path, '.'); - if (dot != NULL) name = dot + 1; - } size_t n = strlen(name); size_t tn = strlen(pkg->test_package); return tn == n + 5 && strncmp(pkg->test_package, name, n) == 0 && strcmp(pkg->test_package + n, "_test") == 0; } +static int +sep_external_production_edge(const struct sepgraph *g, int importer, int dep) +{ + const struct seppkg *from = &g->pkg[importer]; + const struct seppkg *to = &g->pkg[dep]; + return from->variant == SEP_VARIANT_EXTERNAL + && to->variant == SEP_VARIANT_PRODUCTION + && to->role == SEP_ROLE_NORMAL + && strcmp(from->canon, to->canon) == 0; +} + +static int +sep_external_name_matches_production(const struct sepgraph *g, int importer, + int dep) +{ + const struct seppkg *from = &g->pkg[importer]; + const struct seppkg *to = &g->pkg[dep]; + return from->name != NULL && to->name != NULL + && sep_external_production_name(from, to->name); +} + static char *sep_local_import_base(const struct seppkg *); /* Canonical source bindings make a shared action independent of the request @@ -2101,20 +2128,21 @@ static char *sep_local_import_base(const struct seppkg *); * route/root legality deliberately does not enter action identity. */ static int sep_binding_add(struct sepbindset *bindings, char kind, const char *name, - int dep) + int dep, Pos pos) { - for (int i = 0; i < bindings->n; i++) - if (bindings->v[i].kind == kind - && bindings->v[i].dep == dep - && strcmp(bindings->v[i].name, name) == 0) - return 0; if (bindings->n == INT_MAX) return sep_fail_size(); if (sep_reserve((void **)&bindings->v, &bindings->cap, bindings->n + 1, sizeof *bindings->v) < 0) return -1; char *copy = strdup(name); if (copy == NULL) return sep_fail_nomem(); - bindings->v[bindings->n++] = (struct sepbind){ kind, copy, dep }; + char *source = strdup(pos.file ? pos.file : ""); + if (source == NULL) { + free(copy); + return sep_fail_nomem(); + } + bindings->v[bindings->n++] = (struct sepbind){ + kind, copy, source, pos.line, pos.col, dep }; return 0; } @@ -2126,13 +2154,83 @@ sep_binding_cmp(const void *a, const void *b) if (r != 0) return r; if (x->kind != y->kind) return (unsigned char)x->kind - (unsigned char)y->kind; - return x->dep < y->dep ? -1 : x->dep > y->dep; + if (x->dep != y->dep) return x->dep < y->dep ? -1 : 1; + r = strcmp(x->source, y->source); + if (r != 0) return r; + if (x->line != y->line) return x->line - y->line; + return x->col - y->col; +} + +static int +sep_binding_semantic_same(const struct sepbind *a, const struct sepbind *b) +{ + return a->kind == b->kind && a->dep == b->dep + && strcmp(a->name, b->name) == 0; +} + +static int +sep_bindsets_semantically_same(const struct sepbindset *a, + const struct sepbindset *b) +{ + int ai = 0, bi = 0; + while (ai < a->n && bi < b->n) { + if (!sep_binding_semantic_same(&a->v[ai], &b->v[bi])) return 0; + struct sepbind *av = &a->v[ai]; + struct sepbind *bv = &b->v[bi]; + do ai++; while (ai < a->n + && sep_binding_semantic_same(av, &a->v[ai])); + do bi++; while (bi < b->n + && sep_binding_semantic_same(bv, &b->v[bi])); + } + return ai == a->n && bi == b->n; +} + +static int +sep_validate_bindings(const struct sepgraph *g, + const struct sepbindset *bindings) +{ + const char *name = NULL; + int dep = -1; + for (int i = 0; i < bindings->n; i++) { + const struct sepbind *b = &bindings->v[i]; + if (b->kind != 'D') continue; + if (name != NULL && strcmp(name, b->name) == 0 && dep != b->dep) { + Pos pos = { b->source, b->line, b->col }; + errorf(pos, "package path %s resolves to both %s and %s", + b->name, g->pkg[dep].path, g->pkg[b->dep].path); + return -1; + } + name = b->name; + dep = b->dep; + } + return 0; +} + +static int +sep_binding_needs_map(const struct sepgraph *g, const struct sepbind *b) +{ + return b->kind == 'D' && b->dep >= 0 && b->dep < g->n + && strcmp(b->name, g->pkg[b->dep].path) != 0; +} + +static int +sep_binding_first_map(const struct sepgraph *g, + const struct sepbindset *bindings, int i) +{ + if (!sep_binding_needs_map(g, &bindings->v[i])) return 0; + for (int j = i - 1; j >= 0 + && strcmp(bindings->v[j].name, bindings->v[i].name) == 0; j--) + if (sep_binding_needs_map(g, &bindings->v[j])) return 0; + return 1; } static void sep_bindset_free(struct sepbindset *bindings) { - for (int i = 0; i < bindings->n; i++) free(bindings->v[i].name); + for (int i = 0; i < bindings->n; i++) { + free(bindings->v[i].name); + free(bindings->v[i].source); + } free(bindings->v); bindings->v = NULL; bindings->n = bindings->cap = 0; @@ -2403,12 +2501,9 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file, if (nuse > 1) qsort(uses, (size_t)nuse, sizeof *uses, use_node_cmp); int rc = 0; - const char *previous = NULL; for (int i = 0; i < nuse && rc == 0; i++) { Node *u = uses[i]; const char *name = u->usepath ? u->usepath : u->str; - if (previous && strcmp(previous, name) == 0) continue; - previous = name; if (reserved_import_path(name)) { errorf(u->pos, "package path %s is reserved", name); rc = -1; @@ -2442,9 +2537,8 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file, g->context[context].route); int literal_self = route_suffix != NULL ? strcmp(path_form, route_suffix) == 0 - && sep_external_production_name(&g->pkg[pi], name, 1) : strchr(name, '.') == NULL - && sep_external_production_name(&g->pkg[pi], name, 0); + && sep_external_production_name(&g->pkg[pi], name); if (literal_self) { if (g->pkg[pi].import_base != NULL) resolved.identity = strdup(g->pkg[pi].import_base); @@ -2471,18 +2565,17 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file, break; } if (!located) { - const char *dot = strrchr(name, '.'); - const char *leaf = dot ? dot + 1 : name; int inline_package = 0; if (!g->pkg[pi].is_dir) for (Node *package = imports->body; package; package = package->next) - if (strcmp(package->module, leaf) == 0) { + if (strcmp(package->module, name) == 0) { inline_package = 1; break; } if (inline_package) { - if (sep_binding_add(bindings, 'I', name, -1) < 0) + if (sep_binding_add(bindings, 'I', name, -1, + u->pos) < 0) rc = -1; continue; } @@ -2506,10 +2599,7 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file, break; } int self = strcmp(canon, g->pkg[pi].canon) == 0; - if (self - && (sep_external_production_name(&g->pkg[pi], name, 1) - || (g->pkg[pi].variant == SEP_VARIANT_EXTERNAL - && sep_command_declared_name(&g->pkg[pi])))) + if (self && g->pkg[pi].variant == SEP_VARIANT_EXTERNAL) external_production = 1; if (self && !external_production) { const char *owner = g->pkg[pi].path[0] @@ -2574,7 +2664,7 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file, int child_context = sep_child_context_for(g, context, resolved.entry, resolved.source_root); if (child_context < 0 - || sep_binding_add(bindings, 'D', name, di) < 0 + || sep_binding_add(bindings, 'D', name, di, u->pos) < 0 || sep_add_dep(g, pi, di) < 0 || sep_children_add(children, di, child_context) < 0) { free(canon); @@ -2688,6 +2778,7 @@ sep_add_generated_main(struct sepgraph *g, struct sepproduct *product, p->root = 1; p->link_entry = 1; p->generated_main = 1; + p->generated_target = variant; p->loaded = 1; p->emit_context = product->context; if (sep_set_context_state(p, product->context, 2) < 0 @@ -2754,18 +2845,6 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context, for (int i = 0; i < g->pkg[pi].nsources && rc == 0; i++) rc = sep_scan_file(g, pi, g->pkg[pi].sources[i], context, &fv, &bindings, children, 1); - if (rc == 0 && g->pkg[pi].path[0] != '\0' - && !g->pkg[pi].test_support) { - const char *dot = strrchr(g->pkg[pi].path, '.'); - const char *leaf = dot ? dot + 1 : g->pkg[pi].path; - if (strcmp(g->pkg[pi].name, leaf) != 0 - && !sep_command_declared_name(&g->pkg[pi])) { - fprintf(stderr, - "ww: package %s does not match import path %s\n", - g->pkg[pi].name, g->pkg[pi].path); - rc = -1; - } - } } else if (rc == 0) { rc = sep_scan_file(g, pi, g->pkg[pi].entry, context, &fv, &bindings, children, 0); @@ -2774,6 +2853,7 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context, if (bindings.n > 1) qsort(bindings.v, (size_t)bindings.n, sizeof *bindings.v, sep_binding_cmp); + if (rc == 0 && sep_validate_bindings(g, &bindings) < 0) rc = -1; if (rc == 0 && g->pkg[pi].emit_context < 0) { g->pkg[pi].bindings = bindings; bindings.v = NULL; @@ -2781,12 +2861,7 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context, g->pkg[pi].emit_context = context; } else if (rc == 0) { struct sepbindset *want = &g->pkg[pi].bindings; - if (want->n != bindings.n) rc = -1; - for (int i = 0; i < want->n && rc == 0; i++) - if (want->v[i].kind != bindings.v[i].kind - || want->v[i].dep != bindings.v[i].dep - || strcmp(want->v[i].name, bindings.v[i].name) != 0) - rc = -1; + if (!sep_bindsets_semantically_same(want, &bindings)) rc = -1; if (rc < 0) { const char *first = g->context[g->pkg[pi].emit_context].root; @@ -2875,6 +2950,14 @@ sep_load_pkg(struct sepgraph *g, int pi, int context) if (f->pending_dep >= 0) { int dep = f->pending_dep; f->pending_dep = -1; + if (dep != f->pkg + && sep_external_production_edge(g, f->pkg, dep) + && !sep_external_name_matches_production(g, f->pkg, dep)) { + fprintf(stderr, + "ww: external test package %s does not match production package %s\n", + g->pkg[f->pkg].name, g->pkg[dep].name); + goto failed; + } if (dep != f->pkg && sep_forbidden_command_import(g, f->pkg, dep)) { fprintf(stderr, @@ -3055,54 +3138,27 @@ sep_reverse_import_base(const struct sepgraph *g, const struct seppkg *pkg, return 0; } -static char * -sep_ordinary_declared_name(const struct seppkg *p) -{ - if (p->name == NULL || p->name[0] == '\0') return NULL; - size_t n = strlen(p->name); - if (p->variant == SEP_VARIANT_EXTERNAL) { - if (n <= 5 || strcmp(p->name + n - 5, "_test") != 0) { - fprintf(stderr, - "ww: package-test selector does not name an external package\n"); - return NULL; - } - n -= 5; - } - char *out = malloc(n + 1); - if (out == NULL) { - sep_fail_nomem(); - return NULL; - } - memcpy(out, p->name, n); - out[n] = '\0'; - return out; -} - /* The reserved local namespace is reversible, so filesystem identity never - * depends on a hash, request order, output name, or another selected package. */ + * depends on a hash, request order, output name, declared package name, or + * another selected package. */ static char * sep_local_import_base(const struct seppkg *p) { - char *leaf = sep_ordinary_declared_name(p); - if (leaf == NULL) return NULL; size_t prefix = strlen(SEP_LOCAL_IMPORT_PREFIX); - size_t canon_len = strlen(p->canon), leaf_len = strlen(leaf); - if (leaf_len > (size_t)-1 - prefix - 4 - || canon_len > ((size_t)-1 - prefix - 4 - leaf_len) / 4) { + size_t canon_len = strlen(p->canon); + if (canon_len > ((size_t)-1 - prefix - 3) / 4) { sep_fail_size(); - free(leaf); return NULL; } - size_t outsz = prefix + 3 + 4 * canon_len + leaf_len + 1; + size_t outsz = prefix + 2 + 4 * canon_len + 1; char *out = malloc(outsz); if (out == NULL) { sep_fail_nomem(); - free(leaf); return NULL; } size_t off = 0; int n = snprintf(out, outsz, "%s.p", SEP_LOCAL_IMPORT_PREFIX); - if (n < 0 || (size_t)n >= outsz) { free(leaf); free(out); return NULL; } + if (n < 0 || (size_t)n >= outsz) { free(out); return NULL; } off = (size_t)n; static const char hex[] = "0123456789abcdef"; for (const unsigned char *s = (const unsigned char *)p->canon; @@ -3110,25 +3166,21 @@ sep_local_import_base(const struct seppkg *p) unsigned char c = *s; if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { - if (off + 1 >= outsz) { free(leaf); free(out); return NULL; } + if (off + 1 >= outsz) { free(out); return NULL; } out[off++] = (char)c; } else if (c == '_' || c == '/') { - if (off + 2 >= outsz) { free(leaf); free(out); return NULL; } + if (off + 2 >= outsz) { free(out); return NULL; } out[off++] = '_'; out[off++] = c == '_' ? 'u' : 's'; } else { - if (off + 4 >= outsz) { free(leaf); free(out); return NULL; } + if (off + 4 >= outsz) { free(out); return NULL; } out[off++] = '_'; out[off++] = 'x'; out[off++] = hex[c >> 4]; out[off++] = hex[c & 15]; } } - size_t ln = leaf_len; - if (off + 1 + ln + 1 > outsz) { free(leaf); free(out); return NULL; } - out[off++] = '.'; - memcpy(out + off, leaf, ln + 1); - free(leaf); + out[off] = '\0'; return out; } @@ -3193,17 +3245,6 @@ sep_finalize_directory_identities(struct sepgraph *g) struct seppkg *p = &g->pkg[pi]; if (!p->is_dir || p->generated_main || p->failed || !p->loaded) continue; - const char *dot = strrchr(p->path, '.'); - const char *leaf = dot ? dot + 1 : p->path; - int support_alias = p->role == SEP_ROLE_TEST_SUPPORT - && strcmp(p->path, SEP_TEST_SUPPORT_MODULE) == 0 - && strcmp(p->name, "test") == 0; - if (!support_alias && strcmp(p->name, leaf) != 0 - && !sep_command_declared_name(p)) { - fprintf(stderr, "ww: package %s does not match import path %s\n", - p->name, p->path); - return -1; - } free(p->artifact); p->artifact = NULL; if (p->variant == SEP_VARIANT_SAME_TEST) @@ -3418,9 +3459,7 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *unitf) * retarget cannot reuse stale assembly even when export bytes are equal. */ for (int i = 0; i < g->pkg[pi].bindings.n && bodyrc == 0; i++) { struct sepbind *b = &g->pkg[pi].bindings.v[i]; - if (b->kind != 'D' || b->dep < 0 || b->dep >= g->n - || strcmp(b->name, g->pkg[b->dep].path) == 0) - continue; + if (!sep_binding_first_map(g, &g->pkg[pi].bindings, i)) continue; if (fprintf(u, "//ww:import-map %s %s ", b->name, g->pkg[b->dep].path) < 0 || sep_emit_hex(u, g->pkg[b->dep].canon) < 0 @@ -3624,7 +3663,7 @@ static void workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm) { snprintf(buf, bufsz, "ww workdir fmt %d mode %s asm %d\n", - is_test ? 12 : 13, is_test ? "test" : "build", emit_asm); + is_test ? 13 : 14, is_test ? "test" : "build", emit_asm); } /* A stale global builder identity invalidates every committed unit voucher in @@ -3658,6 +3697,20 @@ invalidate_workdir_units(const char *scratch) return rc; } +static int +sep_discard_action_staging(int warm, const char *unit, const char *wwi, + const char *assembly, const char *object, const char *archive) +{ + if (!warm) return 0; + const char *paths[] = { unit, wwi, assembly, object, archive }; + for (size_t i = 0; i < sizeof paths / sizeof paths[0]; i++) { + if (unlink(paths[i]) == 0 || errno == ENOENT) continue; + fprintf(stderr, "ww: cannot remove staged package artifacts\n"); + return -1; + } + return 0; +} + struct sep_created_dirs { char path[PATH_MAX]; unsigned short offset[(PATH_MAX + 1) / 2]; @@ -4200,7 +4253,15 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *cs = warm ? asmnew : asmf; const char *co = warm ? objnew : obj; const char *ca = warm ? anew : apath; + if (sep_discard_action_staging(warm, unitnew, wwinew, asmnew, + objnew, anew) < 0) { + g->pkg[pi].failed = 1; + any_failed = 1; + continue; + } if (sep_compose_unit(g, pi, cu) < 0) { + (void)sep_discard_action_staging(warm, unitnew, wwinew, + asmnew, objnew, anew); g->pkg[pi].failed = 1; any_failed = 1; continue; @@ -4225,26 +4286,30 @@ build_one_sep_impl(const char *src, int entry_is_dir, } int nmaps = 0; for (int k = 0; k < g->pkg[pi].bindings.n; k++) { - struct sepbind *b = &g->pkg[pi].bindings.v[k]; - if (b->kind == 'D' && b->dep >= 0 && b->dep < g->n - && strcmp(b->name, g->pkg[b->dep].path) != 0) { - if (nmaps == INT_MAX) { - sep_fail_size(); - free(order); - return 1; - } - nmaps++; + if (!sep_binding_first_map(g, &g->pkg[pi].bindings, k)) + continue; + if (nmaps == INT_MAX) { + sep_fail_size(); + (void)sep_discard_action_staging(warm, unitnew, wwinew, + asmnew, objnew, anew); + free(order); + return 1; } + nmaps++; } - size_t cargvcap = 12; + size_t cargvcap = 14; if ((size_t)g->pkg[pi].ndeps > ((size_t)-1 - cargvcap) / 3) { fprintf(stderr, "ww: package graph is too large\n"); + (void)sep_discard_action_staging(warm, unitnew, wwinew, + asmnew, objnew, anew); free(order); return 1; } cargvcap += 3 * (size_t)g->pkg[pi].ndeps; if ((size_t)nmaps > ((size_t)-1 - cargvcap) / 3) { fprintf(stderr, "ww: package graph is too large\n"); + (void)sep_discard_action_staging(warm, unitnew, wwinew, + asmnew, objnew, anew); free(order); return 1; } @@ -4257,6 +4322,8 @@ build_one_sep_impl(const char *src, int entry_is_dir, if (cargv == NULL || (g->pkg[pi].ndeps > 0 && importfiles == NULL)) { fprintf(stderr, "ww: out of memory\n"); + (void)sep_discard_action_staging(warm, unitnew, wwinew, + asmnew, objnew, anew); free(importfiles); free(cargv); free(order); @@ -4270,6 +4337,10 @@ build_one_sep_impl(const char *src, int entry_is_dir, cargv[cpos++] = "--entry"; cargv[cpos++] = "--test-support-module"; cargv[cpos++] = (char *)test_support_module; + if (g->pkg[pi].generated_main) { + cargv[cpos++] = "--test-target-package"; + cargv[cpos++] = g->pkg[g->pkg[pi].generated_target].path; + } } else { if (g->pkg[pi].variant == SEP_VARIANT_SAME_TEST || g->pkg[pi].variant == SEP_VARIANT_EXTERNAL) @@ -4294,8 +4365,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, } for (int k = 0; k < g->pkg[pi].bindings.n; k++) { struct sepbind *b = &g->pkg[pi].bindings.v[k]; - if (b->kind != 'D' || b->dep < 0 || b->dep >= g->n - || strcmp(b->name, g->pkg[b->dep].path) == 0) + if (!sep_binding_first_map(g, &g->pkg[pi].bindings, k)) continue; cargv[cpos++] = "--import-map"; cargv[cpos++] = b->name; @@ -4315,6 +4385,8 @@ build_one_sep_impl(const char *src, int entry_is_dir, g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)"); g->pkg[pi].failed = 1; any_failed = 1; + (void)sep_discard_action_staging(warm, unitnew, wwinew, + asmnew, objnew, anew); continue; } g->pkg[pi].export_changed = !warm @@ -4327,6 +4399,8 @@ build_one_sep_impl(const char *src, int entry_is_dir, g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)"); g->pkg[pi].failed = 1; any_failed = 1; + (void)sep_discard_action_staging(warm, unitnew, wwinew, + asmnew, objnew, anew); continue; } } @@ -4338,6 +4412,8 @@ build_one_sep_impl(const char *src, int entry_is_dir, g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)"); g->pkg[pi].failed = 1; any_failed = 1; + (void)sep_discard_action_staging(warm, unitnew, wwinew, + asmnew, objnew, anew); continue; } } @@ -4353,6 +4429,8 @@ build_one_sep_impl(const char *src, int entry_is_dir, g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)"); g->pkg[pi].failed = 1; any_failed = 1; + (void)sep_discard_action_staging(warm, unitnew, wwinew, + asmnew, objnew, anew); continue; } } diff --git a/lib/crypto/sha256/sha256.ww b/lib/crypto/sha256/sha256.ww index 74c533b6..56da0658 100644 --- a/lib/crypto/sha256/sha256.ww +++ b/lib/crypto/sha256/sha256.ww @@ -37,7 +37,6 @@ import crypto.math; import endian; import hash; import io; -import os; // ref/hare/crypto/sha256/sha256.ha:11. export def SZ: size = 32; diff --git a/lib/hash/hash.ww b/lib/hash/hash.ww index f1c8c174..f46a66cb 100644 --- a/lib/hash/hash.ww +++ b/lib/hash/hash.ww @@ -21,7 +21,6 @@ package hash; import io; -import os; // 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 diff --git a/lib/io/stream.ww b/lib/io/stream.ww index 998a010b..56896954 100644 --- a/lib/io/stream.ww +++ b/lib/io/stream.ww @@ -31,6 +31,8 @@ package io; +import errors; + // 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 // `lib/io import os` is the correct direction (os is the import floor). diff --git a/lib/os/os.ww b/lib/os/os.ww index 92c7022b..89f0ea5d 100644 --- a/lib/os/os.ww +++ b/lib/os/os.ww @@ -4,12 +4,9 @@ package os; import time; -// [[args]] allocates the []str view via the `alloc` builtin, whose malloc -// lowers to rt_malloc only when the rt binding is in the bundle (mirror -// lib/strings/strings.ww:30 — every alloc-using module imports rt). os is -// 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; +// [[args]] allocates the []str view via the `alloc` builtin. Package-mode +// lowering targets the runtime ABI directly; no source import is needed merely +// to trigger a linker choice, so the dependency graph remains source-semantic. @symbol("rt_syscall") fn syscall0(num: nr) i64; @symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64; diff --git a/lib/os/os_test.ww b/lib/os/os_test.ww index 74e22f30..8b496e24 100644 --- a/lib/os/os_test.ww +++ b/lib/os/os_test.ww @@ -20,7 +20,6 @@ package os_test; import os; -import rt; import test; diff --git a/lib/ww/syntax/ast.ww b/lib/ww/syntax/ast.ww index 3527c631..643fdd2d 100644 --- a/lib/ww/syntax/ast.ww +++ b/lib/ww/syntax/ast.ww @@ -127,8 +127,11 @@ export type node = struct { type_: *void, // filled in by checker; type.ww treats it as *tinfo tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...) nmod: str, // originating module from `// MODULE: foo`; "" if none - usepath: str, // on an N_USE: full dotted IMPORT path vs leaf alias - // in `str` (M1 #22); "" otherwise + usepath: str, // on N_USE: full canonical import path; `str` becomes + // 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 ` import // 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 // to tokenise identically through C and ww (lex.ww:382 has the // 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; }; diff --git a/lib/ww/syntax/decl.ww b/lib/ww/syntax/decl.ww index be00748b..54b200dd 100644 --- a/lib/ww/syntax/decl.ww +++ b/lib/ww/syntax/decl.ww @@ -2,7 +2,6 @@ package syntax; -import os; import strings; // `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); n.nmod = p.curmod; // 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 - // user writes (`utf8.x`). + // checker's path-keyed module match. n.str is the provisional path leaf; + // direct export metadata later replaces it with the declared default name. let leaf: str; expectident(p, &leaf); let path: str = leaf; diff --git a/lib/ww/syntax/expr.ww b/lib/ww/syntax/expr.ww index bffdf643..9a5e07ab 100644 --- a/lib/ww/syntax/expr.ww +++ b/lib/ww/syntax/expr.ww @@ -2,8 +2,6 @@ package syntax; -import os; - // Inlined to avoid a cross-module `use sym;` for one call site. fn streqlocal(a: str, b: str) bool = { if (a.len != b.len) { return false; }; diff --git a/lib/ww/syntax/parse.ww b/lib/ww/syntax/parse.ww index ad2e961e..f57a8a88 100644 --- a/lib/ww/syntax/parse.ww +++ b/lib/ww/syntax/parse.ww @@ -41,11 +41,14 @@ export type parser = struct { // decls stamp nmod=pathmod and imported=1, and the in-file `package` // clause is an assertion. "" means inactive (root/primary). pathmod: str, - // #57: active `//ww:module-reset ` dotted path. Mangles decls on - // the path WITHOUT imported=1 (primary-ness for -c and the #32 bare-main - // rule stay intact), and the in-file `package` clause asserts (leaf == - // last component) instead of overwriting curmod. "" means inactive. + // #57: active `//ww:module-reset ` canonical identity. Decls + // mangle on it without imported=1; the package clause independently + // supplies the declared name. "" means inactive. 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. // Empty outside that one separate-compilation edge. testmodule: str, @@ -73,6 +76,8 @@ export fn parserinit(p: *parser, l: *lex) void = { p.nocast = 0; p.pathmod = ""; p.resetmod = ""; + p.curpkg = ""; + p.sourceid = 0; p.testmodule = ""; p.commandpackage = false; refill(p); @@ -498,19 +503,23 @@ export fn parseimports(p: *parser) *node = { // the following package clause and imports. if (p.curkind == tkind.TK_MODPATH) { sawpackage = false; + p.sourceid += 1; p.pathmod = p.curtext; p.curmod = p.curtext; p.resetmod = ""; + p.curpkg = ""; advance(p); continue; }; if (p.curkind == tkind.TK_MODRESET) { let rp: str = p.curtext; sawpackage = false; + p.sourceid += 1; advance(p); p.pathmod = ""; p.curmod = rp; p.resetmod = rp; + p.curpkg = ""; continue; }; if (p.curkind == tkind.TK_MODULE) { @@ -527,14 +536,21 @@ export fn parseimports(p: *parser) *node = { let name: str; expectident(p, &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); - pm.nmod = name; + pm.nmod = p.curmod; + pm.pkgname = name; + pm.sourceid = p.sourceid; if (packages == nil) { packages = pm; } else { packagetail.next = pm; }; packagetail = pm; if (!sawpackage) { f.nmod = name; + f.pkgname = name; + f.sourceid = p.sourceid; f.file = pf; f.line = pl; f.col = pc; @@ -549,6 +565,8 @@ export fn parseimports(p: *parser) *node = { if (p.curkind == tkind.TK_USE) { let d: *node = parseuse(p); d.nmod = p.curmod; + d.pkgname = p.curpkg; + d.sourceid = p.sourceid; if (head == nil) { head = d; } else { tail.next = d; }; tail = d; continue; @@ -560,6 +578,8 @@ export fn parseimports(p: *parser) *node = { errmsg(p, "import cannot be exported or attributed"); let d: *node = parseuse(p); d.nmod = p.curmod; + d.pkgname = p.curpkg; + d.sourceid = p.sourceid; if (head == nil) { head = d; } else { tail.next = d; }; tail = d; continue; @@ -573,6 +593,8 @@ export fn parseimports(p: *parser) *node = { errmsg(p, "import cannot be exported or attributed"); let d: *node = parseuse(p); d.nmod = p.curmod; + d.pkgname = p.curpkg; + d.sourceid = p.sourceid; if (head == nil) { head = d; } else { tail.next = d; }; tail = d; continue; @@ -593,6 +615,8 @@ export fn parsefile(p: *parser) *node = { let f = newnode(nkind.N_FILE, p.curfile, p.curline, p.curcol); let head: *node = nil; let tail: *node = nil; + let packages: *node = nil; + let packagetail: *node = nil; let sawpackage: i32 = 0; for (p.curkind != tkind.TK_EOF) { // `package foo;` — directory-as-module declaration. Every @@ -602,57 +626,44 @@ export fn parsefile(p: *parser) *node = { // sep primary-reset (resetmod) regions carry identity // out-of-band and are exempt. if (p.curkind == tkind.TK_MODULE) { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; sawpackage = 1; advance(p); let name: str; expectident(p, &name); expecttok(p, tkind.TK_SEMI, "expected ';' after module name"); - if (p.pathmod.len != 0 || p.resetmod.len != 0) { - // M1 #22: while an import path is active the in-file - // `package` clause is an ASSERTION — its leaf must - // equal the path's last component; it does NOT - // overwrite the path-derived module. #57 extends this - // to the sep primary-reset path (resetmod): the dotted - // reset path is 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.curpkg = name; + if (p.pathmod.len == 0 && p.resetmod.len == 0) { p.curmod = name; - // #11: stamp the primary module identity on the - // N_FILE node so wwi_emit can derive the `package` - // leaf even when the body carries zero module-tagged - // decls. Primary identity only; never the imported - // boundary (TK_MODPATH). - if (f.nmod.len == 0) { f.nmod = name; }; + }; + let pm: *node = newnode(nkind.N_FILE, pf, pl, pc); + pm.nmod = p.curmod; + pm.pkgname = name; + pm.sourceid = p.sourceid; + 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; }; // `//ww:module ` — M1 #22 import boundary. The following - // file's decls mangle on the full dotted import path, not the - // leaf `package` clause, and are flagged imported (gates the + // file's decls mangle on the full dotted import path independently + // of its `package` clause, and are flagged imported (gates the // root-only bare-`main` rule, #32). if (p.curkind == tkind.TK_MODPATH) { sawpackage = 0; + p.sourceid += 1; p.pathmod = p.curtext; p.curmod = p.curtext; p.resetmod = ""; + p.curpkg = ""; + if (f.nmod.len == 0) { f.nmod = p.curtext; }; advance(p); continue; }; @@ -666,6 +677,7 @@ export fn parsefile(p: *parser) *node = { // decls to bare — that usage is deliberate-only. if (p.curkind == tkind.TK_MODRESET) { sawpackage = 0; + p.sourceid += 1; // #57: a path-carrying reset (sep primary body) mangles decls // on the dotted path so definer == importer, but leaves // 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; advance(p); p.pathmod = ""; + p.curpkg = ""; if (rp.len != 0) { p.curmod = rp; p.resetmod = rp; @@ -756,6 +769,9 @@ export fn parsefile(p: *parser) *node = { // M1 #22: flag decls reached via an import-path boundary // (gates the root-only bare-`main` rule, #32). if (p.pathmod.len != 0) { d.imported = 1; }; + d.nmod = p.curmod; + d.pkgname = p.curpkg; + d.sourceid = p.sourceid; if (head == nil) { head = d; tail = d; @@ -766,5 +782,6 @@ export fn parsefile(p: *parser) *node = { }; }; f.list = head; + f.body = packages; return f; }; diff --git a/lib/ww/syntax/stmt.ww b/lib/ww/syntax/stmt.ww index 533bb894..f8817468 100644 --- a/lib/ww/syntax/stmt.ww +++ b/lib/ww/syntax/stmt.ww @@ -2,8 +2,6 @@ package syntax; -import os; - fn parseletlocal(p: *parser) *node = { let pf = p.curfile; let pl = p.curline; diff --git a/lib/ww/syntax/tok.ww b/lib/ww/syntax/tok.ww index 6ee01eba..e108514f 100644 --- a/lib/ww/syntax/tok.ww +++ b/lib/ww/syntax/tok.ww @@ -115,8 +115,8 @@ export type tkind = enum i32 { // reset curmod to "" before a package-less file // (#16 option-B; cstage TK_MODRESET twin) TK_MODPATH = 88, // `//ww:module ` driver import - // boundary; decls mangle on the path, not the - // leaf `package` clause (M1 #22; cstage twin) + // boundary; decls mangle on the path independently + // of the `package` clause (M1 #22; cstage twin) TK_LAST = 89, }; diff --git a/lib/ww/syntax/typ.ww b/lib/ww/syntax/typ.ww index 136c8d9f..f28e743c 100644 --- a/lib/ww/syntax/typ.ww +++ b/lib/ww/syntax/typ.ww @@ -8,8 +8,6 @@ package syntax; -import os; - // Mirror of the C `TypeKind` enum in cmd/wcc/ww.h. Numeric values // are explicit and must stay in sync — the selfhost selfcheck and // typed-AST printers depend on matching numeric layout. diff --git a/selfhost/cmd/w6a/asm.ww b/selfhost/cmd/w6a/asm.ww index 15003b99..474e5779 100644 --- a/selfhost/cmd/w6a/asm.ww +++ b/selfhost/cmd/w6a/asm.ww @@ -4,7 +4,6 @@ package main; import os; -import rt; import strings; export fn emitbyte(a: *asm_, b: u8) void = { diff --git a/selfhost/cmd/w6a/main.ww b/selfhost/cmd/w6a/main.ww index 04fd6b84..b75db3c3 100644 --- a/selfhost/cmd/w6a/main.ww +++ b/selfhost/cmd/w6a/main.ww @@ -5,7 +5,6 @@ package main; import os; -import rt; import strings; fn cstreq(a: *u8, lit: str) bool = { diff --git a/selfhost/cmd/w6c/main.ww b/selfhost/cmd/w6c/main.ww index 53113e9e..ffd88b5a 100644 --- a/selfhost/cmd/w6c/main.ww +++ b/selfhost/cmd/w6c/main.ww @@ -3,7 +3,6 @@ package main; import os; -import rt; import strings; import syntax; import wcc; @@ -95,13 +94,93 @@ fn allocimportptrs(count: i32) ([]*u8 | nomem) = { return value; }; -fn importleaf(path: *u8) str = { - let whole: str = pathstr(path); - let (prefix, suffix) = strings.rcut(whole, "."); - // 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; }; - return whole; +fn allocnodeptrs(count: i32) ([]*syntax.node | nomem) = { + let value: []*syntax.node = alloc([], count: u64)?; + return value; +}; + +// Export data carries canonical owner and declared name independently. Each +// 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 = { @@ -109,6 +188,7 @@ export fn main(argc: i32, argv: **u8) i32 = { let out: *u8 = nil; let wwiout: *u8 = nil; // -I : M2 export-data producer let testsupport: *u8 = nil; + let testtarget: *u8 = nil; let testmode: i32 = 0i32; // #15: `-T` test-mode let testpackage: 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 importpaths: []*u8; let importfiles: []*u8; + let astallocation: ([]*syntax.node | nomem) = allocnodeptrs(argc); + let importasts: []*syntax.node; match (pathallocation) { case let value: []*u8 => importpaths = value; case nomem => { @@ -138,6 +220,15 @@ export fn main(argc: i32, argv: **u8) i32 = { }; importpaths.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 mapallocation: ([]importmap | nomem) = allocimportmaps(argc); let importmaps: []importmap; @@ -187,6 +278,14 @@ export fn main(argc: i32, argv: **u8) i32 = { return 2; }; 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")) { sepmode = 1i32; } else { if (cstreq(a, "--import")) { @@ -221,12 +320,12 @@ export fn main(argc: i32, argv: **u8) i32 = { return 2; }; src = a; - }; }; }; }; }; }; }; }; }; }; }; + }; }; }; }; }; }; }; }; }; }; }; }; i += 1; }; 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); return 2; }; @@ -280,12 +379,6 @@ export fn main(argc: i32, argv: **u8) i32 = { os.write(2, m.ptr, m.len: u64); 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 directi: i32 = 0; for (directi < nimports) { @@ -310,6 +403,27 @@ export fn main(argc: i32, argv: **u8) i32 = { os.write(2, m.ptr, m.len: u64); 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 importtail: *node = nil; @@ -352,6 +466,9 @@ export fn main(argc: i32, argv: **u8) i32 = { if (testsupport != nil) { ips.testmodule = pathstr(testsupport); }; let imported: *node = parsefile(&ips); 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; if (d != nil) { if (importhead == nil) { importhead = d; } @@ -408,6 +525,35 @@ export fn main(argc: i32, argv: **u8) i32 = { }; 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) { importtail.next = f.list; f.list = importhead; @@ -433,8 +579,10 @@ export fn main(argc: i32, argv: **u8) i32 = { let testmodule: str; if (testsupport != nil) { testmodule = pathstr(testsupport); }; + let testtargetmodule: str; + if (testtarget != nil) { testtargetmodule = pathstr(testtarget); }; let interfaceout: str; if (wwiout != nil) { interfaceout = pathstr(wwiout); }; - return wcc.compilefile(f, testmode, testpackage, testmodule, sepmode, - interfaceout, entrymode); + return wcc.compilefile(f, testmode, testpackage, testmodule, + testtargetmodule, sepmode, interfaceout, entrymode); }; diff --git a/selfhost/cmd/w6l/dyn.ww b/selfhost/cmd/w6l/dyn.ww index 27380542..744378af 100644 --- a/selfhost/cmd/w6l/dyn.ww +++ b/selfhost/cmd/w6l/dyn.ww @@ -7,7 +7,6 @@ package main; import os; -import rt; import strings; def ET_DYN_SO: u16 = 3u16; diff --git a/selfhost/cmd/w6l/dynout.ww b/selfhost/cmd/w6l/dynout.ww index 16178794..c61bf3a3 100644 --- a/selfhost/cmd/w6l/dynout.ww +++ b/selfhost/cmd/w6l/dynout.ww @@ -25,7 +25,6 @@ package main; import os; -import rt; import strings; def ET_EXEC_D: u16 = 2u16; diff --git a/selfhost/cmd/w6l/main.ww b/selfhost/cmd/w6l/main.ww index c8dc52ae..54d8f89d 100644 --- a/selfhost/cmd/w6l/main.ww +++ b/selfhost/cmd/w6l/main.ww @@ -5,7 +5,6 @@ package main; import os; -import rt; import strings; def BASE: u64 = 4194304u64; // 0x400000 diff --git a/selfhost/cmd/w6l/obj.ww b/selfhost/cmd/w6l/obj.ww index 2258d20c..22282c67 100644 --- a/selfhost/cmd/w6l/obj.ww +++ b/selfhost/cmd/w6l/obj.ww @@ -3,7 +3,6 @@ package main; import os; -import rt; import strings; def ET_REL: i32 = 1; diff --git a/selfhost/cmd/w6l/out.ww b/selfhost/cmd/w6l/out.ww index dc9a57fb..5091f52c 100644 --- a/selfhost/cmd/w6l/out.ww +++ b/selfhost/cmd/w6l/out.ww @@ -3,7 +3,6 @@ package main; import os; -import rt; def ET_EXEC: u16 = 2u16; def EM_X86_64_W: u16 = 62u16; diff --git a/selfhost/cmd/wcc/api.ww b/selfhost/cmd/wcc/api.ww index d104b2ff..04579ece 100644 --- a/selfhost/cmd/wcc/api.ww +++ b/selfhost/cmd/wcc/api.ww @@ -3,7 +3,8 @@ package wcc; import syntax; 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; syntax.typesinit(&tc); let ck: checker; @@ -11,6 +12,7 @@ export fn compilefile(file: *syntax.node, testmode: i32, testpackage: i32, ck.istest = testmode; ck.istestpackage = testpackage; if (testmodule.len > 0) { ck.testmodule = testmodule; }; + ck.testtarget = testtarget; ck.sepmode = sepmode; checkfile(&ck, file); if (ck.errs > 0) { return 1; }; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 9d3009de..e28bcab5 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -455,7 +455,9 @@ type cgen = struct { // bare-IDENT call mangling — `frob()` from // inside lib/foo binds to `foo.frob` even when // 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) looptop: i32, 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 // itself only interns, so driving curmod here has no other effect. let savedmod: str = c.curmod; + let savedsource: i32 = c.cursource; let d: *syntax.node = file.list; for (d != nil) { c.curmod = d.nmod; + c.cursource = d.sourceid; // #22 M3: skip imported deps so the strlit table (and its _S_ // sequence) is a pure function of THIS package's own decls. A // dep's body initializer would intern here, but its `.wwi` (init @@ -1596,6 +1600,7 @@ fn letpreintern(c: *cgen, file: *syntax.node) void = { d = d.next; }; c.curmod = savedmod; + c.cursource = savedsource; }; // 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 = { + let savedmod: str = c.curmod; + let savedsource: i32 = c.cursource; let d: *syntax.node = file.list; for (d != nil) { + c.curmod = d.nmod; + c.cursource = d.sourceid; // #22 M3 THE ONE REAL GUARD: a `.wwi` dep value-global is // initializer-less; emitting a DATAW for it would DUPLICATE the // 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; }; + c.curmod = savedmod; + c.cursource = savedsource; }; // 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 // gate is for. fn emitdefconstants(c: *cgen, file: *syntax.node) void = { + let savedmod: str = c.curmod; + let savedsource: i32 = c.cursource; let d: *syntax.node = file.list; for (d != nil) { + c.curmod = d.nmod; + c.cursource = d.sourceid; // #22 M3: a `.wwi` dep def with DATA storage (int-fold / float / // struct / array) must NOT re-emit — the dep's own .o owns the // symbol. Str defs are inline-spliced (never emitted here), so @@ -3369,6 +3384,8 @@ fn emitdefconstants(c: *cgen, file: *syntax.node) void = { }; d = d.next; }; + c.curmod = savedmod; + c.cursource = savedsource; }; // emitdatasection — DATA directives for every interned strlit. @@ -3760,6 +3777,7 @@ type modent = struct { mname: str, // the bare ident as it appears in source nmod: str, // the originating module (`// MODULE: foo`) 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, }; @@ -3772,7 +3790,7 @@ fn collectmods(c: *cgen, file: *syntax.node) void = { // M1 #22: record alias→path for the qualified-ref hint. if (d.kind == syntax.nkind.N_USE) { 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; }; }; @@ -3811,7 +3829,7 @@ fn collectmods(c: *cgen, file: *syntax.node) void = { // Explicit entry mode clears sepisdep independently of export // production; every other package's main remains mangled. 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; }; }; @@ -3827,19 +3845,19 @@ fn collectmods(c: *cgen, file: *syntax.node) void = { // retained until then (rule-11). if (d.kind == syntax.nkind.N_DEF) { 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; }; }; if (d.kind == syntax.nkind.N_TYPEDECL) { 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; }; }; if (d.kind == syntax.nkind.N_LET) { 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; }; }; @@ -3859,18 +3877,10 @@ fn modlookup(c: *cgen, name: str) str = { return empty; }; -// usehint — M1 #22: map a qualified-ref alias (`utf8`) to its dotted -// import path (`encoding.utf8`) so the codegen hint keys the path-keyed -// mods map. For single-level packages alias == path (no-op). Returns the -// alias unchanged when no matching `use` exists. -// -// 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). +// usehint — map a declared default qualifier to its canonical import path so +// codegen mangles on package identity. It is source-file local: separate files +// may bind the same declared name to different paths. Raw non-package +// compilation retains its historical single-occurrence fallback. fn usehint(c: *cgen, alias: str) str = { let m: *modent = c.uses; let any: str; @@ -3878,8 +3888,12 @@ fn usehint(c: *cgen, alias: str) str = { any.len = 0; for (m != nil) { if (syntax.streq(m.mname, alias)) { - if (syntax.streq(m.omod, c.curmod)) { return m.nmod; }; - if (any.ptr == nil && any.len == 0) { any = m.nmod; }; + if (m.sourceid == c.cursource && syntax.streq(m.omod, c.curmod)) { + return m.nmod; + }; + if (c.sepmode == 0 && any.ptr == nil && any.len == 0) { + any = m.nmod; + }; }; m = m.mnext; }; diff --git a/selfhost/cmd/wcc/cgendecl.ww b/selfhost/cmd/wcc/cgendecl.ww index ea5937aa..dda5116c 100644 --- a/selfhost/cmd/wcc/cgendecl.ww +++ b/selfhost/cmd/wcc/cgendecl.ww @@ -2,7 +2,6 @@ package wcc; import os; import syntax; -import strconv; fn cgfnparams(c: *cgen, params: *syntax.node) void = { let p: *syntax.node = params; @@ -518,6 +517,7 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = { cgeninit(c); c.fnname = fn_.str; c.curmod = fn_.nmod; + c.cursource = fn_.sourceid; c.fnret = fn_.lhs; // sret callee (#23): return type is plain TY_STRUCT > 24B. diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 2834feee..b030bfe2 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -11,7 +11,6 @@ package wcc; import os; import syntax; -import strconv; // 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 diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index e85846fa..11b58c89 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -5,7 +5,6 @@ package wcc; import os; import syntax; -import strconv; // slicewrap — synthesise an N_TSLICE node wrapping the given element // type AST. Used by the Hare-style variadic path so the local entry diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index a54b683f..c9aebcb8 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -23,6 +23,8 @@ type checker = struct { istestpackage: i32, // validate/retain package-owned @test bodies and // export compiler-private metadata; no entry synth 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 // present, so absent members are hard export errors. synthtestrun: *syntax.node, // exact generated support.run DOT; its @@ -32,7 +34,9 @@ type checker = struct { curmod: str, // importing-module bareword for the decl // currently being walked; "" for primary // 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 // to consult the declaring source's own // `use` directives. @@ -154,6 +158,7 @@ fn declmod(file: *syntax.node, d: *syntax.node) str = { if (d == nil) { return empty; }; if (d.nmod.len == 0) { return empty; }; if (file == nil) { return empty; }; + if (d.imported != 0) { return d.nmod; }; let u: *syntax.node = file.list; for (u != nil) { // 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; }; -// usepath — map a `use` alias (leaf bareword the user writes, `utf8`) -// to the full dotted import path it binds (`encoding.utf8`), for the -// module-qualified resolution and codegen hint (M1 #22). Single-level -// packages have usepath == alias so the result is unchanged. The -// Only an import owned by the referencing package is visible; a matching +// usepath — map a source-file default qualifier (the imported package's +// declared name) to the full canonical import path it binds, for +// module-qualified resolution and codegen hint (M1 #22). Only an import owned +// by the referencing file is visible; a matching // 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; if (file == nil) { 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 leaf: str = suffix; 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; 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)) { let um: str = declmod(file, u); let same: bool = false; if (modtag.len == 0) { if (um.len == 0) { 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; }; 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 // the referencing package did not itself declare that import. 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 // `modtag` carry `use ;`? Mirrors cstage's src_imports — // `modtag.len == 0` means primary, matching declmod's empty-str // 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 (name.len == 0) { return false; }; let u: *syntax.node = file.list; 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 // `use fmt;` while its module tag is also "fmt". // 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. fn directmodvisible(c: *checker, mod: str) bool = { if (mod.len == 0) { return false; }; - let (prefix, suffix) = strings.rcut(mod, "."); - let alias: str = suffix; - if (alias.len == 0) { alias = mod; }; - let path: str = usepathfor(c.file, c.curmod, alias); - return path.len != 0 && syntax.streq(path, mod); + let u: *syntax.node = c.file.list; + for (u != nil) { + if (u.kind == syntax.nkind.N_USE && u.sourceid == c.cursource) { + let um: str = declmod(c.file, u); + 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 = { @@ -268,7 +285,7 @@ fn lookupvisible(c: *checker, name: str) *syntax.sym = { // Flat scope installation coalesces same-leaf N_USE entries. The // source-owned alias map, not the retained marker's mod field, decides // 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; for (q != nil) { 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, missing: bool) void = { - cerr(e.file); cerr(":"); - cerr(strconv.i32tos(e.line, strconv.base.DEC)); cerr(":"); - cerr(strconv.i32tos(e.col, strconv.base.DEC)); + let at: *syntax.node = e; + for (at.kind == syntax.nkind.N_DOT && at.lhs != nil) { at = at.lhs; }; + 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); if (missing) { 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 (!seen) { return; }; - if (!srcimports(c.file, c.curmod, name)) { return; }; + if (!srcimports(c.file, c.curmod, c.cursource, name)) { return; }; cerr(kindstr); cerr(" '"); cerr(name); @@ -437,10 +456,8 @@ fn installdecl(c: *checker, file: *syntax.node, d: *syntax.node) void = { let k: syntax.nkind = d.kind; let nm: str = d.str; let mod: str = declmod(file, d); - // check-(c) self-import: a package may not import itself. Pure - // owner==leaf string compare, package-model-independent. check-(a) - // unused + (b)/(d) membership DEFERRED to task #8 (filename-keyed - // pulls lack import->file->symbol provenance). Message byte-identical + // A package may not import its own canonical owner. Import usage and + // membership are checked with source-file provenance. Message byte-identical // to cstage check.c. if (k == syntax.nkind.N_USE) { // 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 // walking a consumer-owned type; keeping the consumer module // here can bind bare names in the fact to the wrong package. - let savedmod: str = c.curmod; - c.curmod = s.mod; - let under: *syntax.tinfo = tinfofornode(c, body); - c.curmod = savedmod; + let savedmod: str = c.curmod; + let savedsource: i32 = c.cursource; + c.curmod = s.mod; + 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; // type b = a` / `type a = a`) — checked // 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; }; 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 // scopelookup bucket-walks and can bind a same-leaf symbol from // 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 // already carries its RETURN type (fn-decl.lhs), not its fn-type — // 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 // 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 @@ -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 // but cstage handles it at L808). 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) { let bu: *syntax.node = resolvealias(c, unwrapbang(basetn)); 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 = { c.tc = tc; 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.istestpackage = 0i32; c.testmodule = "test"; + let emptytesttarget: str; + c.testtarget = emptytesttarget; c.sepmode = 0i32; // caller (w6c main) sets it from -c c.synthtestrun = nil; c.verbose = 0; c.fnret = nil; let empty: str; c.curmod = empty; + c.cursource = 0; c.file = nil; c.allococtx = nil; seedprimitives(c); @@ -7538,25 +7657,35 @@ fn checkfile(c: *checker, file: *syntax.node) void = { let present: bool = false; let su: *syntax.node = file.list; 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 && syntax.streq(su.usepath, c.testmodule)) { present = true; - break; }; su = su.next; }; if (!present) { let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, file.file, file.line, file.col); - usenode.str = c.testmodule; - usenode.usepath = c.testmodule; - usenode.next = file.list; + usenode.str = c.testmodule; + usenode.usepath = c.testmodule; + usenode.pkgname = file.pkgname; + usenode.sourceid = file.sourceid; + if (c.testtarget.len > 0) { usenode.used = 1i32; }; + usenode.next = file.list; file.list = usenode; }; - }; + }; + checkimportredeclarations(c, file); // Pass 1: install all top-level names. let d: *syntax.node = file.list; for (d != nil) { + c.cursource = d.sourceid; installdecl(c, file, d); 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); nm.str = t.str; - let id: *syntax.node; - if (t.imported != 0 && t.nmod.len > 0) { - let (prefix, suffix) = strings.rcut(t.nmod, "."); - let alias: str = suffix; + let id: *syntax.node; + if (t.imported != 0 && t.nmod.len > 0) { + let alias: str = t.pkgname; 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.lhs = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc); 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); arr.list = rhead; tab = syntax.newnode(syntax.nkind.N_LET, pf, pl, pc); - tab.op = syntax.tkind.TK_CONST; - tab.str = "__wwtests"; + tab.op = syntax.tkind.TK_CONST; + tab.str = "__wwtests"; + tab.pkgname = file.pkgname; + tab.sourceid = file.sourceid; tab.lhs = tsl; tab.rhs = arr; // 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. }; let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc); - m.str = "main"; - m.exported = 1i32; + m.str = "main"; + m.exported = 1i32; + m.pkgname = file.pkgname; + m.sourceid = file.sourceid; let rety: *syntax.node = syntax.newnode(syntax.nkind.N_TNAME, pf, pl, pc); rety.str = "i32"; m.lhs = rety; @@ -7824,6 +7960,7 @@ fn checkfile(c: *checker, file: *syntax.node) void = { d = file.list; for (d != nil) { c.curmod = declmod(file, d); + c.cursource = d.sourceid; // 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("…")` // arg literals (N_STRLIT) outside the post-order exprtype @@ -7918,6 +8055,7 @@ fn checkfile(c: *checker, file: *syntax.node) void = { }; d = d.next; }; + checkimportusageandcollisions(c, file); // Pass 3 (#15, A.6.2.1e): post-checker invariant gate. Walks each // 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; for (d != nil) { c.curmod = declmod(file, d); + c.cursource = d.sourceid; asserttyped(c, d, false); d = d.next; }; @@ -7967,5 +8106,6 @@ fn checkfile(c: *checker, file: *syntax.node) void = { let empty: str; c.curmod = empty; + c.cursource = 0; }; diff --git a/selfhost/cmd/wcc/wwi.ww b/selfhost/cmd/wcc/wwi.ww index 5ebf71ac..b3ea59dd 100644 --- a/selfhost/cmd/wcc/wwi.ww +++ b/selfhost/cmd/wcc/wwi.ww @@ -82,24 +82,11 @@ fn wwimodeq(a: str, b: str) bool = { // 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 // this owner filter is what prevents cross-package alias capture. -fn wwiusepath(c: *checker, owner: str, 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; }; - }; +fn wwiusepath(c: *checker, owner: str, source: i32, alias: str) str = { let u: *syntax.node = c.file.list; 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; if (owner.len == 0) { same = u.imported == 0; @@ -117,24 +104,27 @@ fn wwiusepath(c: *checker, owner: str, alias: str) str = { 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; }; - let dotidx: i32 = -1; - let i: i32 = 0; - for (i < mod.len) { - if (mod[i] == 46u8) { dotidx = i; }; - i += 1; + let u: *syntax.node = c.file.list; + for (u != nil) { + if (u.kind == syntax.nkind.N_USE && u.sourceid == source) { + let same: bool = false; + 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; - 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); + return false; }; -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 i: i32 = 0; for (i < nm.len) { @@ -149,7 +139,7 @@ fn wwitypesym(c: *checker, owner: str, nm: str) *syntax.sym = { let leaf: str; leaf.ptr = nm.ptr + ((dotidx + 1): u64); 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) { s = syntax.scopelookupinmodule(c.cur, mod, leaf); }; @@ -162,7 +152,7 @@ fn wwitypesym(c: *checker, owner: str, nm: str) *syntax.sym = { for (b != nil) { if (b.skind == syntax.skind.SK_TYPE && syntax.streq(b.name, nm) - && wwidirectmodvisible(c, owner, b.mod)) { + && wwidirectmodvisible(c, owner, source, b.mod)) { s = b; 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); }; -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; for (p != nil) { 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; for (s != nil) { 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; }; p = p.parent; @@ -757,27 +748,30 @@ fn wwicollectdecl(fs: *wwifactset, owner: str, d: *syntax.node) void = { if (d.kind == syntax.nkind.N_FNDECL) { let p: *syntax.node = d.list; - for (p != nil) { wwicollecttype(fs, owner, p.lhs); p = p.next; }; - wwicollecttype(fs, owner, d.lhs); + for (p != nil) { wwicollecttype(fs, owner, d.sourceid, p.lhs); p = p.next; }; + wwicollecttype(fs, owner, d.sourceid, d.lhs); } 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) { - wwicollecttype(fs, owner, d.lhs); - wwicollectexpr(fs, owner, d.rhs); + wwicollecttype(fs, owner, d.sourceid, d.lhs); + wwicollectexpr(fs, owner, d.sourceid, d.rhs); } 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; }; let s: *syntax.sym = nil; 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) { if (e.lhs.kind == syntax.nkind.N_IDENT) { - let mod: str = wwiusepath(fs.c, owner, e.lhs.str); - if (mod.len > 0) { s = wwifactvaluesym(fs.c, mod, e.str); }; + let mod: str = wwiusepath(fs.c, owner, source, e.lhs.str); + if (mod.len > 0) { + s = wwifactvaluesym(fs.c, mod, source, e.str); + }; }; }; }; if (s != nil && s.decl != nil) { @@ -790,30 +784,37 @@ fn wwicollectexpr(fs: *wwifactset, owner: str, e: *syntax.node) void = { return; }; if (e.kind == syntax.nkind.N_BIN) { - wwicollectexpr(fs, owner, e.lhs); - wwicollectexpr(fs, owner, e.rhs); + wwicollectexpr(fs, owner, source, e.lhs); + wwicollectexpr(fs, owner, source, e.rhs); } 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) { - wwicollectexpr(fs, owner, e.lhs); - wwicollecttype(fs, owner, e.rhs); + wwicollectexpr(fs, owner, source, e.lhs); + 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 = { let saved: str = fs.c.curmod; + let savesource: i32 = fs.c.cursource; fs.c.curmod = owner; + fs.c.cursource = source; let ok: bool = evaldefconst(fs.c, e, out, 0); fs.c.curmod = saved; + fs.c.cursource = savesource; 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.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) { - 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.decl.kind == syntax.nkind.N_TYPEDECL) { 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_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) { // Array length is resolved type identity, not a source name // dependency. Canonicalize it so private constants stay private // and consumers never need implementation defs merely for layout. if (t.rhs != nil && t.rhs.kind != syntax.nkind.N_INTLIT) { let len: u64 = 0u64; - if (!wwievalconst(fs, owner, t.rhs, &len)) { + if (!wwievalconst(fs, owner, source, t.rhs, &len)) { wwiencodearrayreject(t); fs.bad = 1; } else { @@ -841,19 +842,19 @@ fn wwicollecttype(fs: *wwifactset, owner: str, t: *syntax.node) void = { 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) { let p: *syntax.node = t.list; - for (p != nil) { wwicollecttype(fs, owner, p.lhs); p = p.next; }; - wwicollecttype(fs, owner, t.lhs); + for (p != nil) { wwicollecttype(fs, owner, source, p.lhs); p = p.next; }; + wwicollecttype(fs, owner, source, t.lhs); } else { if (t.kind == syntax.nkind.N_TSTRUCT) { 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) { 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) { - wwicollecttype(fs, owner, t.lhs); + wwicollecttype(fs, owner, source, t.lhs); // Member identifiers refer to prior siblings in this enum, not // package defs; the declaration already carries the whole list. };};};};};};}; @@ -866,6 +867,9 @@ fn wwisortfacts(fs: *wwifactset) void = { let j: i32 = i + 1; for (j < fs.nfacts) { 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 = wwistrcmp(fs.factnodes[j].str, fs.factnodes[best].str); }; 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 - && 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 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; }; let paths: []str = alloc([], nuse: u64)!; paths.len = nuse; let nodes: []*syntax.node = alloc([], nuse: u64)!; nodes.len = nuse; let k: i32 = 0; u = file.list; 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; }; nodes[k] = u; 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 = { // §5: check_exported_type FIRST, before any byte — a producer // 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); wwisortfacts(&fs); - let fd: i32 = os.open(path, - 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. + // Exported declarations are sorted within their source-file sections. let ndecl: i32 = 0; d = file.list; 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; }; + let dkeys: []str; + let dnodes: []*syntax.node; if (ndecl > 0) { - let dkeys: []str = alloc([], ndecl: u64)!; - dkeys.len = ndecl; - let dnodes: []*syntax.node = alloc([], ndecl: u64)!; - dnodes.len = ndecl; + let keys: []str = alloc([], ndecl: u64)!; + keys.len = ndecl; + dkeys = keys; + let nodes: []*syntax.node = alloc([], ndecl: u64)!; + nodes.len = ndecl; + dnodes = nodes; let k: i32 = 0; d = file.list; for (d != nil) { @@ -1104,32 +1089,66 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = { d = d.next; }; wwisortdecls(dkeys, dnodes, ndecl); - let i: i32 = 0; - for (i < ndecl) { - wwidecl(fd, dnodes[i]); - i += 1; + }; + + let fd: i32 = os.open(path, + 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 // ownership but do not create source imports in the eventual consumer. let lastmod: str; + let lastsource: i32 = -1; let fi: i32 = 0; for (fi < fs.nfacts) { 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"); - let dotidx: i32 = -1; - let mi: i32 = 0; - for (mi < mod.len) { if (mod[mi] == 46u8) { dotidx = mi; }; mi += 1; }; - let leaf: str = mod; - if (dotidx >= 0) { - leaf.ptr = mod.ptr + ((dotidx + 1): u64); - leaf.len = mod.len - dotidx - 1; + let factpkg: str = fs.factnodes[fi].pkgname; + if (factpkg.len == 0) { + let dotidx: i32 = -1; + let mi: i32 = 0; + for (mi < mod.len) { if (mod[mi] == 46u8) { dotidx = mi; }; mi += 1; }; + factpkg = mod; + 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"); - wwiemitfactimports(fd, file, mod); + wputs(fd, "package "); wputs(fd, factpkg); wputs(fd, ";\n"); + wwiemitimports(fd, file, mod, source, true); lastmod = mod; + lastsource = source; }; wwidecl(fd, fs.factnodes[fi]); fi += 1; diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index 1cd0aa93..e4f4360a 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -17,7 +17,6 @@ import crypto.sha256; import hash; import os; import os.exec; -import rt; import strings; import syntax; @@ -893,6 +892,9 @@ type lflags = struct { type sepbind = struct { kind: u8, name: str, + source: str, + line: i32, + col: i32, dep: i32, }; @@ -919,6 +921,7 @@ type seppkg = struct { root: bool, // requested usage; never package-action identity linkentry: bool, generatedmain: bool, + generatedtarget: i32, failed: bool, testsupport: bool, loaded: bool, @@ -1609,15 +1612,13 @@ fn seprootiscommand(p: *seppkg) bool = { }; fn sepforbiddencommandimport(g: *sepgraph, importer: i32, dep: i32) bool = { - let from: *seppkg = &g.pkg[importer]; let to: *seppkg = &g.pkg[dep]; if (to.name == nil || !cstreqlit(to.name, "main") || to.role != SEP_ROLE_NORMAL) { return false; }; // An external test's exact colocated production edge is variant wiring, // not a general source-importable command-package alias. - return !(from.variant == SEP_VARIANT_EXTERNAL - && sepcommanddeclaredname(from) - && cstreq(from.canon, to.canon)); + return !(sepexternalproductionedge(g, importer, dep) + && sepexternalnamematchesproduction(g, importer, dep)); }; 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, 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; }; if (g.pkg[i].bindings.ptr != nil) { @@ -2556,41 +2561,41 @@ fn sepvalidateartifactpaths(g: *sepgraph, scratch: *u8) i32 = { return 0; }; -fn sepexternalname(pkg: *seppkg, path: *u8, n: u64, - leafonly: bool) bool = { +fn sepexternalname(pkg: *seppkg, path: *u8, n: u64) bool = { if (pkg.variant != SEP_VARIANT_EXTERNAL || pkg.testpackage == nil) { 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); - if (tn != leafn + 5u64) { return false; }; - if (bytecmp(pkg.testpackage, leafn, path + begin, leafn) != 0) { + if (tn != n + 5u64) { return false; }; + if (bytecmp(pkg.testpackage, n, path, n) != 0) { return false; }; - return pkg.testpackage[leafn] == '_' - && pkg.testpackage[leafn + 1u64] == 't' - && pkg.testpackage[leafn + 2u64] == 'e' - && pkg.testpackage[leafn + 3u64] == 's' - && pkg.testpackage[leafn + 4u64] == 't'; + return pkg.testpackage[n] == '_' + && pkg.testpackage[n + 1u64] == 't' + && pkg.testpackage[n + 2u64] == 'e' + && pkg.testpackage[n + 3u64] == 's' + && 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, - dep: 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; - }; + dep: i32, source: str, line: i32, col: i32) bool = { if (bindings.len == SEP_COUNT_MAX) { sepfailsize(); return false; }; if (!sepreservebinds(bindings, bindings.len + 1)) { return false; @@ -2601,9 +2606,24 @@ fn sepbindadd(bindings: *[]sepbind, kind: u8, name: str, case let value: str => copied = value; 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 { kind = kind, name = copied, + source = sourcecopy, + line = line, + col = col, dep = dep, }); return true; @@ -2616,6 +2636,12 @@ fn sepbindcmp(a: sepbind, b: sepbind) i32 = { if (a.kind > b.kind) { 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; }; @@ -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 = { - 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; - for (i < len(a)) { - if (a[i].kind != b[i].kind || a[i].dep != b[i].dep - || !syntax.streq(a[i].name, b[i].name)) { return false; }; + for (i < bindings.len) { + let b: sepbind = bindings[i]; + 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; }; 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 = { let i: i32 = 0; for (i < children.len) { @@ -2780,6 +2852,18 @@ fn sepresolvesourceimport(g: *sepgraph, context: i32, name: *u8, 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 // (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 @@ -2880,8 +2964,7 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32, for (si < nuse) { let sj: i32 = si; for (sj > 0) { - if (strings.compare(uses[sj - 1].usepath, - uses[sj].usepath) <= 0) { sj = 0; } + if (sepusecmp(uses[sj - 1], uses[sj]) <= 0) { sj = 0; } else { let t: *syntax.node = uses[sj]; uses[sj] = uses[sj - 1]; @@ -2891,14 +2974,9 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32, }; si += 1; }; - let previous: str = ""; ui = 0; for (ui < nuse) { 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 idn: u64 = u.usepath.len: u64; if (reservedimport(u.usepath)) { @@ -2933,11 +3011,10 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32, oi += 1u64; }; let literalself: bool = routesuffix != nil - && cstreq(pathform, routesuffix) - && sepexternalname(&g.pkg[pi], idp, idn, true); + && cstreq(pathform, routesuffix); if (routesuffix == nil) { literalself = ordinaryleaf - && sepexternalname(&g.pkg[pi], idp, idn, false); + && sepexternalname(&g.pkg[pi], idp, idn); }; if (literalself) { 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 self: bool = os.samefile(pathstr(resolved.entry), pathstr(g.pkg[pi].entry)); - if (self && (sepexternalname(&g.pkg[pi], idp, idn, true) - || (g.pkg[pi].variant == SEP_VARIANT_EXTERNAL - && sepcommanddeclaredname(&g.pkg[pi])))) { + if (self && g.pkg[pi].variant == SEP_VARIANT_EXTERNAL) { externalproduction = true; }; if (self && !externalproduction) { @@ -3010,26 +3085,19 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32, let childcontext: i32 = sepchildcontextfor(g, context, resolved.entry, resolved.sourceroot); 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) || !sepchildrenadd(children, di, childcontext)) { return -1; }; } 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; if (g.pkg[pi].isdir == 0) { let pm: *syntax.node = imports.body; for (pm != nil) { if (bytecmp(pm.nmod.ptr, pm.nmod.len: u64, - leafp, leafn) == 0) { inlinepackage = true; }; + idp, idn) == 0) { inlinepackage = true; }; pm = pm.next; }; }; @@ -3040,13 +3108,13 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32, cerr("\n"); return -1; } 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; }; }; }; - }; - ui += 1; + ui += 1; }; return 0; }; @@ -3160,6 +3228,7 @@ fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32, p.root = true; p.linkentry = true; p.generatedmain = true; + p.generatedtarget = variant; p.failed = false; p.testsupport = false; p.loaded = true; @@ -3239,30 +3308,12 @@ fn seppreparepkgcontext(g: *sepgraph, pi: i32, context: i32, }; 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) { rc = sepscanfile(g, pi, g.pkg[pi].entry, context, &fv, &bindings, children, 0); }; }; sepbindsort(&bindings); + if (rc == 0 && !sepvalidatebindings(g, bindings)) { rc = -1; }; if (rc == 0 && g.pkg[pi].emitcontext < 0) { g.pkg[pi].bindings = bindings; g.pkg[pi].emitcontext = context; @@ -3415,6 +3466,20 @@ fn seploadpkg(g: *sepgraph, pi: i32, context: i32) i32 = { if (f.pendingdep >= 0) { let dep: i32 = f.pendingdep; 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)) { cerr("ww: package "); if (g.pkg[dep].path[0u64] != 0u8) { @@ -3603,30 +3668,14 @@ fn sepreverseimportbase(g: *sepgraph, p: *seppkg, context: i32, 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 -// 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 = { - let leaf: *u8 = sepordinarydeclaredname(p); - if (leaf == nil) { return nil; }; let need: u64 = 0u64; if (!sepaddbytes(&need, SEP_LOCAL_IMPORT_PREFIX.len: u64) || !sepaddbytes(&need, 2u64) || !sepmuladdbytes(&need, cstrlen(p.canon), 4u64) - || !sepaddbytes(&need, 1u64) - || !sepaddbytes(&need, cstrlen(leaf)) || !sepaddbytes(&need, 1u64)) { return nil; }; let out: []u8; if (!sepmakebytes(need, &out)) { return nil; }; @@ -3656,8 +3705,6 @@ fn seplocalimportbase(p: *seppkg) *u8 = { }; }; i += 1u64; }; - off = byteinto(out.ptr, off, '.': u8); - off = cstrinto(out.ptr, off, leaf); cstrseal(out.ptr, off); return out.ptr; }; @@ -3725,23 +3772,6 @@ fn sepfinalizedirectoryidentities(g: *sepgraph) i32 = { for (pi < g.n) { let p: *seppkg = &g.pkg[pi]; 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; if (p.variant == SEP_VARIANT_SAME_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; for (bi < g.pkg[pi].bindings.len && bodyrc == 0) { let b: sepbind = g.pkg[pi].bindings[bi]; - if (b.kind == 'D': u8 && b.dep >= 0 && b.dep < g.n - && !syntax.streq(b.name, pathstr(g.pkg[b.dep].path))) { + if (sepbindfirstmap(g, g.pkg[pi].bindings, bi)) { let pre: str = "//ww:import-map "; let space: str = " "; let newline: str = "\n"; @@ -4331,14 +4360,14 @@ fn validatecommandoutputpath(out: *u8) i32 = { fn workdirstamptext(istest: i32, emitasm: i32) str = { if (istest != 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) { - 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 = { @@ -4434,6 +4463,22 @@ fn invalidateworkdirunits(scratch: *u8) i32 = { 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 { path: [4096]u8, offset: [2048]u16, @@ -5092,7 +5137,16 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, cu = unitnew; cw = wwinew; cs = asmnew; 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) { + sepdiscardactionstaging(warm, unitnew, wwinew, asmnew, + objnew, anew); g.pkg[pi].failed = true; anyfailed = true; 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 (os.remove(pathstr(unitnew)) != 0) { cerrpath("ww: cannot remove ", unitnew, "\n"); @@ -5144,17 +5202,19 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, let nmaps: i32 = 0; let mapk: i32 = 0; for (mapk < g.pkg[pi].bindings.len) { - let b: sepbind = g.pkg[pi].bindings[mapk]; - if (b.kind == 'D': u8 && b.dep >= 0 && b.dep < g.n - && !syntax.streq(b.name, - pathstr(g.pkg[b.dep].path))) { - if (nmaps == SEP_COUNT_MAX) { sepfailsize(); return 1; }; + if (sepbindfirstmap(g, g.pkg[pi].bindings, mapk)) { + if (nmaps == SEP_COUNT_MAX) { + sepfailsize(); + sepdiscardactionstaging(warm, unitnew, wwinew, + asmnew, objnew, anew); + return 1; + }; nmaps += 1; }; mapk += 1; }; let alen: i32 = 8; - if (gent) { alen += 4; } + if (gent) { alen += 4; if (g.pkg[pi].generatedmain) { alen += 2; }; } else { if (testpkg) { 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) { sepfailsize(); + sepdiscardactionstaging(warm, unitnew, wwinew, asmnew, + objnew, anew); return 1; }; alen += g.pkg[pi].ndeps * 3; if (nmaps > (SEP_COUNT_MAX - alen) / 3) { sepfailsize(); + sepdiscardactionstaging(warm, unitnew, wwinew, asmnew, + objnew, anew); return 1; }; alen += nmaps * 3; @@ -5175,7 +5239,12 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, let argv: []str; match (allocation) { 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"); if (gent) { @@ -5183,6 +5252,10 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, append(argv, "--entry"); append(argv, "--test-support-module"); append(argv, testsupportmodule); + if (g.pkg[pi].generatedmain) { + append(argv, "--test-target-package"); + append(argv, pathstr(g.pkg[g.pkg[pi].generatedtarget].path)); + }; } else { if (testpkg) { append(argv, "--test-package"); }; if (commandpkg) { append(argv, "--command-package"); }; @@ -5199,16 +5272,18 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, append(argv, "--import"); append(argv, pathstr(g.pkg[dj].path)); 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)); importk += 1; }; mapk = 0; for (mapk < g.pkg[pi].bindings.len) { let b: sepbind = g.pkg[pi].bindings[mapk]; - if (b.kind == 'D': u8 && b.dep >= 0 && b.dep < g.n - && !syntax.streq(b.name, - pathstr(g.pkg[b.dep].path))) { + if (sepbindfirstmap(g, g.pkg[pi].bindings, mapk)) { append(argv, "--import-map"); append(argv, b.name); 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; anyfailed = true; + sepdiscardactionstaging(warm, unitnew, wwinew, asmnew, + objnew, anew); oi += 1; continue; }; @@ -5244,13 +5321,22 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, if (!warm || !fileequal(wwinew, wwi)) { g.pkg[pi].exportchanged = true; }; - if (sepfatalallocation) { return 1; }; + if (sepfatalallocation) { + sepdiscardactionstaging(warm, unitnew, wwinew, asmnew, + objnew, anew); + return 1; + }; if (emitasm == 0) { let argallocation: ([]str | nomem) = sepallocstrs(4); let argv: []str; match (argallocation) { 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, "-o"); @@ -5273,6 +5359,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; g.pkg[pi].failed = true; anyfailed = true; + sepdiscardactionstaging(warm, unitnew, wwinew, asmnew, + objnew, anew); oi += 1; continue; }; @@ -5284,6 +5372,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, cerr("ww: archive failed\n"); g.pkg[pi].failed = true; anyfailed = true; + sepdiscardactionstaging(warm, unitnew, wwinew, asmnew, + objnew, anew); oi += 1; continue; }; @@ -5328,6 +5418,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; g.pkg[pi].failed = true; anyfailed = true; + sepdiscardactionstaging(warm, unitnew, wwinew, asmnew, + objnew, anew); oi += 1; continue; }; diff --git a/selfhost/cmd/wwdump/main.ww b/selfhost/cmd/wwdump/main.ww index 812bef0a..525cd04b 100644 --- a/selfhost/cmd/wwdump/main.ww +++ b/selfhost/cmd/wwdump/main.ww @@ -148,7 +148,7 @@ export fn main(argc: i32, argv: **u8) i32 = { // silently). Mirrors w6c main.ww:162 / cmd/w6c/main.c. if (l.errs > 0 || ps.errs > 0) { return 1; }; 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; };