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

@@ -360,15 +360,23 @@ fn canonicalpkgname(path: str, name: str) bool = {
return true;
};
// A paired interface owns PATH's declared name. Compiler-private names keep
// transitive fact sections semantic and are ignored when a real name exists.
// Package-marker facts are canonical-origin keyed across all supplied
// interfaces; an interface may embed transitive origins besides its container
// owner. Compiler-private names remain recovery metadata and are ignored when
// a real declared name exists.
type pkgnamestate = enum i32 {
PKGNAME_MISSING = 0,
PKGNAME_VALID = 1,
PKGNAME_CONFLICT = 2,
PKGNAME_INVALID = 3,
};
fn importpkgname(asts: []*syntax.node, paths: []*u8, nasts: i32, path: str,
primary: *syntax.node, conflict: *bool) str = {
primary: *syntax.node, resolved: *str) pkgnamestate = {
let name: str;
let placeholder: str;
let i: i32 = 0;
for (i < nasts) {
if (!cstreq(paths[i], path)) { i += 1; continue; };
let f: *syntax.node = asts[i];
let p: *syntax.node = nil;
if (f != nil) { p = f.body; };
@@ -379,9 +387,7 @@ fn importpkgname(asts: []*syntax.node, paths: []*u8, nasts: i32, path: str,
placeholder = p.pkgname;
} else { if (name.len > 0
&& !syntax.streq(name, p.pkgname)) {
*conflict = true;
let empty: str;
return empty;
return pkgnamestate.PKGNAME_CONFLICT;
} else { name = p.pkgname; }; };
};
p = p.next;
@@ -396,45 +402,70 @@ fn importpkgname(asts: []*syntax.node, paths: []*u8, nasts: i32, path: str,
if (canonicalpkgname(path, p.pkgname)) {
placeholder = p.pkgname;
} else { if (name.len > 0 && !syntax.streq(name, p.pkgname)) {
*conflict = true;
let empty: str;
return empty;
return pkgnamestate.PKGNAME_CONFLICT;
} else { name = p.pkgname; }; };
};
p = p.next;
};
if (name.len == 0) { name = placeholder; };
return name;
*resolved = name;
if (name.len == 0) { return pkgnamestate.PKGNAME_MISSING; };
if (syntax.streq(name, "_")) { return pkgnamestate.PKGNAME_INVALID; };
return pkgnamestate.PKGNAME_VALID;
};
fn pathleaf(path: str) str = {
let start: i32 = 0;
let i: i32 = 0;
for (i < path.len) {
if (path[i] == '.') { start = i + 1; };
i += 1;
};
let leaf: str;
leaf.ptr = path.ptr + (start: u64);
leaf.len = path.len - start;
return leaf;
};
fn bindimportnames(list: *syntax.node, asts: []*syntax.node, paths: []*u8,
nasts: i32,
primary: *syntax.node, testsupport: *u8) bool = {
primary: *syntax.node, testsupport: *u8,
externalsupport: *syntax.node) bool = {
let u: *syntax.node = list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.usepath.len > 0) {
let reserved: bool = testsupport != nil
&& cstreq(testsupport, "__wwtest")
&& syntax.streq(u.usepath, "__wwtest");
if (!reserved) {
let conflict: bool = false;
let name: str = importpkgname(asts, paths, nasts, u.usepath,
primary, &conflict);
if (conflict) {
let name: str;
let state: pkgnamestate = importpkgname(asts, paths, nasts,
u.usepath, primary, &name);
if (state == pkgnamestate.PKGNAME_CONFLICT) {
let pre: str = "w6c: package ";
let post: str = " has conflicting declared names in export data\n";
os.write(2, pre.ptr, pre.len: u64);
os.write(2, u.usepath.ptr, u.usepath.len: u64);
os.write(2, post.ptr, post.len: u64);
return false;
};
if (name.len > 0) {
u.usepkgname = name;
if (u.useblank == 0) {
};
if (state == pkgnamestate.PKGNAME_VALID
|| state == pkgnamestate.PKGNAME_INVALID) {
u.usepkgname = name;
if (state == pkgnamestate.PKGNAME_INVALID) {
u.used = 1;
if (u.useblank == 0 && !reserved) {
if (u.usealias.len > 0) { u.str = u.usealias; }
else { u.str = name; };
else { u.str = pathleaf(u.usepath); };
};
} else { if (u.imported == 0) {
} else { if (u.useblank == 0 && !reserved) {
if (u.usealias.len > 0) { u.str = u.usealias; }
else { u.str = name; };
}; };
} else { if (u == externalsupport) {
// A bare direct -T compiler invocation deliberately leaves the
// compiler-generated support hook external.
u.used = 1;
} else { if (u.imported == 0) {
let pre: str = "w6c: import ";
let post: str = " has no declared package name in direct export data\n";
os.write(2, pre.ptr, pre.len: u64);
@@ -449,14 +480,114 @@ fn bindimportnames(list: *syntax.node, asts: []*syntax.node, paths: []*u8,
if (u.usealias.len > 0) { u.str = u.usealias; }
else { u.str = u.usepath; };
};
}; };
};
}; }; };
};
u = u.next;
};
return true;
};
fn pathsethas(paths: []str, path: str) bool = {
if (path.len == 0) { return false; };
let i: i32 = 0;
for (i < paths.len) {
if (syntax.streq(paths[i], path)) { return true; };
i += 1;
};
return false;
};
fn pathsetadd(paths: *[]str, path: str) bool = {
if (path.len == 0 || pathsethas(*paths, path)) { return false; };
append(*paths, path);
return true;
};
fn materializetestsupport(file: *syntax.node, testmode: i32,
testsupport: *u8) *syntax.node = {
if (testmode == 0 || testsupport == nil) { return nil; };
let path: str = pathstr(testsupport);
let u: *syntax.node = file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.imported == 0
&& u.sourceid == file.sourceid
&& syntax.streq(u.usepath, path)) {
u.used = 1;
return nil;
};
u = u.next;
};
u = syntax.newnode(syntax.nkind.N_USE, file.file, file.line, file.col);
u.str = path;
u.usesource = path;
u.usepath = path;
u.usefile = file.file;
u.useline = file.line;
u.usecol = file.col;
u.usepathfile = file.file;
u.usepathline = file.line;
u.usepathcol = file.col;
u.pkgname = file.pkgname;
u.sourceid = file.sourceid;
u.used = 1;
u.next = file.list;
file.list = u;
return u;
};
fn computereachedimports(reached: *[]str, primary: *syntax.node,
asts: []*syntax.node, paths: []*u8, nasts: i32) void = {
let u: *syntax.node = nil;
if (primary != nil) { u = primary.list; };
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.imported == 0) {
pathsetadd(reached, u.usepath);
};
u = u.next;
};
let changed: bool = true;
for (changed) {
changed = false;
let i: i32 = 0;
for (i < nasts) {
u = nil;
if (asts[i] != nil) { u = asts[i].list; };
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.imported != 0
&& u.nmod.len > 0 && u.usepath.len > 0
&& pathsethas(*reached, u.nmod)) {
let name: str;
if (importpkgname(asts, paths, nasts, u.nmod, nil,
&name) == pkgnamestate.PKGNAME_VALID
&& pathsetadd(reached, u.usepath)) {
changed = true;
};
};
u = u.next;
};
i += 1;
};
};
};
fn filterreachedimports(file: *syntax.node, reached: []str,
asts: []*syntax.node, paths: []*u8, nasts: i32) void = {
let prev: *syntax.node = nil;
let d: *syntax.node = file.list;
for (d != nil) {
let next: *syntax.node = d.next;
let name: str;
let keep: bool = d.imported != 0 && d.nmod.len > 0
&& pathsethas(reached, d.nmod)
&& importpkgname(asts, paths, nasts, d.nmod, nil,
&name) == pkgnamestate.PKGNAME_VALID;
if (keep) { prev = d; }
else { if (prev == nil) { file.list = next; }
else { prev.next = next; }; };
d = next;
};
};
export fn main(argc: i32, argv: **u8) i32 = {
let src: *u8 = nil;
let out: *u8 = nil;
@@ -758,8 +889,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
targeti += 1;
};
let importhead: *syntax.node = nil;
let importtail: *syntax.node = nil;
importi = 0;
for (importi < nimports) {
let ibuf: *u8;
@@ -800,15 +929,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
let imported: *syntax.node = syntax.parsefile(&ips);
if (il.errs > 0 || ips.errs > 0) { return 1; };
importasts[importi] = imported;
if (!bindimportnames(imported.list, importasts, importpaths, importi + 1,
nil, testsupport)) { return 1; };
let d: *syntax.node = imported.list;
if (d != nil) {
if (importhead == nil) { importhead = d; }
else { importtail.next = d; };
for (d.next != nil) { d = d.next; };
importtail = d;
};
importi += 1;
};
@@ -860,17 +980,34 @@ export fn main(argc: i32, argv: **u8) i32 = {
};
mapi += 1;
};
// Test compilation has a compiler-required support edge even when no
// equivalent source import occurrence exists. Make that primary root
// explicit before resolving interface metadata.
let externalsupport: *syntax.node = materializetestsupport(f, testmode,
testsupport);
// Standalone interfaces are metadata containers, never graph roots. Reach
// only from primary/compiler-required uses and traverse uses owned by an
// already-reached package whose declared name is valid.
let reached: []str = alloc([], 16u64)!;
computereachedimports(&reached, f, importasts, importpaths, nimports);
importi = 0;
for (importi < nimports) {
filterreachedimports(importasts[importi], reached, importasts,
importpaths, nimports);
importi += 1;
};
// A later direct interface may supply metadata for an origin section used
// by an earlier interface, so bind once more against the complete set.
// by an earlier interface. Bind each now-bounded reached list against the
// complete metadata set, before any concatenation can widen that list.
importi = 0;
for (importi < nimports) {
if (!bindimportnames(importasts[importi].list, importasts, importpaths,
nimports,
nil, testsupport)) { return 1; };
nil, testsupport, nil)) { return 1; };
importi += 1;
};
if (!bindimportnames(f.list, importasts, importpaths, nimports, f,
testsupport)) {
testsupport, externalsupport)) {
return 1;
};
targeti = 0;
@@ -881,7 +1018,12 @@ export fn main(argc: i32, argv: **u8) i32 = {
if (targetuse.kind == syntax.nkind.N_USE
&& targetuse.imported == 0
&& syntax.streq(targetuse.usepath, testtargets[targeti])) {
targetuse.str = targetuse.usepath;
// Valid generated targets use their canonical path as the
// compiler-owned qualifier. Preserve an invalid provider's fake
// explicit-or-leaf spelling for source-local recovery.
if (!syntax.streq(targetuse.usepkgname, "_")) {
targetuse.str = targetuse.usepath;
};
seen += 1;
};
targetuse = targetuse.next;
@@ -893,6 +1035,19 @@ export fn main(argc: i32, argv: **u8) i32 = {
};
targeti += 1;
};
let importhead: *syntax.node = nil;
let importtail: *syntax.node = nil;
importi = 0;
for (importi < nimports) {
let d: *syntax.node = importasts[importi].list;
if (d != nil) {
if (importhead == nil) { importhead = d; }
else { importtail.next = d; };
for (d.next != nil) { d = d.next; };
importtail = d;
};
importi += 1;
};
if (importhead != nil) {
importtail.next = f.list;
f.list = importhead;