compiler: load imports and enforce package exports

This commit is contained in:
2026-08-11 22:25:59 +09:00
parent 7bc96b4e03
commit db96422f74
14 changed files with 894 additions and 118 deletions

View File

@@ -5,6 +5,7 @@ package wcc;
import os;
import syntax;
import strconv;
import strings;
type checker = struct {
tc: *syntax.tctx,
@@ -19,6 +20,10 @@ type checker = struct {
// twin (c->matcharms)
istest: i32, // #15: `w6c_ww -T` — collect @test fns +
// synth the entry; loud-reject a user main.
sepmode: i32, // -c package compilation: imported interfaces are
// present, so absent members are hard export errors.
synthtestrun: *syntax.node, // exact generated test.run DOT; its
// unresolved external hook is intentional
verbose: i32, // when non-zero, log each unresolved name
fnret: *syntax.node, // enclosing fn's return type AST (for `?`)
curmod: str, // importing-module bareword for the decl
@@ -164,17 +169,31 @@ fn declmod(file: *syntax.node, d: *syntax.node) str = {
// to the full dotted import path it binds (`encoding.utf8`), for the
// module-qualified resolution and codegen hint (M1 #22). Single-level
// packages have usepath == alias so the result is unchanged. The
// current tree has one occurrence per leaf, so the map is unambiguous.
fn usepathfor(file: *syntax.node, alias: str) str = {
// Only an import owned by the referencing package is visible; a matching
// alias carried by a transitive interface is deliberately ignored.
fn usepathfor(file: *syntax.node, modtag: str, alias: str) str = {
let empty: str;
if (file == nil) { return empty; };
if (alias.len == 0) { return empty; };
if (modtag.len != 0) {
let (prefix, suffix) = strings.rcut(modtag, ".");
let leaf: str = suffix;
if (leaf.len == 0) { leaf = modtag; };
if (syntax.streq(alias, leaf)) { return modtag; };
};
let u: *syntax.node = file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE) {
if (syntax.streq(u.str, alias)) {
if (u.usepath.len != 0) { return u.usepath; };
return u.str;
let um: str = declmod(file, u);
let same: bool = false;
if (modtag.len == 0) {
if (um.len == 0) { same = true; };
} else { if (syntax.streq(um, modtag)) { same = true; }; };
if (same) {
if (u.usepath.len != 0) { return u.usepath; };
return u.str;
};
};
};
u = u.next;
@@ -182,12 +201,10 @@ fn usepathfor(file: *syntax.node, alias: str) str = {
return empty;
};
// modkeyfor — usepathfor with leaf-alias fallback: the module key for a
// path-keyed scopelookupinmodule given the alias the user wrote (M1 #22).
// modkeyfor — the module key for a directly imported alias. Empty means
// the referencing package did not itself declare that import.
fn modkeyfor(c: *checker, alias: str) str = {
let mk: str = usepathfor(c.file, alias);
if (mk.len == 0) { return alias; };
return mk;
return usepathfor(c.file, c.curmod, alias);
};
// srcimports — does the source file that contributed decl-module
@@ -225,6 +242,132 @@ fn srcimports(file: *syntax.node, modtag: str, name: str) bool = {
return false;
};
// Bare imported declarations remain WW source syntax, but only across a
// direct edge. The syntax scope helpers resolve lexical locals, builtins and
// same-package declarations first; this pass admits a flattened interface
// symbol iff the referencing source package itself imported its module path.
fn directmodvisible(c: *checker, mod: str) bool = {
if (mod.len == 0) { return false; };
let (prefix, suffix) = strings.rcut(mod, ".");
let alias: str = suffix;
if (alias.len == 0) { alias = mod; };
let path: str = usepathfor(c.file, c.curmod, alias);
return path.len != 0 && syntax.streq(path, mod);
};
fn lookupvisible(c: *checker, name: str) *syntax.sym = {
let found: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, name);
let builtin: *syntax.sym = nil;
if (found != nil) {
if (found.decl != nil) { return found; };
builtin = found;
};
// Flat scope installation coalesces same-leaf N_USE entries. The
// source-owned alias map, not the retained marker's mod field, decides
// whether this package can use the qualifier.
if (usepathfor(c.file, c.curmod, name).len != 0) {
let q: *syntax.scope = c.cur;
for (q != nil) {
let u: *syntax.sym = q.first;
for (u != nil) {
if (syntax.streq(u.name, name)
&& (u.skind == syntax.skind.SK_USE
|| u.use_alias != 0i32)) { return u; };
u = u.snext;
};
q = q.parent;
};
};
let p: *syntax.scope = c.cur;
for (p != nil) {
let b: *syntax.sym = p.first;
for (b != nil) {
if (syntax.streq(b.name, name) && directmodvisible(c, b.mod)) {
return b;
};
b = b.snext;
};
p = p.parent;
};
return builtin;
};
fn lookupvisibletype(c: *checker, name: str) *syntax.sym = {
// C resolves intrinsic type names before consulting package symbols.
// Select the empty-module seed here so an imported interface cannot
// redefine a builtin for wwstage; size/opaque have no seed and remain
// handled directly by tinfofornode.
if (builtintypename(name)) {
let empty: str;
return syntax.scopelookuptype(c.cur, empty, name);
};
let found: *syntax.sym = syntax.scopelookuptype(c.cur, c.curmod, name);
if (found != nil) { return found; };
let p: *syntax.scope = c.cur;
for (p != nil) {
let b: *syntax.sym = p.first;
for (b != nil) {
if (b.skind == syntax.skind.SK_TYPE
&& syntax.streq(b.name, name)
&& directmodvisible(c, b.mod)) { return b; };
b = b.snext;
};
p = p.parent;
};
return nil;
};
fn builtintypename(name: str) bool = {
return syntax.streq(name, "void")
|| syntax.streq(name, "bool")
|| syntax.streq(name, "rune")
|| syntax.streq(name, "i8")
|| syntax.streq(name, "i16")
|| syntax.streq(name, "i32")
|| syntax.streq(name, "i64")
|| syntax.streq(name, "u8")
|| syntax.streq(name, "u16")
|| syntax.streq(name, "u32")
|| syntax.streq(name, "u64")
|| syntax.streq(name, "int")
|| syntax.streq(name, "uint")
|| syntax.streq(name, "uintptr")
|| syntax.streq(name, "size")
|| syntax.streq(name, "opaque")
|| syntax.streq(name, "f32")
|| syntax.streq(name, "f64")
|| syntax.streq(name, "str")
|| syntax.streq(name, "never")
|| syntax.streq(name, "nomem")
|| syntax.streq(name, "untyped_int")
|| syntax.streq(name, "untyped_float")
|| syntax.streq(name, "untyped_str")
|| syntax.streq(name, "untyped_rune")
|| syntax.streq(name, "untyped_bool")
|| syntax.streq(name, "untyped_nil");
};
fn packageaccesserr(c: *checker, e: *syntax.node, pkg: str, member: str,
missing: bool) void = {
cerr(e.file); cerr(":");
cerr(strconv.i32tos(e.line, strconv.base.DEC)); cerr(":");
cerr(strconv.i32tos(e.col, strconv.base.DEC));
cerr(": error: package '"); cerr(pkg);
if (missing) {
cerr("' has no exported declaration '"); cerr(member); cerr("'\n");
} else {
cerr("' is not directly imported\n");
};
c.errs += 1;
};
// A bare `w6c -T` leaves the compiler-generated test.run hook external;
// the ordinary driver supplies lib/test. No user-written missing member is
// exempt from package export checking.
fn synthesizedtestrun(c: *checker, e: *syntax.node) bool = {
return c.synthtestrun != nil && c.synthtestrun == e;
};
// checkmoduleshadow — enforce "value names and module names are
// disjoint" at nested-scope binds. Mirrors cstage check_module_shadow
// (cmd/wcc/check.c). Fires for fn params / lets / forrange iters /
@@ -498,7 +641,7 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
if (k == syntax.nkind.N_IDENT) {
let nm: str = n.str;
if (nm.len > 0) {
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, nm);
let s: *syntax.sym = lookupvisible(c, nm);
if (s == nil) {
// Unshadowed abort/assert binds no sym BY
// DESIGN (the EXPR_ASSERT family has no callee
@@ -522,13 +665,14 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
if (k == syntax.nkind.N_TNAME) {
let nm: str = n.str;
if (nm.len > 0) {
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, nm);
let s: *syntax.sym = lookupvisibletype(c, nm);
let builtin: bool = builtintypename(nm);
// `pkg.Type` — strip the last dot prefix and look up
// the leaf with a mod filter so same-leaf-name types
// from different imports (`bufio.stream` vs
// `io.stream`) disambiguate to the right one.
// Mirrors cmd/wcc/check.c resolve_typename.
if (s == nil) {
if (s == nil && !builtin) {
let dot: i32 = nm.len - 1;
for (dot >= 0) {
if (nm[dot] == 46u8) { break; };
@@ -543,18 +687,39 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
let leaf: str;
leaf.ptr = nm.ptr + (dot + 1): u64;
leaf.len = nm.len - (dot + 1);
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
let mk: str = modkeyfor(c, head);
if (mk.len != 0) {
s = syntax.scopelookupinmodule(c.cur, mk, leaf);
};
};
};
};
if (s == nil) {
if (s == nil && !builtin) {
if (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));
cerr(": error: unknown type '"); cerr(nm); cerr("'\n");
c.errs += 1;
n.type_ = c.tc.tyerr: *void;
};
c.nunresolved += 1;
if (c.verbose != 0) {
cerr(" unresolved tname: ");
cerr(nm);
cerr("\n");
};
} else { c.nresolved += 1; };
} else {
c.nresolved += 1;
// Resolve and cache the type while c.curmod still names the
// declaration that owns this syntax node. Exported structs may
// contain a direct dependency's type; consumers need that cached
// shape for structural field access, but must not gain source
// visibility of the dependency qualifier. Cstage does the same
// owner-scoped work in resolve_typedecl.
let ti: *syntax.tinfo = tinfofornode(c, n);
if (ti != nil) { n.type_ = ti: *void; };
};
};
};
@@ -991,6 +1156,25 @@ fn unwrapbang(n: *syntax.node) *syntax.node = {
fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
if (n == nil) { return nil; };
if (n.kind != syntax.nkind.N_TNAME) { return nil; };
// An exported signature is resolved while its owning package is the
// current module. Preserve that compiler-owned binding when the exact
// same AST node is later inspected structurally by a consumer (for
// example `os.filestat.atime.sec`, where atime is time.instant). This
// does not make a consumer-written `time.instant` visible: such a node
// has no owner-resolved named cache unless the consumer imports time.
let bound: *syntax.tinfo = n.type_: *syntax.tinfo;
if (bound != nil) { if (bound.kind == syntax.tykind.TY_NAMED) {
let owner: *syntax.scope = c.cur;
for (owner != nil) {
let bs: *syntax.sym = owner.first;
for (bs != nil) {
if (bs.skind == syntax.skind.SK_TYPE
&& bs.type_ == bound) { return bs; };
bs = bs.snext;
};
owner = owner.parent;
};
}; };
let nm: str = n.str;
// #51: pkg.alias type refs land here as a single TNAME whose
// str is the joined form (lib/ww/parse/parse.ww:258-265 in
@@ -1013,7 +1197,10 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1;
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
let mk: str = modkeyfor(c, head);
if (mk.len != 0) {
s = syntax.scopelookupinmodule(c.cur, mk, leaf);
};
} else {
// #53: same-module preference. Mirrors cstage
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
@@ -1026,7 +1213,7 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
// scopelookupprefer (the #56/#4/#11a wave). The only bare-leaf
// type lookups left — varianterr (:1051) + scruttype (:1098) —
// stay mod-blind but have no reproducible divergence; #58.
s = syntax.scopelookupprefer(c.cur, c.curmod, nm);
s = lookupvisibletype(c, nm);
// #61 A.5: bare TNAME that collides with an imported
// module bareword. Two shapes hit this:
// - `let l: lex;` where `lex` struct lives in
@@ -1049,7 +1236,7 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
// otherwise resolve install-order-dependent here
// when a value binding shadows the leaf; cstage
// passes c->cur_mod to scope_lookup_type (sym.c).
let sm: *syntax.sym = syntax.scopelookuptype(c.cur, c.curmod, nm);
let sm: *syntax.sym = lookupvisibletype(c, nm);
if (sm != nil) { s = sm; };
};
};
@@ -1271,7 +1458,9 @@ fn scruttype(c: *checker, e: *syntax.node) *syntax.node = {
if (e.kind == syntax.nkind.N_DOT) {
if (e.lhs == nil) { return nil; };
if (e.lhs.kind != syntax.nkind.N_IDENT) { return nil; };
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.str), e.str);
let mk: str = modkeyfor(c, e.lhs.str);
if (mk.len == 0) { return nil; };
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, mk, e.str);
if (s == nil) { return nil; };
if (s.decl == nil) { return nil; };
return s.decl.lhs;
@@ -1862,7 +2051,7 @@ fn evaldefconst(c: *checker, n: *syntax.node, out: *u64, depth: i32) bool = {
return true;
};
if (k == syntax.nkind.N_IDENT) {
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, n.str);
let s: *syntax.sym = lookupvisible(c, n.str);
if (s == nil) { return false; };
if (s.skind != syntax.skind.SK_DEF) { return false; };
if (s.decl == nil) { return false; };
@@ -1872,7 +2061,9 @@ fn evaldefconst(c: *checker, n: *syntax.node, out: *u64, depth: i32) bool = {
if (k == syntax.nkind.N_DOT) {
if (n.lhs == nil) { return false; };
if (n.lhs.kind != syntax.nkind.N_IDENT) { return false; };
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, n.lhs.str), n.str);
let mk: str = modkeyfor(c, n.lhs.str);
if (mk.len == 0) { return false; };
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, mk, n.str);
if (s == nil) { return false; };
if (s.skind != syntax.skind.SK_DEF) { return false; };
if (s.decl == nil) { return false; };
@@ -3287,7 +3478,7 @@ fn unoptype(c: *checker, e: *syntax.node) *syntax.node = {
// the foreign signature (scopedefineinmodule prepends);
// cstage types a fn ident via scope_lookup_prefer with
// cur_mod (cmd/wcc/check.c:1305).
let fs: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.lhs.str);
let fs: *syntax.sym = lookupvisible(c, e.lhs.str);
if (fs != nil) {
if (fs.skind == syntax.skind.SK_FN) {
if (fs.decl != nil) {
@@ -3313,7 +3504,11 @@ fn unoptype(c: *checker, e: *syntax.node) *syntax.node = {
// cstage's actual acceptance reason.
if (e.lhs.kind == syntax.nkind.N_DOT) {
if (e.lhs.lhs != nil && e.lhs.lhs.kind == syntax.nkind.N_IDENT) {
let fs: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.lhs.str), e.lhs.str);
let fs: *syntax.sym = nil;
let mk: str = modkeyfor(c, e.lhs.lhs.str);
if (mk.len != 0) {
fs = syntax.scopelookupinmodule(c.cur, mk, e.lhs.str);
};
if (fs != nil) {
if (fs.skind == syntax.skind.SK_FN) {
if (fs.decl != nil) {
@@ -3388,7 +3583,7 @@ fn indexresult(c: *checker, e: *syntax.node) *syntax.node = {
// valid; only the SK_DEF scalar-str operand is
// unindexable. cstage twin: check.c N_INDEX TY_STR arm.
if (e.lhs != nil && e.lhs.kind == syntax.nkind.N_IDENT) {
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.lhs.str);
let s: *syntax.sym = lookupvisible(c, e.lhs.str);
if (s != nil && s.skind == syntax.skind.SK_DEF) {
cerr("error: cannot index a def-constant str '");
cerr(e.lhs.str);
@@ -3536,8 +3731,22 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
// bare `error` then binds strconv.error not io.error). Mirrors
// cstage cmd/wcc/check.c:66 scope_lookup_prefer; sibling #56 at
// L2439, #53 at L688. Tracked in the cluster note at L685-687.
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.str);
if (s == nil) { return nil; };
let s: *syntax.sym = lookupvisible(c, e.str);
if (s == 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) {
cerr(e.file); cerr(":");
cerr(strconv.i32tos(e.line, strconv.base.DEC)); cerr(":");
cerr(strconv.i32tos(e.col, strconv.base.DEC));
cerr(": error: undefined: "); cerr(e.str); cerr("\n");
c.errs += 1;
e.type_ = c.tc.tyerr: *void;
};
return nil;
};
if (s.decl == nil) { return nil; };
// #34: a bare fn-name rvalue types as its FN TYPE, not its return
// type. decl.lhs is the RETURN type for an N_FNDECL, so synthesize
@@ -3632,6 +3841,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
return e.rhs;
};
if (k == syntax.nkind.N_CALL) {
if (e.type_ == c.tc.tyerr: *void) { return nil; };
let callee: *syntax.node = e.lhs;
if (callee == nil) { return nil; };
// #31: synthesize the `alloc(value)` / `alloc([], n)` builtin
@@ -3809,7 +4019,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
// The TY_ERR stamp on the callee is cgen's routing key
// (cstage spells it `n->lhs->type = ty_err`).
if (syntax.streq(callee.str, "abort")
&& syntax.scopelookupprefer(c.cur, c.curmod, "abort") == nil) {
&& lookupvisible(c, "abort") == nil) {
if (e.list != nil) {
let mt: *syntax.node = exprtype(c, e.list, nil);
let conf: bool = false;
@@ -3828,7 +4038,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
return tn;
};
if (syntax.streq(callee.str, "assert") && e.list != nil
&& syntax.scopelookupprefer(c.cur, c.curmod, "assert") == nil) {
&& lookupvisible(c, "assert") == nil) {
let ct: *syntax.node = exprtype(c, e.list, nil);
if (ct != nil) {
// No alias peel: cstage compares ty_bool by
@@ -4010,11 +4220,11 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
if (nm.len > 0) {
let s: *syntax.sym = nil;
if (callee.kind == syntax.nkind.N_IDENT) {
s = syntax.scopelookupprefer(c.cur, c.curmod, nm);
s = lookupvisible(c, nm);
} else {
let ms: *syntax.sym = nil;
if (callee.lhs != nil && callee.lhs.kind == syntax.nkind.N_IDENT) {
ms = syntax.scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
ms = lookupvisible(c, callee.lhs.str);
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, callee.lhs.str);
if (mu != nil) { ms = mu; };
@@ -4036,7 +4246,17 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
// global-leaf path) and harec check_autodereference
// (ref/harec/src/check.c:1566-1581).
if (ms != nil && (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32)) {
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
let mk: str = modkeyfor(c, callee.lhs.str);
if (mk.len == 0) {
if (ms.skind == syntax.skind.SK_USE) {
packageaccesserr(c, callee, callee.lhs.str, nm, false);
e.type_ = c.tc.tyerr: *void;
callee.type_ = c.tc.tyerr: *void;
return nil;
};
} else {
s = syntax.scopelookupinmodule(c.cur, mk, nm);
};
// 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-
@@ -4050,6 +4270,10 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
// keys the CALL off run's `//ww:module test` directive
// either way, so the resolution change is asm-neutral.
if (s == nil) {
if (c.sepmode != 0 && ms.skind == syntax.skind.SK_USE && mk.len != 0
&& !synthesizedtestrun(c, callee)) {
packageaccesserr(c, callee, callee.lhs.str, nm, true);
};
e.type_ = c.tc.tyerr: *void;
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
return nil;
@@ -4106,6 +4330,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
return nil;
};
if (k == syntax.nkind.N_DOT) {
if (e.type_ == c.tc.tyerr: *void) { return nil; };
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
// in A.6.1.5b. SK_USE gates case 1; a #6a-D dot-lhs collision
@@ -4120,7 +4345,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
// ref/harec/src/check.c:4419-4434.
let lhsn: *syntax.node = e.lhs;
if (lhsn != nil) { if (lhsn.kind == syntax.nkind.N_IDENT) {
let ms: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, lhsn.str);
let ms: *syntax.sym = lookupvisible(c, lhsn.str);
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, lhsn.str);
if (mu != nil) { ms = mu; };
@@ -4133,7 +4358,16 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
// qualified resolution and the lenient checker policy
// keeps the silent miss documented at scruttype L656.
if (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32) {
let fs: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, lhsn.str), e.str);
let mk: str = modkeyfor(c, lhsn.str);
if (mk.len == 0 && ms.skind == syntax.skind.SK_USE) {
packageaccesserr(c, e, lhsn.str, e.str, false);
e.type_ = c.tc.tyerr: *void;
return nil;
};
let fs: *syntax.sym = nil;
if (mk.len != 0) {
fs = syntax.scopelookupinmodule(c.cur, mk, e.str);
};
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
@@ -4165,6 +4399,12 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
return tn;
};
}; };
if (c.sepmode != 0 && fs == nil && ms.skind == syntax.skind.SK_USE && mk.len != 0
&& !synthesizedtestrun(c, e)) {
packageaccesserr(c, e, lhsn.str, e.str, true);
e.type_ = c.tc.tyerr: *void;
return nil;
};
};
// Fold case 2 inner: bare `EnumT.MEMBER` where EnumT
// is an SK_TYPE in the flat scope. Mirror cstage
@@ -4310,7 +4550,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
// style anonymous struct lit we don't yet parse — bail.
if (e.lhs == nil) { return nil; };
if (e.lhs.kind == syntax.nkind.N_IDENT) {
let ms: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.lhs.str);
let ms: *syntax.sym = lookupvisible(c, e.lhs.str);
if (ms != nil) { if (ms.skind == syntax.skind.SK_TYPE) { if (ms.decl != nil) {
let tn: *syntax.node = ms.decl.lhs;
if (tn != nil) {
@@ -4906,7 +5146,7 @@ fn assignableaddrfn(c: *checker, dst: *syntax.node, rhs: *syntax.node) bool = {
// silently admitting a *fn into a foreign-sig slot or rejecting a
// valid same-module &fn. cstage uses scope_lookup_prefer with
// cur_mod (cmd/wcc/check.c:410, assignable_addrfn).
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, id.str);
let s: *syntax.sym = lookupvisible(c, id.str);
if (s == nil) { return false; };
if (s.skind != syntax.skind.SK_FN) { return false; };
if (s.decl == nil) { return false; };
@@ -6043,7 +6283,7 @@ fn desugararrayslice(c: *checker, dsttn: *syntax.node, srctn: *syntax.node, val:
fn calleefndecl(c: *checker, callee: *syntax.node) *syntax.node = {
if (callee == nil) { return nil; };
if (callee.kind == syntax.nkind.N_IDENT) {
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, callee.str);
let s: *syntax.sym = lookupvisible(c, callee.str);
if (s != nil) {
if (s.skind == syntax.skind.SK_FN) { return s.decl; };
};
@@ -6052,14 +6292,18 @@ fn calleefndecl(c: *checker, callee: *syntax.node) *syntax.node = {
if (callee.kind == syntax.nkind.N_DOT) {
if (callee.lhs != nil) {
if (callee.lhs.kind == syntax.nkind.N_IDENT) {
let ms: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
let ms: *syntax.sym = lookupvisible(c, callee.lhs.str);
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, callee.lhs.str);
if (mu != nil) { ms = mu; };
};
if (ms != nil) {
if (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32) {
let fs: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), callee.str);
let fs: *syntax.sym = nil;
let mk: str = modkeyfor(c, callee.lhs.str);
if (mk.len != 0) {
fs = syntax.scopelookupinmodule(c.cur, mk, callee.str);
};
if (fs != nil) {
if (fs.skind == syntax.skind.SK_FN) { return fs.decl; };
};
@@ -6218,7 +6462,7 @@ fn checkassign(c: *checker, n: *syntax.node) void = {
// cmd/wcc/check.c:1889-1896.
if (n.lhs.kind == syntax.nkind.N_IDENT) {
if (n.lhs.str.len > 0) {
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, n.lhs.str);
let s: *syntax.sym = lookupvisible(c, n.lhs.str);
if (s != nil) {
if (s.is_const != 0i32) {
cerr("error: cannot assign to const binding\n");
@@ -6763,7 +7007,7 @@ fn exprtypeoftry(c: *checker, e: *syntax.node) *syntax.node = {
// either spuriously rejecting valid `?` code or skipping the F8
// reject. Mirrors exprtype's own N_IDENT arm (scopelookupprefer
// at :2897); cstage types the operand via cexpr with cur_mod.
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.str);
let s: *syntax.sym = lookupvisible(c, e.str);
if (s == nil) { return nil; };
if (s.decl == nil) { return nil; };
return s.decl.lhs;
@@ -6791,7 +7035,7 @@ fn exprtypeoftry(c: *checker, e: *syntax.node) *syntax.node = {
// N_CALL arm (scopelookupprefer at :3336). The N_DOT-callee leaf
// (callee.str above) still resolves bare-leaf, not via the module
// qualifier callee.lhs.str — that module-keyed fix rides task #51.
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, nm);
let s: *syntax.sym = lookupvisible(c, nm);
if (s == nil) { return nil; };
if (s.skind != syntax.skind.SK_FN) { return nil; };
if (s.decl == nil) { return nil; };
@@ -7028,7 +7272,7 @@ fn isassertfam(c: *checker, id: *syntax.node) bool = {
if (!syntax.streq(id.str, "abort") && !syntax.streq(id.str, "assert")) {
return false;
};
return syntax.scopelookupprefer(c.cur, c.curmod, id.str) == nil;
return lookupvisible(c, id.str) == nil;
};
// asserttyped — post-checker invariant gate (#15, A.6.2.1e). Walks the
@@ -7089,7 +7333,10 @@ fn asserttyped(c: *checker, n: *syntax.node, indot: bool) void = {
if (isexpr && k == syntax.nkind.N_IDENT) {
if (indot) { skip = true; };
if (!skip) {
let s: *syntax.sym = syntax.scopelookup(c.cur, n.str);
// Use the same source-visibility rules as expression
// resolution. A transitive same-leaf declaration must not hide
// a decl-less pseudo-builtin from this invariant gate.
let s: *syntax.sym = lookupvisible(c, n.str);
if (s != nil) {
if (s.skind == syntax.skind.SK_USE) { skip = true; };
if (s.decl == nil) { skip = true; };
@@ -7146,6 +7393,8 @@ export 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.sepmode = 0i32; // caller (w6c main) sets it from -c
c.synthtestrun = nil;
c.verbose = 0;
c.fnret = nil;
let empty: str;
@@ -7395,6 +7644,7 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
did.str = "test";
dot.lhs = did;
dot.str = "run";
c.synthtestrun = dot;
call.lhs = dot;
call.list = arg;
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);

View File

@@ -668,10 +668,14 @@ export fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
};
wwisortdecls(upaths, unodes, nuse);
let i: i32 = 0;
let previous: str = "";
for (i < nuse) {
wputs(fd, "import ");
wputs(fd, upaths[i]);
wputs(fd, ";\n");
if (previous.len == 0 || !syntax.streq(previous, upaths[i])) {
wputs(fd, "import ");
wputs(fd, upaths[i]);
wputs(fd, ";\n");
previous = upaths[i];
};
i += 1;
};
};