compiler: implement blank package name semantics

This commit is contained in:
2026-08-23 15:33:59 +09:00
parent e67b8e1bc4
commit 0b24b11d65
14 changed files with 2862 additions and 112 deletions

View File

@@ -282,62 +282,92 @@ consider_pkgname(const char *path, const char *candidate,
*name = candidate;
}
/* A paired interface owns PATH's declared name. Compiler-private names keep
* transitive fact sections semantic and are ignored when a real name exists. */
static const char *
enum pkgname_state {
PKGNAME_MISSING,
PKGNAME_VALID,
PKGNAME_CONFLICT,
PKGNAME_INVALID,
};
/* Standalone interfaces may carry closure facts for several canonical
* packages. Marker.module, rather than the containing --import path, owns
* each declared name. Compiler-private names keep transitive fact sections
* semantic and are ignored when a real name exists. */
static enum pkgname_state
import_pkgname(struct importin *imports, int nimports, const char *path,
Node *primary, int *conflict)
Node *primary, const char **resolved)
{
const char *name = NULL;
const char *placeholder = NULL;
int conflict = 0;
for (int i = 0; i < nimports; i++) {
if (strcmp(imports[i].path, path) != 0) continue;
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;
consider_pkgname(path, p->pkgname, &name, &placeholder,
conflict);
if (*conflict) return NULL;
&conflict);
if (conflict) return PKGNAME_CONFLICT;
}
}
for (Node *p = primary ? primary->body : NULL; p; p = p->next) {
if (p->module == NULL || p->pkgname == NULL
|| strcmp(p->module, path) != 0)
continue;
consider_pkgname(path, p->pkgname, &name, &placeholder, conflict);
if (*conflict) return NULL;
consider_pkgname(path, p->pkgname, &name, &placeholder, &conflict);
if (conflict) return PKGNAME_CONFLICT;
}
return name ? name : placeholder;
*resolved = name ? name : placeholder;
if (*resolved == NULL) return PKGNAME_MISSING;
if (strcmp(*resolved, "_") == 0) return PKGNAME_INVALID;
return PKGNAME_VALID;
}
static const char *
path_leaf(const char *path)
{
const char *dot = path ? strrchr(path, '.') : NULL;
return dot ? dot + 1 : path;
}
static int
bind_import_names(Node *list, struct importin *imports, int nimports,
Node *primary, const char *testsupport)
Node *primary, const char *testsupport, Node *external_support)
{
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) {
int reserved = testsupport != NULL
&& strcmp(testsupport, "__wwtest") == 0
&& strcmp(u->usepath, testsupport) == 0;
const char *name = NULL;
enum pkgname_state state = import_pkgname(imports, nimports,
u->usepath, primary, &name);
if (state == PKGNAME_CONFLICT) {
fprintf(stderr,
"w6c: package %s has conflicting declared names in export data\n",
u->usepath);
return -1;
}
if (name != NULL) {
if (state == PKGNAME_VALID || state == PKGNAME_INVALID) {
u->usepkgname = name;
if (!u->useblank) {
if (state == PKGNAME_INVALID) {
u->used = 1;
if (!u->useblank && !reserved) {
u->str = u->usealias ? u->usealias
: path_leaf(u->usepath);
u->strlen = strlen(u->str);
}
} else if (!u->useblank && !reserved) {
u->str = u->usealias ? u->usealias : name;
u->strlen = strlen(u->str);
}
} else if (u == external_support) {
/* A bare direct -T compiler invocation deliberately leaves the
* compiler-generated support hook external. */
u->used = 1;
} else if (!u->imported) {
fprintf(stderr,
"w6c: import %s has no declared package name in direct export data\n",
@@ -356,6 +386,121 @@ bind_import_names(Node *list, struct importin *imports, int nimports,
return 0;
}
struct pathset {
const char **v;
int n;
int cap;
};
static int
pathset_has(const struct pathset *s, const char *path)
{
if (path == NULL) return 0;
for (int i = 0; i < s->n; i++)
if (strcmp(s->v[i], path) == 0) return 1;
return 0;
}
static int
pathset_add(struct pathset *s, const char *path)
{
if (path == NULL || pathset_has(s, path)) return 0;
if (s->n == s->cap) {
int cap = s->cap ? s->cap * 2 : 16;
const char **v = realloc(s->v, (size_t)cap * sizeof *v);
if (v == NULL) return -1;
s->v = v;
s->cap = cap;
}
s->v[s->n++] = path;
return 1;
}
static Node *
materialize_test_support(Arena *a, Node *file, int testmode,
const char *testsupport)
{
if (!testmode || testsupport == NULL) return NULL;
for (Node *u = file->list; u; u = u->next) {
if (u->kind != N_USE || u->imported
|| u->sourceid != file->sourceid || u->usepath == NULL
|| strcmp(u->usepath, testsupport) != 0)
continue;
u->used = 1;
return NULL;
}
Node *u = newnode(a, N_USE, file->pos);
u->str = testsupport;
u->strlen = strlen(testsupport);
u->usesource = testsupport;
u->usepath = testsupport;
u->usefile = file->pos.file;
u->useline = file->pos.line;
u->usecol = file->pos.col;
u->usepathfile = file->pos.file;
u->usepathline = file->pos.line;
u->usepathcol = file->pos.col;
u->pkgname = file->pkgname;
u->sourceid = file->sourceid;
u->used = 1;
u->next = file->list;
file->list = u;
return u;
}
static int
compute_reached_imports(struct pathset *reached, Node *primary,
struct importin *imports, int nimports)
{
for (Node *u = primary ? primary->list : NULL; u; u = u->next) {
if (u->kind != N_USE || u->imported || u->usepath == NULL) continue;
if (pathset_add(reached, u->usepath) < 0) return -1;
}
int changed;
do {
changed = 0;
for (int i = 0; i < nimports; i++) {
for (Node *u = imports[i].ast ? imports[i].ast->list : NULL;
u; u = u->next) {
if (u->kind != N_USE || !u->imported
|| u->module == NULL || u->usepath == NULL
|| !pathset_has(reached, u->module))
continue;
const char *name = NULL;
if (import_pkgname(imports, nimports, u->module,
NULL, &name) != PKGNAME_VALID)
continue;
int added = pathset_add(reached, u->usepath);
if (added < 0) return -1;
if (added) changed = 1;
}
}
} while (changed);
return 0;
}
static void
filter_reached_imports(Node **list, const struct pathset *reached,
struct importin *imports, int nimports)
{
Node *prev = NULL;
for (Node *d = *list; d; ) {
Node *next = d->next;
const char *name = NULL;
int keep = d->imported && d->module != NULL
&& pathset_has(reached, d->module)
&& import_pkgname(imports, nimports, d->module, NULL,
&name) == PKGNAME_VALID;
if (keep)
prev = d;
else if (prev == NULL)
*list = next;
else
prev->next = next;
d = next;
}
}
static void
appendnodes(Node **head, Node **tail, Node *list)
{
@@ -563,7 +708,6 @@ main(int argc, char **argv)
Checker c;
Cg cg;
Node *head = NULL, *tail = NULL;
for (int i = 0; i < nimports; i++) {
if (slurp(imports[i].file, &imports[i].buf,
&imports[i].len) < 0) {
@@ -583,10 +727,6 @@ main(int argc, char **argv)
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);
}
char *buf;
@@ -619,14 +759,33 @@ 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. */
/* The checker normally synthesizes this compiler-required edge. Make it
* an explicit primary root before interface reachability and name
* resolution so a supplied support interface cannot bypass validation. */
Node *external_support = materialize_test_support(a, file, testmode,
testsupport);
/* Interface containers and embedded origin sections are metadata, not
* roots. Only primary uses seed the closure, and only a reached valid
* owner may contribute its transitive uses. */
struct pathset reached = {0};
if (compute_reached_imports(&reached, file, imports, nimports) < 0) {
fputs("w6c: out of memory\n", stderr);
return 1;
}
for (int i = 0; i < nimports; i++)
filter_reached_imports(&imports[i].ast->list, &reached, imports,
nimports);
/* Bind only retained facts, while consulting the complete package-marker
* metadata. Keeping the standalone lists separate here prevents one
* interface from escaping its reachability boundary through concatenation. */
for (int i = 0; i < nimports; i++)
if (bind_import_names(imports[i].ast->list, imports, nimports,
NULL, testsupport) < 0)
NULL, testsupport, NULL) < 0)
return 1;
if (bind_import_names(file->list, imports, nimports, file,
testsupport) < 0)
testsupport, external_support) < 0)
return 1;
for (int ti = 0; ti < ntesttargets; ti++) {
int seen = 0;
@@ -634,8 +793,14 @@ main(int argc, char **argv)
if (u->kind != N_USE || u->imported || u->usepath == NULL
|| strcmp(u->usepath, testtargets[ti]) != 0)
continue;
u->str = u->usepath;
u->strlen = strlen(u->usepath);
/* Valid generated target imports use their canonical path as
* the compiler-owned qualifier. Preserve an invalid provider's
* fake explicit-or-leaf spelling for source-local recovery. */
if (u->usepkgname == NULL
|| strcmp(u->usepkgname, "_") != 0) {
u->str = u->usepath;
u->strlen = strlen(u->usepath);
}
seen++;
}
if (seen != 1) {
@@ -644,6 +809,11 @@ main(int argc, char **argv)
return 2;
}
}
free(reached.v);
Node *head = NULL, *tail = NULL;
for (int i = 0; i < nimports; i++)
appendnodes(&head, &tail, imports[i].ast->list);
if (head != NULL) {
tail->next = file->list;
file->list = head;