compiler: make package exports self-contained
This commit is contained in:
@@ -1172,6 +1172,16 @@ ffi_resolve(const char *ident)
|
||||
return ident;
|
||||
}
|
||||
|
||||
/* Package compilation must not acquire a hidden source dependency on a
|
||||
* transitive `rt` interface merely so the compiler intrinsic `alloc` learns
|
||||
* its linker spelling. The WW runtime ABI owns this name in -c mode. Keep
|
||||
* raw w6c's declaration-driven behavior for compatibility and FFI tests. */
|
||||
static const char *
|
||||
alloc_resolve(Cg *c)
|
||||
{
|
||||
return c->sep_mode ? "rt_malloc" : ffi_resolve("malloc");
|
||||
}
|
||||
|
||||
static void
|
||||
ffi_collect(Cg *c, Node *file)
|
||||
{
|
||||
@@ -9199,7 +9209,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
char *alloc_ok = mklabel(c, "alloc_ok");
|
||||
char *alloc_done = mklabel(c, "alloc_done");
|
||||
ins2(c, A_MOVQ, aimm(sz), areg(D_DI));
|
||||
ins1(c, A_CALL, asym(ffi_resolve("malloc")));
|
||||
ins1(c, A_CALL, asym(alloc_resolve(c)));
|
||||
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
||||
ins1(c, A_JNE, abranch(alloc_ok));
|
||||
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
|
||||
@@ -13536,7 +13546,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins2(c, A_IMULQ, areg(D_BX), areg(D_AX));
|
||||
}
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_DI));
|
||||
ins1(c, A_CALL, asym(ffi_resolve("malloc")));
|
||||
ins1(c, A_CALL, asym(alloc_resolve(c)));
|
||||
if (via_tryunw) {
|
||||
char *ok = mklabel(c, "tryunw_ok");
|
||||
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
||||
|
||||
389
cmd/w6c/wwi.c
389
cmd/w6c/wwi.c
@@ -15,12 +15,13 @@
|
||||
* stays dead on the normal `.s` path. It rejects exactly one thing:
|
||||
* an exported signature naming a non-exported nominal type.
|
||||
*
|
||||
* A `.wwi` is ONE package's interface. The M2 gate feeds w6c a driver-
|
||||
* combined unit (imports concatenated ahead of the target), so the emit
|
||||
* filters to PRIMARY decls (imported == 0) — in the real per-package
|
||||
* compile every decl is primary, so the filter is a no-op there. Output
|
||||
* is a pure function of the exported API: package line, byte-sorted
|
||||
* imports, byte-sorted decls (no map/hash iteration order).
|
||||
* A `.wwi` is ONE package's self-contained interface. The primary section
|
||||
* is followed by compiler-owned `//ww:module <path>` sections for exported
|
||||
* foreign type/const facts recursively needed by the public surface. The
|
||||
* package driver therefore only needs to pass a package's DIRECT imports;
|
||||
* the interface itself carries the selected public type closure. Output is
|
||||
* a pure function of the exported API: byte-sorted imports, declarations,
|
||||
* fact owners and fact names (no map/hash iteration order).
|
||||
*/
|
||||
#include "gc.h"
|
||||
#include <stdio.h>
|
||||
@@ -41,24 +42,78 @@ wwi_primary(Node *n)
|
||||
* leaf. By the time the producer runs, check_file has finished and
|
||||
* c->cur == c->top.
|
||||
*/
|
||||
static int
|
||||
wwi_mod_eq(const char *a, const char *b)
|
||||
{
|
||||
if (a == NULL || b == NULL) return a == b;
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
/* The import alias map is source-owner-local. Imported `.wwi` sections are
|
||||
* flat in the parser, so consulting every N_USE without this owner filter
|
||||
* would let one dependency accidentally resolve another dependency's alias. */
|
||||
static const char *
|
||||
wwi_use_path(Checker *c, const char *owner, const char *alias)
|
||||
{
|
||||
if (owner && alias) {
|
||||
const char *dot = strrchr(owner, '.');
|
||||
const char *leaf = dot ? dot + 1 : owner;
|
||||
if (strcmp(alias, leaf) == 0)
|
||||
return owner;
|
||||
}
|
||||
for (Node *u = c->file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->str == NULL
|
||||
|| strcmp(u->str, alias) != 0)
|
||||
continue;
|
||||
int same = owner == NULL ? u->imported == 0
|
||||
: u->imported != 0 && wwi_mod_eq(u->module, owner);
|
||||
if (same)
|
||||
return u->usepath ? u->usepath : u->str;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_direct_mod_visible(Checker *c, const char *owner, const char *mod)
|
||||
{
|
||||
if (mod == NULL || mod[0] == '\0') return 0;
|
||||
const char *dot = strrchr(mod, '.');
|
||||
const char *alias = dot ? dot + 1 : mod;
|
||||
const char *path = wwi_use_path(c, owner, alias);
|
||||
return path != NULL && strcmp(path, mod) == 0;
|
||||
}
|
||||
|
||||
static Sym *
|
||||
wwi_typesym(Checker *c, const char *nm)
|
||||
wwi_typesym(Checker *c, const char *owner, const char *nm)
|
||||
{
|
||||
if (nm == NULL) return NULL;
|
||||
const char *dot = strrchr(nm, '.');
|
||||
Sym *s = NULL;
|
||||
if (dot) {
|
||||
size_t n = (size_t)(dot - nm);
|
||||
for (Node *u = c->file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || !wwi_primary(u) || u->str == NULL
|
||||
|| strlen(u->str) != n || strncmp(u->str, nm, n) != 0)
|
||||
continue;
|
||||
const char *mod = u->usepath ? u->usepath : u->str;
|
||||
char *alias = malloc(n + 1);
|
||||
if (alias == NULL) fatal("wwi: out of memory");
|
||||
memcpy(alias, nm, n);
|
||||
alias[n] = '\0';
|
||||
const char *mod = wwi_use_path(c, owner, alias);
|
||||
if (mod)
|
||||
s = scope_lookup_in_module(c->cur, mod, dot + 1);
|
||||
break;
|
||||
}
|
||||
free(alias);
|
||||
} else {
|
||||
s = scope_lookup_type(c->cur, NULL, nm);
|
||||
s = scope_lookup_type(c->cur, owner, nm);
|
||||
if (s == NULL) {
|
||||
for (Scope *p = c->cur; p; p = p->parent) {
|
||||
for (Sym *b = p->first; b; b = b->next) {
|
||||
if (b->kind == SK_TYPE
|
||||
&& strcmp(b->name, nm) == 0
|
||||
&& wwi_direct_mod_visible(c, owner, b->mod)) {
|
||||
s = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s != NULL) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s && s->kind == SK_TYPE)
|
||||
return s;
|
||||
@@ -66,14 +121,14 @@ wwi_typesym(Checker *c, const char *nm)
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_check_type(Checker *c, Pos loc, Node *t)
|
||||
wwi_check_type(Checker *c, const char *owner, Pos loc, Node *t)
|
||||
{
|
||||
int bad = 0;
|
||||
if (t == NULL)
|
||||
return 0;
|
||||
switch (t->kind) {
|
||||
case N_TNAME: {
|
||||
Sym *s = wwi_typesym(c, t->str);
|
||||
Sym *s = wwi_typesym(c, owner, t->str);
|
||||
/* Sym.exported is vestigial (the checker never sets it); the
|
||||
* nominal's export status lives on its decl node, parser-set.
|
||||
* No file-guard twin to wwstage's: cstage resolves `nomem` and
|
||||
@@ -92,30 +147,30 @@ wwi_check_type(Checker *c, Pos loc, Node *t)
|
||||
case N_TSLICE:
|
||||
case N_TBANG:
|
||||
case N_TCHAN:
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, t->lhs);
|
||||
break;
|
||||
case N_TARRAY:
|
||||
/* element only; the length is a const-expr, not a type. */
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, t->lhs);
|
||||
break;
|
||||
case N_TFN:
|
||||
for (Node *p = t->list; p; p = p->next)
|
||||
bad |= wwi_check_type(c, loc, p->lhs);
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, p->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, t->lhs);
|
||||
break;
|
||||
case N_TSTRUCT:
|
||||
for (Node *f = t->list; f; f = f->next)
|
||||
bad |= wwi_check_type(c, loc, f->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, f->lhs);
|
||||
break;
|
||||
case N_TTAGGED:
|
||||
case N_TTUPLE:
|
||||
for (Node *e = t->list; e; e = e->next)
|
||||
bad |= wwi_check_type(c, loc, e);
|
||||
bad |= wwi_check_type(c, owner, loc, e);
|
||||
break;
|
||||
case N_TENUM:
|
||||
/* the inline enum body is the definition, not a reference;
|
||||
* recurse only its storage type (members are values). */
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
bad |= wwi_check_type(c, owner, loc, t->lhs);
|
||||
break;
|
||||
default:
|
||||
/* a non-type node in type position should not occur — leaf. */
|
||||
@@ -131,18 +186,18 @@ wwi_check_decl(Checker *c, Node *d)
|
||||
switch (d->kind) {
|
||||
case N_FNDECL:
|
||||
for (Node *p = d->list; p; p = p->next)
|
||||
bad |= wwi_check_type(c, d->pos, p->lhs);
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs); /* ret */
|
||||
bad |= wwi_check_type(c, NULL, d->pos, p->lhs);
|
||||
bad |= wwi_check_type(c, NULL, d->pos, d->lhs); /* ret */
|
||||
break;
|
||||
case N_TYPEDECL:
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs);
|
||||
bad |= wwi_check_type(c, NULL, d->pos, d->lhs);
|
||||
break;
|
||||
case N_DEF:
|
||||
/* the declared type; the rhs const value is not a type. */
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs);
|
||||
bad |= wwi_check_type(c, NULL, d->pos, d->lhs);
|
||||
break;
|
||||
case N_LET:
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs);
|
||||
bad |= wwi_check_type(c, NULL, d->pos, d->lhs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -357,6 +412,13 @@ wwi_expr(FILE *of, Node *e)
|
||||
fputs(tokname(e->op), of);
|
||||
wwi_expr(of, e->lhs);
|
||||
break;
|
||||
case N_CAST:
|
||||
fputc('(', of);
|
||||
wwi_expr(of, e->lhs);
|
||||
fputs(": ", of);
|
||||
wwi_type(of, e->rhs);
|
||||
fputc(')', of);
|
||||
break;
|
||||
default:
|
||||
fatal("wwi: unhandled const-expr node kind %d", e->kind);
|
||||
}
|
||||
@@ -434,7 +496,7 @@ wwi_decl(FILE *of, Node *d)
|
||||
* fold of an aggregate def's FIELDS — a no-op, ww never folds
|
||||
* struct-literal field access, and an aggregate-def field
|
||||
* demanded in a const-fold context stays a LOUD error (task #71). */
|
||||
if (d->rhs && (d->rhs->kind == N_STRUCTLIT
|
||||
if (d->rhs == NULL || (d->rhs->kind == N_STRUCTLIT
|
||||
|| d->rhs->kind == N_ARRLIT)) {
|
||||
fputs(";\n", of);
|
||||
} else {
|
||||
@@ -462,6 +524,16 @@ wwi_decl(FILE *of, Node *d)
|
||||
|
||||
struct declent { Node *d; int idx; };
|
||||
struct useent { const char *path; int idx; };
|
||||
struct factent { Node *d; const char *mod; int idx; };
|
||||
|
||||
struct factset {
|
||||
Checker *c;
|
||||
struct factent *facts;
|
||||
int nfacts, capfacts;
|
||||
struct factent *seen;
|
||||
int nseen, capseen;
|
||||
int bad;
|
||||
};
|
||||
|
||||
static int
|
||||
declcmp(const void *a, const void *b)
|
||||
@@ -481,6 +553,20 @@ usecmp(const void *a, const void *b)
|
||||
return x->idx - y->idx;
|
||||
}
|
||||
|
||||
static int
|
||||
factcmp(const void *a, const void *b)
|
||||
{
|
||||
const struct factent *x = a, *y = b;
|
||||
int r = strcmp(x->mod, y->mod);
|
||||
if (r != 0) return r;
|
||||
int xr = x->d->kind == N_TYPEDECL ? 0 : 1;
|
||||
int yr = y->d->kind == N_TYPEDECL ? 0 : 1;
|
||||
if (xr != yr) return xr - yr;
|
||||
r = strcmp(x->d->str, y->d->str);
|
||||
if (r != 0) return r;
|
||||
return x->idx - y->idx;
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_is_decl(Node *d)
|
||||
{
|
||||
@@ -488,6 +574,214 @@ wwi_is_decl(Node *d)
|
||||
|| d->kind == N_DEF || d->kind == N_LET;
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_fact_same(struct factent *f, const char *mod, Node *d)
|
||||
{
|
||||
return wwi_mod_eq(f->mod, mod) && f->d->kind == d->kind
|
||||
&& strcmp(f->d->str, d->str) == 0;
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_fact_grow(struct factent **v, int *cap, int need)
|
||||
{
|
||||
if (*cap >= need) return;
|
||||
int ncap = *cap ? *cap * 2 : 16;
|
||||
while (ncap < need) ncap *= 2;
|
||||
struct factent *nv = realloc(*v, (size_t)ncap * sizeof *nv);
|
||||
if (nv == NULL) fatal("wwi: out of memory");
|
||||
*v = nv;
|
||||
*cap = ncap;
|
||||
}
|
||||
|
||||
static Sym *
|
||||
wwi_valuesym(Checker *c, const char *owner, const char *name)
|
||||
{
|
||||
for (Scope *p = c->top; p; p = p->parent)
|
||||
for (Sym *s = p->first; s; s = s->next)
|
||||
if (s->kind == SK_DEF && strcmp(s->name, name) == 0
|
||||
&& wwi_mod_eq(s->mod, owner))
|
||||
return s;
|
||||
for (Scope *p = c->top; p; p = p->parent)
|
||||
for (Sym *s = p->first; s; s = s->next)
|
||||
if (s->kind == SK_DEF && strcmp(s->name, name) == 0
|
||||
&& wwi_direct_mod_visible(c, owner, s->mod))
|
||||
return s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void wwi_collect_decl(struct factset*, const char*, Node*);
|
||||
static void wwi_collect_type(struct factset*, const char*, Node*);
|
||||
|
||||
static void
|
||||
wwi_collect_expr(struct factset *fs, const char *owner, Node *e)
|
||||
{
|
||||
if (e == NULL) return;
|
||||
Sym *s = NULL;
|
||||
if (e->kind == N_IDENT) {
|
||||
s = wwi_valuesym(fs->c, owner, e->str);
|
||||
} else if (e->kind == N_DOT && e->lhs
|
||||
&& e->lhs->kind == N_IDENT) {
|
||||
const char *mod = wwi_use_path(fs->c, owner, e->lhs->str);
|
||||
if (mod) s = wwi_valuesym(fs->c, mod, e->str);
|
||||
}
|
||||
if (s && s->decl && s->decl->kind == N_DEF) {
|
||||
if (!s->decl->export) {
|
||||
errorf(e->pos, "exported declaration references "
|
||||
"unexported def '%s'", e->str);
|
||||
fs->bad = 1;
|
||||
return;
|
||||
}
|
||||
wwi_collect_decl(fs, s->mod, s->decl);
|
||||
return;
|
||||
}
|
||||
if (e->kind == N_BIN) {
|
||||
wwi_collect_expr(fs, owner, e->lhs);
|
||||
wwi_collect_expr(fs, owner, e->rhs);
|
||||
} else if (e->kind == N_UN) {
|
||||
wwi_collect_expr(fs, owner, e->lhs);
|
||||
} else if (e->kind == N_CAST) {
|
||||
wwi_collect_expr(fs, owner, e->lhs);
|
||||
wwi_collect_type(fs, owner, e->rhs);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_collect_type(struct factset *fs, const char *owner, Node *t)
|
||||
{
|
||||
if (t == NULL) return;
|
||||
switch (t->kind) {
|
||||
case N_TNAME: {
|
||||
Sym *s = wwi_typesym(fs->c, owner, t->str);
|
||||
if (s && s->decl && s->decl->kind == N_TYPEDECL)
|
||||
wwi_collect_decl(fs, s->mod, s->decl);
|
||||
break;
|
||||
}
|
||||
case N_TPTR:
|
||||
case N_TSLICE:
|
||||
case N_TBANG:
|
||||
case N_TCHAN:
|
||||
wwi_collect_type(fs, owner, t->lhs);
|
||||
break;
|
||||
case N_TARRAY:
|
||||
/* Array length is part of the resolved type identity, not a source
|
||||
* name dependency. Canonicalize it so private constants stay private
|
||||
* and a consumer never needs an implementation def to size the type. */
|
||||
if (t->rhs != NULL && t->rhs->kind != N_INTLIT) {
|
||||
u64 len;
|
||||
if (!check_eval_const(fs->c, t->rhs, owner, &len)) {
|
||||
errorf(t->pos, "cannot encode array dimension");
|
||||
fs->bad = 1;
|
||||
} else {
|
||||
Node *e = t->rhs;
|
||||
e->kind = N_INTLIT;
|
||||
e->uval = len;
|
||||
e->tsuffix = NULL;
|
||||
e->lhs = e->rhs = e->list = NULL;
|
||||
}
|
||||
}
|
||||
wwi_collect_type(fs, owner, t->lhs);
|
||||
break;
|
||||
case N_TFN:
|
||||
for (Node *p = t->list; p; p = p->next)
|
||||
wwi_collect_type(fs, owner, p->lhs);
|
||||
wwi_collect_type(fs, owner, t->lhs);
|
||||
break;
|
||||
case N_TSTRUCT:
|
||||
for (Node *f = t->list; f; f = f->next)
|
||||
wwi_collect_type(fs, owner, f->lhs);
|
||||
break;
|
||||
case N_TTAGGED:
|
||||
case N_TTUPLE:
|
||||
for (Node *e = t->list; e; e = e->next)
|
||||
wwi_collect_type(fs, owner, e);
|
||||
break;
|
||||
case N_TENUM:
|
||||
wwi_collect_type(fs, owner, t->lhs);
|
||||
/* Member identifiers are enum-local prior-sibling references, not
|
||||
* package defs; the complete member list already carries their facts. */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_collect_decl(struct factset *fs, const char *owner, Node *d)
|
||||
{
|
||||
for (int i = 0; i < fs->nseen; i++)
|
||||
if (wwi_fact_same(&fs->seen[i], owner, d))
|
||||
return;
|
||||
wwi_fact_grow(&fs->seen, &fs->capseen, fs->nseen + 1);
|
||||
fs->seen[fs->nseen] = (struct factent){d, owner, fs->nseen};
|
||||
fs->nseen++;
|
||||
|
||||
if (owner != NULL) {
|
||||
if (!d->export) {
|
||||
errorf(d->pos, "exported declaration references unexported "
|
||||
"%s '%s'", d->kind == N_TYPEDECL ? "type" : "def",
|
||||
d->str);
|
||||
fs->bad = 1;
|
||||
return;
|
||||
}
|
||||
wwi_fact_grow(&fs->facts, &fs->capfacts, fs->nfacts + 1);
|
||||
fs->facts[fs->nfacts] = (struct factent){d, owner, fs->nfacts};
|
||||
fs->nfacts++;
|
||||
}
|
||||
|
||||
switch (d->kind) {
|
||||
case N_FNDECL:
|
||||
for (Node *p = d->list; p; p = p->next)
|
||||
wwi_collect_type(fs, owner, p->lhs);
|
||||
wwi_collect_type(fs, owner, d->lhs);
|
||||
break;
|
||||
case N_TYPEDECL:
|
||||
wwi_collect_type(fs, owner, d->lhs);
|
||||
break;
|
||||
case N_DEF:
|
||||
wwi_collect_type(fs, owner, d->lhs);
|
||||
wwi_collect_expr(fs, owner, d->rhs);
|
||||
break;
|
||||
case N_LET:
|
||||
wwi_collect_type(fs, owner, d->lhs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_use_owned(Node *u, const char *owner)
|
||||
{
|
||||
return u->kind == N_USE && u->imported != 0
|
||||
&& wwi_mod_eq(u->module, owner);
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_emit_fact_imports(FILE *of, Node *file, const char *owner)
|
||||
{
|
||||
int nuse = 0;
|
||||
for (Node *u = file->list; u; u = u->next)
|
||||
if (wwi_use_owned(u, owner)) nuse++;
|
||||
if (nuse == 0) return;
|
||||
struct useent *us = malloc((size_t)nuse * sizeof *us);
|
||||
if (us == NULL) fatal("wwi: out of memory");
|
||||
int k = 0;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (!wwi_use_owned(u, owner)) continue;
|
||||
us[k].path = u->usepath ? u->usepath : u->str;
|
||||
us[k].idx = k;
|
||||
k++;
|
||||
}
|
||||
qsort(us, (size_t)nuse, sizeof *us, usecmp);
|
||||
const char *previous = NULL;
|
||||
for (int i = 0; i < nuse; i++) {
|
||||
if (previous && strcmp(previous, us[i].path) == 0) continue;
|
||||
fprintf(of, "import %s;\n", us[i].path);
|
||||
previous = us[i].path;
|
||||
}
|
||||
free(us);
|
||||
}
|
||||
|
||||
int
|
||||
wwi_emit(Checker *c, FILE *of, Node *file)
|
||||
{
|
||||
@@ -502,6 +796,22 @@ wwi_emit(Checker *c, FILE *of, Node *file)
|
||||
if (bad)
|
||||
return 1;
|
||||
|
||||
/* Relocate the exported API's reachable foreign type facts into this
|
||||
* package's own interface. The driver remains deliberately ignorant of
|
||||
* the representation and will pass only this artifact for a direct dep. */
|
||||
struct factset fs = {.c = c};
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (!wwi_primary(d) || !d->export || !wwi_is_decl(d)) continue;
|
||||
wwi_collect_decl(&fs, NULL, d);
|
||||
}
|
||||
if (fs.bad) {
|
||||
free(fs.facts);
|
||||
free(fs.seen);
|
||||
return 1;
|
||||
}
|
||||
if (fs.nfacts > 1)
|
||||
qsort(fs.facts, (size_t)fs.nfacts, sizeof *fs.facts, factcmp);
|
||||
|
||||
/* package line: leaf of the first primary decl's module tag. */
|
||||
const char *pkg = "main";
|
||||
int found = 0;
|
||||
@@ -573,5 +883,24 @@ wwi_emit(Checker *c, FILE *of, Node *file)
|
||||
wwi_decl(of, ds[i].d);
|
||||
free(ds);
|
||||
}
|
||||
|
||||
/* Compiler-owned public fact closure. A module marker changes semantic
|
||||
* ownership without making the namespace a source import of the eventual
|
||||
* consumer; direct visibility continues to come solely from its own N_USE. */
|
||||
const char *lastmod = NULL;
|
||||
for (int i = 0; i < fs.nfacts; i++) {
|
||||
struct factent *f = &fs.facts[i];
|
||||
if (lastmod == NULL || strcmp(lastmod, f->mod) != 0) {
|
||||
const char *dot = strrchr(f->mod, '.');
|
||||
const char *leaf = dot ? dot + 1 : f->mod;
|
||||
fprintf(of, "//ww:module %s\n", f->mod);
|
||||
fprintf(of, "package %s;\n", leaf);
|
||||
wwi_emit_fact_imports(of, file, f->mod);
|
||||
lastmod = f->mod;
|
||||
}
|
||||
wwi_decl(of, f->d);
|
||||
}
|
||||
free(fs.facts);
|
||||
free(fs.seen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -631,6 +631,16 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
check_eval_const(Checker *c, Node *n, const char *owner, u64 *out)
|
||||
{
|
||||
const char *saved = c->cur_mod;
|
||||
c->cur_mod = owner;
|
||||
int ok = eval_def_const(c, n, out, 0);
|
||||
c->cur_mod = saved;
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* stamp_intlit — rewrite a const-folded def rhs in place to the
|
||||
* literal it evaluates to, preserving the node's cexpr-resolved type
|
||||
* so the downstream DATA-row emit width and the invariant checks see
|
||||
@@ -3083,6 +3093,27 @@ check_module_shadow(Checker *c, const char *name, Pos pos,
|
||||
kindstr, name, name);
|
||||
}
|
||||
|
||||
/* Self-contained `.wwi` files can carry the same origin-owned type/const
|
||||
* fact through two direct dependencies (a diamond), or alongside a direct
|
||||
* import of that origin. In strict package mode those compiler-generated
|
||||
* declarations describe one canonical package symbol: reuse the first exact
|
||||
* (module, kind, name) binding so nominal Type pointer identity is preserved.
|
||||
* Raw w6c mode deliberately keeps the historical duplicate diagnostics. */
|
||||
static Sym *
|
||||
same_import_fact(Checker *c, Node *d, const char *mod, Skind kind)
|
||||
{
|
||||
if (!c->sep_mode || d == NULL || !d->imported || !d->export
|
||||
|| mod == NULL || mod[0] == '\0')
|
||||
return NULL;
|
||||
if (kind != SK_TYPE && kind != SK_DEF)
|
||||
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)
|
||||
return NULL;
|
||||
return s;
|
||||
}
|
||||
|
||||
void
|
||||
check_file(Checker *c, Node *file)
|
||||
{
|
||||
@@ -3145,9 +3176,14 @@ check_file(Checker *c, Node *file)
|
||||
continue;
|
||||
}
|
||||
if (d->kind != N_TYPEDECL) continue;
|
||||
const char *mod = decl_mod(file, d);
|
||||
Sym *fact = same_import_fact(c, d, mod, SK_TYPE);
|
||||
if (fact != NULL) {
|
||||
d->type = fact->type;
|
||||
continue;
|
||||
}
|
||||
Type *named = type_named(c->a, d->str, NULL);
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
const char *mod = decl_mod(file, d);
|
||||
if (prev && prev->kind == SK_USE) {
|
||||
/* `use mod; ... type mod = ...;` — promote the
|
||||
* SK_USE to the type symbol but remember it was
|
||||
@@ -3175,6 +3211,8 @@ check_file(Checker *c, Node *file)
|
||||
if (d->kind != N_DEF) continue;
|
||||
c->cur_mod = decl_mod(file, d);
|
||||
const char *mod = decl_mod(file, d);
|
||||
if (same_import_fact(c, d, mod, SK_DEF) != NULL)
|
||||
continue;
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
if (prev && prev->kind == SK_USE) {
|
||||
prev->kind = SK_DEF; prev->decl = d;
|
||||
@@ -3200,9 +3238,14 @@ check_file(Checker *c, Node *file)
|
||||
break;
|
||||
case N_DEF: {
|
||||
Type *t = resolve_type(c, d->lhs);
|
||||
const char *mod = decl_mod(file, d);
|
||||
Sym *fact = same_import_fact(c, d, mod, SK_DEF);
|
||||
if (fact != NULL) {
|
||||
d->type = fact->type ? fact->type : t;
|
||||
break;
|
||||
}
|
||||
d->type = t;
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
const char *mod = decl_mod(file, d);
|
||||
if (prev && prev->kind == SK_DEF && prev->decl == d) {
|
||||
/* #141: the foldable stub bound before type-body
|
||||
* resolution; fill in its now-resolved type. */
|
||||
|
||||
@@ -609,4 +609,9 @@ void check_file(Checker*, Node *file);
|
||||
* (eval_enum_value's leaf delegation, emit_defs's DATA-row gate). */
|
||||
int fold_int_literal(Node*, u64*);
|
||||
|
||||
/* Re-evaluate a checked integer constant under the declaration owner's
|
||||
* import scope. The compiler export writer uses this to canonicalize array
|
||||
* dimensions without serializing source-level constant dependencies. */
|
||||
int check_eval_const(Checker*, Node*, const char *owner, u64*);
|
||||
|
||||
#endif /* WW_H */
|
||||
|
||||
@@ -4012,6 +4012,14 @@ fn ffiresolve(c: *cgen, ident: str) str = {
|
||||
return ident;
|
||||
};
|
||||
|
||||
// The compiler intrinsic `alloc` has a package-mode runtime ABI independent
|
||||
// of whichever transitive interfaces happen to be present. Raw w6c remains
|
||||
// declaration-driven so existing @symbol/FFI behavior is unchanged.
|
||||
fn allocresolve(c: *cgen) str = {
|
||||
if (c.sepmode != 0) { return "rt_malloc"; };
|
||||
return ffiresolve(c, "malloc");
|
||||
};
|
||||
|
||||
fn argregname(i: i32) str = {
|
||||
if (i == 0) { return "DI"; };
|
||||
if (i == 1) { return "SI"; };
|
||||
|
||||
@@ -6242,7 +6242,7 @@ fn cgalloc(c: *cgen, n: *syntax.node) void = {
|
||||
emitint(sz: i64);
|
||||
emitline(", DI\n");
|
||||
emitline("\tCALL\t");
|
||||
emitline(ffiresolve(c, "malloc"));
|
||||
emitline(allocresolve(c));
|
||||
emitline("(SB)\n");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJNE\t"); emitline(okl); emitline("\n");
|
||||
|
||||
@@ -2276,7 +2276,7 @@ fn cgletbody(c: *cgen, n: *syntax.node, off: i32) void = {
|
||||
};
|
||||
emitline("\tMOVQ\tAX, DI\n");
|
||||
emitline("\tCALL\t");
|
||||
emitline(ffiresolve(c, "malloc"));
|
||||
emitline(allocresolve(c));
|
||||
emitline("(SB)\n");
|
||||
if (viatryunw) {
|
||||
let okl: str = mklabel(c, "tryunw_ok");
|
||||
|
||||
@@ -518,6 +518,21 @@ fn installdecl(c: *checker, file: *syntax.node, d: *syntax.node) void = {
|
||||
// checkinit-synthesized N_TYPEDECL with an empty .file) — user decls
|
||||
// always carry their parsed source file.
|
||||
fn installtop(c: *checker, d: *syntax.node, nm: str, mod: str, k: syntax.skind, kind: str) void = {
|
||||
// A self-contained interface can carry the same origin-owned type/def
|
||||
// fact through both arms of a dependency diamond. In -c package mode,
|
||||
// reuse the first exact compiler-export binding so all references obtain
|
||||
// one nominal tinfo identity. Raw w6c keeps strict duplicate diagnostics.
|
||||
if (c.sepmode != 0 && d.imported != 0 && d.exported != 0 && mod.len > 0) {
|
||||
if (k == syntax.skind.SK_TYPE || k == syntax.skind.SK_DEF) {
|
||||
let same: *syntax.sym = syntax.scopesamekeysym(c.top, nm, mod);
|
||||
if (same != nil) {
|
||||
if (same.skind == k && same.decl != nil && same.decl != d
|
||||
&& same.decl.imported != 0 && same.decl.exported != 0) {
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// #30: a top-level value/type decl whose leaf ALSO names an imported
|
||||
// module PROMOTES that same-leaf SK_USE in place — one correctly-kinded
|
||||
// sym carrying use_alias=1, so bare refs (call/structlit/var) resolve to
|
||||
@@ -638,6 +653,17 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
// `use IDENT;` — name is a module label, not a free ident.
|
||||
if (k == syntax.nkind.N_USE) { return; };
|
||||
|
||||
// `_ = rhs` is a discard assignment. The parser represents `_` as an
|
||||
// empty identifier; it is an lvalue marker, not an unresolved package
|
||||
// value. Evaluate the rhs for its normal checks and never send the blank
|
||||
// lhs through ordinary identifier resolution (cstage N_ASSIGN twin).
|
||||
if (k == syntax.nkind.N_ASSIGN && n.lhs != nil) {
|
||||
if (n.lhs.kind == syntax.nkind.N_IDENT && n.lhs.str.len == 0) {
|
||||
if (n.rhs != nil) { resolvewalk(c, n.rhs); };
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
||||
if (k == syntax.nkind.N_IDENT) {
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
@@ -663,6 +689,12 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
};
|
||||
|
||||
if (k == syntax.nkind.N_TNAME) {
|
||||
// A declaration-owned type reference may be revisited when its AST
|
||||
// is propagated as an inferred consumer type. Its canonical tinfo was
|
||||
// resolved while c.curmod named the declaring package; retain that
|
||||
// compiler fact instead of reinterpreting the spelling as consumer
|
||||
// source (which would incorrectly require a transitive import).
|
||||
if (c.sepmode != 0 && n.type_ != nil) { c.nresolved += 1; return; };
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
let s: *syntax.sym = lookupvisibletype(c, nm);
|
||||
@@ -695,7 +727,7 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
};
|
||||
};
|
||||
if (s == nil && !builtin) {
|
||||
if (syntax.scopelookup(c.cur, nm) != nil) {
|
||||
if (c.sepmode != 0 || syntax.scopelookup(c.cur, nm) != nil) {
|
||||
cerr(n.file); cerr(":");
|
||||
cerr(strconv.i32tos(n.line, strconv.base.DEC)); cerr(":");
|
||||
cerr(strconv.i32tos(n.col, strconv.base.DEC));
|
||||
@@ -981,7 +1013,15 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
};
|
||||
};
|
||||
let l: *syntax.node = n.list;
|
||||
for (l != nil) { resolvewalk(c, l); l = l.next; };
|
||||
for (l != nil) {
|
||||
// A blank multi-assignment target is the same discard marker as
|
||||
// `_ = rhs`, not a value lookup. stamptuplebinds below still
|
||||
// stamps its slot from the rhs tuple for downstream invariants.
|
||||
if (l.kind != syntax.nkind.N_IDENT || l.str.len != 0) {
|
||||
resolvewalk(c, l);
|
||||
};
|
||||
l = l.next;
|
||||
};
|
||||
stamptuplebinds(c, n.list, pt, false, "");
|
||||
return;
|
||||
};
|
||||
@@ -1006,6 +1046,13 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// Enum member value identifiers name prior siblings in the same enum;
|
||||
// they are not package-scope values. The enum-specific evaluator below
|
||||
// validates and folds the complete member list, then stampenumvals gives
|
||||
// every expression node its storage type. Do not feed those identifiers
|
||||
// through the ordinary package/local expression lookup.
|
||||
if (k == syntax.nkind.N_TENUMMEMBER) { return; };
|
||||
|
||||
// Walk children (mirroring ast.ww's printer descent order).
|
||||
if (n.attr != nil) { resolvewalk(c, n.attr); };
|
||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||
@@ -1724,6 +1771,22 @@ fn astunsized(c: *checker, t: *syntax.node) bool = {
|
||||
// call use it). The #241 `yield <binder>` fallback (the dominant
|
||||
// match-bind-then-yield idiom, Hare's parseint `case let t => yield t`)
|
||||
// stays, now resolving btype to a tinfo.
|
||||
fn exprusesname(n: *syntax.node, name: str) bool = {
|
||||
if (n == nil || name.len == 0) { return false; };
|
||||
if (n.kind == syntax.nkind.N_IDENT && syntax.streq(n.str, name)) {
|
||||
return true;
|
||||
};
|
||||
if (exprusesname(n.lhs, name) || exprusesname(n.rhs, name)
|
||||
|| exprusesname(n.cond, name) || exprusesname(n.body, name)
|
||||
|| exprusesname(n.els, name)) { return true; };
|
||||
let e: *syntax.node = n.list;
|
||||
for (e != nil) {
|
||||
if (exprusesname(e, name)) { return true; };
|
||||
e = e.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn matchyieldtype(c: *checker, body: *syntax.node, bname: str, btype: *syntax.node,
|
||||
nodeout: **syntax.node) *syntax.tinfo = {
|
||||
if (body == nil) { return nil; };
|
||||
@@ -1746,6 +1809,19 @@ fn matchyieldtype(c: *checker, body: *syntax.node, bname: str, btype: *syntax.no
|
||||
};
|
||||
return body.lhs.type_: *syntax.tinfo;
|
||||
};
|
||||
// The pre-walk runs before the N_MCASE scope exists. Resolve the
|
||||
// declared arm binder from the case node directly; calling exprtype
|
||||
// first would misclassify this valid local as an undefined package
|
||||
// name now that -c rejects genuinely absent transitive values.
|
||||
if (body.lhs.kind == syntax.nkind.N_IDENT && bname.len > 0
|
||||
&& syntax.streq(body.lhs.str, bname)) {
|
||||
*nodeout = btype;
|
||||
return tinfofornode(c, btype);
|
||||
};
|
||||
// A derived expression such as `yield sub.len` also depends on the
|
||||
// arm scope. The pre-walk cannot type it yet; defer to the normal
|
||||
// in-scope N_MCASE walk, whose cached type_ the post-walk reads.
|
||||
if (exprusesname(body.lhs, bname)) { return nil; };
|
||||
let t: *syntax.node = exprtype(c, body.lhs, nil);
|
||||
if (t != nil) {
|
||||
*nodeout = t;
|
||||
@@ -2602,7 +2678,14 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
|
||||
let named: *syntax.tinfo = syntax.typenamed(s.name, nil);
|
||||
s.type_ = named;
|
||||
named.resolving = 1;
|
||||
// Resolve a named declaration's body in the package that
|
||||
// owns it. A direct dependency's fact may be demanded while
|
||||
// walking a consumer-owned type; keeping the consumer module
|
||||
// here can bind bare names in the fact to the wrong package.
|
||||
let savedmod: str = c.curmod;
|
||||
c.curmod = s.mod;
|
||||
let under: *syntax.tinfo = tinfofornode(c, body);
|
||||
c.curmod = savedmod;
|
||||
// #62/#69: alias-root cycle (`type a = b;
|
||||
// type b = a` / `type a = a`) — checked
|
||||
// BEFORE clearing the flag so self-aliases
|
||||
@@ -3733,11 +3816,15 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// L2439, #53 at L688. Tracked in the cluster note at L685-687.
|
||||
let s: *syntax.sym = lookupvisible(c, e.str);
|
||||
if (s == nil) {
|
||||
// abort/assert are dedicated compiler builtins and intentionally
|
||||
// have no callee symbol; the surrounding N_CALL arm validates and
|
||||
// stamps them. Package-mode undefined checks must not preempt it.
|
||||
if (isassertfam(c, e)) { return nil; };
|
||||
// A same-named flattened symbol that fails lookupvisible is a
|
||||
// transitive implementation fact, not an unresolved external.
|
||||
// Diagnose it like cstage's N_IDENT path and stamp tyerr so
|
||||
// later call checking does not obscure the causal error.
|
||||
if (syntax.scopelookup(c.cur, e.str) != nil) {
|
||||
if (c.sepmode != 0 || syntax.scopelookup(c.cur, e.str) != nil) {
|
||||
cerr(e.file); cerr(":");
|
||||
cerr(strconv.i32tos(e.line, strconv.base.DEC)); cerr(":");
|
||||
cerr(strconv.i32tos(e.col, strconv.base.DEC));
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
// - check_exported_type rides the producer entry (flag-gated), off on
|
||||
// the normal `.s` path. It rejects exactly one thing: an exported
|
||||
// signature naming a non-exported nominal type.
|
||||
// - A `.wwi` is ONE package's interface; the emit filters to PRIMARY
|
||||
// decls (imported==0).
|
||||
// - A `.wwi` is ONE package's self-contained interface. Its primary
|
||||
// section is followed by compiler-owned origin sections containing the
|
||||
// exported foreign type/const facts reachable from the public surface.
|
||||
|
||||
package wcc;
|
||||
|
||||
@@ -72,8 +73,67 @@ fn wquote(fd: i32, s: str) void = {
|
||||
// resolve, no double error). A primitive/keyword resolves to no SK_TYPE
|
||||
// → leaf. By producer time checkfile has finished and c.cur == c.top.
|
||||
|
||||
fn wwitypesym(c: *checker, nm: str) *syntax.sym = {
|
||||
fn wwimodeq(a: str, b: str) bool = {
|
||||
if (a.len == 0 || b.len == 0) { return a.len == b.len; };
|
||||
return syntax.streq(a, b);
|
||||
};
|
||||
|
||||
// Map an import alias in the source package that owns the reference. The
|
||||
// flattened parser file contains every imported interface's N_USE nodes, so
|
||||
// this owner filter is what prevents cross-package alias capture.
|
||||
fn wwiusepath(c: *checker, owner: str, alias: str) str = {
|
||||
if (owner.len > 0) {
|
||||
let dotidx: i32 = -1;
|
||||
let i: i32 = 0;
|
||||
for (i < owner.len) {
|
||||
if (owner[i] == 46u8) { dotidx = i; };
|
||||
i += 1;
|
||||
};
|
||||
let leaf: str = owner;
|
||||
if (dotidx >= 0) {
|
||||
leaf.ptr = owner.ptr + ((dotidx + 1): u64);
|
||||
leaf.len = owner.len - dotidx - 1;
|
||||
};
|
||||
if (syntax.streq(alias, leaf)) { return owner; };
|
||||
};
|
||||
let u: *syntax.node = c.file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && syntax.streq(u.str, alias)) {
|
||||
let same: bool = false;
|
||||
if (owner.len == 0) {
|
||||
same = u.imported == 0;
|
||||
} else {
|
||||
same = u.imported != 0 && syntax.streq(u.nmod, owner);
|
||||
};
|
||||
if (same) {
|
||||
if (u.usepath.len > 0) { return u.usepath; };
|
||||
return u.str;
|
||||
};
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
let empty: str;
|
||||
return empty;
|
||||
};
|
||||
|
||||
fn wwidirectmodvisible(c: *checker, owner: str, mod: str) bool = {
|
||||
if (mod.len == 0) { return false; };
|
||||
let dotidx: i32 = -1;
|
||||
let i: i32 = 0;
|
||||
for (i < mod.len) {
|
||||
if (mod[i] == 46u8) { dotidx = i; };
|
||||
i += 1;
|
||||
};
|
||||
let alias: str = mod;
|
||||
if (dotidx >= 0) {
|
||||
alias.ptr = mod.ptr + ((dotidx + 1): u64);
|
||||
alias.len = mod.len - dotidx - 1;
|
||||
};
|
||||
let path: str = wwiusepath(c, owner, alias);
|
||||
return path.len > 0 && syntax.streq(path, mod);
|
||||
};
|
||||
|
||||
fn wwitypesym(c: *checker, owner: str, nm: str) *syntax.sym = {
|
||||
let dotidx: i32 = -1;
|
||||
let i: i32 = 0;
|
||||
for (i < nm.len) {
|
||||
@@ -88,20 +148,28 @@ fn wwitypesym(c: *checker, nm: str) *syntax.sym = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
|
||||
leaf.len = nm.len - dotidx - 1;
|
||||
let u: *syntax.node = c.file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && wwiprimary(u)) {
|
||||
if (syntax.streq(u.str, head)) {
|
||||
let mod: str = u.usepath;
|
||||
if (mod.len == 0) { mod = u.str; };
|
||||
s = syntax.scopelookupinmodule(c.cur, mod, leaf);
|
||||
break;
|
||||
};
|
||||
};
|
||||
u = u.next;
|
||||
let mod: str = wwiusepath(c, owner, head);
|
||||
if (mod.len > 0) {
|
||||
s = syntax.scopelookupinmodule(c.cur, mod, leaf);
|
||||
};
|
||||
} else {
|
||||
s = syntax.scopelookuptype(c.cur, empty, nm);
|
||||
s = syntax.scopelookuptype(c.cur, owner, nm);
|
||||
if (s == nil) {
|
||||
let p: *syntax.scope = c.cur;
|
||||
for (p != nil && s == nil) {
|
||||
let b: *syntax.sym = p.first;
|
||||
for (b != nil) {
|
||||
if (b.skind == syntax.skind.SK_TYPE
|
||||
&& syntax.streq(b.name, nm)
|
||||
&& wwidirectmodvisible(c, owner, b.mod)) {
|
||||
s = b;
|
||||
break;
|
||||
};
|
||||
b = b.snext;
|
||||
};
|
||||
p = p.parent;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (s == nil) { return nil; };
|
||||
if (s.skind != syntax.skind.SK_TYPE) { return nil; };
|
||||
@@ -120,15 +188,15 @@ fn wwireject(d: *syntax.node, nm: str) void = {
|
||||
};
|
||||
|
||||
// Returns 1 if a non-exported nominal was named (loud), else 0.
|
||||
fn wwichecktype(c: *checker, d: *syntax.node, t: *syntax.node) i32 = {
|
||||
fn wwichecktype(c: *checker, owner: str, d: *syntax.node, t: *syntax.node) i32 = {
|
||||
if (t == nil) { return 0; };
|
||||
// rule-10: the wwstage checker wraps N_TTUPLE.list elements in
|
||||
// N_TPARAM (ast.ww:101); cstage keeps the type-AST pristine. Unwrap
|
||||
// transparently so the recursion sees the same shape cstage walks.
|
||||
if (t.kind == syntax.nkind.N_TPARAM) { return wwichecktype(c, d, t.lhs); };
|
||||
if (t.kind == syntax.nkind.N_TPARAM) { return wwichecktype(c, owner, d, t.lhs); };
|
||||
let bad: i32 = 0;
|
||||
if (t.kind == syntax.nkind.N_TNAME) {
|
||||
let s: *syntax.sym = wwitypesym(c, t.str);
|
||||
let s: *syntax.sym = wwitypesym(c, owner, t.str);
|
||||
// sym.exported is vestigial (never set); the nominal's export
|
||||
// status lives on its decl node, parser-set.
|
||||
if (s != nil) {
|
||||
@@ -156,53 +224,54 @@ fn wwichecktype(c: *checker, d: *syntax.node, t: *syntax.node) i32 = {
|
||||
t.kind == syntax.nkind.N_TBANG ||
|
||||
t.kind == syntax.nkind.N_TCHAN
|
||||
) {
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
bad = bad | wwichecktype(c, owner, d, t.lhs);
|
||||
} else { if (t.kind == syntax.nkind.N_TARRAY) {
|
||||
// element only; the length is a const-expr, not a type.
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
bad = bad | wwichecktype(c, owner, d, t.lhs);
|
||||
} else { if (t.kind == syntax.nkind.N_TFN) {
|
||||
let p: *syntax.node = t.list;
|
||||
for (p != nil) {
|
||||
bad = bad | wwichecktype(c, d, p.lhs);
|
||||
bad = bad | wwichecktype(c, owner, d, p.lhs);
|
||||
p = p.next;
|
||||
};
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
bad = bad | wwichecktype(c, owner, d, t.lhs);
|
||||
} else { if (t.kind == syntax.nkind.N_TSTRUCT) {
|
||||
let f: *syntax.node = t.list;
|
||||
for (f != nil) {
|
||||
bad = bad | wwichecktype(c, d, f.lhs);
|
||||
bad = bad | wwichecktype(c, owner, d, f.lhs);
|
||||
f = f.next;
|
||||
};
|
||||
} else { if (t.kind == syntax.nkind.N_TTAGGED || t.kind == syntax.nkind.N_TTUPLE) {
|
||||
let e: *syntax.node = t.list;
|
||||
for (e != nil) {
|
||||
bad = bad | wwichecktype(c, d, e);
|
||||
bad = bad | wwichecktype(c, owner, d, e);
|
||||
e = e.next;
|
||||
};
|
||||
} else { if (t.kind == syntax.nkind.N_TENUM) {
|
||||
// the inline enum body is the definition, not a reference;
|
||||
// recurse only its storage type (members are values).
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
bad = bad | wwichecktype(c, owner, d, t.lhs);
|
||||
};};};};};};};
|
||||
return bad;
|
||||
};
|
||||
|
||||
fn wwicheckdecl(c: *checker, d: *syntax.node) i32 = {
|
||||
let owner: str;
|
||||
let bad: i32 = 0;
|
||||
if (d.kind == syntax.nkind.N_FNDECL) {
|
||||
let p: *syntax.node = d.list;
|
||||
for (p != nil) {
|
||||
bad = bad | wwichecktype(c, d, p.lhs);
|
||||
bad = bad | wwichecktype(c, owner, d, p.lhs);
|
||||
p = p.next;
|
||||
};
|
||||
bad = bad | wwichecktype(c, d, d.lhs);
|
||||
bad = bad | wwichecktype(c, owner, d, d.lhs);
|
||||
} else { if (d.kind == syntax.nkind.N_TYPEDECL) {
|
||||
bad = bad | wwichecktype(c, d, d.lhs);
|
||||
bad = bad | wwichecktype(c, owner, d, d.lhs);
|
||||
} else { if (d.kind == syntax.nkind.N_DEF) {
|
||||
// the declared type; the rhs const value is not a type.
|
||||
bad = bad | wwichecktype(c, d, d.lhs);
|
||||
bad = bad | wwichecktype(c, owner, d, d.lhs);
|
||||
} else { if (d.kind == syntax.nkind.N_LET) {
|
||||
bad = bad | wwichecktype(c, d, d.lhs);
|
||||
bad = bad | wwichecktype(c, owner, d, d.lhs);
|
||||
};};};};
|
||||
return bad;
|
||||
};
|
||||
@@ -303,10 +372,16 @@ fn wwiexpr(fd: i32, e: *syntax.node) void = {
|
||||
} else { if (e.kind == syntax.nkind.N_UN) {
|
||||
wputs(fd, syntax.tokname(e.op));
|
||||
wwiexpr(fd, e.lhs);
|
||||
} else { if (e.kind == syntax.nkind.N_CAST) {
|
||||
wputb(fd, '(');
|
||||
wwiexpr(fd, e.lhs);
|
||||
wputs(fd, ": ");
|
||||
wwitype(fd, e.rhs);
|
||||
wputb(fd, ')');
|
||||
} else {
|
||||
wputs(2, "wwi: unhandled const-expr node kind\n");
|
||||
os.exit(1);
|
||||
};};};};};};};};};};
|
||||
};};};};};};};};};};};
|
||||
};
|
||||
|
||||
fn wwiparam(fd: i32, p: *syntax.node) void = {
|
||||
@@ -501,7 +576,7 @@ fn wwidecl(fd: i32, d: *syntax.node) void = {
|
||||
// side const-fold of an aggregate def's FIELDS — a no-op, ww
|
||||
// never folds struct-literal field access, and an aggregate-def
|
||||
// field demanded in a const-fold context stays a LOUD error (#71).
|
||||
if (d.rhs != nil && (d.rhs.kind == syntax.nkind.N_STRUCTLIT ||
|
||||
if (d.rhs == nil || (d.rhs.kind == syntax.nkind.N_STRUCTLIT ||
|
||||
d.rhs.kind == syntax.nkind.N_ARRLIT)) {
|
||||
wputs(fd, ";\n");
|
||||
} else {
|
||||
@@ -569,6 +644,265 @@ fn wwisortdecls(keys: []str, nodes: []*syntax.node, n: i32) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// Compiler-owned reachable facts. Parallel arrays keep the self-hosted
|
||||
// representation narrow and make the deterministic ordering explicit.
|
||||
type wwifactset = struct {
|
||||
c: *checker,
|
||||
factnodes: []*syntax.node,
|
||||
factmods: []str,
|
||||
factranks: []i32,
|
||||
nfacts: i32,
|
||||
seennodes: []*syntax.node,
|
||||
seenmods: []str,
|
||||
seenranks: []i32,
|
||||
nseen: i32,
|
||||
bad: i32,
|
||||
};
|
||||
|
||||
fn wwifactrank(d: *syntax.node) i32 = {
|
||||
if (d.kind == syntax.nkind.N_TYPEDECL) { return 0i32; };
|
||||
if (d.kind == syntax.nkind.N_DEF) { return 1i32; };
|
||||
if (d.kind == syntax.nkind.N_FNDECL) { return 2i32; };
|
||||
return 3i32;
|
||||
};
|
||||
|
||||
fn wwifactsame(mod: str, rank: i32, d: *syntax.node,
|
||||
smod: str, srank: i32, sd: *syntax.node) bool = {
|
||||
return rank == srank && wwimodeq(mod, smod) && syntax.streq(d.str, sd.str);
|
||||
};
|
||||
|
||||
fn wwifactvaluesym(c: *checker, owner: str, name: str) *syntax.sym = {
|
||||
let p: *syntax.scope = c.top;
|
||||
for (p != nil) {
|
||||
let s: *syntax.sym = p.first;
|
||||
for (s != nil) {
|
||||
if (s.skind == syntax.skind.SK_DEF && syntax.streq(s.name, name)
|
||||
&& wwimodeq(s.mod, owner)) { return s; };
|
||||
s = s.snext;
|
||||
};
|
||||
p = p.parent;
|
||||
};
|
||||
p = c.top;
|
||||
for (p != nil) {
|
||||
let s: *syntax.sym = p.first;
|
||||
for (s != nil) {
|
||||
if (s.skind == syntax.skind.SK_DEF && syntax.streq(s.name, name)
|
||||
&& wwidirectmodvisible(c, owner, s.mod)) { return s; };
|
||||
s = s.snext;
|
||||
};
|
||||
p = p.parent;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
fn wwifactreject(d: *syntax.node, kind: str, name: str) void = {
|
||||
wputs(2, d.file);
|
||||
wputs(2, ":");
|
||||
wputs(2, strconv.u64tos(d.line: u64, strconv.base.DEC));
|
||||
wputs(2, ":");
|
||||
wputs(2, strconv.u64tos(d.col: u64, strconv.base.DEC));
|
||||
wputs(2, ": error: exported declaration references unexported ");
|
||||
wputs(2, kind);
|
||||
wputs(2, " '");
|
||||
wputs(2, name);
|
||||
wputs(2, "'\n");
|
||||
};
|
||||
|
||||
fn wwiencodearrayreject(d: *syntax.node) void = {
|
||||
wputs(2, d.file);
|
||||
wputs(2, ":");
|
||||
wputs(2, strconv.u64tos(d.line: u64, strconv.base.DEC));
|
||||
wputs(2, ":");
|
||||
wputs(2, strconv.u64tos(d.col: u64, strconv.base.DEC));
|
||||
wputs(2, ": error: cannot encode array dimension\n");
|
||||
};
|
||||
|
||||
fn wwicollectdecl(fs: *wwifactset, owner: str, d: *syntax.node) void = {
|
||||
let rank: i32 = wwifactrank(d);
|
||||
let i: i32 = 0;
|
||||
for (i < fs.nseen) {
|
||||
if (wwifactsame(owner, rank, d, fs.seenmods[i],
|
||||
fs.seenranks[i], fs.seennodes[i])) { return; };
|
||||
i += 1;
|
||||
};
|
||||
fs.seennodes[fs.nseen] = d;
|
||||
fs.seenmods[fs.nseen] = owner;
|
||||
fs.seenranks[fs.nseen] = rank;
|
||||
fs.nseen += 1;
|
||||
|
||||
if (owner.len > 0) {
|
||||
if (d.exported == 0) {
|
||||
let kind: str = "def";
|
||||
if (d.kind == syntax.nkind.N_TYPEDECL) { kind = "type"; };
|
||||
wwifactreject(d, kind, d.str);
|
||||
fs.bad = 1;
|
||||
return;
|
||||
};
|
||||
fs.factnodes[fs.nfacts] = d;
|
||||
fs.factmods[fs.nfacts] = owner;
|
||||
fs.factranks[fs.nfacts] = rank;
|
||||
fs.nfacts += 1;
|
||||
};
|
||||
|
||||
if (d.kind == syntax.nkind.N_FNDECL) {
|
||||
let p: *syntax.node = d.list;
|
||||
for (p != nil) { wwicollecttype(fs, owner, p.lhs); p = p.next; };
|
||||
wwicollecttype(fs, owner, d.lhs);
|
||||
} else { if (d.kind == syntax.nkind.N_TYPEDECL) {
|
||||
wwicollecttype(fs, owner, d.lhs);
|
||||
} else { if (d.kind == syntax.nkind.N_DEF) {
|
||||
wwicollecttype(fs, owner, d.lhs);
|
||||
wwicollectexpr(fs, owner, d.rhs);
|
||||
} else { if (d.kind == syntax.nkind.N_LET) {
|
||||
wwicollecttype(fs, owner, d.lhs);
|
||||
};};};};
|
||||
};
|
||||
|
||||
fn wwicollectexpr(fs: *wwifactset, owner: str, e: *syntax.node) void = {
|
||||
if (e == nil) { return; };
|
||||
let s: *syntax.sym = nil;
|
||||
if (e.kind == syntax.nkind.N_IDENT) {
|
||||
s = wwifactvaluesym(fs.c, owner, e.str);
|
||||
} else { if (e.kind == syntax.nkind.N_DOT && e.lhs != nil) {
|
||||
if (e.lhs.kind == syntax.nkind.N_IDENT) {
|
||||
let mod: str = wwiusepath(fs.c, owner, e.lhs.str);
|
||||
if (mod.len > 0) { s = wwifactvaluesym(fs.c, mod, e.str); };
|
||||
};
|
||||
}; };
|
||||
if (s != nil && s.decl != nil) {
|
||||
if (s.decl.exported == 0) {
|
||||
wwifactreject(e, "def", e.str);
|
||||
fs.bad = 1;
|
||||
return;
|
||||
};
|
||||
wwicollectdecl(fs, s.mod, s.decl);
|
||||
return;
|
||||
};
|
||||
if (e.kind == syntax.nkind.N_BIN) {
|
||||
wwicollectexpr(fs, owner, e.lhs);
|
||||
wwicollectexpr(fs, owner, e.rhs);
|
||||
} else { if (e.kind == syntax.nkind.N_UN) {
|
||||
wwicollectexpr(fs, owner, e.lhs);
|
||||
} else { if (e.kind == syntax.nkind.N_CAST) {
|
||||
wwicollectexpr(fs, owner, e.lhs);
|
||||
wwicollecttype(fs, owner, e.rhs);
|
||||
}; }; };
|
||||
};
|
||||
|
||||
fn wwievalconst(fs: *wwifactset, owner: str, e: *syntax.node,
|
||||
out: *u64) bool = {
|
||||
let saved: str = fs.c.curmod;
|
||||
fs.c.curmod = owner;
|
||||
let ok: bool = evaldefconst(fs.c, e, out, 0);
|
||||
fs.c.curmod = saved;
|
||||
return ok;
|
||||
};
|
||||
|
||||
fn wwicollecttype(fs: *wwifactset, owner: str, t: *syntax.node) void = {
|
||||
if (t == nil) { return; };
|
||||
if (t.kind == syntax.nkind.N_TPARAM) { wwicollecttype(fs, owner, t.lhs); return; };
|
||||
if (t.kind == syntax.nkind.N_TNAME) {
|
||||
let s: *syntax.sym = wwitypesym(fs.c, owner, t.str);
|
||||
if (s != nil && s.decl != nil) {
|
||||
if (s.decl.kind == syntax.nkind.N_TYPEDECL) {
|
||||
wwicollectdecl(fs, s.mod, s.decl);
|
||||
};
|
||||
};
|
||||
} else { if (
|
||||
t.kind == syntax.nkind.N_TPTR || t.kind == syntax.nkind.N_TSLICE ||
|
||||
t.kind == syntax.nkind.N_TBANG || t.kind == syntax.nkind.N_TCHAN
|
||||
) {
|
||||
wwicollecttype(fs, owner, t.lhs);
|
||||
} else { if (t.kind == syntax.nkind.N_TARRAY) {
|
||||
// Array length is resolved type identity, not a source name
|
||||
// dependency. Canonicalize it so private constants stay private
|
||||
// and consumers never need implementation defs merely for layout.
|
||||
if (t.rhs != nil && t.rhs.kind != syntax.nkind.N_INTLIT) {
|
||||
let len: u64 = 0u64;
|
||||
if (!wwievalconst(fs, owner, t.rhs, &len)) {
|
||||
wwiencodearrayreject(t);
|
||||
fs.bad = 1;
|
||||
} else {
|
||||
let e: *syntax.node = t.rhs;
|
||||
e.kind = syntax.nkind.N_INTLIT;
|
||||
e.uval = len;
|
||||
e.lhs = nil; e.rhs = nil; e.list = nil;
|
||||
let empty: str; e.tsuffix = empty;
|
||||
};
|
||||
};
|
||||
wwicollecttype(fs, owner, t.lhs);
|
||||
} else { if (t.kind == syntax.nkind.N_TFN) {
|
||||
let p: *syntax.node = t.list;
|
||||
for (p != nil) { wwicollecttype(fs, owner, p.lhs); p = p.next; };
|
||||
wwicollecttype(fs, owner, t.lhs);
|
||||
} else { if (t.kind == syntax.nkind.N_TSTRUCT) {
|
||||
let f: *syntax.node = t.list;
|
||||
for (f != nil) { wwicollecttype(fs, owner, f.lhs); f = f.next; };
|
||||
} else { if (t.kind == syntax.nkind.N_TTAGGED || t.kind == syntax.nkind.N_TTUPLE) {
|
||||
let e: *syntax.node = t.list;
|
||||
for (e != nil) { wwicollecttype(fs, owner, e); e = e.next; };
|
||||
} else { if (t.kind == syntax.nkind.N_TENUM) {
|
||||
wwicollecttype(fs, owner, t.lhs);
|
||||
// Member identifiers refer to prior siblings in this enum, not
|
||||
// package defs; the declaration already carries the whole list.
|
||||
};};};};};};};
|
||||
};
|
||||
|
||||
fn wwisortfacts(fs: *wwifactset) void = {
|
||||
let i: i32 = 0;
|
||||
for (i < fs.nfacts) {
|
||||
let best: i32 = i;
|
||||
let j: i32 = i + 1;
|
||||
for (j < fs.nfacts) {
|
||||
let r: i32 = wwistrcmp(fs.factmods[j], fs.factmods[best]);
|
||||
if (r == 0) { r = fs.factranks[j] - fs.factranks[best]; };
|
||||
if (r == 0) { r = wwistrcmp(fs.factnodes[j].str, fs.factnodes[best].str); };
|
||||
if (r < 0) { best = j; };
|
||||
j += 1;
|
||||
};
|
||||
if (best != i) {
|
||||
let tn: *syntax.node = fs.factnodes[i]; fs.factnodes[i] = fs.factnodes[best]; fs.factnodes[best] = tn;
|
||||
let tm: str = fs.factmods[i]; fs.factmods[i] = fs.factmods[best]; fs.factmods[best] = tm;
|
||||
let tr: i32 = fs.factranks[i]; fs.factranks[i] = fs.factranks[best]; fs.factranks[best] = tr;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
fn wwiowneduse(u: *syntax.node, owner: str) bool = {
|
||||
return u.kind == syntax.nkind.N_USE && u.imported != 0
|
||||
&& syntax.streq(u.nmod, owner);
|
||||
};
|
||||
|
||||
fn wwiemitfactimports(fd: i32, file: *syntax.node, owner: str) void = {
|
||||
let nuse: i32 = 0;
|
||||
let u: *syntax.node = file.list;
|
||||
for (u != nil) { if (wwiowneduse(u, owner)) { nuse += 1; }; u = u.next; };
|
||||
if (nuse == 0) { return; };
|
||||
let paths: []str = alloc([], nuse: u64)!; paths.len = nuse;
|
||||
let nodes: []*syntax.node = alloc([], nuse: u64)!; nodes.len = nuse;
|
||||
let k: i32 = 0;
|
||||
u = file.list;
|
||||
for (u != nil) {
|
||||
if (wwiowneduse(u, owner)) {
|
||||
if (u.usepath.len > 0) { paths[k] = u.usepath; } else { paths[k] = u.str; };
|
||||
nodes[k] = u;
|
||||
k += 1;
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
wwisortdecls(paths, nodes, nuse);
|
||||
let previous: str;
|
||||
let i: i32 = 0;
|
||||
for (i < nuse) {
|
||||
if (previous.len == 0 || !syntax.streq(previous, paths[i])) {
|
||||
wputs(fd, "import "); wputs(fd, paths[i]); wputs(fd, ";\n");
|
||||
previous = paths[i];
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
export fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
|
||||
// §5: check_exported_type FIRST, before any byte — a producer
|
||||
// without it can emit a dangling `.wwi`.
|
||||
@@ -582,6 +916,34 @@ export fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
|
||||
};
|
||||
if (bad != 0) { return 1i32; };
|
||||
|
||||
// The complete closure cannot contain more declarations than the checked
|
||||
// compilation unit. Allocate once; collection deduplicates by semantic
|
||||
// (owner, kind, name), then sorting supplies the byte-stable order.
|
||||
let nall: i32 = 0;
|
||||
d = file.list;
|
||||
for (d != nil) { if (wwiisdecl(d)) { nall += 1; }; d = d.next; };
|
||||
if (nall == 0) { nall = 1; };
|
||||
let fs: wwifactset;
|
||||
fs.c = c;
|
||||
let factnodes: []*syntax.node = alloc([], nall: u64)!; factnodes.len = nall;
|
||||
let factmods: []str = alloc([], nall: u64)!; factmods.len = nall;
|
||||
let factranks: []i32 = alloc([], nall: u64)!; factranks.len = nall;
|
||||
let seennodes: []*syntax.node = alloc([], nall: u64)!; seennodes.len = nall;
|
||||
let seenmods: []str = alloc([], nall: u64)!; seenmods.len = nall;
|
||||
let seenranks: []i32 = alloc([], nall: u64)!; seenranks.len = nall;
|
||||
fs.factnodes = factnodes; fs.factmods = factmods; fs.factranks = factranks;
|
||||
fs.seennodes = seennodes; fs.seenmods = seenmods; fs.seenranks = seenranks;
|
||||
let primary: str;
|
||||
d = file.list;
|
||||
for (d != nil) {
|
||||
if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) {
|
||||
wwicollectdecl(&fs, primary, d);
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
if (fs.bad != 0) { return 1i32; };
|
||||
wwisortfacts(&fs);
|
||||
|
||||
let fd: i32 = os.open(path,
|
||||
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (fd < 0) {
|
||||
@@ -710,6 +1072,30 @@ export fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
|
||||
};
|
||||
};
|
||||
|
||||
// Compiler-owned public fact closure. Origin markers preserve nominal
|
||||
// ownership but do not create source imports in the eventual consumer.
|
||||
let lastmod: str;
|
||||
let fi: i32 = 0;
|
||||
for (fi < fs.nfacts) {
|
||||
let mod: str = fs.factmods[fi];
|
||||
if (lastmod.len == 0 || !syntax.streq(lastmod, mod)) {
|
||||
wputs(fd, "//ww:module "); wputs(fd, mod); wputs(fd, "\n");
|
||||
let dotidx: i32 = -1;
|
||||
let mi: i32 = 0;
|
||||
for (mi < mod.len) { if (mod[mi] == 46u8) { dotidx = mi; }; mi += 1; };
|
||||
let leaf: str = mod;
|
||||
if (dotidx >= 0) {
|
||||
leaf.ptr = mod.ptr + ((dotidx + 1): u64);
|
||||
leaf.len = mod.len - dotidx - 1;
|
||||
};
|
||||
wputs(fd, "package "); wputs(fd, leaf); wputs(fd, ";\n");
|
||||
wwiemitfactimports(fd, file, mod);
|
||||
lastmod = mod;
|
||||
};
|
||||
wwidecl(fd, fs.factnodes[fi]);
|
||||
fi += 1;
|
||||
};
|
||||
|
||||
os.close(fd);
|
||||
return 0i32;
|
||||
};
|
||||
|
||||
@@ -8,8 +8,8 @@ package wwi_test;
|
||||
// 10): the `w6c -I <out.wwi>` producer IS the live import path, and
|
||||
// `.wwi` is a cross-stage byte-id substrate pinned directly. POSITIVE:
|
||||
// for ascii/strings/getopt (drew2-audited leak-free), drive the target
|
||||
// as the PRIMARY module of a driver-combined unit, then (1) w6c -I and
|
||||
// w6c_ww -I both succeed, (2) cs.wwi == ww.wwi byte-for-byte, (3) the
|
||||
// as the PRIMARY module of a driver-combined unit, then (1) w6c -c -I and
|
||||
// w6c_ww -c -I both succeed, (2) cs.wwi == ww.wwi byte-for-byte, (3) the
|
||||
// emitted .wwi re-parses (wwdump -a exit 0). getopt is the recursion
|
||||
// stressor. A synth fixture covers the decl-kinds + type-nodes no lib
|
||||
// package reaches (def const-expr fold, let global, [N]T, fn-ptr, !T,
|
||||
@@ -94,10 +94,13 @@ fn m2positive(pkg: str) void = {
|
||||
|
||||
let cs: str = strings.concat(td, "/cs.wwi");
|
||||
let ws: str = strings.concat(td, "/ww.wwi");
|
||||
let cav: []str = [testenv.driver("w6c"), "-I", cs, comb];
|
||||
if (!runok(td, "w6c", cav)) { fail(pkg, "w6c -I rejected"); };
|
||||
let wav: []str = [testenv.driver("w6c_ww"), "-I", ws, comb];
|
||||
if (!runok(td, "w6c_ww", wav)) { fail(pkg, "w6c_ww -I rejected"); };
|
||||
// The driver-composed unit uses package separators and may contain the
|
||||
// same compiler-owned origin fact through multiple direct interfaces;
|
||||
// exact fact interning is intentionally tied to -c package mode.
|
||||
let cav: []str = [testenv.driver("w6c"), "-c", "-I", cs, comb];
|
||||
if (!runok(td, "w6c", cav)) { fail(pkg, "w6c -c -I rejected"); };
|
||||
let wav: []str = [testenv.driver("w6c_ww"), "-c", "-I", ws, comb];
|
||||
if (!runok(td, "w6c_ww", wav)) { fail(pkg, "w6c_ww -c -I rejected"); };
|
||||
|
||||
if (!testenv.same(testenv.readfile(cs), testenv.readfile(ws))) {
|
||||
fail(pkg, "cs.wwi != ww.wwi (byte-id broken on the .wwi substrate)");
|
||||
@@ -125,7 +128,8 @@ fn m2positive(pkg: str) void = {
|
||||
let td: str = testenv.fresh();
|
||||
let src: str = strings.concat(
|
||||
"package synth;\n",
|
||||
"export type color = enum { RED, GREEN = 5, BLUE };\n",
|
||||
"def GREEN: i32 = 99;\n",
|
||||
"export type color = enum { RED, GREEN = 5, BLUE = GREEN + 1 };\n",
|
||||
"export def LIMIT: i32 = 10 + 2 * 3;\n",
|
||||
"export def NAME: str = \"hi\\n\";\n",
|
||||
"export def FLAG: bool = true;\n",
|
||||
@@ -154,8 +158,15 @@ fn m2positive(pkg: str) void = {
|
||||
"cs.wwi != ww.wwi (const-expr / decl-kind unparse diverges)");
|
||||
};
|
||||
if (!testenv.has(testenv.readfile(cs),
|
||||
"export let grouped: [((1 + 2) * 3)]u8;")) {
|
||||
fail("synth", ".wwi changed grouped array-dimension semantics");
|
||||
"export let grouped: [9]u8;")
|
||||
|| !testenv.has(testenv.readfile(cs),
|
||||
"export fn matrix() [16]u8;")) {
|
||||
fail("synth", ".wwi did not canonicalize checked array dimensions");
|
||||
};
|
||||
if (!testenv.has(testenv.readfile(cs),
|
||||
"export type color = enum { RED, GREEN = 5, BLUE = (GREEN + 1) };")
|
||||
|| testenv.has(testenv.readfile(cs), "def GREEN: i32 = 99")) {
|
||||
fail("synth", ".wwi confused an enum sibling with a package def");
|
||||
};
|
||||
// #47: the @symbol attribute must survive the round-trip verbatim.
|
||||
if (!testenv.has(testenv.readfile(cs),
|
||||
@@ -195,10 +206,14 @@ fn m2positive(pkg: str) void = {
|
||||
if (!testenv.same(testenv.readfile(cs), testenv.readfile(ws))) {
|
||||
fail("qualified", "cs.wwi != ww.wwi");
|
||||
};
|
||||
if (!testenv.has(testenv.readfile(cs),
|
||||
"export fn use(x: dep.Clash) i32;")) {
|
||||
let body: str = testenv.readfile(cs);
|
||||
if (!testenv.has(body, "export fn use(x: dep.Clash) i32;")) {
|
||||
fail("qualified", ".wwi dropped the qualified signature");
|
||||
};
|
||||
if (!testenv.has(body, "//ww:module a.dep\n")
|
||||
|| !testenv.has(body, "export type Clash = struct { x: i32 };")) {
|
||||
fail("qualified", ".wwi omitted the signature's origin-owned type fact");
|
||||
};
|
||||
let dav: []str = [testenv.driver("wwdump"), "-a", cs];
|
||||
if (!runok(td, "wwdump", dav)) {
|
||||
fail("qualified", "emitted .wwi does not re-parse");
|
||||
|
||||
Reference in New Issue
Block a user