compiler: implement blank package name semantics
This commit is contained in:
232
cmd/w6c/main.c
232
cmd/w6c/main.c
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user