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;
|
||||
|
||||
@@ -62,6 +62,8 @@ static const char *use_path(Node *file, const char *curmod, int source,
|
||||
const char *alias);
|
||||
static const char *find_use_path(Node *file, const char *curmod, int source,
|
||||
const char *alias, int mark);
|
||||
static Node *find_use_binding(Node *file, const char *curmod, int source,
|
||||
const char *alias, int mark);
|
||||
static int src_imports(Node *file, const char *modtag, int source,
|
||||
const char *name);
|
||||
static Sym *lookup_visible(Checker *c, const char *name);
|
||||
@@ -96,6 +98,11 @@ resolve_typename(Checker *c, Node *n)
|
||||
char *head = astrndup(c->a, nm, hl);
|
||||
Sym *m = scope_lookup(c->cur, head);
|
||||
if (m && (m->kind == SK_USE || m->use_alias)) {
|
||||
Node *u = find_use_binding(c->file, c->cur_mod,
|
||||
c->cur_source, head, 1);
|
||||
if (u != NULL && u->usepkgname != NULL
|
||||
&& strcmp(u->usepkgname, "_") == 0)
|
||||
return ty_err;
|
||||
/* 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,
|
||||
@@ -1521,6 +1528,11 @@ cexpr(Checker *c, Node *n)
|
||||
if (n->lhs && n->lhs->kind == N_IDENT) {
|
||||
Sym *ms = lookup_visible(c, n->lhs->str);
|
||||
if (ms && (ms->kind == SK_USE || ms->use_alias)) {
|
||||
Node *u = find_use_binding(c->file, c->cur_mod,
|
||||
c->cur_source, n->lhs->str, 1);
|
||||
if (u != NULL && u->usepkgname != NULL
|
||||
&& strcmp(u->usepkgname, "_") == 0)
|
||||
return n->type = ty_err;
|
||||
/* Module-qualified ref. `use_alias` covers
|
||||
* the self-import case where the module's
|
||||
* type name shadowed the SK_USE; the leaf
|
||||
@@ -3016,6 +3028,18 @@ import_binding_pos(Node *u)
|
||||
return p;
|
||||
}
|
||||
|
||||
static Pos
|
||||
import_path_pos(Node *u)
|
||||
{
|
||||
Pos p = u->pos;
|
||||
if (u->usepathfile != NULL) {
|
||||
p.file = u->usepathfile;
|
||||
p.line = u->usepathline;
|
||||
p.col = u->usepathcol;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
* use_path — map a source-file default qualifier (the imported package's
|
||||
* declared name) to the full canonical import path it binds, for
|
||||
@@ -3056,6 +3080,33 @@ find_use_path(Node *file, const char *curmod, int source, const char *alias,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static Node *
|
||||
find_use_binding(Node *file, const char *curmod, int source,
|
||||
const char *alias, int mark)
|
||||
{
|
||||
if (file == NULL || alias == NULL) return NULL;
|
||||
/* Source-zero self-qualification wins before import lookup in
|
||||
* find_use_path and therefore cannot denote an imported package object. */
|
||||
if (source == 0 && curmod != NULL) {
|
||||
const char *dot = strrchr(curmod, '.');
|
||||
const char *leaf = dot ? dot + 1 : curmod;
|
||||
if (strcmp(alias, leaf) == 0) return NULL;
|
||||
}
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->str == NULL || invalid_init_import(u)
|
||||
|| u->sourceid != source || strcmp(u->str, alias) != 0)
|
||||
continue;
|
||||
const char *um = decl_mod(file, u);
|
||||
int same = (um == NULL) ? (curmod == NULL)
|
||||
: (curmod != NULL && strcmp(um, curmod) == 0);
|
||||
if (same) {
|
||||
if (mark) u->used = 1;
|
||||
return u;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *
|
||||
use_path(Node *file, const char *curmod, int source, const char *alias)
|
||||
{
|
||||
@@ -4379,11 +4430,48 @@ check_test_target(const Checker *c, const char *path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
reject_blank_package_names(Checker *c, Node *file)
|
||||
{
|
||||
for (Node *p = file->body; p; p = p->next)
|
||||
if (p->kind == N_FILE && p->pkgname != NULL
|
||||
&& strcmp(p->pkgname, "_") == 0)
|
||||
err(c, p->pos, "invalid package name _");
|
||||
}
|
||||
|
||||
static void
|
||||
reject_invalid_imports(Checker *c, Node *file)
|
||||
{
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->usepath == NULL
|
||||
|| u->usepkgname == NULL
|
||||
|| strcmp(u->usepkgname, "_") != 0)
|
||||
continue;
|
||||
u->used = 1;
|
||||
int seen = 0;
|
||||
for (Node *v = file->list; v != u; v = v->next) {
|
||||
if (v->kind == N_USE && v->usepath != NULL
|
||||
&& v->usepkgname != NULL
|
||||
&& strcmp(v->usepkgname, "_") == 0
|
||||
&& strcmp(v->usepath, u->usepath) == 0) {
|
||||
seen = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!seen)
|
||||
err(c, import_path_pos(u),
|
||||
"could not import %s (invalid package name: \"_\")",
|
||||
u->usepath);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
check_file(Checker *c, Node *file)
|
||||
{
|
||||
if (file == NULL || file->kind != N_FILE) return;
|
||||
c->file = file;
|
||||
reject_blank_package_names(c, file);
|
||||
reject_invalid_imports(c, file);
|
||||
|
||||
/* Under -T, prepend the dispatcher support import before pass 1 so
|
||||
* decl_mod keys the runner under the selected support module. A pure
|
||||
|
||||
@@ -92,6 +92,24 @@ expectident(Parser *p)
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Package declarations admit the blank identifier syntactically. Keep this
|
||||
* private to the package-name slot: every ordinary identifier production must
|
||||
* retain expectident's rejection of TK_UNDER. The checker owns the semantic
|
||||
* BlankPkgName rejection. */
|
||||
static const char *
|
||||
expectpackagename(Parser *p)
|
||||
{
|
||||
if (p->cur.kind != TK_IDENT && p->cur.kind != TK_UNDER) {
|
||||
errorf(p->cur.pos, "expected identifier, got %s",
|
||||
tokname(p->cur.kind));
|
||||
p->errs++;
|
||||
return "<err>";
|
||||
}
|
||||
const char *s = p->cur.text;
|
||||
advance(p);
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Like expectident but also accepts a bare `_` discard marker. The
|
||||
* returned string is the empty string "" so the checker skips
|
||||
* scope_define. Callers that care can detect this with `s[0] == '\0'`. */
|
||||
@@ -1340,19 +1358,25 @@ parseuse(Parser *p)
|
||||
n->usefile = p->cur.pos.file;
|
||||
n->useline = p->cur.pos.line;
|
||||
n->usecol = p->cur.pos.col;
|
||||
Pos pathpos = p->cur.pos;
|
||||
const char *alias = NULL;
|
||||
const char *first;
|
||||
if (p->cur.kind == TK_UNDER) {
|
||||
n->useblank = 1;
|
||||
advance(p);
|
||||
pathpos = p->cur.pos;
|
||||
first = expectident(p);
|
||||
} else {
|
||||
first = expectident(p);
|
||||
if (p->cur.kind == TK_IDENT) {
|
||||
alias = first;
|
||||
pathpos = p->cur.pos;
|
||||
first = expectident(p);
|
||||
}
|
||||
}
|
||||
n->usepathfile = pathpos.file;
|
||||
n->usepathline = pathpos.line;
|
||||
n->usepathcol = pathpos.col;
|
||||
const char *leaf = first;
|
||||
const char *path = leaf;
|
||||
while (accept(p, TK_DOT)) {
|
||||
@@ -1383,11 +1407,13 @@ parseheaderuse(Parser *p)
|
||||
n->usefile = p->cur.pos.file;
|
||||
n->useline = p->cur.pos.line;
|
||||
n->usecol = p->cur.pos.col;
|
||||
Pos pathpos = p->cur.pos;
|
||||
const char *alias = NULL;
|
||||
const char *first;
|
||||
if (p->cur.kind == TK_UNDER) {
|
||||
n->useblank = 1;
|
||||
advance(p);
|
||||
pathpos = p->cur.pos;
|
||||
} else if (p->cur.kind != TK_IDENT) {
|
||||
errorf(p->cur.pos, "expected identifier, got %s",
|
||||
tokname(p->cur.kind));
|
||||
@@ -1404,9 +1430,13 @@ parseheaderuse(Parser *p)
|
||||
advance(p);
|
||||
if (!n->useblank && p->cur.kind == TK_IDENT) {
|
||||
alias = first;
|
||||
pathpos = p->cur.pos;
|
||||
first = p->cur.text;
|
||||
advance(p);
|
||||
}
|
||||
n->usepathfile = pathpos.file;
|
||||
n->usepathline = pathpos.line;
|
||||
n->usepathcol = pathpos.col;
|
||||
const char *leaf = first;
|
||||
const char *path = leaf;
|
||||
while (p->cur.kind == TK_DOT) {
|
||||
@@ -1467,13 +1497,12 @@ parsepackageheader(Parser *p)
|
||||
}
|
||||
Pos pp = p->cur.pos;
|
||||
advance(p);
|
||||
if (p->cur.kind != TK_IDENT) {
|
||||
if (p->cur.kind != TK_IDENT && p->cur.kind != TK_UNDER) {
|
||||
errorf(p->cur.pos, "invalid or missing package clause");
|
||||
p->errs++;
|
||||
return file;
|
||||
}
|
||||
const char *name = p->cur.text;
|
||||
advance(p);
|
||||
const char *name = expectpackagename(p);
|
||||
if (p->cur.kind != TK_SEMI) {
|
||||
errorf(p->cur.pos, "expected ';' after package name");
|
||||
p->errs++;
|
||||
@@ -1548,14 +1577,14 @@ parseimports(Parser *p)
|
||||
Pos pp = p->cur.pos;
|
||||
previmport = 1;
|
||||
advance(p);
|
||||
if (p->cur.kind != TK_IDENT) {
|
||||
if (p->cur.kind != TK_IDENT && p->cur.kind != TK_UNDER) {
|
||||
errorf(p->cur.pos, "invalid or missing package clause");
|
||||
p->errs++;
|
||||
sawpackage = 1;
|
||||
skipdecl(p);
|
||||
continue;
|
||||
}
|
||||
const char *name = expectident(p);
|
||||
const char *name = expectpackagename(p);
|
||||
expect(p, TK_SEMI);
|
||||
p->curpkg = name;
|
||||
if (p->pathmod == NULL && p->resetmod == NULL)
|
||||
@@ -1754,13 +1783,16 @@ parsefile(Parser *p)
|
||||
sawpackage = 1;
|
||||
previmport = 1;
|
||||
advance(p);
|
||||
const char *name = expectident(p);
|
||||
Pos namepos = p->cur.pos;
|
||||
const char *name = expectpackagename(p);
|
||||
expect(p, TK_SEMI);
|
||||
p->curpkg = name;
|
||||
if (p->pathmod == NULL && p->resetmod == NULL) {
|
||||
p->curmod = name;
|
||||
}
|
||||
Node *package = newnode(p->a, N_FILE, packagepos);
|
||||
Pos markerpos = strcmp(name, "_") == 0
|
||||
? namepos : packagepos;
|
||||
Node *package = newnode(p->a, N_FILE, markerpos);
|
||||
package->module = p->curmod;
|
||||
package->pkgname = name;
|
||||
package->sourceid = p->sourceid;
|
||||
|
||||
@@ -354,6 +354,10 @@ struct Node {
|
||||
* alias when explicit, path otherwise. */
|
||||
int useline;
|
||||
int usecol;
|
||||
const char *usepathfile; /* N_USE: first path-token position,
|
||||
* independent of an explicit/blank alias. */
|
||||
int usepathline;
|
||||
int usepathcol;
|
||||
const char *usepkgname; /* N_USE: imported declared package name,
|
||||
* independent of the visible binding in `str`. */
|
||||
int useblank; /* N_USE: `_` spelling; no source binding. */
|
||||
|
||||
@@ -6982,8 +6982,11 @@ test-runtime gap:
|
||||
with official anchors in
|
||||
[`internal/types/testdata/check/blank.go`](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/blank.go#L1-L5)
|
||||
and [`test/blank1.go`](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/blank1.go#L1-L10).
|
||||
Direct production, imported, and test-only probes were rejected identically
|
||||
by both WW stages. This candidate was aligned.
|
||||
Direct production, imported, and test-only probes were rejected by both WW
|
||||
stages, but only through the loader's generic `invalid or missing package
|
||||
clause`; the direct compilers also produced different syntax-recovery
|
||||
streams. Equal rejection was not semantic alignment. The gap was different
|
||||
and is completed in section 11.56.
|
||||
- **Import:** pinned `unusedImports` requires every nonblank import binding to
|
||||
be used
|
||||
([`cmd/compile/internal/types2/resolver.go`, lines 706–740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740)); official
|
||||
@@ -10354,11 +10357,11 @@ a raw source and does not trigger the non-directory test-source omission in
|
||||
- **directly measured WW behavior** — direct `SIGTERM` of either driver while a
|
||||
selected warm build is blocked in compilation is an inherited lifecycle
|
||||
non-effect, not part of the classifier change. Both stages terminate with
|
||||
shell status 143, reap the complete owned process group, and preserve the
|
||||
prior public output and committed semantic bytes, but the existing directory
|
||||
machinery leaves three request-private `.new` files. That independently
|
||||
verified cleanup gap remains open and this slice does not describe it as
|
||||
fixed.
|
||||
shell status 143 and preserve the prior public output and committed semantic
|
||||
bytes, but leave the directly spawned compiler alive and exactly three
|
||||
fixed-name `.new` files. A later persistent request rejects the existing
|
||||
`.unit.new`. That independently verified supervision/cleanup gap remains open
|
||||
and this slice does not describe it as fixed.
|
||||
- **directly measured WW behavior** — Cstage and WWstage agree byte-for-byte
|
||||
on status, stdout, stderr, diagnostics, public output, and every semantic
|
||||
artifact for selected success and failure rows. Complete
|
||||
@@ -10852,6 +10855,319 @@ every other package name remain ordinary.
|
||||
remains `3`; no action descriptor, cache/result record, transaction marker,
|
||||
manifest, database, or lock is added.
|
||||
|
||||
### 11.56 Implemented blank declared package-name checking
|
||||
|
||||
The exact declared name in `package _;` is now valid package-clause syntax and
|
||||
an invalid package name. Loaders retain the clause, its imports, and an
|
||||
otherwise coherent source family long enough to construct the applicable
|
||||
ordinary action; the compiler checker then reports exactly
|
||||
`invalid package name _` at the underscore token and continues checking that
|
||||
source. The underscore is accepted only in this package-name grammar slot. It
|
||||
does not become an ordinary identifier, import alias, qualifier, canonical
|
||||
package name, or successful exported identity.
|
||||
|
||||
#### Pinned authority, tests, and applicability
|
||||
|
||||
The sole authority is official Go 1.26.5 at commit
|
||||
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||
|
||||
- **behavior directly implemented or asserted by pinned Go** — the compiler
|
||||
scanner admits `_` to identifier scanning, dispatches it through the name
|
||||
path, and returns it as a name token; the parser accepts and stores that token
|
||||
in the package-name position
|
||||
([`cmd/compile/internal/syntax/scanner.go`, lines 88–107](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/scanner.go#L88-L107),
|
||||
[368–394](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/scanner.go#L368-L394),
|
||||
and
|
||||
[437–439](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/scanner.go#L437-L439),
|
||||
[`parser.go`, lines 397–420](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/parser.go#L397-L420)
|
||||
and
|
||||
[2751–2763](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/parser.go#L2751-L2763)).
|
||||
The types2 checker rejects the retained node as `invalid package name _`
|
||||
and continues file initialization
|
||||
([`cmd/compile/internal/types2/check.go`, lines 336–355](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/check.go#L336-L355));
|
||||
syntax errors prevent types2 from running
|
||||
([`cmd/compile/internal/noder/noder.go`, lines 45–77](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/noder/noder.go#L45-L77),
|
||||
[`irgen.go`, lines 23–99](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/noder/irgen.go#L23-L99)).
|
||||
- **behavior directly implemented or asserted by pinned Go** — `go/build`
|
||||
reads `_` without declaration-error parsing, classifies production and test
|
||||
roles, and records imports before cmd/go builds package actions
|
||||
([`go/build/read.go`, lines 55–56](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/read.go#L55-L56),
|
||||
[187–198](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/read.go#L187-L198),
|
||||
and
|
||||
[265–340](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/read.go#L265-L340),
|
||||
[`go/build/build.go`, lines 931–1039](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L931-L1039)).
|
||||
Named files use one synthetic package loader
|
||||
([`cmd/go/internal/load/pkg.go`, lines 3244–3315](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L3244-L3315));
|
||||
test synthesis augments production with internal-test files rather than
|
||||
treating them as unrelated packages
|
||||
([`cmd/go/internal/load/test.go`, lines 175–226](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L175-L226)).
|
||||
- **behavior directly implemented or asserted by pinned Go** —
|
||||
[`test/blank1.go`, lines 1–31](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/blank1.go#L1-L31)
|
||||
asserts the blank-name error plus later checker errors, directly proving
|
||||
continued checking.
|
||||
[`internal/types/testdata/check/blank.go`, lines 1–5](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/blank.go#L1-L5)
|
||||
separately asserts only the blank-name error. Official role controls
|
||||
[`build_test_only.txt`, lines 1–18](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_test_only.txt#L1-L18)
|
||||
and
|
||||
[`build_no_go.txt`, lines 1–30](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_no_go.txt#L1-L30)
|
||||
anchor the surrounding test-only selection rules. The pinned repository has
|
||||
no official cmd/go test matrix for named, multiple, mixed,
|
||||
test-only, artifact, rollback, concurrency, or interruption forms of
|
||||
`package _`; those are WW-native proofs, not attributed to an absent Go
|
||||
script.
|
||||
- **behavior directly implemented or asserted by pinned Go** — after primary
|
||||
syntax succeeds, types2 validates the imported package object's name before
|
||||
consulting a local alias. Name `_` reports
|
||||
`could not import PATH (invalid package name: "_")` at the import path;
|
||||
an empty name instead quotes the actual empty value as
|
||||
`invalid package name: ""`. The resolver
|
||||
installs and caches a path-leaf-named fake package, marks the occurrence
|
||||
used, and continues checking
|
||||
([`cmd/compile/internal/types2/resolver.go`, lines 125–180 and 248–335](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L125-L180)).
|
||||
The public checker twin implements the same rule
|
||||
([`go/types/resolver.go`, lines 157–190 and 263–350](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/resolver.go#L157-L190)).
|
||||
- No official pinned test directly supplies an imported `Package` whose name
|
||||
is `_`. The two primary-source blank-name tests above do not assert this
|
||||
importer-result validation. Its imported-interface matrix is therefore
|
||||
WW-native proof grounded in the pinned resolver implementation, not a claim
|
||||
about absent official testdata.
|
||||
- **behavior derived from the pinned implementation** — the diagnostic owns
|
||||
the underscore position, normally line 1 column 9, and belongs after source
|
||||
loading, graph construction, complete-file syntax, and dependency-eligible
|
||||
producer ordering. WW has the same explicit package clause, blank token,
|
||||
syntax/check split, package families, imports, and package actions, so the
|
||||
rule applies without adding modules, manifests, registries, generalized
|
||||
imports, or a new identity model.
|
||||
|
||||
Before this slice, **directly measured WW behavior** was a generic positioned
|
||||
`invalid or missing package clause` from both public stages before any
|
||||
producer. Direct Cstage `w6c` emitted four package-clause recovery diagnostics
|
||||
while WWstage `w6c_ww` emitted three. A visible named `_test.ww` ordinary build
|
||||
was rejected at its header instead of being validated and omitted. These facts
|
||||
were a real package/build/test/import difference despite stage-equal public
|
||||
rejection. After the primary-source parser/checker repair and before the
|
||||
imported-interface completion, **directly measured WW behavior** also accepted
|
||||
an owner-matched hand-authored `.wwi` declaring `package _;`: default,
|
||||
explicit, and blank controls accepted and published usable semantic facts, and
|
||||
the explicit alias control produced successful byte-identical Cstage/WWstage
|
||||
assembly and interface output. Ordinary producers could not create that
|
||||
metadata, but direct `--import` and caller-owned persistent interfaces made the
|
||||
gap externally observable.
|
||||
|
||||
#### Semantic ownership
|
||||
|
||||
Primary package-slot syntax and marker positions remain owned by the C/WW
|
||||
parser twins `cmd/wcc/parse.c` and `lib/ww/syntax/parse.ww`; primary and
|
||||
imported blank-name checker diagnostics and fake-package resolution are owned
|
||||
by `cmd/wcc/check.c` and `selfhost/cmd/wcc/check.ww`. Imported export-data
|
||||
materialization, four-state package metadata, primary-rooted reachability,
|
||||
owner filtering, recovery qualifier assignment, and delayed concatenation are
|
||||
owned by `cmd/w6c/main.c` and `selfhost/cmd/w6c/main.ww`. Exact import-path
|
||||
token positions are private `N_USE` state owned by `cmd/wcc/ww.h`,
|
||||
`lib/ww/syntax/ast.ww`, both parser twins, and
|
||||
`lib/ww/syntax/decl.ww`.
|
||||
|
||||
The public drivers and package coordinator already supply canonical direct
|
||||
interface arguments and inherit the validation without edits. No loader,
|
||||
assembler, archiver, linker, runtime, package-identity, or import-syntax owner
|
||||
changes in this completion. This complete ownership replaces the earlier
|
||||
transitional assumption that the slice had only four syntax/checker owners.
|
||||
|
||||
#### Four-axis behavior and phase order
|
||||
|
||||
- **Build:** an eligible blank production source passes the loader, contributes
|
||||
its ordinary package action and import closure, and fails in checker entry.
|
||||
A visible literal named `_test.ww` still follows section 11.51: after its
|
||||
header and contiguous imports are valid, ordinary build omits it before body
|
||||
parsing or action construction, including when the declared name is `_`.
|
||||
`ww run` retains its earlier MainOnly boundary: a blank root is not `main`,
|
||||
so a successfully loaded root is rejected before any producer or compiler.
|
||||
- **Test:** an all-blank production, internal-test, or honest test-only family
|
||||
reaches its applicable test-package compiler action and fails there. A valid
|
||||
production `p` plus blank test `_`, or blank production `_` plus unrelated
|
||||
`p`/`p_test`, remains a coordinator family mismatch before tools. Existing
|
||||
raw/directory `FAIL` placement, compile-only empty stdout, and directory
|
||||
`-S` CLI-shape diagnostics are unchanged; no failed blank product runs.
|
||||
- **Package:** `_` is retained only as a declared source-family observation.
|
||||
It is not canonical identity. In an all-blank selected action, the checker
|
||||
emits one positioned BlankPkgName diagnostic per retained source marker in
|
||||
deterministic source-section order and continues later checking. Mixed
|
||||
declared names retain loader conflict precedence.
|
||||
- **Import:** a valid contiguous import in a blank source is a normal source
|
||||
occurrence and graph edge. Missing or invalid recursive dependencies can
|
||||
therefore fail before the parent checker. An ordinary blank source provider
|
||||
cannot publish a successful interface or archive. A reached hand-authored or
|
||||
corrupted `.wwi` that declares `_` is nevertheless parsed and defensively
|
||||
rejected by the consuming compiler; an unused interface is inert. Default,
|
||||
explicit, and blank aliases cannot mask the invalid provider name.
|
||||
Visibility, vendor, cycle, and initialization rules do not change.
|
||||
|
||||
The complete observable phase order is: source eligibility and loader-visible
|
||||
header/family checks; recursive import loading; MainOnly rejection for `run`;
|
||||
eligible dependency producers for build/test or a run root that passed
|
||||
MainOnly; the parent compiler's complete-file syntax; BlankPkgName checking;
|
||||
reached imported-package validation; later checker diagnostics; then existing
|
||||
driver/coordinator failure trailers.
|
||||
Consequently malformed contiguous imports precede graph construction, a
|
||||
missing dependency can suppress both a later body syntax error and
|
||||
BlankPkgName, and full-file syntax suppresses checker diagnostics. A blank run
|
||||
root never starts even valid dependency producers because MainOnly is earlier.
|
||||
|
||||
#### Test graph, actions, and identity boundaries
|
||||
|
||||
A production-only blank package under `ww test` has one ordinary production
|
||||
compiler action. Production `_` plus a same-package `_test.ww` also declared
|
||||
`_` forms one internal-test family: its augmented action owns both source sets,
|
||||
and `sep_recompile_for_test` substitutes it throughout the product closure, so
|
||||
the separate production node is not independently compiled for that product.
|
||||
One compiler invocation reports one BlankPkgName per retained marker. A
|
||||
test-only blank `_test.ww` forms one test-only internal action and reports once.
|
||||
A blank production plus the actually related external name `__test` passes
|
||||
family classification, but its blank production dependency fails before the
|
||||
external action, generated main, link, or runtime can complete.
|
||||
|
||||
Direct raw files retain `__root.*`; dotted directories and providers retain
|
||||
their canonical dotted package/import/action/artifact identities; production,
|
||||
internal, external, recompiled, support, and generated-main actions retain
|
||||
their existing distinctions. Physical paths, parents, source spellings, and
|
||||
symlink targets remain loader or diagnostic observations. No successful
|
||||
ordinary producer `.wwi` can advertise `_`; a supplied or corrupted `.wwi`
|
||||
that does is invalid input, not an alternate symbol, publication, or
|
||||
persistence identity.
|
||||
|
||||
#### Imported `.wwi` validation, reachability, and recovery
|
||||
|
||||
The rules in this subsection are **behavior derived from the pinned
|
||||
implementation** for WW's supported source-like export-data channel. They do
|
||||
not claim an official imported-blank fixture that does not exist.
|
||||
|
||||
Every sorted `--import CANONICAL FILE.wwi` is first read, owner-checked, and
|
||||
syntax-parsed into its own standalone AST. Interface read, owner, syntax, and
|
||||
structural import-map errors keep their existing precedence. The compiler then
|
||||
syntax-parses the primary input. Any primary syntax error returns before
|
||||
imported-package semantic validation, so it is never accompanied by the
|
||||
broken-import diagnostic.
|
||||
|
||||
After successful primary parsing and import-map application, compiler test
|
||||
mode materializes any required support `N_USE` before resolution. The node has
|
||||
the primary owner/source ID, is marked used, uses visible name `test` or the
|
||||
collision-safe reserved `__wwtest`, and is positioned at the generated primary
|
||||
root because no source path token exists. An existing equivalent primary
|
||||
occurrence prevents duplication. When no support interface is supplied, the
|
||||
node retains the established raw external-support fallback; a supplied
|
||||
blank-named support interface is validated like every source import. Reserved
|
||||
`__wwtest` preserves its compiler-selected visible spelling after valid
|
||||
resolution but cannot skip provider-name validation. Existing
|
||||
`--test-target-package` roots already require a primary occurrence and add no
|
||||
second node.
|
||||
|
||||
A metadata-only pass classifies each represented canonical package as valid,
|
||||
missing, conflicting, or invalid; invalid means its one nonconflicting real
|
||||
declared name is exactly `_`. Compiler-private placeholder packages remain
|
||||
valid recovery metadata. Reachability is seeded only by canonical primary and
|
||||
compiler-required `N_USE.usepath` occurrences, then reaches a fixed point over
|
||||
standalone interface lists. An imported use contributes an edge only when its
|
||||
owning canonical package is already reached and valid. Interface containers,
|
||||
arbitrary embedded origins, and invalid, missing, conflicting, or unreachable
|
||||
owners are never roots or traversal sources.
|
||||
|
||||
Before binding or concatenation, each standalone list is filtered to nodes
|
||||
whose canonical owner is both reached and valid. Invalid-owner, unreachable,
|
||||
and ownerless hand-authored facts are discarded. Thus an unused invalid
|
||||
interface is wholly inert even when it contains an embedded valid-origin
|
||||
section that imports the invalid path: it emits no diagnostic, installs no
|
||||
declaration or scope, contributes no output or serialized fact, and leaves an
|
||||
otherwise valid primary byte-equivalent to the no-interface control. If the
|
||||
primary independently reaches that valid origin, its retained import can then
|
||||
reach and diagnose the invalid provider. Binding runs on those filtered lists
|
||||
and the primary list before the lists are concatenated, so unreachable
|
||||
internal uses cannot manufacture missing, conflict, or invalid effects.
|
||||
|
||||
Every retained occurrence resolving to an invalid provider is marked used and
|
||||
records declared provider name `_`. A nonblank occurrence receives a recovery
|
||||
package binding using its explicit alias or, by default, the final component of
|
||||
the canonical dotted path; a blank occurrence installs no visible binding.
|
||||
The fake package has an empty scope. Qualified value, call, and type gateways
|
||||
therefore recover as the error type without missing-member,
|
||||
unknown-type/export, or calling-nonfunction cascades, while a lexically closer
|
||||
value binding still shadows the recovery qualifier normally.
|
||||
|
||||
At checker entry, immediately after primary BlankPkgName diagnostics, the
|
||||
first retained invalid occurrence of each canonical path emits exactly
|
||||
`could not import PATH (invalid package name: "_")`; later occurrences of the
|
||||
same path are deduplicated, while distinct paths diagnose in retained
|
||||
occurrence order. Independent checker diagnostics continue afterward.
|
||||
Deduplication uses canonical dotted path because WW has no Go source-directory
|
||||
import-key component. Default, explicit, and blank alias forms all position
|
||||
this diagnostic at the path's first identifier, never at an explicit alias.
|
||||
The existing alias-or-path first-spec position remains unchanged for every
|
||||
other binding diagnostic. Full parsing, imports-only parsing, and named-source
|
||||
header parsing all retain both position families.
|
||||
|
||||
Public persistent build and test actions consume the same supported interface
|
||||
channel. If a reached caller-owned committed `.wwi` is corrupted to declare
|
||||
`package _;`, the consumer compiler fails after primary syntax and resolution;
|
||||
that action's assembler and downstream archive, link, retention, or runtime do
|
||||
not complete. A package-action failure prevents generated main; a generated-main
|
||||
action that is itself the consumer performs the same validation before its own
|
||||
assembly. Raw, production, internal, external, test-only, generated-main, `-c`,
|
||||
and applicable `-S` consumer actions use the same rule. A same-named `.ww` file
|
||||
remains an import decoy rather than a provider. An
|
||||
unreferenced corrupt interface remains inert and does not invalidate or alter
|
||||
the consumer. A failing direct compiler returns status 1 with empty stdout;
|
||||
public test presentation retains its existing running `FAIL` and
|
||||
package-trailer rules, while compile-only and assembly-only forms retain empty
|
||||
stdout.
|
||||
|
||||
#### Artifacts, rollback, concurrency, parity, and formats
|
||||
|
||||
The failed primary blank action and a consumer rejecting a reached blank-named
|
||||
interface emit neither compiler assembly nor `.wwi`, so their downstream
|
||||
assembler, archiver, linker, test harness, and user runtime do not run. Valid
|
||||
dependencies or test support that precede either failure may execute their
|
||||
ordinary producers, but request rollback removes every request-owned stage and
|
||||
commits no failed generation, public product, retained test binary, unit,
|
||||
interface, assembly, object, archive, executable, stamp, or status. Direct
|
||||
named `-o`/`-I` outputs, public outputs, retained tests, and committed semantic
|
||||
bytes remain byte-identical. Restoring the exact valid source or `.wwi` bytes
|
||||
uses the existing content-identity reuse path; invalid bytes never publish a
|
||||
replacement consumer generation. Reached interface bytes already participate
|
||||
through the existing compiler input and invalidation rules; semantic validation
|
||||
adds no graph, action, or persistence key. A build-omitted named `_test.ww`
|
||||
contributes no action or invalidation key.
|
||||
|
||||
Blank-package state, imported metadata, reachability sets, deduplication, and
|
||||
fake bindings are compiler/checker-process-local. Independent concurrent
|
||||
requests cannot share diagnostics, graph state, staging, or cleanup. Normal
|
||||
failure is waited and rolled back through the existing transaction owner and
|
||||
leaves no anonymous descriptor, `.new`, `.old`, `.install`, `.wwtxn.*`,
|
||||
capture, result, request scratch, or child.
|
||||
Direct `w6c` and `w6c_ww`, and public `ww` and `ww_ww`, agree on status,
|
||||
stdout, exact path-token positions and diagnostic order, fake recovery,
|
||||
output absence, prior-byte preservation, and every comparable semantic
|
||||
dependency artifact. Producer provenance remains the established intentional
|
||||
stage difference.
|
||||
|
||||
No signal-supervision behavior changed. Direct external `SIGTERM` during a
|
||||
blocked persistent compilation still preserves prior committed/public bytes
|
||||
but can leave the spawned compiler and fixed-name `.new` staging that poisons a
|
||||
later request. That independently verified gap remains open and is not claimed
|
||||
fixed by normal BlankPkgName rollback.
|
||||
|
||||
No serialized representation changed. The full parser changes only the
|
||||
in-memory position of a blank package marker to the underscore token; outer
|
||||
file/header/import-only markers and valid package markers keep their former
|
||||
positions. Each twin's private in-memory `Node` gains only
|
||||
`usepathfile/usepathline/usepathcol`; AST enum values, AST printing, `.wwi`
|
||||
schema, build workdir format `18`, test workdir format `19`, and semantic
|
||||
storage format `3` remain unchanged. No cache, result record, manifest, action
|
||||
descriptor, transaction marker, database, or lock is added. This closes one
|
||||
coherent semantic slice across all four axes;
|
||||
it does not complete the remaining suffix-first run front, multiple named
|
||||
source packages, shared test-process state and failure topology, RE2-compatible
|
||||
flat `-run`, finite special-source handling, or external-driver interruption
|
||||
recovery.
|
||||
|
||||
## 12. Candidate architectures and hard-gate decision
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
106
docs/spec.md
106
docs/spec.md
@@ -273,7 +273,8 @@ does not return (e.g. a call to `abort`).
|
||||
|
||||
```
|
||||
SourceFile = PackageClause { ImportDecl } { TopDecl } .
|
||||
PackageClause = "package" ident ";" .
|
||||
PackageClause = "package" PackageName ";" .
|
||||
PackageName = ident .
|
||||
ImportDecl = "import" ( ImportPath | ImportName ImportPath ) ";" .
|
||||
ImportName = ident .
|
||||
ImportPath = ident { "." ident } .
|
||||
@@ -285,6 +286,101 @@ ImportPath = ident { "." ident } .
|
||||
use the related `p_test`, and the actions remain separate even though one
|
||||
canonical directory owns their test product. The declared name need not equal
|
||||
the directory name or the final component of its canonical import identity.
|
||||
- The discard identifier `_` is syntactically valid as `PackageName`, but it
|
||||
is never a valid declared package name. A complete source with
|
||||
`package _;` reaches checker initialization, which reports exactly
|
||||
`invalid package name _` at the underscore token and continues checking the
|
||||
retained file. The package-clause parser admits the discard token only in
|
||||
this grammar slot; no other identifier position is broadened. A malformed or
|
||||
missing package name remains a parser error, and any complete-file syntax
|
||||
error prevents this checker diagnostic.
|
||||
|
||||
Loading may retain `_` transiently to compare declared source families,
|
||||
record imports, and construct the applicable action, but it is not canonical
|
||||
package, import, graph, action, symbol, `.wwi`, artifact, publication, or
|
||||
persistence identity. Direct sources retain `__root`; dotted directories
|
||||
and providers retain their dotted identities. Selected files with distinct
|
||||
declared names remain a loader/family conflict. In an all-blank action the
|
||||
checker emits one blank-name diagnostic per retained package marker in
|
||||
deterministic source order.
|
||||
|
||||
Observable ordering is source eligibility and loader-visible header/family
|
||||
validation, recursive import loading, the `ww run` main-package check,
|
||||
eligible dependency producers, complete parent-source parsing, then the
|
||||
blank-name, reached imported-package, and later checker diagnostics. Thus
|
||||
missing or invalid imports may precede the parent check; a blank run root is
|
||||
not `main` and starts no producer; and a full-source syntax error suppresses
|
||||
the blank-name error. An
|
||||
ordinary blank source provider cannot publish an interface or archive for an
|
||||
importer. A supplied or caller-corrupted `.wwi` can nevertheless contain
|
||||
that spelling and is defensively validated when reached.
|
||||
|
||||
Import interfaces are read, owner-checked, and syntax-parsed as separate
|
||||
lists before the primary source is parsed. Interface structural errors keep
|
||||
their existing precedence, but a primary syntax error returns before
|
||||
imported-package semantic checking and therefore suppresses every
|
||||
blank-provider import diagnostic. After successful primary syntax, compiler
|
||||
test mode first materializes its required `test` or collision-safe
|
||||
`__wwtest` support occurrence unless an equivalent primary occurrence
|
||||
exists. With no matching interface that occurrence retains the external
|
||||
support fallback; with a matching interface its provider name is validated,
|
||||
and the reserved spelling does not bypass the check.
|
||||
|
||||
Each represented canonical interface package is classified as valid,
|
||||
missing, conflicting, or invalid, where invalid means one nonconflicting
|
||||
real declared name `_`. Imported-interface reachability is rooted only at
|
||||
canonical uses in the primary and compiler-required lists. A reached valid
|
||||
interface owner may contribute its imported uses transitively; an invalid,
|
||||
missing, conflicting, unreachable, or ownerless section may not. Before
|
||||
interface facts are bound or merged, every declaration and use whose owner
|
||||
is not both reached and valid is discarded. An unused invalid interface is
|
||||
therefore wholly inert, even when it embeds a valid-origin section that
|
||||
imports the invalid path: it emits no diagnostic, installs no scope or
|
||||
declaration, changes no output, and is byte-equivalent to supplying no such
|
||||
interface. If primary source separately reaches that valid origin, its
|
||||
retained edge may legitimately reach and diagnose the invalid provider.
|
||||
|
||||
A retained use of an invalid provider is marked used. A nonblank use receives
|
||||
a fake empty-scope package binding under its explicit alias or, without one,
|
||||
the canonical path leaf; a blank use creates no visible binding. This
|
||||
recovery prevents qualified values, calls, and types from producing
|
||||
missing-member, unknown-type, export, or calling-nonfunction cascades.
|
||||
Ordinary lexical shadowing of a nonblank recovery alias still applies.
|
||||
|
||||
Immediately after primary blank-name diagnostics, the first retained use of
|
||||
each invalid canonical path reports exactly
|
||||
`could not import PATH (invalid package name: "_")`; later uses of that path
|
||||
are deduplicated, distinct paths retain occurrence order, and independent
|
||||
checker errors continue. Default, explicit, and blank import forms all
|
||||
position this error at the path's first identifier rather than at the alias.
|
||||
Canonical path, source spelling, alias, declared provider name, owner marker,
|
||||
placeholder, edge, and binding remain separate facts. A physical `.wwi`
|
||||
path is observation metadata and a same-named `.ww` file remains an import
|
||||
decoy.
|
||||
|
||||
Ordinary `ww build` still omits a valid visible literal `*_test.ww` after
|
||||
header loading, so `package _;` in that omitted role has no action or
|
||||
diagnostic. Under `ww test`, production plus a same-package blank test forms
|
||||
one augmented internal-test action; the test recompile substitutes for the
|
||||
separate production node and one compiler invocation diagnoses every
|
||||
retained blank marker. A test-only blank source has one test-package action.
|
||||
Mixed valid/blank production and test names retain family-mismatch
|
||||
precedence, and no failed blank product reaches generated main, link, or
|
||||
runtime.
|
||||
|
||||
Normal blank-package failure publishes no assembly, interface, object,
|
||||
archive, executable, retained test, or new semantic generation. Existing
|
||||
request rollback removes owned stages and preserves prior public and
|
||||
committed bytes; a public build or test consuming a reached corrupted
|
||||
committed interface likewise publishes no replacement consumer generation,
|
||||
retained test, or downstream artifact. Exact valid-interface restoration
|
||||
follows ordinary reuse. Independent requests share no blank-name,
|
||||
reachability, deduplication, or fake-binding state. Cstage and WWstage have
|
||||
the same path-positioned diagnostic stream and artifact outcome. The private
|
||||
in-memory AST adds only path-position fields; AST enum/printing, `.wwi`
|
||||
schema, build workdir format 18, test workdir format 19, and semantic storage
|
||||
format 3 do not change. External driver interruption is unchanged; the fixed
|
||||
`.new` residue and later persistent request poisoning remain open.
|
||||
- Each source file has one contiguous import section immediately after its
|
||||
package clause. Once a non-import top-level declaration begins, a later
|
||||
`import` is rejected as `imports must appear before other declarations`.
|
||||
@@ -643,10 +739,10 @@ ImportPath = ident { "." ident } .
|
||||
interruption preserve existing directory contents and remove only
|
||||
request-created prefixes and stages. Direct external `SIGTERM` of a build
|
||||
driver is a verified-open exception: both stages preserve public and
|
||||
committed work bytes and reap their process group, but may leave `.new`
|
||||
staging files that make a later persistent-work request reject until those
|
||||
files are removed. A non-directory output retains the single-product
|
||||
file/archive rule.
|
||||
committed work bytes, but leave the directly spawned compiler alive and
|
||||
exactly three fixed-name `.new` staging files; the existing `.unit.new`
|
||||
makes a later persistent-work request reject. A non-directory output retains
|
||||
the single-product file/archive rule.
|
||||
If a lone
|
||||
command's synthesized default basename already names a directory, loading
|
||||
and graph validation complete and the build rejects before tools without
|
||||
|
||||
@@ -339,6 +339,146 @@ Build workdir format remains 18, test workdir format remains 19, and semantic
|
||||
storage format remains 3; no test-result cache, schema, action descriptor,
|
||||
transaction marker, or lock is introduced.
|
||||
|
||||
The blank declared package name has a different contract from documentation
|
||||
suppression. The sole authority is official Go 1.26.5 at commit
|
||||
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||
|
||||
- **behavior directly implemented or asserted by pinned Go** — the compiler
|
||||
scanner admits and dispatches `_` through its name path
|
||||
(`cmd/compile/internal/syntax/scanner.go:88–107,368–394,437–439`), the parser
|
||||
accepts it in package syntax (`parser.go:397–420,2751–2763`), and types2
|
||||
rejects the retained node as `invalid package name _`
|
||||
(`cmd/compile/internal/types2/check.go:336–355`).
|
||||
- **behavior directly implemented or asserted by pinned Go** — official
|
||||
`test/blank1.go:1–31` asserts the blank-name error and later checker errors,
|
||||
proving continuation. `internal/types/testdata/check/blank.go:1–5` asserts
|
||||
only the blank-name error. `cmd/go/testdata/script/build_test_only.txt:1–18`
|
||||
and `build_no_go.txt:1–30` are surrounding source-role controls.
|
||||
- The official tree contains no cmd/go blank-name matrix for named, multiple,
|
||||
mixed, imported, test-only, action, artifact, rollback, concurrency, or
|
||||
interruption cases; the focused WW package observer owns those proofs.
|
||||
- **behavior directly implemented or asserted by pinned Go** — types2
|
||||
validates an importer-supplied package object before applying its local
|
||||
alias. Provider name `_` emits
|
||||
`could not import PATH (invalid package name: "_")` at the source import
|
||||
path, caches a path-leaf-named fake package, marks the use, and continues
|
||||
(`cmd/compile/internal/types2/resolver.go:125–180,248–335`; public twin
|
||||
`go/types/resolver.go:157–190,263–350`). An empty provider name quotes the
|
||||
actual empty value instead.
|
||||
- No official pinned test directly supplies an imported `Package` named `_`.
|
||||
**behavior derived from the pinned implementation** — the
|
||||
imported-interface rows are WW-native proof of applying those resolver
|
||||
semantics to WW's supported source-like interface channel.
|
||||
|
||||
The slice is not owned only by the four primary parser/checker files. The two
|
||||
`w6c` command fronts own test-support materialization, imported-package
|
||||
metadata, reachability/filtering, fake qualifier assignment, and delayed AST
|
||||
concatenation; the checker twins own deduplicated diagnostics and empty-scope
|
||||
fake recovery; the AST/parser twins own the independent path-token position.
|
||||
The package coordinator and public drivers exercise the same interface channel
|
||||
without redefining package or action identity.
|
||||
|
||||
The coordinator treats `_` as a syntactically loaded declared name, not a
|
||||
generic missing-clause error. A production-only blank package under `ww test`
|
||||
selects its one ordinary production compiler action and fails there. Blank
|
||||
production plus a same-package blank `_test.ww` selects one augmented internal
|
||||
test action containing both source sets. `sep_recompile_for_test` substitutes
|
||||
that action for the separate production node throughout the product closure,
|
||||
so it is compiled once and emits one `invalid package name _` per retained
|
||||
source marker in deterministic unit order. A test-only blank `_test.ww` forms
|
||||
one test-only internal action and reports once. Valid production `p` plus blank
|
||||
test `_`, or blank production `_` plus an unrelated `p` or `p_test`, keeps the
|
||||
existing family-mismatch rejection before tools. Blank production plus the
|
||||
actually related external name `__test` passes family classification, but the
|
||||
production dependency fails before the external action, support/generated
|
||||
main, link, or runtime completes.
|
||||
|
||||
The observable test phase order is selected-source validation and package/test
|
||||
family classification, recursive import loading, eligible dependency and
|
||||
support producers, complete parent-source parsing, blank-name checking,
|
||||
imported-package validation, later checker diagnostics, and the
|
||||
existing package failure trailer. A missing or invalid dependency may
|
||||
therefore suppress both a later body syntax diagnostic and BlankPkgName; a
|
||||
complete-file syntax error suppresses checker diagnostics.
|
||||
Raw and directory running requests keep their established command-owned
|
||||
`FAIL\n` placement, while `-c` and applicable `-S` paths retain empty stdout.
|
||||
Directory `-S` option-shape errors remain earlier than source selection. No
|
||||
blank product begins a test or user runtime.
|
||||
|
||||
The imported-interface observer uses the supported repeatable
|
||||
`w6c -c --import PATH FILE.wwi` channel and its public persistent build/test
|
||||
equivalent. Each compiler first read/owner/syntax-checks sorted standalone
|
||||
interfaces, then parses primary source. Primary syntax failure precedes
|
||||
imported-package semantic validation. After successful primary syntax, `-T`
|
||||
materializes a missing compiler-required `test` or collision-safe `__wwtest`
|
||||
use in the primary list before resolution. It is marked used and rooted at the
|
||||
generated primary file position. With no matching import it keeps the existing
|
||||
external-support fallback; with a matching blank-named interface it receives
|
||||
the ordinary invalid-provider diagnostic. Existing explicit test-target roots
|
||||
already have a primary occurrence and are not duplicated.
|
||||
|
||||
The observer requires metadata classification as valid, missing, conflicting,
|
||||
or invalid, followed by reachability seeded solely by canonical primary and
|
||||
compiler-required uses. Only a reached valid interface owner may contribute
|
||||
transitive imports. Before binding and delayed concatenation, invalid-owner,
|
||||
unreachable, and ownerless standalone facts are removed. The unused-interface
|
||||
control includes a mixed-origin invalid interface whose embedded valid origin
|
||||
imports the bad path: because that origin is not independently rooted, the
|
||||
whole interface is inert and the primary outputs equal the no-interface
|
||||
control. A companion primary-reached origin proves that legitimate traversal
|
||||
does reach and diagnose the bad provider.
|
||||
|
||||
For every retained bad use, default, explicit, and blank aliases all validate
|
||||
the provider name. Nonblank uses receive an explicit-alias or canonical-path-
|
||||
leaf fake binding with empty scope; blank uses install none. All are marked
|
||||
used. Qualified value/call/type recovery produces no missing-member,
|
||||
unknown-type, export, or calling-nonfunction cascade, while lexical shadowing
|
||||
still applies. One canonical bad path emits one diagnostic despite repeated
|
||||
occurrences; two paths emit one each in retained order, and an independent
|
||||
checker diagnostic follows. Every source-created form points at the path's
|
||||
first identifier, not the explicit alias. The compiler-generated support use
|
||||
uses its generated root position because no path token exists.
|
||||
|
||||
Public proof corrupts a caller-owned committed provider `.wwi`, then exercises
|
||||
build and test consumers through the existing canonical interface action.
|
||||
Reached corruption fails the affected compiler action before its assembler and
|
||||
downstream archive, link, retention, or runtime. A package-action failure
|
||||
prevents generated main; a generated-main action that is itself the consumer
|
||||
performs the same validation before its assembly. A same-named `.ww` file is
|
||||
still an import decoy. Raw, production, internal, external, test-only,
|
||||
generated-main, `-c`, and applicable `-S` shapes share the same validation.
|
||||
Prior public, retained, and semantic bytes survive, invalid bytes cannot commit
|
||||
a replacement consumer, and restoring valid interface bytes follows ordinary
|
||||
reuse. An unreferenced corrupt interface remains inert and causes no
|
||||
invalidation.
|
||||
|
||||
The package-name token does not alter production/internal/external/recompiled,
|
||||
support, or generated-main identities. Direct actions retain `__root`, dotted
|
||||
actions retain dotted identity, and no successful ordinary producer interface
|
||||
can advertise a blank provider. A supplied or corrupted interface that does is
|
||||
invalid consumer input. A failed primary blank action or reached invalid
|
||||
interface consumer emits no assembly or interface, so its assembler, archiver,
|
||||
linker, generated main, harness, and runtime do not run; earlier valid
|
||||
dependency/support producers may run normally. Request rollback removes owned
|
||||
stages and commits no failed unit, interface, assembly, object, archive, binary,
|
||||
status, retained output, or tool stamp. Warm prior public and semantic bytes
|
||||
remain unchanged, exact source/interface restoration uses ordinary reuse, and
|
||||
concurrent requests keep parser/checker reachability, deduplication, fake
|
||||
bindings, and cleanup independent.
|
||||
|
||||
Direct `w6c`/`w6c_ww` and public `ww`/`ww_ww` are required to agree on status,
|
||||
stdout, exact path-token positions and diagnostic order, fake recovery, output
|
||||
absence, and prior-byte preservation for the full role/import matrix. The
|
||||
syntax/check change adds no test-process topology, test-result cache, schema,
|
||||
action descriptor, or stored identity. Private in-memory `N_USE` nodes gain
|
||||
only path-position fields; AST enum/printing and `.wwi` serialization are
|
||||
unchanged. Build workdir format remains 18, test workdir format remains 19, and
|
||||
semantic storage format remains 3. Normal checker failure leaves no anonymous
|
||||
descriptor, adjacent stage, request residue, or child. External-driver SIGTERM
|
||||
supervision is unchanged: the known orphan compiler, fixed `.new` staging, and
|
||||
later persistent-request poisoning remain open and are not credited to this
|
||||
slice.
|
||||
|
||||
An existing local directory whose requested build basename ends `.ww`
|
||||
(including a visible `_test.ww` symlink to a directory) remains a directory
|
||||
package, not a raw named test source. WWstage `ww build` now uses the same
|
||||
@@ -1144,8 +1284,15 @@ timeout policy in this architecture.
|
||||
|
||||
## Open driver work
|
||||
|
||||
None; directory-package `-c` and `-o` now have the applicable Go 1.26.5
|
||||
retention, naming, fan-out, execution, and publication behavior.
|
||||
Directory-package `-c` and `-o` have the applicable Go 1.26.5 retention,
|
||||
naming, fan-out, execution, and publication behavior. Direct external SIGTERM
|
||||
of either driver during blocked persistent compilation remains open: prior
|
||||
public and committed semantic bytes survive, but the directly spawned compiler
|
||||
can survive with three fixed-name `.new` stages, and a later persistent request
|
||||
then rejects the existing `.unit.new`. The required repair is driver-owned
|
||||
child-process-group supervision and normal request rollback before re-raising
|
||||
the original signal; blind removal of `.new` files cannot distinguish foreign
|
||||
or concurrent stages.
|
||||
|
||||
## Validation policy
|
||||
|
||||
|
||||
@@ -133,6 +133,9 @@ export type node = struct {
|
||||
usefile: str, // N_USE: first spec token (alias, otherwise path)
|
||||
useline: i32,
|
||||
usecol: i32,
|
||||
usepathfile: str,// N_USE: first path token, independent of alias
|
||||
usepathline: i32,
|
||||
usepathcol: i32,
|
||||
usepkgname: str,// N_USE: imported declared package name
|
||||
useblank: i32, // N_USE: `_` spelling; no source binding
|
||||
pkgname: str, // declared package name; independent of canonical nmod
|
||||
@@ -153,7 +156,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="", usesource="", usepath="", usealias="", usefile="", useline=0, usecol=0, usepkgname="", useblank=0, pkgname="", sourceid=0, used=0, initfn=0, initsynthetic=0, runtimeinit=0, initorder=0u64, linksym="", refdecl=nil, initmark=0u64, 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="", usesource="", usepath="", usealias="", usefile="", useline=0, usecol=0, usepathfile="", usepathline=0, usepathcol=0, usepkgname="", useblank=0, pkgname="", sourceid=0, used=0, initfn=0, initsynthetic=0, runtimeinit=0, initorder=0u64, linksym="", refdecl=nil, initmark=0u64, imported=0})!;
|
||||
return n;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,19 +19,31 @@ fn parseuse(p: *parser) *node = {
|
||||
n.usefile = p.curfile;
|
||||
n.useline = p.curline;
|
||||
n.usecol = p.curcol;
|
||||
let pathfile: str = p.curfile;
|
||||
let pathline: i32 = p.curline;
|
||||
let pathcol: i32 = p.curcol;
|
||||
let alias: str;
|
||||
let first: str;
|
||||
if (p.curkind == tkind.TK_UNDER) {
|
||||
n.useblank = 1;
|
||||
advance(p);
|
||||
pathfile = p.curfile;
|
||||
pathline = p.curline;
|
||||
pathcol = p.curcol;
|
||||
expectident(p, &first);
|
||||
} else {
|
||||
expectident(p, &first);
|
||||
if (p.curkind == tkind.TK_IDENT) {
|
||||
alias = first;
|
||||
pathfile = p.curfile;
|
||||
pathline = p.curline;
|
||||
pathcol = p.curcol;
|
||||
expectident(p, &first);
|
||||
};
|
||||
};
|
||||
n.usepathfile = pathfile;
|
||||
n.usepathline = pathline;
|
||||
n.usepathcol = pathcol;
|
||||
let leaf: str = first;
|
||||
let path: str = leaf;
|
||||
for (p.curkind == tkind.TK_DOT) {
|
||||
|
||||
@@ -145,6 +145,19 @@ fn expectident(p: *parser, into: *str) bool = {
|
||||
return true;
|
||||
};
|
||||
|
||||
// Package declarations admit the blank identifier syntactically. Keep this
|
||||
// private to the package-name slot; the checker owns BlankPkgName rejection.
|
||||
fn expectpackagename(p: *parser, into: *str) bool = {
|
||||
if (p.curkind != tkind.TK_IDENT && p.curkind != tkind.TK_UNDER) {
|
||||
errmsg(p, strings.concat("expected identifier, got ",
|
||||
tokname(p.curkind)));
|
||||
return false;
|
||||
};
|
||||
*into = p.curtext;
|
||||
advance(p);
|
||||
return true;
|
||||
};
|
||||
|
||||
// On `_`, returns "" so the checker skips scope_define for the binding.
|
||||
fn expectbindname(p: *parser, into: *str) bool = {
|
||||
if (p.curkind == tkind.TK_UNDER) {
|
||||
@@ -513,11 +526,17 @@ fn parseheaderuse(p: *parser) *node = {
|
||||
n.usefile = p.curfile;
|
||||
n.useline = p.curline;
|
||||
n.usecol = p.curcol;
|
||||
let pathfile: str = p.curfile;
|
||||
let pathline: i32 = p.curline;
|
||||
let pathcol: i32 = p.curcol;
|
||||
let alias: str;
|
||||
let first: str;
|
||||
if (p.curkind == tkind.TK_UNDER) {
|
||||
n.useblank = 1;
|
||||
advance(p);
|
||||
pathfile = p.curfile;
|
||||
pathline = p.curline;
|
||||
pathcol = p.curcol;
|
||||
};
|
||||
if (p.curkind != tkind.TK_IDENT) {
|
||||
errmsg(p, strings.concat("expected identifier, got ",
|
||||
@@ -528,9 +547,15 @@ fn parseheaderuse(p: *parser) *node = {
|
||||
advance(p);
|
||||
if (n.useblank == 0 && p.curkind == tkind.TK_IDENT) {
|
||||
alias = first;
|
||||
pathfile = p.curfile;
|
||||
pathline = p.curline;
|
||||
pathcol = p.curcol;
|
||||
first = p.curtext;
|
||||
advance(p);
|
||||
};
|
||||
n.usepathfile = pathfile;
|
||||
n.usepathline = pathline;
|
||||
n.usepathcol = pathcol;
|
||||
let leaf: str = first;
|
||||
let path: str = leaf;
|
||||
for (p.curkind == tkind.TK_DOT) {
|
||||
@@ -585,12 +610,12 @@ export fn parsepackageheader(p: *parser) *node = {
|
||||
let pl: i32 = p.curline;
|
||||
let pc: i32 = p.curcol;
|
||||
advance(p);
|
||||
if (p.curkind != tkind.TK_IDENT) {
|
||||
if (p.curkind != tkind.TK_IDENT && p.curkind != tkind.TK_UNDER) {
|
||||
errmsg(p, "invalid or missing package clause");
|
||||
return f;
|
||||
};
|
||||
let name: str = p.curtext;
|
||||
advance(p);
|
||||
let name: str;
|
||||
expectpackagename(p, &name);
|
||||
if (p.curkind != tkind.TK_SEMI) {
|
||||
errmsg(p, "expected ';' after package name");
|
||||
return f;
|
||||
@@ -659,14 +684,15 @@ export fn parseimports(p: *parser) *node = {
|
||||
let pc: i32 = p.curcol;
|
||||
previmport = true;
|
||||
advance(p);
|
||||
if (p.curkind != tkind.TK_IDENT) {
|
||||
if (p.curkind != tkind.TK_IDENT
|
||||
&& p.curkind != tkind.TK_UNDER) {
|
||||
errmsg(p, "invalid or missing package clause");
|
||||
sawpackage = true;
|
||||
skipimportdecl(p);
|
||||
continue;
|
||||
};
|
||||
let name: str;
|
||||
expectident(p, &name);
|
||||
expectpackagename(p, &name);
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
|
||||
p.curpkg = name;
|
||||
if (p.pathmod.len == 0 && p.resetmod.len == 0) {
|
||||
@@ -771,14 +797,25 @@ export fn parsefile(p: *parser) *node = {
|
||||
sawpackage = 1;
|
||||
previmport = true;
|
||||
advance(p);
|
||||
let nf: str = p.curfile;
|
||||
let nl: i32 = p.curline;
|
||||
let nc: i32 = p.curcol;
|
||||
let name: str;
|
||||
expectident(p, &name);
|
||||
expectpackagename(p, &name);
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
|
||||
p.curpkg = name;
|
||||
if (p.pathmod.len == 0 && p.resetmod.len == 0) {
|
||||
p.curmod = name;
|
||||
};
|
||||
let pm: *node = newnode(nkind.N_FILE, pf, pl, pc);
|
||||
let mf: str = pf;
|
||||
let ml: i32 = pl;
|
||||
let mc: i32 = pc;
|
||||
if (streq(name, "_")) {
|
||||
mf = nf;
|
||||
ml = nl;
|
||||
mc = nc;
|
||||
};
|
||||
let pm: *node = newnode(nkind.N_FILE, mf, ml, mc);
|
||||
pm.nmod = p.curmod;
|
||||
pm.pkgname = name;
|
||||
pm.sourceid = p.sourceid;
|
||||
|
||||
@@ -360,15 +360,23 @@ fn canonicalpkgname(path: str, name: str) bool = {
|
||||
return true;
|
||||
};
|
||||
|
||||
// A paired interface owns PATH's declared name. Compiler-private names keep
|
||||
// transitive fact sections semantic and are ignored when a real name exists.
|
||||
// Package-marker facts are canonical-origin keyed across all supplied
|
||||
// interfaces; an interface may embed transitive origins besides its container
|
||||
// owner. Compiler-private names remain recovery metadata and are ignored when
|
||||
// a real declared name exists.
|
||||
type pkgnamestate = enum i32 {
|
||||
PKGNAME_MISSING = 0,
|
||||
PKGNAME_VALID = 1,
|
||||
PKGNAME_CONFLICT = 2,
|
||||
PKGNAME_INVALID = 3,
|
||||
};
|
||||
|
||||
fn importpkgname(asts: []*syntax.node, paths: []*u8, nasts: i32, path: str,
|
||||
primary: *syntax.node, conflict: *bool) str = {
|
||||
primary: *syntax.node, resolved: *str) pkgnamestate = {
|
||||
let name: str;
|
||||
let placeholder: str;
|
||||
let i: i32 = 0;
|
||||
for (i < nasts) {
|
||||
if (!cstreq(paths[i], path)) { i += 1; continue; };
|
||||
let f: *syntax.node = asts[i];
|
||||
let p: *syntax.node = nil;
|
||||
if (f != nil) { p = f.body; };
|
||||
@@ -379,9 +387,7 @@ fn importpkgname(asts: []*syntax.node, paths: []*u8, nasts: i32, path: str,
|
||||
placeholder = p.pkgname;
|
||||
} else { if (name.len > 0
|
||||
&& !syntax.streq(name, p.pkgname)) {
|
||||
*conflict = true;
|
||||
let empty: str;
|
||||
return empty;
|
||||
return pkgnamestate.PKGNAME_CONFLICT;
|
||||
} else { name = p.pkgname; }; };
|
||||
};
|
||||
p = p.next;
|
||||
@@ -396,45 +402,70 @@ fn importpkgname(asts: []*syntax.node, paths: []*u8, nasts: i32, path: str,
|
||||
if (canonicalpkgname(path, p.pkgname)) {
|
||||
placeholder = p.pkgname;
|
||||
} else { if (name.len > 0 && !syntax.streq(name, p.pkgname)) {
|
||||
*conflict = true;
|
||||
let empty: str;
|
||||
return empty;
|
||||
return pkgnamestate.PKGNAME_CONFLICT;
|
||||
} else { name = p.pkgname; }; };
|
||||
};
|
||||
p = p.next;
|
||||
};
|
||||
if (name.len == 0) { name = placeholder; };
|
||||
return name;
|
||||
*resolved = name;
|
||||
if (name.len == 0) { return pkgnamestate.PKGNAME_MISSING; };
|
||||
if (syntax.streq(name, "_")) { return pkgnamestate.PKGNAME_INVALID; };
|
||||
return pkgnamestate.PKGNAME_VALID;
|
||||
};
|
||||
|
||||
fn pathleaf(path: str) str = {
|
||||
let start: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < path.len) {
|
||||
if (path[i] == '.') { start = i + 1; };
|
||||
i += 1;
|
||||
};
|
||||
let leaf: str;
|
||||
leaf.ptr = path.ptr + (start: u64);
|
||||
leaf.len = path.len - start;
|
||||
return leaf;
|
||||
};
|
||||
|
||||
fn bindimportnames(list: *syntax.node, asts: []*syntax.node, paths: []*u8,
|
||||
nasts: i32,
|
||||
primary: *syntax.node, testsupport: *u8) bool = {
|
||||
primary: *syntax.node, testsupport: *u8,
|
||||
externalsupport: *syntax.node) 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, paths, nasts, u.usepath,
|
||||
primary, &conflict);
|
||||
if (conflict) {
|
||||
let name: str;
|
||||
let state: pkgnamestate = importpkgname(asts, paths, nasts,
|
||||
u.usepath, primary, &name);
|
||||
if (state == pkgnamestate.PKGNAME_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.usepkgname = name;
|
||||
if (u.useblank == 0) {
|
||||
};
|
||||
if (state == pkgnamestate.PKGNAME_VALID
|
||||
|| state == pkgnamestate.PKGNAME_INVALID) {
|
||||
u.usepkgname = name;
|
||||
if (state == pkgnamestate.PKGNAME_INVALID) {
|
||||
u.used = 1;
|
||||
if (u.useblank == 0 && !reserved) {
|
||||
if (u.usealias.len > 0) { u.str = u.usealias; }
|
||||
else { u.str = name; };
|
||||
else { u.str = pathleaf(u.usepath); };
|
||||
};
|
||||
} else { if (u.imported == 0) {
|
||||
} else { if (u.useblank == 0 && !reserved) {
|
||||
if (u.usealias.len > 0) { u.str = u.usealias; }
|
||||
else { u.str = name; };
|
||||
}; };
|
||||
} else { if (u == externalsupport) {
|
||||
// A bare direct -T compiler invocation deliberately leaves the
|
||||
// compiler-generated support hook external.
|
||||
u.used = 1;
|
||||
} 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);
|
||||
@@ -449,14 +480,114 @@ fn bindimportnames(list: *syntax.node, asts: []*syntax.node, paths: []*u8,
|
||||
if (u.usealias.len > 0) { u.str = u.usealias; }
|
||||
else { u.str = u.usepath; };
|
||||
};
|
||||
}; };
|
||||
};
|
||||
}; }; };
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
fn pathsethas(paths: []str, path: str) bool = {
|
||||
if (path.len == 0) { return false; };
|
||||
let i: i32 = 0;
|
||||
for (i < paths.len) {
|
||||
if (syntax.streq(paths[i], path)) { return true; };
|
||||
i += 1;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn pathsetadd(paths: *[]str, path: str) bool = {
|
||||
if (path.len == 0 || pathsethas(*paths, path)) { return false; };
|
||||
append(*paths, path);
|
||||
return true;
|
||||
};
|
||||
|
||||
fn materializetestsupport(file: *syntax.node, testmode: i32,
|
||||
testsupport: *u8) *syntax.node = {
|
||||
if (testmode == 0 || testsupport == nil) { return nil; };
|
||||
let path: str = pathstr(testsupport);
|
||||
let u: *syntax.node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && u.imported == 0
|
||||
&& u.sourceid == file.sourceid
|
||||
&& syntax.streq(u.usepath, path)) {
|
||||
u.used = 1;
|
||||
return nil;
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
u = syntax.newnode(syntax.nkind.N_USE, file.file, file.line, file.col);
|
||||
u.str = path;
|
||||
u.usesource = path;
|
||||
u.usepath = path;
|
||||
u.usefile = file.file;
|
||||
u.useline = file.line;
|
||||
u.usecol = file.col;
|
||||
u.usepathfile = file.file;
|
||||
u.usepathline = file.line;
|
||||
u.usepathcol = file.col;
|
||||
u.pkgname = file.pkgname;
|
||||
u.sourceid = file.sourceid;
|
||||
u.used = 1;
|
||||
u.next = file.list;
|
||||
file.list = u;
|
||||
return u;
|
||||
};
|
||||
|
||||
fn computereachedimports(reached: *[]str, primary: *syntax.node,
|
||||
asts: []*syntax.node, paths: []*u8, nasts: i32) void = {
|
||||
let u: *syntax.node = nil;
|
||||
if (primary != nil) { u = primary.list; };
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && u.imported == 0) {
|
||||
pathsetadd(reached, u.usepath);
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
let changed: bool = true;
|
||||
for (changed) {
|
||||
changed = false;
|
||||
let i: i32 = 0;
|
||||
for (i < nasts) {
|
||||
u = nil;
|
||||
if (asts[i] != nil) { u = asts[i].list; };
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && u.imported != 0
|
||||
&& u.nmod.len > 0 && u.usepath.len > 0
|
||||
&& pathsethas(*reached, u.nmod)) {
|
||||
let name: str;
|
||||
if (importpkgname(asts, paths, nasts, u.nmod, nil,
|
||||
&name) == pkgnamestate.PKGNAME_VALID
|
||||
&& pathsetadd(reached, u.usepath)) {
|
||||
changed = true;
|
||||
};
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fn filterreachedimports(file: *syntax.node, reached: []str,
|
||||
asts: []*syntax.node, paths: []*u8, nasts: i32) void = {
|
||||
let prev: *syntax.node = nil;
|
||||
let d: *syntax.node = file.list;
|
||||
for (d != nil) {
|
||||
let next: *syntax.node = d.next;
|
||||
let name: str;
|
||||
let keep: bool = d.imported != 0 && d.nmod.len > 0
|
||||
&& pathsethas(reached, d.nmod)
|
||||
&& importpkgname(asts, paths, nasts, d.nmod, nil,
|
||||
&name) == pkgnamestate.PKGNAME_VALID;
|
||||
if (keep) { prev = d; }
|
||||
else { if (prev == nil) { file.list = next; }
|
||||
else { prev.next = next; }; };
|
||||
d = next;
|
||||
};
|
||||
};
|
||||
|
||||
export fn main(argc: i32, argv: **u8) i32 = {
|
||||
let src: *u8 = nil;
|
||||
let out: *u8 = nil;
|
||||
@@ -758,8 +889,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
targeti += 1;
|
||||
};
|
||||
|
||||
let importhead: *syntax.node = nil;
|
||||
let importtail: *syntax.node = nil;
|
||||
importi = 0;
|
||||
for (importi < nimports) {
|
||||
let ibuf: *u8;
|
||||
@@ -800,15 +929,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
let imported: *syntax.node = syntax.parsefile(&ips);
|
||||
if (il.errs > 0 || ips.errs > 0) { return 1; };
|
||||
importasts[importi] = imported;
|
||||
if (!bindimportnames(imported.list, importasts, importpaths, importi + 1,
|
||||
nil, testsupport)) { return 1; };
|
||||
let d: *syntax.node = imported.list;
|
||||
if (d != nil) {
|
||||
if (importhead == nil) { importhead = d; }
|
||||
else { importtail.next = d; };
|
||||
for (d.next != nil) { d = d.next; };
|
||||
importtail = d;
|
||||
};
|
||||
importi += 1;
|
||||
};
|
||||
|
||||
@@ -860,17 +980,34 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
};
|
||||
mapi += 1;
|
||||
};
|
||||
// Test compilation has a compiler-required support edge even when no
|
||||
// equivalent source import occurrence exists. Make that primary root
|
||||
// explicit before resolving interface metadata.
|
||||
let externalsupport: *syntax.node = materializetestsupport(f, testmode,
|
||||
testsupport);
|
||||
// Standalone interfaces are metadata containers, never graph roots. Reach
|
||||
// only from primary/compiler-required uses and traverse uses owned by an
|
||||
// already-reached package whose declared name is valid.
|
||||
let reached: []str = alloc([], 16u64)!;
|
||||
computereachedimports(&reached, f, importasts, importpaths, nimports);
|
||||
importi = 0;
|
||||
for (importi < nimports) {
|
||||
filterreachedimports(importasts[importi], reached, importasts,
|
||||
importpaths, nimports);
|
||||
importi += 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.
|
||||
// by an earlier interface. Bind each now-bounded reached list against the
|
||||
// complete metadata set, before any concatenation can widen that list.
|
||||
importi = 0;
|
||||
for (importi < nimports) {
|
||||
if (!bindimportnames(importasts[importi].list, importasts, importpaths,
|
||||
nimports,
|
||||
nil, testsupport)) { return 1; };
|
||||
nil, testsupport, nil)) { return 1; };
|
||||
importi += 1;
|
||||
};
|
||||
if (!bindimportnames(f.list, importasts, importpaths, nimports, f,
|
||||
testsupport)) {
|
||||
testsupport, externalsupport)) {
|
||||
return 1;
|
||||
};
|
||||
targeti = 0;
|
||||
@@ -881,7 +1018,12 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
if (targetuse.kind == syntax.nkind.N_USE
|
||||
&& targetuse.imported == 0
|
||||
&& syntax.streq(targetuse.usepath, testtargets[targeti])) {
|
||||
targetuse.str = targetuse.usepath;
|
||||
// Valid generated targets use their canonical path as the
|
||||
// compiler-owned qualifier. Preserve an invalid provider's fake
|
||||
// explicit-or-leaf spelling for source-local recovery.
|
||||
if (!syntax.streq(targetuse.usepkgname, "_")) {
|
||||
targetuse.str = targetuse.usepath;
|
||||
};
|
||||
seen += 1;
|
||||
};
|
||||
targetuse = targetuse.next;
|
||||
@@ -893,6 +1035,19 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
};
|
||||
targeti += 1;
|
||||
};
|
||||
let importhead: *syntax.node = nil;
|
||||
let importtail: *syntax.node = nil;
|
||||
importi = 0;
|
||||
for (importi < nimports) {
|
||||
let d: *syntax.node = importasts[importi].list;
|
||||
if (d != nil) {
|
||||
if (importhead == nil) { importhead = d; }
|
||||
else { importtail.next = d; };
|
||||
for (d.next != nil) { d = d.next; };
|
||||
importtail = d;
|
||||
};
|
||||
importi += 1;
|
||||
};
|
||||
if (importhead != nil) {
|
||||
importtail.next = f.list;
|
||||
f.list = importhead;
|
||||
|
||||
@@ -179,19 +179,18 @@ fn invalidinitimport(u: *syntax.node) bool = {
|
||||
&& syntax.streq(u.str, "init");
|
||||
};
|
||||
|
||||
// Map a source-file qualifier to canonical identity. Looking up the marker for
|
||||
// a possible DOT must not itself count as usage; only qualified resolution
|
||||
// Map a source-file qualifier to its exact retained import occurrence. Looking
|
||||
// up a possible DOT must not itself count as usage; only qualified resolution
|
||||
// marks the owning occurrence.
|
||||
fn findusepath(file: *syntax.node, modtag: str, source: i32, alias: str,
|
||||
mark: bool) str = {
|
||||
let empty: str;
|
||||
if (file == nil) { return empty; };
|
||||
if (alias.len == 0) { return empty; };
|
||||
fn findusebinding(file: *syntax.node, modtag: str, source: i32, alias: str,
|
||||
mark: bool) *syntax.node = {
|
||||
if (file == nil) { return nil; };
|
||||
if (alias.len == 0) { return nil; };
|
||||
if (source == 0 && modtag.len != 0) {
|
||||
let (prefix, suffix) = strings.rcut(modtag, ".");
|
||||
let leaf: str = suffix;
|
||||
if (leaf.len == 0) { leaf = modtag; };
|
||||
if (syntax.streq(alias, leaf)) { return modtag; };
|
||||
if (syntax.streq(alias, leaf)) { return nil; };
|
||||
};
|
||||
let u: *syntax.node = file.list;
|
||||
for (u != nil) {
|
||||
@@ -204,15 +203,33 @@ fn findusepath(file: *syntax.node, modtag: str, source: i32, alias: str,
|
||||
if (modtag.len == 0) {
|
||||
if (um.len == 0) { same = true; };
|
||||
} else { if (syntax.streq(um, modtag)) { same = true; }; };
|
||||
if (same) {
|
||||
if (mark) { u.used = 1; };
|
||||
if (u.usepath.len != 0) { return u.usepath; };
|
||||
return u.str;
|
||||
if (same) {
|
||||
if (mark) { u.used = 1; };
|
||||
return u;
|
||||
};
|
||||
};
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
fn findusepath(file: *syntax.node, modtag: str, source: i32, alias: str,
|
||||
mark: bool) str = {
|
||||
let empty: str;
|
||||
if (file == nil) { return empty; };
|
||||
if (alias.len == 0) { return empty; };
|
||||
if (source == 0 && modtag.len != 0) {
|
||||
let (prefix, suffix) = strings.rcut(modtag, ".");
|
||||
let leaf: str = suffix;
|
||||
if (leaf.len == 0) { leaf = modtag; };
|
||||
if (syntax.streq(alias, leaf)) { return modtag; };
|
||||
};
|
||||
let u: *syntax.node = findusebinding(file, modtag, source, alias, mark);
|
||||
if (u != nil) {
|
||||
if (u.usepath.len != 0) { return u.usepath; };
|
||||
return u.str;
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
|
||||
@@ -298,6 +315,13 @@ fn localshadowsimport(c: *checker, name: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
fn fakeimportalias(c: *checker, alias: str) bool = {
|
||||
if (localshadowsimport(c, alias)) { return false; };
|
||||
let u: *syntax.node = findusebinding(c.file, c.curmod, c.cursource,
|
||||
alias, true);
|
||||
return u != nil && syntax.streq(u.usepkgname, "_");
|
||||
};
|
||||
|
||||
fn lookupvisible(c: *checker, name: str) *syntax.sym = {
|
||||
let found: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, name);
|
||||
let builtin: *syntax.sym = nil;
|
||||
@@ -726,6 +750,14 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
c.nresolved += 1;
|
||||
return;
|
||||
};
|
||||
if (strings.contains(nm, ".")) {
|
||||
let (head, leaf) = strings.rcut(nm, ".");
|
||||
if (fakeimportalias(c, head)) {
|
||||
n.type_ = c.tc.tyerr: *void;
|
||||
c.nresolved += 1;
|
||||
return;
|
||||
};
|
||||
};
|
||||
let s: *syntax.sym = lookupvisibletype(c, nm);
|
||||
let builtin: bool = builtintypename(nm);
|
||||
// `pkg.Type` — strip the last dot prefix and look up
|
||||
@@ -2860,6 +2892,13 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
|
||||
syntax.tinfocachebind(c.tc, n, c.tc.tyerr);
|
||||
return c.tc.tyerr;
|
||||
};
|
||||
if (n.file.len != 0 && strings.contains(nm, ".")) {
|
||||
let (head, leaf) = strings.rcut(nm, ".");
|
||||
if (fakeimportalias(c, head)) {
|
||||
syntax.tinfocachebind(c.tc, n, c.tc.tyerr);
|
||||
return c.tc.tyerr;
|
||||
};
|
||||
};
|
||||
if (syntax.streq(nm, "void")) { r = c.tc.tyvoid; };
|
||||
if (syntax.streq(nm, "bool")) { r = c.tc.tybool; };
|
||||
if (syntax.streq(nm, "rune")) { r = c.tc.tyrune; };
|
||||
@@ -4217,6 +4256,14 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
if (callee.kind == syntax.nkind.N_DOT && callee.lhs != nil
|
||||
&& callee.lhs.kind == syntax.nkind.N_IDENT
|
||||
&& fakeimportalias(c, callee.lhs.str)) {
|
||||
callee.lhs.type_ = c.tc.tyerr: *void;
|
||||
callee.type_ = c.tc.tyerr: *void;
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
// A module-qualified leaf may already have been rejected while the
|
||||
// N_DOT callee was checked on an earlier resolve walk. cstage caches
|
||||
// that failure on the call; mirror its once-only diagnostic here rather
|
||||
@@ -4738,6 +4785,12 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// harec's enum-resolve constexpr set at
|
||||
// ref/harec/src/check.c:4419-4434.
|
||||
let lhsn: *syntax.node = e.lhs;
|
||||
if (lhsn != nil && lhsn.kind == syntax.nkind.N_IDENT
|
||||
&& fakeimportalias(c, lhsn.str)) {
|
||||
lhsn.type_ = c.tc.tyerr: *void;
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
if (lhsn != nil) { if (lhsn.kind == syntax.nkind.N_IDENT) {
|
||||
let ms: *syntax.sym = lookupvisible(c, lhsn.str);
|
||||
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
|
||||
@@ -7446,6 +7499,14 @@ fn exprtypeoftry(c: *checker, e: *syntax.node) *syntax.node = {
|
||||
// nkind.N_DOT). We need the fn-decl's lhs (return-type AST).
|
||||
let callee: *syntax.node = e.lhs;
|
||||
if (callee == nil) { return nil; };
|
||||
if (callee.kind == syntax.nkind.N_DOT && callee.lhs != nil
|
||||
&& callee.lhs.kind == syntax.nkind.N_IDENT
|
||||
&& fakeimportalias(c, callee.lhs.str)) {
|
||||
callee.lhs.type_ = c.tc.tyerr: *void;
|
||||
callee.type_ = c.tc.tyerr: *void;
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (callee.kind == syntax.nkind.N_IDENT) { nm = callee.str; };
|
||||
@@ -7837,6 +7898,20 @@ fn importbindingdiagprefix(n: *syntax.node) void = {
|
||||
cerr(strconv.i32tos(col, strconv.base.DEC)); cerr(": error: ");
|
||||
};
|
||||
|
||||
fn importpathdiagprefix(n: *syntax.node) void = {
|
||||
let file: str = n.usepathfile;
|
||||
let line: i32 = n.usepathline;
|
||||
let col: i32 = n.usepathcol;
|
||||
if (file.len == 0) {
|
||||
file = n.file;
|
||||
line = n.line;
|
||||
col = n.col;
|
||||
};
|
||||
cerr(file); cerr(":");
|
||||
cerr(strconv.i32tos(line, strconv.base.DEC)); cerr(":");
|
||||
cerr(strconv.i32tos(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(":");
|
||||
@@ -7860,6 +7935,35 @@ fn rejectinitimports(c: *checker, file: *syntax.node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
fn rejectinvalidimports(c: *checker, file: *syntax.node) void = {
|
||||
let u: *syntax.node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE
|
||||
&& u.usepath.len != 0
|
||||
&& syntax.streq(u.usepkgname, "_")) {
|
||||
u.used = 1;
|
||||
let duplicate: bool = false;
|
||||
let p: *syntax.node = file.list;
|
||||
for (p != u) {
|
||||
if (p.kind == syntax.nkind.N_USE && p.usepath.len != 0
|
||||
&& syntax.streq(p.usepkgname, "_")
|
||||
&& syntax.streq(p.usepath, u.usepath)) {
|
||||
duplicate = true;
|
||||
break;
|
||||
};
|
||||
p = p.next;
|
||||
};
|
||||
if (!duplicate) {
|
||||
importpathdiagprefix(u);
|
||||
cerr("could not import "); cerr(u.usepath);
|
||||
cerr(" (invalid package name: \"_\")\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
};
|
||||
|
||||
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
|
||||
@@ -9051,10 +9155,25 @@ fn checktesttarget(c: *checker, path: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
fn rejectblankpackagenames(c: *checker, file: *syntax.node) void = {
|
||||
let p: *syntax.node = file.body;
|
||||
for (p != nil) {
|
||||
if (p.kind == syntax.nkind.N_FILE
|
||||
&& syntax.streq(p.pkgname, "_")) {
|
||||
importdiagprefix(p);
|
||||
cerr("invalid package name _\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
p = p.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn checkfile(c: *checker, file: *syntax.node) void = {
|
||||
if (file == nil) { return; };
|
||||
if (file.kind != syntax.nkind.N_FILE) { return; };
|
||||
c.file = file;
|
||||
rejectblankpackagenames(c, file);
|
||||
rejectinvalidimports(c, file);
|
||||
|
||||
// Under -T, prepend the dispatcher support import before Pass 1 so
|
||||
// declmod keys the runner under the selected support module. This is a
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,6 +63,167 @@ check_module_stamps(const char *src, const char *want_first, const char *want_la
|
||||
return ok;
|
||||
}
|
||||
|
||||
enum packagemode {
|
||||
PACKAGE_FULL,
|
||||
PACKAGE_IMPORTS,
|
||||
PACKAGE_HEADER,
|
||||
};
|
||||
|
||||
static const char *
|
||||
packagemodename(enum packagemode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case PACKAGE_FULL: return "full";
|
||||
case PACKAGE_IMPORTS: return "imports";
|
||||
case PACKAGE_HEADER: return "header";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static Node *
|
||||
parsepackage(Parser *p, enum packagemode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case PACKAGE_FULL: return parsefile(p);
|
||||
case PACKAGE_IMPORTS: return parseimports(p);
|
||||
case PACKAGE_HEADER: return parsepackageheader(p);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The three package-clause parsers intentionally expose different AST
|
||||
* boundaries. Header/imports outer nodes stay on the package keyword; the
|
||||
* full parser's outer file node retains its historical 1:1 root position.
|
||||
* Imports-only parsing keeps its package marker on the keyword, while full
|
||||
* parsing moves only a blank package marker to the underscore so the checker
|
||||
* can diagnose the name token. An ordinary package marker never moves.
|
||||
*/
|
||||
static int
|
||||
check_package_shape(enum packagemode mode, const char *name,
|
||||
int markerline, int markercol)
|
||||
{
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
char src[160];
|
||||
snprintf(src, sizeof src,
|
||||
"// leading comment\npackage %s;\nimport alpha;\n"
|
||||
"fn x() void = {};\n", name);
|
||||
lexinit(&l, a, "shape.ww", src, strlen(src));
|
||||
parserinit(&p, a, &l);
|
||||
Node *file = parsepackage(&p, mode);
|
||||
Node *marker = file ? file->body : NULL;
|
||||
int wantmarker = mode != PACKAGE_HEADER;
|
||||
int outerline = mode == PACKAGE_FULL ? 1 : 2;
|
||||
int ok = file != NULL && file->kind == N_FILE
|
||||
&& p.errs == 0 && l.errs == 0
|
||||
&& file->pkgname != NULL && strcmp(file->pkgname, name) == 0
|
||||
&& file->pos.line == outerline && file->pos.col == 1
|
||||
&& ((mode == PACKAGE_FULL)
|
||||
|| (file->module != NULL && strcmp(file->module, name) == 0))
|
||||
&& ((!wantmarker && marker == NULL)
|
||||
|| (wantmarker && marker != NULL && marker->next == NULL
|
||||
&& marker->kind == N_FILE
|
||||
&& marker->pkgname != NULL
|
||||
&& strcmp(marker->pkgname, name) == 0
|
||||
&& marker->pos.line == markerline
|
||||
&& marker->pos.col == markercol));
|
||||
if (!ok) {
|
||||
fprintf(stderr,
|
||||
"package shape mismatch: mode=%s name=%s "
|
||||
"errs=%d/%d outer=%d:%d marker=%d:%d\n",
|
||||
packagemodename(mode), name, p.errs, l.errs,
|
||||
file ? file->pos.line : 0, file ? file->pos.col : 0,
|
||||
marker ? marker->pos.line : 0, marker ? marker->pos.col : 0);
|
||||
}
|
||||
freearena(a);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
rejects_malformed_package(enum packagemode mode)
|
||||
{
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
const char *src = "package ;\nfn x() void = {};\n";
|
||||
char *diag = NULL;
|
||||
size_t diaglen = 0;
|
||||
FILE *prev = errout;
|
||||
FILE *capture = open_memstream(&diag, &diaglen);
|
||||
if (capture != NULL)
|
||||
errout = capture;
|
||||
lexinit(&l, a, "malformed.ww", src, strlen(src));
|
||||
parserinit(&p, a, &l);
|
||||
Node *file = parsepackage(&p, mode);
|
||||
int ok = file != NULL && p.errs + l.errs > 0;
|
||||
if (capture != NULL) {
|
||||
fclose(capture);
|
||||
errout = prev;
|
||||
}
|
||||
if (!ok)
|
||||
fprintf(stderr, "malformed package accepted in %s mode\n",
|
||||
packagemodename(mode));
|
||||
free(diag);
|
||||
freearena(a);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*
|
||||
* N_USE keeps the first import-spec token (alias when present) separately
|
||||
* from the first dotted-path token. That distinction is checker-visible for
|
||||
* import errors, so pin it in each parser boundary rather than inferring the
|
||||
* path position later from the binding spelling.
|
||||
*/
|
||||
static int
|
||||
check_import_path_position(enum packagemode mode, const char *spec,
|
||||
int speccol, int pathcol, const char *alias, const char *binding,
|
||||
int blank)
|
||||
{
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
char src[192];
|
||||
snprintf(src, sizeof src,
|
||||
"package foo;\nimport %s;\nfn x() void = {};\n", spec);
|
||||
lexinit(&l, a, "usepath.ww", src, strlen(src));
|
||||
parserinit(&p, a, &l);
|
||||
Node *file = parsepackage(&p, mode);
|
||||
Node *u = file ? file->list : NULL;
|
||||
while (u != NULL && u->kind != N_USE)
|
||||
u = u->next;
|
||||
int ok = file != NULL && p.errs == 0 && l.errs == 0
|
||||
&& u != NULL
|
||||
&& u->pos.file != NULL && strcmp(u->pos.file, "usepath.ww") == 0
|
||||
&& u->pos.line == 2 && u->pos.col == 1
|
||||
&& u->usefile != NULL && strcmp(u->usefile, "usepath.ww") == 0
|
||||
&& u->useline == 2 && u->usecol == speccol
|
||||
&& u->usepathfile != NULL
|
||||
&& strcmp(u->usepathfile, "usepath.ww") == 0
|
||||
&& u->usepathline == 2 && u->usepathcol == pathcol
|
||||
&& u->usesource != NULL
|
||||
&& strcmp(u->usesource, "alpha.beta") == 0
|
||||
&& u->usepath != NULL && strcmp(u->usepath, "alpha.beta") == 0
|
||||
&& ((alias == NULL && u->usealias == NULL)
|
||||
|| (alias != NULL && u->usealias != NULL
|
||||
&& strcmp(u->usealias, alias) == 0))
|
||||
&& ((binding == NULL && u->str == NULL)
|
||||
|| (binding != NULL && u->str != NULL
|
||||
&& strcmp(u->str, binding) == 0))
|
||||
&& u->useblank == blank;
|
||||
if (!ok) {
|
||||
fprintf(stderr,
|
||||
"import path position mismatch: mode=%s spec=%s "
|
||||
"errs=%d/%d specpos=%d:%d pathpos=%d:%d\n",
|
||||
packagemodename(mode), spec, p.errs, l.errs,
|
||||
u ? u->useline : 0, u ? u->usecol : 0,
|
||||
u ? u->usepathline : 0, u ? u->usepathcol : 0);
|
||||
}
|
||||
freearena(a);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
@@ -114,6 +275,69 @@ main(void)
|
||||
else { fprintf(stderr, "738[6] dotted import leaf-store FAILED\n"); fail++; }
|
||||
}
|
||||
|
||||
/* `package _;` is syntax in every loader/compiler parser mode. */
|
||||
if (check_package_shape(PACKAGE_HEADER, "_", 0, 0)) pass++;
|
||||
else { fprintf(stderr, "738[7] blank package header shape FAILED\n"); fail++; }
|
||||
|
||||
if (check_package_shape(PACKAGE_IMPORTS, "_", 2, 1)) pass++;
|
||||
else { fprintf(stderr, "738[8] blank imports-only shape FAILED\n"); fail++; }
|
||||
|
||||
if (check_package_shape(PACKAGE_FULL, "_", 2, 9)) pass++;
|
||||
else { fprintf(stderr, "738[9] blank full-parser shape FAILED\n"); fail++; }
|
||||
|
||||
/* Nonblank package-marker positions remain on the package keyword. */
|
||||
if (check_package_shape(PACKAGE_HEADER, "foo", 0, 0)) pass++;
|
||||
else { fprintf(stderr, "738[10] named package header shape FAILED\n"); fail++; }
|
||||
|
||||
if (check_package_shape(PACKAGE_IMPORTS, "foo", 2, 1)) pass++;
|
||||
else { fprintf(stderr, "738[11] named imports-only shape FAILED\n"); fail++; }
|
||||
|
||||
if (check_package_shape(PACKAGE_FULL, "foo", 2, 1)) pass++;
|
||||
else { fprintf(stderr, "738[12] named full-parser shape FAILED\n"); fail++; }
|
||||
|
||||
/* Extending the name slot to `_` must not accept a missing name. */
|
||||
if (rejects_malformed_package(PACKAGE_HEADER)) pass++;
|
||||
else { fprintf(stderr, "738[13] malformed package header FAILED\n"); fail++; }
|
||||
|
||||
if (rejects_malformed_package(PACKAGE_IMPORTS)) pass++;
|
||||
else { fprintf(stderr, "738[14] malformed imports-only package FAILED\n"); fail++; }
|
||||
|
||||
if (rejects_malformed_package(PACKAGE_FULL)) pass++;
|
||||
else { fprintf(stderr, "738[15] malformed full-parser package FAILED\n"); fail++; }
|
||||
|
||||
/* Default imports use the first dotted-path identifier for both slots. */
|
||||
for (enum packagemode mode = PACKAGE_FULL; mode <= PACKAGE_HEADER; mode++) {
|
||||
if (check_import_path_position(mode, "alpha.beta", 8, 8,
|
||||
NULL, "beta", 0)) pass++;
|
||||
else {
|
||||
fprintf(stderr, "738 default import path position FAILED (%s)\n",
|
||||
packagemodename(mode));
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Explicit aliases keep the binding/spec token at col 8, path at 14. */
|
||||
for (enum packagemode mode = PACKAGE_FULL; mode <= PACKAGE_HEADER; mode++) {
|
||||
if (check_import_path_position(mode, "local alpha.beta", 8, 14,
|
||||
"local", "local", 0)) pass++;
|
||||
else {
|
||||
fprintf(stderr, "738 explicit import path position FAILED (%s)\n",
|
||||
packagemodename(mode));
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* A blank alias likewise keeps `_` at col 8 and the path at col 10. */
|
||||
for (enum packagemode mode = PACKAGE_FULL; mode <= PACKAGE_HEADER; mode++) {
|
||||
if (check_import_path_position(mode, "_ alpha.beta", 8, 10,
|
||||
NULL, NULL, 1)) pass++;
|
||||
else {
|
||||
fprintf(stderr, "738 blank import path position FAILED (%s)\n",
|
||||
packagemodename(mode));
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
printf("738_module_decl: %d pass, %d fail\n", pass, fail);
|
||||
return fail == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user