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

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