compiler: separate package exports from entry roots
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user