compiler: separate package exports from entry roots
This commit is contained in:
109
cmd/wcc/check.c
109
cmd/wcc/check.c
@@ -91,9 +91,14 @@ resolve_typename(Checker *c, Node *n)
|
||||
* import path (symbols are path-keyed). */
|
||||
const char *mk = use_path(c->file, c->cur_mod,
|
||||
head);
|
||||
if (mk != NULL)
|
||||
s = scope_lookup_in_module(c->cur, mk,
|
||||
dot + 1);
|
||||
if (mk != NULL)
|
||||
s = scope_lookup_in_module(c->cur, mk,
|
||||
dot + 1);
|
||||
if (s && s->decl && s->decl->imported
|
||||
&& !s->decl->export)
|
||||
return err(c, n->pos,
|
||||
"package '%s' has no exported declaration '%s'",
|
||||
head, dot + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1480,6 +1485,11 @@ cexpr(Checker *c, Node *n)
|
||||
} else {
|
||||
Sym *fs = scope_lookup_in_module(c->cur,
|
||||
mk, n->str);
|
||||
if (fs && fs->decl && fs->decl->imported
|
||||
&& !fs->decl->export && !n->imported)
|
||||
return n->type = err(c, n->pos,
|
||||
"package '%s' has no exported declaration '%s'",
|
||||
n->lhs->str, n->str);
|
||||
if (fs)
|
||||
return n->type = fs->type;
|
||||
/* A bare `w6c -T` intentionally leaves the
|
||||
@@ -2843,6 +2853,7 @@ check_init(Checker *c, Arena *a)
|
||||
memset(c, 0, sizeof *c);
|
||||
c->a = a;
|
||||
c->is_test = 0; /* #15: caller (w6c main) sets it after init */
|
||||
c->is_test_package = 0;
|
||||
c->test_module = "test";
|
||||
typesinit(a);
|
||||
c->top = newscope(a, NULL);
|
||||
@@ -3103,14 +3114,19 @@ check_module_shadow(Checker *c, const char *name, Pos pos,
|
||||
static Sym *
|
||||
same_import_fact(Checker *c, Node *d, const char *mod, Skind kind)
|
||||
{
|
||||
if (!c->sep_mode || d == NULL || !d->imported || !d->export
|
||||
if (!c->sep_mode || d == NULL || !d->imported
|
||||
|| mod == NULL || mod[0] == '\0')
|
||||
return NULL;
|
||||
if (kind != SK_TYPE && kind != SK_DEF)
|
||||
return NULL;
|
||||
/* Compiler-private nominal facts may recur through a self-contained
|
||||
* export diamond. Private defs are never valid closure facts. */
|
||||
if (!d->export && kind != SK_TYPE)
|
||||
return NULL;
|
||||
Sym *s = scope_lookup_in_module(c->cur, mod, d->str);
|
||||
if (s == NULL || s->kind != kind || s->decl == NULL || s->decl == d
|
||||
|| !s->decl->imported || !s->decl->export)
|
||||
|| !s->decl->imported
|
||||
|| (!s->decl->export && kind != SK_TYPE))
|
||||
return NULL;
|
||||
return s;
|
||||
}
|
||||
@@ -3130,12 +3146,21 @@ check_file(Checker *c, Node *file)
|
||||
* (the //ww:module directive, mod_collect), NOT this scope keying — cgen
|
||||
* already emits the qualified call. wwstage twin in check.ww. */
|
||||
if (c->is_test) {
|
||||
Node *usenode = newnode(c->a, N_USE, file->pos);
|
||||
usenode->str = c->test_module;
|
||||
usenode->strlen = strlen(c->test_module);
|
||||
usenode->usepath = c->test_module;
|
||||
usenode->next = file->list;
|
||||
file->list = usenode;
|
||||
int present = 0;
|
||||
for (Node *u = file->list; u; u = u->next)
|
||||
if (u->kind == N_USE && !u->imported
|
||||
&& u->usepath && strcmp(u->usepath, c->test_module) == 0) {
|
||||
present = 1;
|
||||
break;
|
||||
}
|
||||
if (!present) {
|
||||
Node *usenode = newnode(c->a, N_USE, file->pos);
|
||||
usenode->str = c->test_module;
|
||||
usenode->strlen = strlen(c->test_module);
|
||||
usenode->usepath = c->test_module;
|
||||
usenode->next = file->list;
|
||||
file->list = usenode;
|
||||
}
|
||||
}
|
||||
|
||||
/* pass 1: install names (types first, then defs/fns).
|
||||
@@ -3391,24 +3416,23 @@ check_file(Checker *c, Node *file)
|
||||
* synth runs post-pass-1, so lib/test's `run` sits in the same flat ""
|
||||
* bucket as the @test fns — bare, like the @test calls themselves).
|
||||
*/
|
||||
if (c->is_test) {
|
||||
if (c->is_test || c->is_test_package) {
|
||||
Pos fp = file->pos;
|
||||
/* (b) the synth entry OWNS `main` — loud-reject a user one. */
|
||||
for (Node *d = file->list; d; d = d->next)
|
||||
if (d->kind == N_FNDECL && d->str
|
||||
&& strcmp(d->str, "main") == 0 && d->body != NULL)
|
||||
err(c, d->pos, "test mode: main is synthesized "
|
||||
"by -T; remove the explicit main");
|
||||
/* #24(b): the synth table OWNS `__wwtests` — loud-reject a user
|
||||
* decl of that name (twin of the `main` reservation above). A user
|
||||
* __wwtests whose type happens to match run()'s [](str,*fn()void)
|
||||
* param slips the general call-arg check but still silently shadows
|
||||
* the synth table; reserve the NAME so the collision is loud
|
||||
* regardless of type. Any decl kind. */
|
||||
for (Node *d = file->list; d; d = d->next)
|
||||
if (d->str && strcmp(d->str, "__wwtests") == 0)
|
||||
err(c, d->pos, "test mode: __wwtests is reserved "
|
||||
"by -T; rename the declaration");
|
||||
if (c->is_test) {
|
||||
/* (b) the synth entry OWNS `main` — loud-reject a user one. */
|
||||
for (Node *d = file->list; d; d = d->next)
|
||||
if (!d->imported && d->kind == N_FNDECL && d->str
|
||||
&& strcmp(d->str, "main") == 0 && d->body != NULL)
|
||||
err(c, d->pos, "test mode: main is synthesized "
|
||||
"by -T; remove the explicit main");
|
||||
/* The generated table name is reserved only in the owning source,
|
||||
* never by an unrelated declaration carried in dependency exports. */
|
||||
for (Node *d = file->list; d; d = d->next)
|
||||
if (!d->imported && d->str
|
||||
&& strcmp(d->str, "__wwtests") == 0)
|
||||
err(c, d->pos, "test mode: __wwtests is reserved "
|
||||
"by -T; rename the declaration");
|
||||
}
|
||||
/* (c) collect @test fns in file->list order; build one table row
|
||||
* `("<name>", &<name>)` per validated @test fn. */
|
||||
Node *rhead = NULL, *rtail = NULL;
|
||||
@@ -3436,7 +3460,7 @@ check_file(Checker *c, Node *file)
|
||||
d->str);
|
||||
continue;
|
||||
}
|
||||
if (d->body == NULL) {
|
||||
if (d->body == NULL && !d->imported) {
|
||||
err(c, d->pos, "@test fn '%s' needs a body",
|
||||
d->str);
|
||||
continue;
|
||||
@@ -3452,11 +3476,30 @@ check_file(Checker *c, Node *file)
|
||||
d->str);
|
||||
continue;
|
||||
}
|
||||
/* A package-test variant validates and retains the body. Its
|
||||
* interface records this declaration as compiler-private metadata;
|
||||
* only a separate -T generated-main action consumes that metadata. */
|
||||
if (!c->is_test)
|
||||
continue;
|
||||
Node *nm = newnode(c->a, N_STRLIT, fp);
|
||||
nm->str = d->str;
|
||||
nm->strlen = strlen(d->str);
|
||||
Node *id = newnode(c->a, N_IDENT, fp);
|
||||
id->str = d->str;
|
||||
Node *id;
|
||||
if (d->imported && d->module && d->module[0]) {
|
||||
const char *dotp = strrchr(d->module, '.');
|
||||
const char *alias = dotp ? dotp + 1 : d->module;
|
||||
id = newnode(c->a, N_DOT, fp);
|
||||
id->lhs = newnode(c->a, N_IDENT, fp);
|
||||
id->lhs->str = alias;
|
||||
id->str = d->str;
|
||||
/* Nested nodes never acquire imported from parsing. This marks
|
||||
* the compiler-generated private metadata reference so ordinary
|
||||
* source qualification remains export-checked. */
|
||||
id->imported = 1;
|
||||
} else {
|
||||
id = newnode(c->a, N_IDENT, fp);
|
||||
id->str = d->str;
|
||||
}
|
||||
Node *amp = newnode(c->a, N_UN, fp);
|
||||
amp->op = TK_AMP;
|
||||
amp->lhs = id;
|
||||
@@ -3469,6 +3512,7 @@ check_file(Checker *c, Node *file)
|
||||
ntest++;
|
||||
}
|
||||
|
||||
if (c->is_test) {
|
||||
Node *body = newnode(c->a, N_BLOCK, fp);
|
||||
Node *tab = NULL;
|
||||
if (ntest == 0) {
|
||||
@@ -3552,6 +3596,7 @@ check_file(Checker *c, Node *file)
|
||||
if (tab) { tl->next = tab; tab->next = m; }
|
||||
else tl->next = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* pass 2: check def initialisers and fn bodies */
|
||||
@@ -3754,7 +3799,7 @@ check_file(Checker *c, Node *file)
|
||||
* synth main calls the @test fns, so they must remain. Prereq for
|
||||
* in-package @test colocation (#9). Twin: selfhost/cmd/wcc/check.ww.
|
||||
*/
|
||||
if (!c->is_test) {
|
||||
if (!c->is_test && !c->is_test_package) {
|
||||
Node *prev = NULL;
|
||||
for (Node *d = file->list; d; ) {
|
||||
int istest = 0;
|
||||
|
||||
@@ -589,7 +589,10 @@ struct Checker {
|
||||
int matcharms; /* nesting count for yield */
|
||||
int errs;
|
||||
int is_test; /* #15: `w6c -T` — collect @test fns + synth
|
||||
* the entry; loud-reject a user main. */
|
||||
* the entry; loud-reject a user main. */
|
||||
int is_test_package; /* package-test variant: validate/retain @test
|
||||
* bodies and export compiler-private metadata,
|
||||
* but do not synthesize an entry. */
|
||||
const char *test_module; /* generated dispatcher support qualifier */
|
||||
int sep_mode; /* -c package compilation: imported interfaces are
|
||||
* present, so absent members are hard export errors. */
|
||||
|
||||
Reference in New Issue
Block a user