/* * 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 ` 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 #include #include static int wwi_primary(Node *n) { /* imported == 1 marks a decl reached through a `//ww:module ` * 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; }