// wwi.ww — `.wwi` export-data producer (w6c_ww -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. // // wwstage twin of cmd/w6c/wwi.c — byte-identical output is a rule-10 // requirement (`.wwi` is a cross-stage byte-id substrate). Specs: // .ai/rob-M2-spec.md + .ai/drew-M2-checkexported.md. // // - Unparse walks the AST type-expr subtree (the N_T* nodes), NOT the // tinfo — tinfo collapses nominal pkg.Name identity. // - Reachable owner-private nominal types are encoded without `export`. // Consumers reconstruct public signatures from them, but source cannot // qualify those private spellings. // - 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; import os; import syntax; import strconv; let wwiwritefailed: i32 = 0; fn wwrite(fd: i32, p: *u8, n: u64) void = { if (wwiwritefailed != 0) { return; }; match (os.writeall(fd, p, n)) { case let wrote: i64 => { if (wrote < 0 || (wrote: u64) != n) { wwiwritefailed = 1; }; }; case let e: os.oserror => wwiwritefailed = 1; }; }; fn wputs(fd: i32, s: str) void = { wwrite(fd, s.ptr, s.len: u64); }; fn wputb(fd: i32, b: u8) void = { let buf: [1]u8; buf[0] = b; wwrite(fd, buf.ptr, 1u64); }; fn wquote(fd: i32, s: str) void = { wputb(fd, '"'); let i: i32 = 0; for (i < s.len) { let c: u8 = s[i]; if (c == '"') { wputs(fd, "\\\""); } else { if (c == '\\') { wputs(fd, "\\\\"); } else { if (c == '\n') { wputs(fd, "\\n"); } else { if (c == '\t') { wputs(fd, "\\t"); } else { if (c < 32u8) { let hi: u8 = c >> 4u8; let lo: u8 = c & 15u8; let h: u8 = 0u8; let l: u8 = 0u8; if (hi < 10u8) { h = hi + 48u8; } else { h = (hi - 10u8) + 97u8; }; if (lo < 10u8) { l = lo + 48u8; } else { l = (lo - 10u8) + 97u8; }; let buf: [4]u8; buf[0] = 92u8; buf[1] = 120u8; buf[2] = h; buf[3] = l; wwrite(fd, buf.ptr, 4u64); } else { wputb(fd, c); };};};};}; i += 1; }; wputb(fd, '"'); }; // check_exported_type (drew): resolve an N_TNAME to its type sym via the // public sym helpers (mirror of cstage wwi_typesym / scope_lookup_type) // WITHOUT the side effects of the checker's aliassym (no on-demand // 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 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, source: i32, alias: str) str = { let u: *syntax.node = c.file.list; for (u != nil) { if (u.kind == syntax.nkind.N_USE && u.useblank == 0 && u.sourceid == source && 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; }; // Canonical export spelling depends only on path identity, never on the // source alias or imported declared package name. fn wwicanonicalalias(fd: i32, path: str) void = { wputs(fd, "__wwi_"); let i: i32 = 0; for (i < path.len) { let hi: u8 = path[i] >> 4u8; let lo: u8 = path[i] & 15u8; if (hi < 10u8) { wputb(fd, hi + 48u8); } else { wputb(fd, hi - 10u8 + 97u8); }; if (lo < 10u8) { wputb(fd, lo + 48u8); } else { wputb(fd, lo - 10u8 + 97u8); }; i += 1; }; }; fn wwiname(c: *checker, fd: i32, owner: str, source: i32, name: str) void = { let dotidx: i32 = -1; let i: i32 = 0; for (i < name.len) { if (name[i] == 46u8) { dotidx = i; }; i += 1; }; if (dotidx >= 0) { let head: str; head.ptr = name.ptr; head.len = dotidx; let path: str = wwiusepath(c, owner, source, head); if (path.len > 0) { wwicanonicalalias(fd, path); let tail: str; tail.ptr = name.ptr + (dotidx: u64); tail.len = name.len - dotidx; wputs(fd, tail); return; }; }; wputs(fd, name); }; fn wwitypesym(c: *checker, owner: str, source: i32, nm: str) *syntax.sym = { let dotidx: i32 = -1; let i: i32 = 0; for (i < nm.len) { if (nm[i] == 46u8) { dotidx = i; }; i += 1; }; let s: *syntax.sym = nil; if (dotidx >= 0) { let head: str; head.ptr = nm.ptr; head.len = dotidx; let leaf: str; leaf.ptr = nm.ptr + ((dotidx + 1): u64); leaf.len = nm.len - dotidx - 1; let mod: str = wwiusepath(c, owner, source, head); if (mod.len > 0) { s = syntax.scopelookupinmodule(c.cur, mod, leaf); }; } else { s = syntax.scopelookuptype(c.cur, owner, nm); }; if (s == nil) { return nil; }; if (s.skind != syntax.skind.SK_TYPE) { return nil; }; return s; }; fn wwireject(d: *syntax.node, nm: 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 type '"); wputs(2, nm); wputs(2, "'\n"); }; // Returns 1 if a non-exported nominal was named (loud), else 0. 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, owner, d, t.lhs); }; let bad: i32 = 0; if (t.kind == syntax.nkind.N_TNAME) { // Nominal references, including private ones, are collected into // the self-contained fact closure below. } 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 ) { 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, 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, owner, d, p.lhs); p = p.next; }; 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, 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, 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, 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, owner, d, p.lhs); p = p.next; }; bad = bad | wwichecktype(c, owner, d, d.lhs); } else { if (d.kind == syntax.nkind.N_TYPEDECL) { 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, owner, d, d.lhs); } else { if (d.kind == syntax.nkind.N_LET) { bad = bad | wwichecktype(c, owner, d, d.lhs); };};};}; return bad; }; // Type-expr + const-expr unparser (rob §2.2/§2.4). wwihexdigits is // byte-identical to cstage's fprintf("%0Nx"). fn wwihexdigits(fd: i32, v: u64, n: i32) void = { let i: i32 = n - 1; for (i >= 0) { let d: u64 = (v >> ((i: u64) * 4u64)) & 15u64; let c: u8 = 0u8; if (d < 10u64) { c = (d: u8) + 48u8; } else { c = ((d: u8) - 10u8) + 97u8; }; wputb(fd, c); i -= 1; }; }; // #50: the lexer now reads \u/\U, so wide codepoints round-trip as those // escapes (write-twin of lex.ww lexunicode). Codepoints <=0xff keep the // \xHH spelling they already round-tripped as. fn wwirune(fd: i32, cp: u64) void = { if (cp > 65535u64) { wputb(fd, '\''); wputs(fd, "\\U"); wwihexdigits(fd, cp, 8); wputb(fd, '\''); return; }; if (cp > 255u64) { wputb(fd, '\''); wputs(fd, "\\u"); wwihexdigits(fd, cp, 4); wputb(fd, '\''); return; }; let c: u8 = cp: u8; wputb(fd, '\''); if (c == '\'') { wputs(fd, "\\'"); } else { if (c == '\\') { wputs(fd, "\\\\"); } else { if (c == '\n') { wputs(fd, "\\n"); } else { if (c == '\t') { wputs(fd, "\\t"); } else { if (c < 32u8 || c >= 127u8) { let hi: u8 = c >> 4u8; let lo: u8 = c & 15u8; let h: u8 = 0u8; let l: u8 = 0u8; if (hi < 10u8) { h = hi + 48u8; } else { h = (hi - 10u8) + 97u8; }; if (lo < 10u8) { l = lo + 48u8; } else { l = (lo - 10u8) + 97u8; }; let buf: [4]u8; buf[0] = 92u8; buf[1] = 120u8; buf[2] = h; buf[3] = l; wwrite(fd, buf.ptr, 4u64); } else { wputb(fd, c); };};};};}; wputb(fd, '\''); }; fn wwiexpr(c: *checker, fd: i32, owner: str, source: i32, e: *syntax.node) void = { if (e == nil) { return; }; if (e.kind == syntax.nkind.N_INTLIT) { wputs(fd, strconv.u64tos(e.uval, strconv.base.DEC)); if (e.tsuffix.len > 0) { wputs(fd, e.tsuffix); }; } else { if (e.kind == syntax.nkind.N_IDENT || e.kind == syntax.nkind.N_TNAME) { wwiname(c, fd, owner, source, e.str); } else { if (e.kind == syntax.nkind.N_DOT) { if (e.lhs != nil && e.lhs.kind == syntax.nkind.N_IDENT) { let path: str = wwiusepath(c, owner, source, e.lhs.str); if (path.len > 0) { wwicanonicalalias(fd, path); } else { wwiexpr(c, fd, owner, source, e.lhs); }; } else { wwiexpr(c, fd, owner, source, e.lhs); }; wputb(fd, '.'); wputs(fd, e.str); } else { if (e.kind == syntax.nkind.N_TRUE) { wputs(fd, "true"); } else { if (e.kind == syntax.nkind.N_FALSE) { wputs(fd, "false"); } else { if (e.kind == syntax.nkind.N_NIL) { wputs(fd, "nil"); } else { if (e.kind == syntax.nkind.N_STRLIT) { wquote(fd, e.str); } else { if (e.kind == syntax.nkind.N_RUNELIT) { // 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). wwirune(fd, e.uval); } else { if (e.kind == syntax.nkind.N_BIN) { wputb(fd, '('); wwiexpr(c, fd, owner, source, e.lhs); wputb(fd, 32u8); wputs(fd, syntax.tokname(e.op)); wputb(fd, 32u8); wwiexpr(c, fd, owner, source, e.rhs); wputb(fd, ')'); } else { if (e.kind == syntax.nkind.N_UN) { wputs(fd, syntax.tokname(e.op)); wwiexpr(c, fd, owner, source, e.lhs); } else { if (e.kind == syntax.nkind.N_CAST) { wputb(fd, '('); wwiexpr(c, fd, owner, source, e.lhs); wputs(fd, ": "); wwitype(c, fd, owner, source, e.rhs); wputb(fd, ')'); } else { wputs(2, "wwi: unhandled const-expr node kind\n"); os.exit(1); };};};};};};};};};};}; }; fn wwiparam(c: *checker, fd: i32, owner: str, source: i32, p: *syntax.node) void = { if (syntax.streq(p.str, "...")) { // C-style FFI `...` wputs(fd, "..."); return; }; if (p.str.len > 0) { wputs(fd, p.str); wputs(fd, ": "); }; // rule-10: the wwstage checker desugars a Hare variadic `T...` param // in place to `[]T` (lhs becomes N_TSLICE); cstage leaves lhs == T. // Peel the inserted slice so both stages emit the surface `T...`. if (p.op == syntax.tkind.TK_ELLIPSIS && p.lhs != nil && p.lhs.kind == syntax.nkind.N_TSLICE) { wwitype(c, fd, owner, source, p.lhs.lhs); } else { wwitype(c, fd, owner, source, p.lhs); }; if (p.op == syntax.tkind.TK_ELLIPSIS) { // Hare `T...` variadic wputs(fd, "..."); }; }; fn wwitype(c: *checker, fd: i32, owner: str, source: i32, t: *syntax.node) void = { if (t == nil) { // absent return type spells void wputs(fd, "void"); return; }; // rule-10: unwrap the wwstage-only N_TPARAM tuple-element wrapper // (ast.ww:101) so the unparse matches cstage's pristine type-AST. if (t.kind == syntax.nkind.N_TPARAM) { wwitype(c, fd, owner, source, t.lhs); return; }; if (t.kind == syntax.nkind.N_TNAME) { if (t.str.len > 0) { wwiname(c, fd, owner, source, t.str); } else { wputs(fd, "void"); }; } else { if (t.kind == syntax.nkind.N_TPTR) { wputb(fd, '*'); wwitype(c, fd, owner, source, t.lhs); } else { if (t.kind == syntax.nkind.N_TSLICE) { wputs(fd, "[]"); wwitype(c, fd, owner, source, t.lhs); } else { if (t.kind == syntax.nkind.N_TARRAY) { wputb(fd, '['); if (t.rhs != nil) { wwiexpr(c, fd, owner, source, t.rhs); } else { wputb(fd, '_'); }; wputb(fd, ']'); wwitype(c, fd, owner, source, t.lhs); } else { if (t.kind == syntax.nkind.N_TBANG) { wputb(fd, '!'); wwitype(c, fd, owner, source, t.lhs); } else { if (t.kind == syntax.nkind.N_TCHAN) { wputs(fd, "chan "); wwitype(c, fd, owner, source, t.lhs); } else { if (t.kind == syntax.nkind.N_TFN) { wputs(fd, "fn("); let p: *syntax.node = t.list; for (p != nil) { if (p != t.list) { wputs(fd, ", "); }; wwiparam(c, fd, owner, source, p); p = p.next; }; wputs(fd, ") "); wwitype(c, fd, owner, source, t.lhs); } else { if (t.kind == syntax.nkind.N_TSTRUCT) { // re-emit `@packed` so the flag round-trips through // sep-compile (harec unparse/type.ha:122-126). if (t.packed != 0) { wputs(fd, "struct @packed { "); } else { wputs(fd, "struct { "); }; let f: *syntax.node = t.list; for (f != nil) { if (f != t.list) { wputs(fd, ", "); }; if (f.str.len > 0) { wputs(fd, f.str); wputs(fd, ": "); }; wwitype(c, fd, owner, source, f.lhs); f = f.next; }; wputs(fd, " }"); } else { if (t.kind == syntax.nkind.N_TTUPLE) { wputb(fd, '('); let e: *syntax.node = t.list; for (e != nil) { if (e != t.list) { wputs(fd, ", "); }; wwitype(c, fd, owner, source, e); e = e.next; }; wputb(fd, ')'); } else { if (t.kind == syntax.nkind.N_TTAGGED) { wputb(fd, '('); let e: *syntax.node = t.list; for (e != nil) { if (e != t.list) { wputs(fd, " | "); }; // #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 == syntax.tkind.TK_ELLIPSIS) { wputs(fd, "..."); }; wwitype(c, fd, owner, source, e); e = e.next; }; wputb(fd, ')'); } else { if (t.kind == syntax.nkind.N_TENUM) { wputs(fd, "enum "); if (t.lhs != nil) { wwitype(c, fd, owner, source, t.lhs); wputb(fd, 32u8); }; wputs(fd, "{ "); let m: *syntax.node = t.list; for (m != nil) { if (m != t.list) { wputs(fd, ", "); }; wputs(fd, m.str); if (m.lhs != nil) { wputs(fd, " = "); wwiexpr(c, fd, owner, source, m.lhs); }; m = m.next; }; wputs(fd, " }"); } else { wputs(2, "wwi: unhandled type node kind\n"); os.exit(1); };};};};};};};};};};}; }; // 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. fn wwiattrrelevant(nm: str) bool = { return syntax.streq(nm, "symbol") || syntax.streq(nm, "test"); }; fn wwiattrs(c: *checker, fd: i32, owner: str, d: *syntax.node) void = { let a: *syntax.node = d.attr; for (a != nil) { if (a.kind == syntax.nkind.N_ATTR && wwiattrrelevant(a.str)) { wputb(fd, '@'); wputs(fd, a.str); if (a.list != nil) { wputb(fd, '('); let arg: *syntax.node = a.list; for (arg != nil) { if (arg != a.list) { wputs(fd, ", "); }; wwiexpr(c, fd, owner, d.sourceid, arg); arg = arg.next; }; wputb(fd, ')'); }; wputb(fd, 32u8); }; a = a.next; }; }; fn wwidecl(c: *checker, fd: i32, owner: str, d: *syntax.node) void = { if (d.kind == syntax.nkind.N_FNDECL) { wwiattrs(c, fd, owner, d); if (d.exported != 0) { wputs(fd, "export fn "); } else { wputs(fd, "fn "); }; wputs(fd, d.str); wputb(fd, '('); let p: *syntax.node = d.list; for (p != nil) { if (p != d.list) { wputs(fd, ", "); }; wwiparam(c, fd, owner, d.sourceid, p); p = p.next; }; wputs(fd, ") "); wwitype(c, fd, owner, d.sourceid, d.lhs); wputs(fd, ";\n"); } else { if (d.kind == syntax.nkind.N_TYPEDECL) { if (d.exported != 0) { wputs(fd, "export type "); } else { wputs(fd, "type "); }; wputs(fd, d.str); wputs(fd, " = "); wwitype(c, fd, owner, d.sourceid, d.lhs); wputs(fd, ";\n"); } else { if (d.kind == syntax.nkind.N_DEF) { if (d.exported != 0) { wputs(fd, "export def "); } else { wputs(fd, "def "); }; wputs(fd, d.str); wputs(fd, ": "); wwitype(c, fd, owner, d.sourceid, 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 (DATA-suppression // already in cgen) and field/element reads become external 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 (#71). if (d.rhs == nil || (d.rhs.kind == syntax.nkind.N_STRUCTLIT || d.rhs.kind == syntax.nkind.N_ARRLIT)) { wputs(fd, ";\n"); } else { wputs(fd, " = "); wwiexpr(c, fd, owner, d.sourceid, d.rhs); wputs(fd, ";\n"); }; } else { if (d.kind == syntax.nkind.N_LET) { if (d.exported != 0) { wputs(fd, "export let "); } else { wputs(fd, "let "); }; wputs(fd, d.str); wputs(fd, ": "); if (d.lhs == nil) { wputs(2, "wwi: exported let has no declared type\n"); os.exit(1); }; wwitype(c, fd, owner, d.sourceid, d.lhs); wputs(fd, ";\n"); };};};}; }; fn wwiprimary(n: *syntax.node) bool = { // imported==1 marks a decl reached through a `//ww:module ` // boundary (an imported module's concatenated section). if (n == nil) { return false; }; return n.imported == 0; }; fn wwiisdecl(d: *syntax.node) bool = { if (d.initfn != 0 || d.initsynthetic != 0) { return false; }; return d.kind == syntax.nkind.N_FNDECL || d.kind == syntax.nkind.N_TYPEDECL || d.kind == syntax.nkind.N_DEF || d.kind == syntax.nkind.N_LET; }; fn wwihasattr(d: *syntax.node, name: str) bool = { let a: *syntax.node = d.attr; for (a != nil) { if (a.kind == syntax.nkind.N_ATTR && syntax.streq(a.str, name)) { return true; }; a = a.next; }; return false; }; // Deterministic ordering (rob §3): byte-lexicographic, mirror C strcmp // sign (<0/0/>0). Both stages key the sort identically, so the `.wwi` // order is deterministic. fn wwistrcmp(a: str, b: str) i32 = { let i: i32 = 0; for (i < a.len && i < b.len) { let ca: i32 = a[i]: i32; let cb: i32 = b[i]: i32; if (ca != cb) { return ca - cb; }; i += 1; }; return a.len - b.len; }; // Total order keyed on the symbol name; ties broken by original index — // stable regardless of any same-name collision, matching cstage's qsort // + idx tiebreak. fn wwisortdecls(keys: []str, nodes: []*syntax.node, n: i32) void = { let i: i32 = 0; for (i < n) { let best: i32 = i; let j: i32 = i + 1; for (j < n) { let r: i32 = wwistrcmp(keys[j], keys[best]); if (r < 0) { best = j; }; j += 1; }; if (best != i) { let tk: str = keys[i]; keys[i] = keys[best]; keys[best] = tk; let tn: *syntax.node = nodes[i]; nodes[i] = nodes[best]; nodes[best] = tn; }; i += 1; }; }; // Compiler-owned reachable facts. Parallel arrays keep the self-hosted // representation narrow and make the deterministic ordering explicit. type wwifactset = struct { c: *checker, privatenodes: []*syntax.node, privatekeys: []str, nprivate: i32, 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, source: i32, 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; }; 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 && d.exported == 0) { // checkinit's synthetic nomem/void declarations have no source file. // They are builtins, not owner-private package facts; cstage resolves // these through lookup_builtin and therefore never collects them. if (d.file.len == 0) { return; }; if (d.kind != syntax.nkind.N_TYPEDECL) { wwifactreject(d, "def", d.str); fs.bad = 1; return; }; fs.privatenodes[fs.nprivate] = d; fs.privatekeys[fs.nprivate] = d.str; fs.nprivate += 1; } else { if (owner.len > 0) { if (d.exported == 0 && d.kind != syntax.nkind.N_TYPEDECL) { 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, d.sourceid, p.lhs); p = p.next; }; wwicollecttype(fs, owner, d.sourceid, d.lhs); } else { if (d.kind == syntax.nkind.N_TYPEDECL) { wwicollecttype(fs, owner, d.sourceid, d.lhs); } else { if (d.kind == syntax.nkind.N_DEF) { wwicollecttype(fs, owner, d.sourceid, d.lhs); wwicollectexpr(fs, owner, d.sourceid, d.rhs); } else { if (d.kind == syntax.nkind.N_LET) { wwicollecttype(fs, owner, d.sourceid, d.lhs); };};};}; }; fn wwicollectexpr(fs: *wwifactset, owner: str, source: i32, 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, source, 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, source, e.lhs.str); if (mod.len > 0) { s = wwifactvaluesym(fs.c, mod, source, 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, source, e.lhs); wwicollectexpr(fs, owner, source, e.rhs); } else { if (e.kind == syntax.nkind.N_UN) { wwicollectexpr(fs, owner, source, e.lhs); } else { if (e.kind == syntax.nkind.N_CAST) { wwicollectexpr(fs, owner, source, e.lhs); wwicollecttype(fs, owner, source, e.rhs); }; }; }; }; fn wwievalconst(fs: *wwifactset, owner: str, source: i32, e: *syntax.node, out: *u64) bool = { let saved: str = fs.c.curmod; let savesource: i32 = fs.c.cursource; fs.c.curmod = owner; fs.c.cursource = source; let ok: bool = evaldefconst(fs.c, e, out, 0); fs.c.curmod = saved; fs.c.cursource = savesource; return ok; }; fn wwicollecttype(fs: *wwifactset, owner: str, source: i32, t: *syntax.node) void = { if (t == nil) { return; }; if (t.kind == syntax.nkind.N_TPARAM) { wwicollecttype(fs, owner, source, t.lhs); return; }; if (t.kind == syntax.nkind.N_TNAME) { let s: *syntax.sym = wwitypesym(fs.c, owner, source, 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, source, 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, source, 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, source, t.lhs); } else { if (t.kind == syntax.nkind.N_TFN) { let p: *syntax.node = t.list; for (p != nil) { wwicollecttype(fs, owner, source, p.lhs); p = p.next; }; wwicollecttype(fs, owner, source, t.lhs); } else { if (t.kind == syntax.nkind.N_TSTRUCT) { let f: *syntax.node = t.list; for (f != nil) { wwicollecttype(fs, owner, source, 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, source, e); e = e.next; }; } else { if (t.kind == syntax.nkind.N_TENUM) { wwicollecttype(fs, owner, source, 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.factnodes[j].sourceid - fs.factnodes[best].sourceid; }; 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, source: i32) bool = { return u.kind == syntax.nkind.N_USE && u.useblank == 0 && u.imported != 0 && u.sourceid == source && wwimodeq(u.nmod, owner); }; fn wwiemitimports(fd: i32, file: *syntax.node, owner: str, source: i32, imported: bool) void = { let nuse: i32 = 0; let u: *syntax.node = file.list; for (u != nil) { let owned: bool = false; if (imported) { owned = wwiowneduse(u, owner, source); } else { owned = u.kind == syntax.nkind.N_USE && u.useblank == 0 && u.imported == 0 && u.sourceid == source; }; if (owned) { 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) { let owned: bool = false; if (imported) { owned = wwiowneduse(u, owner, source); } else { owned = u.kind == syntax.nkind.N_USE && u.useblank == 0 && u.imported == 0 && u.sourceid == source; }; if (owned) { 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 "); wwicanonicalalias(fd, paths[i]); wputb(fd, 32u8); wputs(fd, paths[i]); wputs(fd, ";\n"); previous = paths[i]; }; i += 1; }; }; fn wwiprimarysectionhas(c: *checker, file: *syntax.node, fs: *wwifactset, exports: []*syntax.node, nexports: i32, source: i32) bool = { let u: *syntax.node = file.list; for (u != nil) { if (u.kind == syntax.nkind.N_USE && u.useblank == 0 && u.imported == 0 && u.sourceid == source) { return true; }; u = u.next; }; let i: i32 = 0; for (i < fs.nprivate) { if (fs.privatenodes[i].sourceid == source) { return true; }; i += 1; }; i = 0; for (i < nexports) { if (exports[i].sourceid == source) { return true; }; i += 1; }; if (c.istestpackage != 0) { let d: *syntax.node = file.list; for (d != nil) { if (wwiprimary(d) && d.sourceid == source && d.kind == syntax.nkind.N_FNDECL && d.exported == 0 && wwihasattr(d, "test")) { return true; }; d = d.next; }; }; return false; }; fn wwifirstprimarysource(file: *syntax.node) i32 = { let found: bool = false; let source: i32 = 0; let d: *syntax.node = file.list; for (d != nil) { if (wwiprimary(d) && (!found || d.sourceid < source)) { source = d.sourceid; found = true; }; d = d.next; }; if (found) { return source; }; return file.sourceid; }; fn wwiemitprimarysection(c: *checker, fd: i32, file: *syntax.node, fs: *wwifactset, exports: []*syntax.node, nexports: i32, owner: str, pkgname: str, source: i32) void = { if (owner.len > 0) { wputs(fd, "//ww:module "); wputs(fd, owner); wputs(fd, "\n"); }; let pkg: str = pkgname; if (pkg.len == 0) { pkg = "main"; }; wputs(fd, "package "); wputs(fd, pkg); wputs(fd, ";\n"); wwiemitimports(fd, file, owner, source, false); let i: i32 = 0; for (i < fs.nprivate) { if (fs.privatenodes[i].sourceid == source) { let primaryowner: str; wwidecl(c, fd, primaryowner, fs.privatenodes[i]); }; i += 1; }; if (c.istestpackage != 0) { let d: *syntax.node = file.list; for (d != nil) { if (wwiprimary(d) && d.sourceid == source && d.kind == syntax.nkind.N_FNDECL && d.exported == 0 && wwihasattr(d, "test")) { let primaryowner: str; wwidecl(c, fd, primaryowner, d); }; d = d.next; }; }; i = 0; for (i < nexports) { if (exports[i].sourceid == source) { let primaryowner: str; wwidecl(c, fd, primaryowner, exports[i]); }; i += 1; }; }; fn wwiemitfd(c: *checker, file: *syntax.node, fd: i32) i32 = { wwiwritefailed = 0; // §5: check_exported_type FIRST, before any byte — a producer // without it can emit a dangling `.wwi`. let bad: i32 = 0; let d: *syntax.node = file.list; for (d != nil) { if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) { bad = bad | wwicheckdecl(c, d); }; d = d.next; }; 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 privatenodes: []*syntax.node = alloc([], nall: u64)!; privatenodes.len = nall; let privatekeys: []str = alloc([], nall: u64)!; privatekeys.len = nall; 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.privatenodes = privatenodes; fs.privatekeys = privatekeys; 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; }; wwisortdecls(fs.privatekeys, fs.privatenodes, fs.nprivate); wwisortfacts(&fs); // Exported declarations are sorted within their source-file sections. let ndecl: i32 = 0; d = file.list; for (d != nil) { if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) { ndecl += 1; }; d = d.next; }; let dkeys: []str; let dnodes: []*syntax.node; if (ndecl > 0) { let keys: []str = alloc([], ndecl: u64)!; keys.len = ndecl; dkeys = keys; let nodes: []*syntax.node = alloc([], ndecl: u64)!; nodes.len = ndecl; dnodes = nodes; let k: i32 = 0; d = file.list; for (d != nil) { if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) { dkeys[k] = d.str; dnodes[k] = d; k += 1; }; d = d.next; }; wwisortdecls(dkeys, dnodes, ndecl); }; // Canonical ownership, declared name, and lexical source scope are // independent export facts. Repeated owner sections preserve the file // that owns each binding while every symbol/action remains keyed by owner. let nsection: i32 = 0; let p: *syntax.node = file.body; for (p != nil) { if (p.imported == 0 && wwiprimarysectionhas(c, file, &fs, dnodes, ndecl, p.sourceid)) { let owner: str = p.nmod; if (owner.len == 0) { owner = file.nmod; }; let pkg: str = p.pkgname; if (pkg.len == 0) { pkg = file.pkgname; }; wwiemitprimarysection(c, fd, file, &fs, dnodes, ndecl, owner, pkg, p.sourceid); nsection += 1; }; p = p.next; }; if (nsection == 0) { wwiemitprimarysection(c, fd, file, &fs, dnodes, ndecl, file.nmod, file.pkgname, wwifirstprimarysource(file)); }; // Compiler-owned public fact closure. Origin markers preserve nominal // ownership but do not create source imports in the eventual consumer. let lastmod: str; let lastsource: i32 = -1; let fi: i32 = 0; for (fi < fs.nfacts) { let mod: str = fs.factmods[fi]; let source: i32 = fs.factnodes[fi].sourceid; if (lastmod.len == 0 || !syntax.streq(lastmod, mod) || lastsource != source) { wputs(fd, "//ww:module "); wputs(fd, mod); wputs(fd, "\n"); wputs(fd, "package "); wwicanonicalalias(fd, mod); wputs(fd, ";\n"); wwiemitimports(fd, file, mod, source, true); lastmod = mod; lastsource = source; }; wwidecl(c, fd, mod, fs.factnodes[fi]); fi += 1; }; return wwiwritefailed; };