ww: separate package identity from declared name

This commit is contained in:
2026-08-14 03:07:58 +09:00
parent 028da6323e
commit 6efe9b70d4
37 changed files with 1678 additions and 886 deletions

View File

@@ -39,6 +39,7 @@ struct importin {
const char *file;
char *buf;
u64 len;
Node *ast;
};
struct importmap {
@@ -47,13 +48,6 @@ struct importmap {
int seen;
};
static const char *
importleaf(const char *path)
{
const char *dot = strrchr(path, '.');
return dot != NULL ? dot + 1 : path;
}
static Node *
parseinput(Arena *a, const char *file, char *buf, u64 len,
const char *mod, const char *testsupport, int commandpackage, int *bad)
@@ -73,6 +67,79 @@ parseinput(Arena *a, const char *file, char *buf, u64 len,
return f;
}
/* Export data carries canonical owner and declared package name separately.
* Search every direct interface's package-clause markers because its
* self-contained fact closure can also name a transitive owner. */
static const char *
import_pkgname(struct importin *imports, int nimports, const char *path,
Node *primary, int *conflict)
{
const char *name = NULL;
for (int i = 0; i < nimports; i++) {
Node *file = imports[i].ast;
for (Node *p = file ? file->body : NULL; p; p = p->next) {
if (p->module == NULL || p->pkgname == NULL
|| strcmp(p->module, path) != 0)
continue;
if (name != NULL && strcmp(name, p->pkgname) != 0) {
*conflict = 1;
return NULL;
}
name = p->pkgname;
}
}
for (Node *p = primary ? primary->body : NULL; p; p = p->next) {
if (p->module == NULL || p->pkgname == NULL
|| strcmp(p->module, path) != 0)
continue;
if (name != NULL && strcmp(name, p->pkgname) != 0) {
*conflict = 1;
return NULL;
}
name = p->pkgname;
}
return name;
}
static int
bind_import_names(Node *list, struct importin *imports, int nimports,
Node *primary, const char *testsupport)
{
for (Node *u = list; u; u = u->next) {
if (u->kind != N_USE || u->usepath == NULL) continue;
/* The reserved test-support spelling is a compiler-owned alias, not
* source default-import syntax. */
if (testsupport != NULL && strcmp(testsupport, "__wwtest") == 0
&& strcmp(u->usepath, testsupport) == 0)
continue;
int conflict = 0;
const char *name = import_pkgname(imports, nimports, u->usepath,
primary, &conflict);
if (conflict) {
fprintf(stderr,
"w6c: package %s has conflicting declared names in export data\n",
u->usepath);
return -1;
}
if (name != NULL) {
u->str = name;
u->strlen = strlen(name);
} else if (!u->imported) {
fprintf(stderr,
"w6c: import %s has no declared package name in direct export data\n",
u->usepath);
return -1;
} else {
/* A closure-only import need not contribute declarations to this
* interface. Keep it canonical-path keyed without reinstalling
* the historical path-leaf qualifier. */
u->str = u->usepath;
u->strlen = strlen(u->usepath);
}
}
return 0;
}
static void
appendnodes(Node **head, Node **tail, Node *list)
{
@@ -90,6 +157,7 @@ main(int argc, char **argv)
const char *out = NULL;
const char *wwiout = NULL; /* -I <out.wwi>: M2 export-data producer */
const char *testsupport = NULL;
const char *testtarget = NULL;
int testmode = 0;
int testpackage = 0;
int commandpackage = 0;
@@ -127,6 +195,12 @@ main(int argc, char **argv)
return 2;
}
testsupport = argv[++i];
} else if (strcmp(a, "--test-target-package") == 0) {
if (i + 1 >= argc) {
fputs("w6c: --test-target-package requires arg\n", stderr);
return 2;
}
testtarget = argv[++i];
} else if (strcmp(a, "-c") == 0) {
sepmode = 1;
} else if (strcmp(a, "--import") == 0) {
@@ -156,7 +230,7 @@ main(int argc, char **argv)
}
}
if (src == NULL) {
fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [-c] [-I out.wwi] "
fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [--test-target-package path] [-c] [-I out.wwi] "
"[--import path dep.wwi]... [--import-map source path]... [-o out.s] file.ww\n", stderr);
return 2;
}
@@ -196,11 +270,6 @@ main(int argc, char **argv)
stderr);
return 2;
}
if (strcmp(importleaf(maps[i].source),
importleaf(maps[i].path)) != 0) {
fputs("w6c: --import-map must preserve import leaf\n", stderr);
return 2;
}
int direct = 0;
for (int j = 0; j < nimports; j++)
if (strcmp(maps[i].path, imports[j].path) == 0) {
@@ -219,6 +288,22 @@ main(int argc, char **argv)
fputs("w6c: invalid --test-support-module\n", stderr);
return 2;
}
if (testtarget != NULL && (!sepmode || !testmode
|| testtarget[0] == '\0')) {
fputs("w6c: invalid --test-target-package\n", stderr);
return 2;
}
if (testtarget != NULL) {
int direct = 0;
for (int i = 0; i < nimports; i++)
if (strcmp(imports[i].path, testtarget) == 0)
direct = 1;
if (!direct) {
fputs("w6c: --test-target-package is not a direct import\n",
stderr);
return 2;
}
}
Arena *a = newarena();
Checker c;
@@ -243,6 +328,10 @@ main(int argc, char **argv)
Node *f = parseinput(a, imports[i].file, imports[i].buf,
imports[i].len, imports[i].path, testsupport, 0, &bad);
if (bad) return 1;
imports[i].ast = f;
if (bind_import_names(f->list, imports, i + 1, NULL,
testsupport) < 0)
return 1;
appendnodes(&head, &tail, f->list);
}
@@ -259,7 +348,7 @@ main(int argc, char **argv)
Node *file = parseinput(a, src, buf, len, NULL, testsupport,
commandpackage || entrymode, &bad);
if (bad) return 1;
/* Source keeps its effective spelling and leaf alias, while package
/* Source keeps its effective spelling and position, while package
* resolution supplies the expanded canonical owner. Rewrite only the
* primary import key before imported interface nodes are prepended. */
for (Node *u = file->list; u; u = u->next) {
@@ -276,6 +365,31 @@ main(int argc, char **argv)
fputs("w6c: --import-map source is not in primary input\n", stderr);
return 2;
}
/* Later direct interfaces may supply names for origin sections referenced
* by an earlier interface, so perform one complete metadata pass now. */
for (int i = 0; i < nimports; i++)
if (bind_import_names(imports[i].ast->list, imports, nimports,
NULL, testsupport) < 0)
return 1;
if (bind_import_names(file->list, imports, nimports, file,
testsupport) < 0)
return 1;
if (testtarget != NULL) {
int seen = 0;
for (Node *u = file->list; u; u = u->next) {
if (u->kind != N_USE || u->imported || u->usepath == NULL
|| strcmp(u->usepath, testtarget) != 0)
continue;
u->str = u->usepath;
u->strlen = strlen(u->usepath);
seen++;
}
if (seen != 1) {
fputs("w6c: generated test target import is not unique\n",
stderr);
return 2;
}
}
if (head != NULL) {
tail->next = file->list;
file->list = head;
@@ -285,6 +399,7 @@ main(int argc, char **argv)
c.is_test = testmode;
c.is_test_package = testpackage;
if (testsupport != NULL) c.test_module = testsupport;
c.test_target = testtarget;
c.sep_mode = sepmode;
check_file(&c, file);
if (c.errs) return 1;