compiler: separate package exports from entry roots

This commit is contained in:
2026-08-12 21:59:51 +09:00
parent a88078faf8
commit fc4bde703e
15 changed files with 420 additions and 190 deletions

View File

@@ -66,6 +66,8 @@ export fn main(argc: i32, argv: **u8) i32 = {
let wwiout: *u8 = nil; // -I <out.wwi>: M2 export-data producer
let testsupport: *u8 = nil;
let testmode: i32 = 0i32; // #15: `-T` test-mode
let testpackage: i32 = 0i32;
let entrymode: i32 = 0i32;
let sepmode: i32 = 0i32; // -c: #22 M3 separate-compile / primary-
// only codegen (emit imported==0 decls
// only; treat `.wwi` deps as external)
@@ -96,6 +98,10 @@ export fn main(argc: i32, argv: **u8) i32 = {
wwiout = argv[i];
} else { if (cstreq(a, "-T")) {
testmode = 1i32;
} else { if (cstreq(a, "--test-package")) {
testpackage = 1i32;
} else { if (cstreq(a, "--entry")) {
entrymode = 1i32;
} else { if (cstreq(a, "--test-support-module")) {
i += 1;
if (i >= argc) {
@@ -127,12 +133,12 @@ export fn main(argc: i32, argv: **u8) i32 = {
return 2;
};
src = a;
}; }; }; }; }; }; };
}; }; }; }; }; }; }; }; };
i += 1;
};
if (src == nil) {
let m: str = "usage: w6c_ww [-T] [-c] [-I out.wwi] [--import path dep.wwi]... [-o out.s] file.ww\n";
let m: str = "usage: w6c_ww [-T|--test-package] [--entry] [-c] [-I out.wwi] [--import path dep.wwi]... [-o out.s] file.ww\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
@@ -141,6 +147,16 @@ export fn main(argc: i32, argv: **u8) i32 = {
os.write(2, m.ptr, m.len: u64);
return 2;
};
if ((entrymode != 0 || testpackage != 0) && sepmode == 0) {
let m: str = "w6c: --entry and --test-package require -c\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
if (testmode != 0 && testpackage != 0) {
let m: str = "w6c: -T and --test-package are mutually exclusive\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
let importi: i32 = 0;
for (importi < nimports) {
if (importpaths[importi][0u64] == 0u8) {
@@ -249,5 +265,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
if (testsupport != nil) { testmodule = pathstr(testsupport); };
let interfaceout: str;
if (wwiout != nil) { interfaceout = pathstr(wwiout); };
return wcc.compilefile(f, testmode, testmodule, sepmode, interfaceout);
return wcc.compilefile(f, testmode, testpackage, testmodule, sepmode,
interfaceout, entrymode);
};

View File

@@ -249,8 +249,10 @@ export fn main(argc: i32, argv: **u8) i32 = {
let l: *lnk = mklnk();
// Seed _start so libwwrt-style start.o is recognised as wanted.
// Seed both accepted entries before archive loading. This lets the root
// package arrive as an ordinary selective-pull archive.
intern(l, "_start");
intern(l, "main");
// Load positional inputs first (preserving order).
let k: i32 = 0;

View File

@@ -2,13 +2,14 @@ package wcc;
import syntax;
export fn compilefile(file: *syntax.node, testmode: i32, testmodule: str,
sepmode: i32, wwiout: str) i32 = {
export fn compilefile(file: *syntax.node, testmode: i32, testpackage: i32,
testmodule: str, sepmode: i32, wwiout: str, entrymode: i32) i32 = {
let tc: syntax.tctx;
syntax.typesinit(&tc);
let ck: checker;
checkinit(&ck, &tc);
ck.istest = testmode;
ck.istestpackage = testpackage;
if (testmodule.len > 0) { ck.testmodule = testmodule; };
ck.sepmode = sepmode;
checkfile(&ck, file);
@@ -20,7 +21,7 @@ export fn compilefile(file: *syntax.node, testmode: i32, testmodule: str,
let cg: cgen;
cgeninit(&cg);
cg.sepmode = sepmode;
if (wwiout.len > 0) { cg.sepisdep = 1i32; };
if (wwiout.len > 0 && entrymode == 0) { cg.sepisdep = 1i32; };
cgfile(&cg, file);
return 0;
};

View File

@@ -502,10 +502,8 @@ type cgen = struct {
// like strlits/ffis. Symmetric with cstage Cg.sep_mode.
sepmode: i32,
// #99: this unit is a sep DEPENDENCY, not the root/link-entry unit.
// Set from `wwiout != nil` in main — the producer passes -I (.wwi
// output) to DEP units only; the root's .wwi is stripped (#69), so
// wwiout==nil <=> root/link-entry unit. Gates the bare-`main` carve-
// out: a dep's `fn main` mangles on its path like any decl; only the
// The explicit entry mode is independent of `.wwi` production. It gates
// the bare-`main` carve-out: a dep's `fn main` mangles like any decl; only the
// root entry stays bare. Like sepmode, NOT reset by cgeninit (per-fn).
// Symmetric with cstage Cg.sep_isdep.
sepisdep: i32,
@@ -3810,8 +3808,8 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
// #99: under sep a dep unit's main is imported==0 too (its
// body is composed with a path-carrying `//ww:module-reset`,
// #57), so imported==0 no longer means "root unit" per-unit.
// sepisdep (wwiout==nil <=> root/link-entry unit, #69)
// re-mangles a dep's main; only the root's stays bare.
// Explicit entry mode clears sepisdep independently of export
// production; every other package's main remains mangled.
if (!syntax.streq(d.str, "main") || d.imported != 0 || c.sepisdep != 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;

View File

@@ -19,7 +19,9 @@ type checker = struct {
matcharms: i32, // yield match-arm-nesting guard; cstage
// twin (c->matcharms)
istest: i32, // #15: `w6c_ww -T` — collect @test fns +
// synth the entry; loud-reject a user main.
// synth the entry; loud-reject a user main.
istestpackage: i32, // validate/retain package-owned @test bodies and
// export compiler-private metadata; no entry synth
testmodule: str, // generated dispatcher support qualifier
sepmode: i32, // -c package compilation: imported interfaces are
// present, so absent members are hard export errors.
@@ -523,12 +525,14 @@ fn installtop(c: *checker, d: *syntax.node, nm: str, mod: str, k: syntax.skind,
// 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 (c.sepmode != 0 && d.imported != 0 && mod.len > 0
&& (d.exported != 0 || k == syntax.skind.SK_TYPE)) {
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) {
&& same.decl.imported != 0
&& (same.decl.exported != 0 || k == syntax.skind.SK_TYPE)) {
return;
};
};
@@ -723,6 +727,13 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
let mk: str = modkeyfor(c, head);
if (mk.len != 0) {
s = syntax.scopelookupinmodule(c.cur, mk, leaf);
if (s != nil && s.decl != nil
&& s.decl.imported != 0 && s.decl.exported == 0) {
packageaccesserr(c, n, head, leaf, true);
n.type_ = c.tc.tyerr: *void;
c.nresolved += 1;
return;
};
};
};
};
@@ -4345,6 +4356,13 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
} else {
s = syntax.scopelookupinmodule(c.cur, mk, nm);
};
if (s != nil && s.decl != nil && s.decl.imported != 0
&& s.decl.exported == 0 && callee.imported == 0) {
packageaccesserr(c, callee, callee.lhs.str, nm, true);
e.type_ = c.tc.tyerr: *void;
callee.type_ = c.tc.tyerr: *void;
return nil;
};
// Module-qualified callee whose leaf isn't scope-keyed
// under its module: align to cstage, which stamps ty_err
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
@@ -4456,6 +4474,12 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
if (mk.len != 0) {
fs = syntax.scopelookupinmodule(c.cur, mk, e.str);
};
if (fs != nil && fs.decl != nil && fs.decl.imported != 0
&& fs.decl.exported == 0 && e.imported == 0) {
packageaccesserr(c, e, lhsn.str, e.str, true);
e.type_ = c.tc.tyerr: *void;
return nil;
};
if (fs != nil) { if (fs.decl != nil) {
// #34: a module-qualified bare fn rvalue `mod.fn` types as
// its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is
@@ -7481,6 +7505,7 @@ fn checkinit(c: *checker, tc: *syntax.tctx) void = {
c.nunresolved = 0;
c.errs = 0;
c.istest = 0i32; // #15: caller (w6c main) sets it after init
c.istestpackage = 0i32;
c.testmodule = "test";
c.sepmode = 0i32; // caller (w6c main) sets it from -c
c.synthtestrun = nil;
@@ -7508,11 +7533,23 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
// scope keying — cgen already emits the qualified call. Twin of
// cstage cmd/wcc/check.c.
if (c.istest != 0) {
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, file.file, file.line, file.col);
usenode.str = c.testmodule;
usenode.usepath = c.testmodule;
usenode.next = file.list;
file.list = usenode;
let present: bool = false;
let su: *syntax.node = file.list;
for (su != nil) {
if (su.kind == syntax.nkind.N_USE && su.imported == 0
&& syntax.streq(su.usepath, c.testmodule)) {
present = true;
break;
};
su = su.next;
};
if (!present) {
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, file.file, file.line, file.col);
usenode.str = c.testmodule;
usenode.usepath = c.testmodule;
usenode.next = file.list;
file.list = usenode;
};
};
// Pass 1: install all top-level names.
@@ -7571,33 +7608,27 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
// Table rides cgen's #117 slice-of-tuple-global path; `run` resolves
// bare against the auto-bundled lib/test (synth runs post-pass-1, so
// run sits in the same flat "" bucket as the @test fns).
if (c.istest != 0) {
if (c.istest != 0 || c.istestpackage != 0) {
let pf: str = file.file;
let pl: i32 = file.line;
let pc: i32 = file.col;
// (b) the synth entry OWNS `main` — loud-reject a user one.
let u: *syntax.node = file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_FNDECL && u.body != nil
&& syntax.streq(u.str, "main")) {
cerr(u.file);
cerr(": error: test mode: main is synthesized by -T; remove the explicit main\n");
c.errs += 1;
if (c.istest != 0) {
// The generated entry owns these names only in its own source.
let u: *syntax.node = file.list;
for (u != nil) {
if (u.imported == 0 && u.kind == syntax.nkind.N_FNDECL
&& u.body != nil && syntax.streq(u.str, "main")) {
cerr(u.file);
cerr(": error: test mode: main is synthesized by -T; remove the explicit main\n");
c.errs += 1;
};
if (u.imported == 0 && syntax.streq(u.str, "__wwtests")) {
cerr(u.file);
cerr(": error: test mode: __wwtests is reserved by -T; rename the declaration\n");
c.errs += 1;
};
u = u.next;
};
// #24(b): the synth table OWNS `__wwtests` — loud-reject a user
// decl of that name (mirror the `main` reservation; cstage
// cmd/wcc/check.c). A user `__wwtests` whose type HAPPENS to
// match run()'s `[](str, *fn()void)` param slips the general
// call-arg check (a) but still silently shadows the synth table,
// so the synth `run(__wwtests)` iterates the user's table, not
// the collected @tests — reserve the NAME so the collision is
// loud regardless of type. Any decl kind (const/let/fn).
if (syntax.streq(u.str, "__wwtests")) {
cerr(u.file);
cerr(": error: test mode: __wwtests is reserved by -T; rename the declaration\n");
c.errs += 1;
};
u = u.next;
};
// (c) collect @test fns in file.list order; build one table row
// `("<name>", &<name>)` per validated @test fn.
@@ -7632,7 +7663,7 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
cerr(t.str);
cerr("' cannot be exported\n");
c.errs += 1;
} else { if (ntestattr == 1 && t.body == nil) {
} else { if (ntestattr == 1 && t.body == nil && t.imported == 0) {
cerr(t.file);
cerr(": error: @test fn '");
cerr(t.str);
@@ -7652,10 +7683,30 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
cerr("' must be fn() void\n");
c.errs += 1;
} else {
// Package variants retain and export compiler-private test
// metadata; only the generated-main action builds rows.
if (c.istest == 0) {
t = t.next;
continue;
};
let nm: *syntax.node = syntax.newnode(syntax.nkind.N_STRLIT, pf, pl, pc);
nm.str = t.str;
let id: *syntax.node = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
id.str = t.str;
let id: *syntax.node;
if (t.imported != 0 && t.nmod.len > 0) {
let (prefix, suffix) = strings.rcut(t.nmod, ".");
let alias: str = suffix;
if (alias.len == 0) { alias = t.nmod; };
id = syntax.newnode(syntax.nkind.N_DOT, pf, pl, pc);
id.lhs = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
id.lhs.str = alias;
id.str = t.str;
// Parser-created nested expressions never carry imported=1.
// This marks the compiler-generated private metadata use.
id.imported = 1i32;
} else {
id = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
id.str = t.str;
};
let amp: *syntax.node = syntax.newnode(syntax.nkind.N_UN, pf, pl, pc);
amp.op = syntax.tkind.TK_AMP;
amp.lhs = id;
@@ -7672,6 +7723,7 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
t = t.next;
};
if (c.istest != 0) {
let body: *syntax.node = syntax.newnode(syntax.nkind.N_BLOCK, pf, pl, pc);
let tab: *syntax.node = nil;
if (ntest == 0i32) {
@@ -7760,6 +7812,7 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
if (tab != nil) { tl.next = tab; tab.next = m; }
else { tl.next = m; };
};
};
};
// Pass 2: walk decl bodies/types and resolve identifiers.
@@ -7884,7 +7937,7 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
// passes — they stay checked, never reach cgen. The -T path is
// untouched: its synth main calls the @test fns, so they must remain.
// Twin: cmd/wcc/check.c.
if (c.istest == 0) {
if (c.istest == 0 && c.istestpackage == 0) {
let prev: *syntax.node = nil;
let e: *syntax.node = file.list;
for (e != nil) {

View File

@@ -1,5 +1,6 @@
// wwi.ww — `.wwi` export-data producer (w6c_ww -I): a re-parseable
// ww-prototype rendering of a package's EXPORTED surface. Since the
// 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.
@@ -10,9 +11,9 @@
//
// - Unparse walks the AST type-expr subtree (the N_T* nodes), NOT the
// tinfo — tinfo collapses nominal pkg.Name identity.
// - 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.
// - 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.
@@ -196,28 +197,8 @@ fn wwichecktype(c: *checker, owner: str, d: *syntax.node, t: *syntax.node) i32 =
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, owner, t.str);
// sym.exported is vestigial (never set); the nominal's export
// status lives on its decl node, parser-set.
if (s != nil) {
if (s.decl != nil) {
if (s.decl.kind == syntax.nkind.N_TYPEDECL) {
// file.len == 0 ⇒ a checkinit-synthesized
// predeclared builtin (the ONLY empty-source
// decls: nomemdecl check.ww:126, the `void`
// TNAME check.ww:122), not a user nominal — ww's
// analogue of harec's STORAGE_NOMEM leaf-arm, and
// why cstage (lookup_builtin, no scope SK_TYPE)
// needs no such guard.
if (s.decl.file.len > 0) {
if (s.decl.exported == 0) {
wwireject(d, t.str);
bad = 1;
};
};
};
};
};
// 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 ||
@@ -513,10 +494,9 @@ fn wwitype(fd: i32, t: *syntax.node) void = {
// 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 never reaches a `.wwi` (test fns are not
// export-marked), so it needs no exclusion arm.
// today (task #47 report). @test is compiler-private package-test metadata.
fn wwiattrrelevant(nm: str) bool = {
return syntax.streq(nm, "symbol");
return syntax.streq(nm, "symbol") || syntax.streq(nm, "test");
};
fn wwiattrs(fd: i32, d: *syntax.node) void = {
@@ -544,7 +524,8 @@ fn wwiattrs(fd: i32, d: *syntax.node) void = {
fn wwidecl(fd: i32, d: *syntax.node) void = {
if (d.kind == syntax.nkind.N_FNDECL) {
wwiattrs(fd, d);
wputs(fd, "export fn ");
if (d.exported != 0) { wputs(fd, "export fn "); }
else { wputs(fd, "fn "); };
wputs(fd, d.str);
wputb(fd, '(');
let p: *syntax.node = d.list;
@@ -557,13 +538,15 @@ fn wwidecl(fd: i32, d: *syntax.node) void = {
wwitype(fd, d.lhs);
wputs(fd, ";\n");
} else { if (d.kind == syntax.nkind.N_TYPEDECL) {
wputs(fd, "export type ");
if (d.exported != 0) { wputs(fd, "export type "); }
else { wputs(fd, "type "); };
wputs(fd, d.str);
wputs(fd, " = ");
wwitype(fd, d.lhs);
wputs(fd, ";\n");
} else { if (d.kind == syntax.nkind.N_DEF) {
wputs(fd, "export def ");
if (d.exported != 0) { wputs(fd, "export def "); }
else { wputs(fd, "def "); };
wputs(fd, d.str);
wputs(fd, ": ");
wwitype(fd, d.lhs);
@@ -585,7 +568,8 @@ fn wwidecl(fd: i32, d: *syntax.node) void = {
wputs(fd, ";\n");
};
} else { if (d.kind == syntax.nkind.N_LET) {
wputs(fd, "export let ");
if (d.exported != 0) { wputs(fd, "export let "); }
else { wputs(fd, "let "); };
wputs(fd, d.str);
wputs(fd, ": ");
if (d.lhs == nil) {
@@ -609,6 +593,17 @@ fn wwiisdecl(d: *syntax.node) bool = {
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.
@@ -648,6 +643,9 @@ fn wwisortdecls(keys: []str, nodes: []*syntax.node, n: i32) void = {
// 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,
@@ -730,8 +728,21 @@ fn wwicollectdecl(fs: *wwifactset, owner: str, d: *syntax.node) void = {
fs.seenranks[fs.nseen] = rank;
fs.nseen += 1;
if (owner.len > 0) {
if (d.exported == 0) {
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);
@@ -742,7 +753,7 @@ fn wwicollectdecl(fs: *wwifactset, owner: str, d: *syntax.node) void = {
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;
@@ -925,12 +936,15 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
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;
@@ -942,6 +956,7 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
d = d.next;
};
if (fs.bad != 0) { return 1i32; };
wwisortdecls(fs.privatekeys, fs.privatenodes, fs.nprivate);
wwisortfacts(&fs);
let fd: i32 = os.open(path,
@@ -982,7 +997,7 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
// #11: a decl-less / export-less primary body carries no
// module-tagged decl, so the scan above finds nothing; fall back to
// the primary module identity stamped on the N_FILE node at parse
// time. A real root `package main` arrives via a bare module-reset
// time. A raw single-file `package main` root arrives via a bare reset
// and leaves file.nmod empty, so it stays "main". The detector is
// scan-miss (found==0), NOT pkg=="main", to match cstage byte-for-
// byte (rule 10) when a tagged decl legitimately leafs to "main".
@@ -1042,6 +1057,27 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
};
};
// Compiler-private owner nominals precede public declarations. They are
// available to export decoding but remain absent from source visibility.
let pri: i32 = 0;
for (pri < fs.nprivate) {
wwidecl(fd, fs.privatenodes[pri]);
pri += 1;
};
// Package-test metadata is private and deterministic. Only the distinct
// generated-main package consumes these declarations through --import.
if (c.istestpackage != 0) {
d = file.list;
for (d != nil) {
if (wwiprimary(d) && d.kind == syntax.nkind.N_FNDECL
&& d.exported == 0 && wwihasattr(d, "test")) {
wwidecl(fd, d);
};
d = d.next;
};
};
// decls — exported primary, byte-sorted by symbol name.
let ndecl: i32 = 0;
d = file.list;

View File

@@ -148,7 +148,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
// silently). Mirrors w6c main.ww:162 / cmd/w6c/main.c.
if (l.errs > 0 || ps.errs > 0) { return 1; };
let empty: str;
if (wcc.compilefile(f, 0, empty, 0, empty) != 0) { return 1; };
if (wcc.compilefile(f, 0, 0, empty, 0, empty, 0) != 0) { return 1; };
};};};};
if (l.errs > 0) { return 1; };