compiler: make package exports self-contained

This commit is contained in:
2026-08-12 01:12:42 +09:00
parent 52f6d6f0d4
commit 9b970eb16a
10 changed files with 965 additions and 82 deletions

View File

@@ -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"; };

View File

@@ -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");

View File

@@ -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");

View File

@@ -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));

View File

@@ -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;
};