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 @@
// (the package, not the file, is the unit of testing).
package strings_test;
import bytes;
import strings;
import os;
import test;

View File

@@ -77,9 +77,26 @@ fn accepttok(p: *parser, k: tkind) bool = {
return false;
};
fn putdec(v: i32) void = {
let digits: [16]u8;
let n: i32 = 0;
let x: i32 = v;
if (x <= 0) { digits[n] = '0'; n += 1; }
else {
for (x > 0) {
digits[n] = ((x % 10) + ('0': i32)): u8;
n += 1;
x = x / 10;
};
};
for (n > 0) { n -= 1; os.write(2, &digits[n], 1u64); };
};
fn errmsg(p: *parser, msg: str) void = {
let pre = "parse: ";
os.write(2, pre.ptr, pre.len: u64);
os.write(2, p.curfile.ptr, p.curfile.len: u64);
os.write(2, ":".ptr, 1u64); putdec(p.curline);
os.write(2, ":".ptr, 1u64); putdec(p.curcol);
os.write(2, ": error: ".ptr, 9u64);
os.write(2, msg.ptr, msg.len: u64);
os.write(2, "\n".ptr, 1u64);
p.errs += 1;
@@ -91,11 +108,12 @@ fn expecttok(p: *parser, k: tkind, what: str) bool = {
return false;
};
// Returns the empty str on error (and advances to make progress).
// Returns false without consuming the unexpected token; the enclosing
// production owns recovery, matching the C frontend.
fn expectident(p: *parser, into: *str) bool = {
if (p.curkind != tkind.TK_IDENT) {
errmsg(p, "expected identifier");
advance(p);
errmsg(p, strings.concat("expected identifier, got ",
tokname(p.curkind)));
return false;
};
*into = p.curtext;
@@ -413,6 +431,153 @@ fn isassignop(k: tkind) bool = {
return false;
};
fn skipimportdecl(p: *parser) void = {
let paren: i32 = 0;
let bracket: i32 = 0;
let brace: i32 = 0;
for (p.curkind != tkind.TK_EOF) {
if (p.curkind == tkind.TK_LPAREN) { paren += 1; }
else { if (p.curkind == tkind.TK_RPAREN) {
if (paren > 0) { paren -= 1; };
} else { if (p.curkind == tkind.TK_LBRACK) { bracket += 1; }
else { if (p.curkind == tkind.TK_RBRACK) {
if (bracket > 0) { bracket -= 1; };
} else { if (p.curkind == tkind.TK_LBRACE) { brace += 1; }
else { if (p.curkind == tkind.TK_RBRACE) {
if (brace > 0) { brace -= 1; };
} else { if (p.curkind == tkind.TK_SEMI) {
advance(p);
if (paren == 0 && bracket == 0 && brace == 0) { return; };
continue;
};};};};};};};
advance(p);
};
};
// Consume attributes without parsing their argument expressions. The
// imports-only pass only needs to recognize and reject an attributed import.
fn skipimportattrs(p: *parser) void = {
for (p.curkind == tkind.TK_AT) {
advance(p);
if (p.curkind == tkind.TK_IDENT) { advance(p); }
else {
errmsg(p, strings.concat("expected identifier, got ",
tokname(p.curkind)));
};
if (p.curkind == tkind.TK_LPAREN) {
advance(p);
let depth: i32 = 1;
for (p.curkind != tkind.TK_EOF && depth > 0) {
if (p.curkind == tkind.TK_LPAREN) { depth += 1; }
else { if (p.curkind == tkind.TK_RPAREN) { depth -= 1; }; };
advance(p);
};
if (depth > 0) { errmsg(p, "expected ')' after attribute"); };
};
};
};
export fn parseimports(p: *parser) *node = {
let f = newnode(nkind.N_FILE, p.curfile, 1, 1);
let head: *node = nil;
let tail: *node = nil;
let packages: *node = nil;
let packagetail: *node = nil;
let sawpackage: bool = false;
for (p.curkind != tkind.TK_EOF) {
// Compiler/driver bundle markers carry package identity out of
// band. They are not source declarations, so keep scanning for
// the following package clause and imports.
if (p.curkind == tkind.TK_MODPATH) {
sawpackage = false;
p.pathmod = p.curtext;
p.curmod = p.curtext;
p.resetmod = "";
advance(p);
continue;
};
if (p.curkind == tkind.TK_MODRESET) {
let rp: str = p.curtext;
sawpackage = false;
advance(p);
p.pathmod = "";
p.curmod = rp;
p.resetmod = rp;
continue;
};
if (p.curkind == tkind.TK_MODULE) {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
advance(p);
if (p.curkind != tkind.TK_IDENT) {
errmsg(p, "invalid or missing package clause");
sawpackage = true;
skipimportdecl(p);
continue;
};
let name: str;
expectident(p, &name);
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
p.curmod = name;
let pm = newnode(nkind.N_FILE, pf, pl, pc);
pm.nmod = name;
if (packages == nil) { packages = pm; }
else { packagetail.next = pm; };
packagetail = pm;
if (!sawpackage) {
f.nmod = name;
f.file = pf;
f.line = pl;
f.col = pc;
sawpackage = true;
};
continue;
};
if (!sawpackage && p.pathmod.len == 0 && p.resetmod.len == 0) {
errmsg(p, "invalid or missing package clause");
sawpackage = true;
};
if (p.curkind == tkind.TK_USE) {
let d: *node = parseuse(p);
d.nmod = p.curmod;
if (head == nil) { head = d; } else { tail.next = d; };
tail = d;
continue;
};
if (p.curkind == tkind.TK_AT) {
skipimportattrs(p);
if (p.curkind == tkind.TK_EXPORT) { advance(p); };
if (p.curkind == tkind.TK_USE) {
errmsg(p, "import cannot be exported or attributed");
let d: *node = parseuse(p);
d.nmod = p.curmod;
if (head == nil) { head = d; } else { tail.next = d; };
tail = d;
continue;
};
skipimportdecl(p);
continue;
};
if (p.curkind == tkind.TK_EXPORT) {
advance(p);
if (p.curkind == tkind.TK_USE) {
errmsg(p, "import cannot be exported or attributed");
let d: *node = parseuse(p);
d.nmod = p.curmod;
if (head == nil) { head = d; } else { tail.next = d; };
tail = d;
continue;
};
};
skipimportdecl(p);
};
f.list = head;
// body carries package-clause markers; list remains imports only.
f.body = packages;
return f;
};
// Forward references between parseunary/parseexpr/parsebin/parsepostfix
// are resolved by the two-pass checker — no body-less prototypes needed.
@@ -523,6 +688,9 @@ export fn parsefile(p: *parser) *node = {
let d: *node = nil;
if (p.curkind == tkind.TK_USE) {
if (attrs != nil || exported != 0) {
errmsg(p, "import cannot be exported or attributed");
};
d = parseuse(p);
} else { if (p.curkind == tkind.TK_DEF) {
d = parsedef(p, exported);

View File

@@ -110,11 +110,9 @@ export fn scopelookup(s: *scope, name: str) *sym = {
// scopelookupinmodule(c, leaf, leaf) won't find it.
//
// #58/#50: within each scope, Pass-1 prefers an SK_TYPE whose `sym.mod`
// matches `mod`; Pass-2 falls back to the first SK_TYPE regardless of
// mod (chain-first, the prior behavior). scopedefineinmodule PREPENDS,
// so chain-first = last-registered — when two modules export the same
// type leaf the bare walk silently picked the newest-installed one,
// install-order-dependent, while cstage is deterministic on cur_mod.
// matches `mod`; Pass-2 accepts only a primary-package or builtin type. The
// synthesized global `nomem` has an empty source location and is builtin too.
// Flattened transitive interface types are never an unqualified fallback.
// Mirrors cstage cmd/wcc/sym.c scope_lookup_type(s, mod, name) (the
// kind-filtered + mod-preferring single walk); sole caller passes
// c.curmod. i32-correct: streq throughout, only `.len > 0` guards (the
@@ -136,7 +134,12 @@ export fn scopelookuptype(s: *scope, mod: str, name: str) *sym = {
};
};
};
if (fallback == nil) { fallback = b; };
let builtin: bool = b.decl == nil;
if (!builtin && b.decl.file.len == 0 && b.decl.line == 0) {
builtin = true;
};
if (b.mod.len == 0 && (mod.len == 0 || builtin)
&& fallback == nil) { fallback = b; };
};
};
b = b.hashnext;
@@ -220,8 +223,10 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
};
// Within each scope's bucket: Pass 1 prefers entries whose
// `sym.mod` matches `mod`; Pass 2 falls back to the first match
// regardless of mod (same semantics as scopelookup). We only descend
// `sym.mod` matches `mod`; Pass 2 accepts only a source-visible bare entry.
// For an imported package that means a lexical local (child scope) or a
// decl-less builtin, never another package's flattened interface symbol.
// We only descend
// to the parent scope when the current scope has no matching entry at
// all — so a local binding in a closer scope still shadows a same-name
// fn from a parent scope, even when the parent entry mod-matches.
@@ -234,7 +239,6 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
// io.read that happens to hash earlier into the flat scope. Mirrors
// cmd/wcc/sym.c scope_lookup_prefer.
export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
if (mod.len == 0) { return scopelookup(s, name); };
let p: *scope = s;
for (p != nil) {
let h: u64 = hashstr(name);
@@ -243,12 +247,13 @@ export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
let fallback: *sym = nil;
for (b != nil) {
if (streq(b.name, name)) {
if (b.mod.len > 0) {
if (mod.len > 0 && b.mod.len > 0) {
if (streq(b.mod, mod)) {
return b;
};
};
if (fallback == nil) { fallback = b; };
if (b.mod.len == 0 && (mod.len == 0 || p.parent != nil
|| b.decl == nil) && fallback == nil) { fallback = b; };
};
b = b.hashnext;
};