Files
ww/cmd/w6c/wwi.c

983 lines
27 KiB
C

/*
* wwi.c — `.wwi` export-data producer (w6c -I): a re-parseable ww-prototype
* rendering of a package's exported surface and compiler-private closure.
* Since the sep-compile flip
* (epic #22) this is the LIVE import path — the driver runs one `w6c -c -I`
* per package and feeds each dep's `.wwi` to its importers through a separate
* canonical `--import` input; the combined.ww amalgamator is gone.
*
* Specs: .ai/rob-M2-spec.md (producer) + .ai/drew-M2-checkexported.md
* (check_exported_type). Two load-bearing choices follow them:
*
* - Unparse walks the AST type-expr subtree (the N_T* nodes), NOT the
* checked Type* — tinfo collapses nominal pkg.Name identity (MEMORY
* tinfo_lossy_nominal). The AST preserves surface syntax + names.
* - Reachable owner-private nominal types are encoded without `export`.
* They let a consumer reconstruct public signatures without making the
* private spelling source-importable.
*
* 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>
#include <stdlib.h>
#include <string.h>
static int
wwi_primary(Node *n)
{
/* imported == 1 marks a decl reached through a `//ww:module <path>`
* boundary (an imported module's concatenated section). */
return n && n->imported == 0;
}
/* check_exported_type (drew): resolve an N_TNAME to its type sym WITHOUT
* the side effects of resolve_typename (no on-demand resolve, no double
* "unknown type" error). A primitive/keyword resolves to no SK_TYPE →
* 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, int source, const char *alias)
{
for (Node *u = c->file->list; u; u = u->next) {
if (u->kind != N_USE || u->str == NULL
|| u->sourceid != source || 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, int source,
const char *mod)
{
if (mod == NULL || mod[0] == '\0') return 0;
for (Node *u = c->file->list; u; u = u->next) {
if (u->kind != N_USE || u->sourceid != source) continue;
int same = owner == NULL ? u->imported == 0
: u->imported != 0 && wwi_mod_eq(u->module, owner);
const char *path = u->usepath ? u->usepath : u->str;
if (same && path != NULL && strcmp(path, mod) == 0) return 1;
}
return 0;
}
static Sym *
wwi_typesym(Checker *c, const char *owner, int source, 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);
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, source, alias);
if (mod)
s = scope_lookup_in_module(c->cur, mod, dot + 1);
free(alias);
} else {
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, source,
b->mod)) {
s = b;
break;
}
}
if (s != NULL) break;
}
}
}
if (s && s->kind == SK_TYPE)
return s;
return NULL;
}
static int
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: {
/* Nominal references, including private ones, are collected into
* the self-contained fact closure below. */
break;
}
case N_TPTR:
case N_TSLICE:
case N_TBANG:
case N_TCHAN:
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, owner, loc, t->lhs);
break;
case N_TFN:
for (Node *p = t->list; p; p = p->next)
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, 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, 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, owner, loc, t->lhs);
break;
default:
/* a non-type node in type position should not occur — leaf. */
break;
}
return bad;
}
static int
wwi_check_decl(Checker *c, Node *d)
{
int bad = 0;
switch (d->kind) {
case N_FNDECL:
for (Node *p = d->list; p; p = p->next)
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, 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, NULL, d->pos, d->lhs);
break;
case N_LET:
bad |= wwi_check_type(c, NULL, d->pos, d->lhs);
break;
default:
break;
}
return bad;
}
/* type-expr unparse per rob §2.2; const-expr per rob §2.4. */
static void wwi_expr(FILE *of, Node *e);
static void wwi_type(FILE *of, Node *t);
static void
wwi_quote(FILE *of, const char *s, u64 n)
{
fputc('"', of);
for (u64 i = 0; i < n; i++) {
unsigned char c = (unsigned char)s[i];
switch (c) {
case '"': fputs("\\\"", of); break;
case '\\': fputs("\\\\", of); break;
case '\n': fputs("\\n", of); break;
case '\t': fputs("\\t", of); break;
default:
if (c < 0x20) fprintf(of, "\\x%02x", c);
else fputc(c, of);
}
}
fputc('"', of);
}
static void
wwi_rune(FILE *of, u64 cp)
{
/* #50: the lexer now reads \u/\U, so wide codepoints round-trip as
* those escapes (write-twin of lex.c lexunicode). Codepoints <=0xff
* keep the \xHH spelling they already round-tripped as. */
if (cp > 0xffff) {
fprintf(of, "'\\U%08x'", (unsigned)cp);
return;
}
if (cp > 0xff) {
fprintf(of, "'\\u%04x'", (unsigned)cp);
return;
}
unsigned char c = (unsigned char)cp;
fputc('\'', of);
switch (c) {
case '\'': fputs("\\'", of); break;
case '\\': fputs("\\\\", of); break;
case '\n': fputs("\\n", of); break;
case '\t': fputs("\\t", of); break;
default:
if (c < 0x20 || c >= 0x7f) fprintf(of, "\\x%02x", c);
else fputc(c, of);
}
fputc('\'', of);
}
static void
wwi_param(FILE *of, Node *p)
{
if (p->str && strcmp(p->str, "...") == 0) { /* C-style FFI `...` */
fputs("...", of);
return;
}
if (p->str && p->str[0]) {
fputs(p->str, of);
fputs(": ", of);
}
wwi_type(of, p->lhs);
if (p->op == TK_ELLIPSIS) /* Hare `T...` variadic */
fputs("...", of);
}
static void
wwi_type(FILE *of, Node *t)
{
if (t == NULL) { /* absent return type spells void */
fputs("void", of);
return;
}
switch (t->kind) {
case N_TNAME:
fputs(t->str ? t->str : "void", of);
break;
case N_TPTR:
fputc('*', of);
wwi_type(of, t->lhs);
break;
case N_TSLICE:
fputs("[]", of);
wwi_type(of, t->lhs);
break;
case N_TARRAY:
fputc('[', of);
if (t->rhs) wwi_expr(of, t->rhs);
else fputc('_', of);
fputc(']', of);
wwi_type(of, t->lhs);
break;
case N_TBANG:
fputc('!', of);
wwi_type(of, t->lhs);
break;
case N_TCHAN:
fputs("chan ", of);
wwi_type(of, t->lhs);
break;
case N_TFN:
fputs("fn(", of);
for (Node *p = t->list; p; p = p->next) {
if (p != t->list) fputs(", ", of);
wwi_param(of, p);
}
fputs(") ", of);
wwi_type(of, t->lhs);
break;
case N_TSTRUCT:
/* re-emit the `@packed` attr so the flag round-trips
* through sep-compile (harec unparse/type.ha:122-126). */
fputs(t->packed ? "struct @packed { " : "struct { ", of);
for (Node *f = t->list; f; f = f->next) {
if (f != t->list) fputs(", ", of);
if (f->str && f->str[0]) {
fputs(f->str, of);
fputs(": ", of);
}
wwi_type(of, f->lhs);
}
fputs(" }", of);
break;
case N_TTUPLE:
fputc('(', of);
for (Node *e = t->list; e; e = e->next) {
if (e != t->list) fputs(", ", of);
wwi_type(of, e);
}
fputc(')', of);
break;
case N_TTAGGED:
fputc('(', of);
for (Node *e = t->list; e; e = e->next) {
if (e != t->list) fputs(" | ", of);
/* #95: re-emit the spread marker purely syntactically;
* the consumer's type-store flattens (ref/hare/hare/
* unparse/type.ha:290-300). */
if (e->op == TK_ELLIPSIS)
fputs("...", of);
wwi_type(of, e);
}
fputc(')', of);
break;
case N_TENUM:
fputs("enum ", of);
if (t->lhs) {
wwi_type(of, t->lhs);
fputc(' ', of);
}
fputs("{ ", of);
for (Node *m = t->list; m; m = m->next) {
if (m != t->list) fputs(", ", of);
fputs(m->str, of);
if (m->lhs) {
fputs(" = ", of);
wwi_expr(of, m->lhs);
}
}
fputs(" }", of);
break;
default:
fatal("wwi: unhandled type node kind %d", t->kind);
}
}
static void
wwi_expr(FILE *of, Node *e)
{
if (e == NULL)
return;
switch (e->kind) {
case N_INTLIT:
fprintf(of, "%llu", (unsigned long long)e->uval);
if (e->tsuffix) fputs(e->tsuffix, of);
break;
case N_IDENT:
case N_TNAME:
fputs(e->str, of);
break;
case N_DOT:
wwi_expr(of, e->lhs);
fputc('.', of);
fputs(e->str, of);
break;
case N_TRUE: fputs("true", of); break;
case N_FALSE: fputs("false", of); break;
case N_NIL: fputs("nil", of); break;
case N_STRLIT: wwi_quote(of, e->str, e->strlen); break;
/* A bare rune literal in a const-expr (types::RUNE_MIN = '\0'); casts
* never reach here — the checker folds a const cast to an integer
* literal before the producer runs (RUNE_MAX `0x10ffff: rune` emits
* as the plain int 1114111). */
case N_RUNELIT: wwi_rune(of, e->uval); break;
case N_BIN:
fputc('(', of);
wwi_expr(of, e->lhs);
fprintf(of, " %s ", tokname(e->op));
wwi_expr(of, e->rhs);
fputc(')', of);
break;
case N_UN:
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);
}
}
/* Only codegen/link-relevant attributes round-trip into the `.wwi`. Today
* that is exactly @symbol (the FFI link-symbol override, read back at cgen
* fficollect — dropping it makes sep-compile emit `CALL malloc` for a
* `@symbol("rt_malloc")` fn). Named for the class so @align/@offset would
* slot in here IF ww ever grows field-layout attributes — it has none
* today (task #47 report). @test is compiler-private package-test metadata. */
static int
wwi_attr_relevant(const char *nm)
{
return nm && (strcmp(nm, "symbol") == 0
|| strcmp(nm, "test") == 0);
}
static void
wwi_attrs(FILE *of, Node *d)
{
for (Node *a = d->attr; a; a = a->next) {
if (a->kind != N_ATTR || !wwi_attr_relevant(a->str))
continue;
fputc('@', of);
fputs(a->str, of);
if (a->list) {
fputc('(', of);
for (Node *arg = a->list; arg; arg = arg->next) {
if (arg != a->list) fputs(", ", of);
wwi_expr(of, arg);
}
fputc(')', of);
}
fputc(' ', of);
}
}
static void
wwi_decl(FILE *of, Node *d)
{
switch (d->kind) {
case N_FNDECL:
wwi_attrs(of, d);
fputs(d->export ? "export fn " : "fn ", of);
fputs(d->str, of);
fputc('(', of);
for (Node *p = d->list; p; p = p->next) {
if (p != d->list) fputs(", ", of);
wwi_param(of, p);
}
fputs(") ", of);
wwi_type(of, d->lhs);
fputs(";\n", of);
break;
case N_TYPEDECL:
fputs(d->export ? "export type " : "type ", of);
fputs(d->str, of);
fputs(" = ", of);
wwi_type(of, d->lhs);
fputs(";\n", of);
break;
case N_DEF:
fputs(d->export ? "export def " : "def ", of);
fputs(d->str, of);
fputs(": ", of);
wwi_type(of, d->lhs);
/* An aggregate initializer (N_STRUCTLIT/N_ARRLIT) is a DATA-
* global (#52): emit a value-LESS prototype `export def X: T;`.
* The defining package's own .o emits the struct/array DATA; the
* importer registers the def by TYPE only (the DATA-suppression
* if(sep_mode && imported)continue at cgen.c is already in place)
* and field/element reads become external LEAQ refs the linker
* fills. Boundary (rule 7): this gives up importer-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 (task #71). */
if (d->rhs == NULL || (d->rhs->kind == N_STRUCTLIT
|| d->rhs->kind == N_ARRLIT)) {
fputs(";\n", of);
} else {
fputs(" = ", of);
wwi_expr(of, d->rhs);
fputs(";\n", of);
}
break;
case N_LET:
fputs(d->export ? "export let " : "let ", of);
fputs(d->str, of);
fputs(": ", of);
if (d->lhs == NULL)
fatal("wwi: exported let '%s' has no declared type",
d->str);
wwi_type(of, d->lhs);
fputs(";\n", of);
break;
default:
break;
}
}
/* deterministic ordering per rob §3. */
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 declent *privatefacts;
int nprivate, capprivate;
struct factent *facts;
int nfacts, capfacts;
struct factent *seen;
int nseen, capseen;
int bad;
};
static void
wwi_decl_grow(struct declent **v, int *cap, int need)
{
if (*cap >= need) return;
int ncap = *cap ? *cap * 2 : 16;
while (ncap < need) ncap *= 2;
struct declent *nv = realloc(*v, (size_t)ncap * sizeof *nv);
if (nv == NULL) fatal("wwi: out of memory");
*v = nv;
*cap = ncap;
}
static int
declcmp(const void *a, const void *b)
{
const struct declent *x = a, *y = b;
int r = strcmp(x->d->str, y->d->str); /* symbol name ONLY */
if (r != 0) return r;
return x->idx - y->idx; /* total order, stable */
}
static int
usecmp(const void *a, const void *b)
{
const struct useent *x = a, *y = b;
int r = strcmp(x->path, y->path);
if (r != 0) return r;
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;
if (x->d->sourceid != y->d->sourceid)
return x->d->sourceid - y->d->sourceid;
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)
{
return d->kind == N_FNDECL || d->kind == N_TYPEDECL
|| d->kind == N_DEF || d->kind == N_LET;
}
static int
wwi_has_attr(Node *d, const char *name)
{
for (Node *a = d ? d->attr : NULL; a; a = a->next)
if (a->kind == N_ATTR && a->str && strcmp(a->str, name) == 0)
return 1;
return 0;
}
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, int source, 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, source, 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*, int, Node*);
static void
wwi_collect_expr(struct factset *fs, const char *owner, int source, Node *e)
{
if (e == NULL) return;
Sym *s = NULL;
if (e->kind == N_IDENT) {
s = wwi_valuesym(fs->c, owner, source, e->str);
} else if (e->kind == N_DOT && e->lhs
&& e->lhs->kind == N_IDENT) {
const char *mod = wwi_use_path(fs->c, owner, source,
e->lhs->str);
if (mod) s = wwi_valuesym(fs->c, mod, source, 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, source, e->lhs);
wwi_collect_expr(fs, owner, source, e->rhs);
} else if (e->kind == N_UN) {
wwi_collect_expr(fs, owner, source, e->lhs);
} else if (e->kind == N_CAST) {
wwi_collect_expr(fs, owner, source, e->lhs);
wwi_collect_type(fs, owner, source, e->rhs);
}
}
static void
wwi_collect_type(struct factset *fs, const char *owner, int source, Node *t)
{
if (t == NULL) return;
switch (t->kind) {
case N_TNAME: {
Sym *s = wwi_typesym(fs->c, owner, source, 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, source, 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, source, &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, source, t->lhs);
break;
case N_TFN:
for (Node *p = t->list; p; p = p->next)
wwi_collect_type(fs, owner, source, p->lhs);
wwi_collect_type(fs, owner, source, t->lhs);
break;
case N_TSTRUCT:
for (Node *f = t->list; f; f = f->next)
wwi_collect_type(fs, owner, source, f->lhs);
break;
case N_TTAGGED:
case N_TTUPLE:
for (Node *e = t->list; e; e = e->next)
wwi_collect_type(fs, owner, source, e);
break;
case N_TENUM:
wwi_collect_type(fs, owner, source, 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 && !d->export) {
if (d->kind != N_TYPEDECL) {
errorf(d->pos, "exported declaration references unexported "
"def '%s'", d->str);
fs->bad = 1;
return;
}
wwi_decl_grow(&fs->privatefacts, &fs->capprivate,
fs->nprivate + 1);
fs->privatefacts[fs->nprivate] =
(struct declent){d, fs->nprivate};
fs->nprivate++;
} else if (owner != NULL) {
if (!d->export && d->kind != N_TYPEDECL) {
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, d->sourceid, p->lhs);
wwi_collect_type(fs, owner, d->sourceid, d->lhs);
break;
case N_TYPEDECL:
wwi_collect_type(fs, owner, d->sourceid, d->lhs);
break;
case N_DEF:
wwi_collect_type(fs, owner, d->sourceid, d->lhs);
wwi_collect_expr(fs, owner, d->sourceid, d->rhs);
break;
case N_LET:
wwi_collect_type(fs, owner, d->sourceid, d->lhs);
break;
default:
break;
}
}
static int
wwi_use_owned(Node *u, const char *owner, int source)
{
return u->kind == N_USE && u->imported != 0
&& u->sourceid == source && wwi_mod_eq(u->module, owner);
}
static void
wwi_emit_imports(FILE *of, Node *file, const char *owner, int source,
int imported)
{
int nuse = 0;
for (Node *u = file->list; u; u = u->next) {
int owned = imported ? wwi_use_owned(u, owner, source)
: u->kind == N_USE && u->imported == 0
&& u->sourceid == source;
if (owned) 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) {
int owned = imported ? wwi_use_owned(u, owner, source)
: u->kind == N_USE && u->imported == 0
&& u->sourceid == source;
if (!owned) 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);
}
static int
wwi_primary_section_has(Checker *c, Node *file, struct factset *fs,
struct declent *exports, int nexports, int source)
{
for (Node *u = file->list; u; u = u->next)
if (u->kind == N_USE && u->imported == 0
&& u->sourceid == source)
return 1;
for (int i = 0; i < fs->nprivate; i++)
if (fs->privatefacts[i].d->sourceid == source) return 1;
for (int i = 0; i < nexports; i++)
if (exports[i].d->sourceid == source) return 1;
if (c->is_test_package)
for (Node *d = file->list; d; d = d->next)
if (wwi_primary(d) && d->sourceid == source
&& d->kind == N_FNDECL && !d->export
&& wwi_has_attr(d, "test"))
return 1;
return 0;
}
static int
wwi_first_primary_source(Node *file)
{
int found = 0;
int source = 0;
for (Node *d = file->list; d; d = d->next) {
if (!wwi_primary(d)) continue;
if (!found || d->sourceid < source) {
source = d->sourceid;
found = 1;
}
}
return found ? source : file->sourceid;
}
static void
wwi_emit_primary_section(Checker *c, FILE *of, Node *file,
struct factset *fs, struct declent *exports, int nexports,
const char *owner, const char *pkg, int source)
{
if (owner != NULL && owner[0] != '\0')
fprintf(of, "//ww:module %s\n", owner);
fprintf(of, "package %s;\n", pkg && pkg[0] ? pkg : "main");
wwi_emit_imports(of, file, owner, source, 0);
for (int i = 0; i < fs->nprivate; i++)
if (fs->privatefacts[i].d->sourceid == source)
wwi_decl(of, fs->privatefacts[i].d);
if (c->is_test_package)
for (Node *d = file->list; d; d = d->next)
if (wwi_primary(d) && d->sourceid == source
&& d->kind == N_FNDECL && !d->export
&& wwi_has_attr(d, "test"))
wwi_decl(of, d);
for (int i = 0; i < nexports; i++)
if (exports[i].d->sourceid == source)
wwi_decl(of, exports[i].d);
}
int
wwi_emit(Checker *c, FILE *of, Node *file)
{
/* §5: check_exported_type FIRST, before any byte is written — a
* producer without it can emit a dangling `.wwi`. */
int bad = 0;
for (Node *d = file->list; d; d = d->next) {
if (!wwi_primary(d) || !d->export || !wwi_is_decl(d))
continue;
bad |= wwi_check_decl(c, d);
}
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.privatefacts);
free(fs.facts);
free(fs.seen);
return 1;
}
if (fs.nprivate > 1)
qsort(fs.privatefacts, (size_t)fs.nprivate,
sizeof *fs.privatefacts, declcmp);
if (fs.nfacts > 1)
qsort(fs.facts, (size_t)fs.nfacts, sizeof *fs.facts, factcmp);
/* Exported declarations are sorted within their source-file sections. */
int ndecl = 0;
for (Node *d = file->list; d; d = d->next)
if (wwi_primary(d) && d->export && wwi_is_decl(d))
ndecl++;
struct declent *ds = NULL;
if (ndecl > 0) {
ds = malloc((size_t)ndecl * sizeof *ds);
if (ds == NULL) fatal("wwi: out of memory");
int k = 0;
for (Node *d = file->list; d; d = d->next) {
if (!wwi_primary(d) || !d->export || !wwi_is_decl(d))
continue;
ds[k].d = d;
ds[k].idx = k;
k++;
}
qsort(ds, (size_t)ndecl, sizeof *ds, declcmp);
}
/* Canonical ownership, declared name, and lexical source scope are three
* independent export facts. Repeated owner sections preserve the file that
* owns each binding while keeping every symbol/action keyed by OWNER. */
int nsection = 0;
for (Node *p = file->body; p; p = p->next) {
if (p->imported || !wwi_primary_section_has(c, file, &fs, ds,
ndecl, p->sourceid))
continue;
const char *owner = p->module && p->module[0]
? p->module : file->module;
const char *pkg = p->pkgname && p->pkgname[0]
? p->pkgname : file->pkgname;
wwi_emit_primary_section(c, of, file, &fs, ds, ndecl, owner,
pkg, p->sourceid);
nsection++;
}
if (nsection == 0) {
const char *pkg = file->pkgname && file->pkgname[0]
? file->pkgname : "main";
wwi_emit_primary_section(c, of, file, &fs, ds, ndecl,
file->module, pkg, wwi_first_primary_source(file));
}
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;
int lastsource = -1;
for (int i = 0; i < fs.nfacts; i++) {
struct factent *f = &fs.facts[i];
if (lastmod == NULL || strcmp(lastmod, f->mod) != 0
|| lastsource != f->d->sourceid) {
const char *dot = strrchr(f->mod, '.');
const char *factpkg = f->d->pkgname && f->d->pkgname[0]
? f->d->pkgname : (dot ? dot + 1 : f->mod);
fprintf(of, "//ww:module %s\n", f->mod);
fprintf(of, "package %s;\n", factpkg);
wwi_emit_imports(of, file, f->mod, f->d->sourceid, 1);
lastmod = f->mod;
lastsource = f->d->sourceid;
}
wwi_decl(of, f->d);
}
free(fs.privatefacts);
free(fs.facts);
free(fs.seen);
return 0;
}