compiler: implement blank package name semantics

This commit is contained in:
2026-08-23 15:33:59 +09:00
parent e67b8e1bc4
commit 0b24b11d65
14 changed files with 2862 additions and 112 deletions

View File

@@ -179,19 +179,18 @@ fn invalidinitimport(u: *syntax.node) bool = {
&& syntax.streq(u.str, "init");
};
// Map a source-file qualifier to canonical identity. Looking up the marker for
// a possible DOT must not itself count as usage; only qualified resolution
// Map a source-file qualifier to its exact retained import occurrence. Looking
// up a possible DOT must not itself count as usage; only qualified resolution
// marks the owning occurrence.
fn findusepath(file: *syntax.node, modtag: str, source: i32, alias: str,
mark: bool) str = {
let empty: str;
if (file == nil) { return empty; };
if (alias.len == 0) { return empty; };
fn findusebinding(file: *syntax.node, modtag: str, source: i32, alias: str,
mark: bool) *syntax.node = {
if (file == nil) { return nil; };
if (alias.len == 0) { return nil; };
if (source == 0 && 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; };
if (syntax.streq(alias, leaf)) { return nil; };
};
let u: *syntax.node = file.list;
for (u != nil) {
@@ -204,15 +203,33 @@ fn findusepath(file: *syntax.node, modtag: str, source: i32, alias: str,
if (modtag.len == 0) {
if (um.len == 0) { same = true; };
} else { if (syntax.streq(um, modtag)) { same = true; }; };
if (same) {
if (mark) { u.used = 1; };
if (u.usepath.len != 0) { return u.usepath; };
return u.str;
if (same) {
if (mark) { u.used = 1; };
return u;
};
};
};
u = u.next;
};
return nil;
};
fn findusepath(file: *syntax.node, modtag: str, source: i32, alias: str,
mark: bool) str = {
let empty: str;
if (file == nil) { return empty; };
if (alias.len == 0) { return empty; };
if (source == 0 && 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 = findusebinding(file, modtag, source, alias, mark);
if (u != nil) {
if (u.usepath.len != 0) { return u.usepath; };
return u.str;
};
return empty;
};
@@ -298,6 +315,13 @@ fn localshadowsimport(c: *checker, name: str) bool = {
return false;
};
fn fakeimportalias(c: *checker, alias: str) bool = {
if (localshadowsimport(c, alias)) { return false; };
let u: *syntax.node = findusebinding(c.file, c.curmod, c.cursource,
alias, true);
return u != nil && syntax.streq(u.usepkgname, "_");
};
fn lookupvisible(c: *checker, name: str) *syntax.sym = {
let found: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, name);
let builtin: *syntax.sym = nil;
@@ -726,6 +750,14 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
c.nresolved += 1;
return;
};
if (strings.contains(nm, ".")) {
let (head, leaf) = strings.rcut(nm, ".");
if (fakeimportalias(c, head)) {
n.type_ = c.tc.tyerr: *void;
c.nresolved += 1;
return;
};
};
let s: *syntax.sym = lookupvisibletype(c, nm);
let builtin: bool = builtintypename(nm);
// `pkg.Type` — strip the last dot prefix and look up
@@ -2860,6 +2892,13 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
syntax.tinfocachebind(c.tc, n, c.tc.tyerr);
return c.tc.tyerr;
};
if (n.file.len != 0 && strings.contains(nm, ".")) {
let (head, leaf) = strings.rcut(nm, ".");
if (fakeimportalias(c, head)) {
syntax.tinfocachebind(c.tc, n, c.tc.tyerr);
return c.tc.tyerr;
};
};
if (syntax.streq(nm, "void")) { r = c.tc.tyvoid; };
if (syntax.streq(nm, "bool")) { r = c.tc.tybool; };
if (syntax.streq(nm, "rune")) { r = c.tc.tyrune; };
@@ -4217,6 +4256,14 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
e.type_ = c.tc.tyerr: *void;
return nil;
};
if (callee.kind == syntax.nkind.N_DOT && callee.lhs != nil
&& callee.lhs.kind == syntax.nkind.N_IDENT
&& fakeimportalias(c, callee.lhs.str)) {
callee.lhs.type_ = c.tc.tyerr: *void;
callee.type_ = c.tc.tyerr: *void;
e.type_ = c.tc.tyerr: *void;
return nil;
};
// A module-qualified leaf may already have been rejected while the
// N_DOT callee was checked on an earlier resolve walk. cstage caches
// that failure on the call; mirror its once-only diagnostic here rather
@@ -4738,6 +4785,12 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
// harec's enum-resolve constexpr set at
// ref/harec/src/check.c:4419-4434.
let lhsn: *syntax.node = e.lhs;
if (lhsn != nil && lhsn.kind == syntax.nkind.N_IDENT
&& fakeimportalias(c, lhsn.str)) {
lhsn.type_ = c.tc.tyerr: *void;
e.type_ = c.tc.tyerr: *void;
return nil;
};
if (lhsn != nil) { if (lhsn.kind == syntax.nkind.N_IDENT) {
let ms: *syntax.sym = lookupvisible(c, lhsn.str);
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
@@ -7446,6 +7499,14 @@ fn exprtypeoftry(c: *checker, e: *syntax.node) *syntax.node = {
// nkind.N_DOT). We need the fn-decl's lhs (return-type AST).
let callee: *syntax.node = e.lhs;
if (callee == nil) { return nil; };
if (callee.kind == syntax.nkind.N_DOT && callee.lhs != nil
&& callee.lhs.kind == syntax.nkind.N_IDENT
&& fakeimportalias(c, callee.lhs.str)) {
callee.lhs.type_ = c.tc.tyerr: *void;
callee.type_ = c.tc.tyerr: *void;
e.type_ = c.tc.tyerr: *void;
return nil;
};
let nm: str;
nm.ptr = nil; nm.len = 0;
if (callee.kind == syntax.nkind.N_IDENT) { nm = callee.str; };
@@ -7837,6 +7898,20 @@ fn importbindingdiagprefix(n: *syntax.node) void = {
cerr(strconv.i32tos(col, strconv.base.DEC)); cerr(": error: ");
};
fn importpathdiagprefix(n: *syntax.node) void = {
let file: str = n.usepathfile;
let line: i32 = n.usepathline;
let col: i32 = n.usepathcol;
if (file.len == 0) {
file = n.file;
line = n.line;
col = n.col;
};
cerr(file); cerr(":");
cerr(strconv.i32tos(line, strconv.base.DEC)); cerr(":");
cerr(strconv.i32tos(col, strconv.base.DEC)); cerr(": error: ");
};
fn importdiagalt(n: *syntax.node, name: str) void = {
cerr("\t"); cerr(n.file); cerr(":");
cerr(strconv.i32tos(n.line, strconv.base.DEC)); cerr(":");
@@ -7860,6 +7935,35 @@ fn rejectinitimports(c: *checker, file: *syntax.node) void = {
};
};
fn rejectinvalidimports(c: *checker, file: *syntax.node) void = {
let u: *syntax.node = file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE
&& u.usepath.len != 0
&& syntax.streq(u.usepkgname, "_")) {
u.used = 1;
let duplicate: bool = false;
let p: *syntax.node = file.list;
for (p != u) {
if (p.kind == syntax.nkind.N_USE && p.usepath.len != 0
&& syntax.streq(p.usepkgname, "_")
&& syntax.streq(p.usepath, u.usepath)) {
duplicate = true;
break;
};
p = p.next;
};
if (!duplicate) {
importpathdiagprefix(u);
cerr("could not import "); cerr(u.usepath);
cerr(" (invalid package name: \"_\")\n");
c.errs += 1;
};
};
u = u.next;
};
};
fn topdeclkind(d: *syntax.node) bool = {
return d != nil && (d.kind == syntax.nkind.N_TYPEDECL
|| d.kind == syntax.nkind.N_DEF || d.kind == syntax.nkind.N_FNDECL
@@ -9051,10 +9155,25 @@ fn checktesttarget(c: *checker, path: str) bool = {
return false;
};
fn rejectblankpackagenames(c: *checker, file: *syntax.node) void = {
let p: *syntax.node = file.body;
for (p != nil) {
if (p.kind == syntax.nkind.N_FILE
&& syntax.streq(p.pkgname, "_")) {
importdiagprefix(p);
cerr("invalid package name _\n");
c.errs += 1;
};
p = p.next;
};
};
fn checkfile(c: *checker, file: *syntax.node) void = {
if (file == nil) { return; };
if (file.kind != syntax.nkind.N_FILE) { return; };
c.file = file;
rejectblankpackagenames(c, file);
rejectinvalidimports(c, file);
// Under -T, prepend the dispatcher support import before Pass 1 so
// declmod keys the runner under the selected support module. This is a