ww: add file-scoped import aliases

This commit is contained in:
2026-08-14 10:56:34 +09:00
parent 351f4a25bc
commit 792b6ecbe5
14 changed files with 587 additions and 430 deletions

View File

@@ -99,26 +99,52 @@ fn allocnodeptrs(count: i32) ([]*syntax.node | nomem) = {
return value;
};
// Export data carries canonical owner and declared name independently. Each
// direct interface's package-clause markers also describe its reachable fact
// closure, so search all parsed interfaces for a canonical owner.
fn importpkgname(asts: []*syntax.node, nasts: i32, path: str,
fn canonicalpkgname(path: str, name: str) bool = {
let prefix: str = "__wwi_";
if (name.len != prefix.len + path.len * 2) { return false; };
let i: i32 = 0;
for (i < prefix.len) {
if (name[i] != prefix[i]) { return false; };
i += 1;
};
i = 0;
for (i < path.len) {
let hi: u8 = path[i] >> 4u8;
let lo: u8 = path[i] & 15u8;
let hib: u8 = hi + 48u8;
let lob: u8 = lo + 48u8;
if (hi >= 10u8) { hib = hi - 10u8 + 97u8; };
if (lo >= 10u8) { lob = lo - 10u8 + 97u8; };
if (name[prefix.len + i * 2] != hib
|| name[prefix.len + i * 2 + 1] != lob) { return false; };
i += 1;
};
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.
fn importpkgname(asts: []*syntax.node, paths: []*u8, nasts: i32, path: str,
primary: *syntax.node, conflict: *bool) str = {
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; };
for (p != nil) {
if (p.nmod.len > 0 && p.pkgname.len > 0
&& syntax.streq(p.nmod, path)) {
if (name.len > 0 && !syntax.streq(name, p.pkgname)) {
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;
};
name = p.pkgname;
} else { name = p.pkgname; }; };
};
p = p.next;
};
@@ -129,19 +155,22 @@ fn importpkgname(asts: []*syntax.node, nasts: i32, path: str,
for (p != nil) {
if (p.nmod.len > 0 && p.pkgname.len > 0
&& syntax.streq(p.nmod, path)) {
if (name.len > 0 && !syntax.streq(name, p.pkgname)) {
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;
};
name = p.pkgname;
} else { name = p.pkgname; }; };
};
p = p.next;
};
if (name.len == 0) { name = placeholder; };
return name;
};
fn bindimportnames(list: *syntax.node, asts: []*syntax.node, nasts: i32,
fn bindimportnames(list: *syntax.node, asts: []*syntax.node, paths: []*u8,
nasts: i32,
primary: *syntax.node, testsupport: *u8) bool = {
let u: *syntax.node = list;
for (u != nil) {
@@ -151,7 +180,7 @@ fn bindimportnames(list: *syntax.node, asts: []*syntax.node, nasts: i32,
&& syntax.streq(u.usepath, "__wwtest");
if (!reserved) {
let conflict: bool = false;
let name: str = importpkgname(asts, nasts, u.usepath,
let name: str = importpkgname(asts, paths, nasts, u.usepath,
primary, &conflict);
if (conflict) {
let pre: str = "w6c: package ";
@@ -162,7 +191,9 @@ fn bindimportnames(list: *syntax.node, asts: []*syntax.node, nasts: i32,
return false;
};
if (name.len > 0) {
u.str = name;
u.usepkgname = name;
if (u.usealias.len > 0) { u.str = u.usealias; }
else { u.str = name; };
} else { if (u.imported == 0) {
let pre: str = "w6c: import ";
let post: str = " has no declared package name in direct export data\n";
@@ -174,7 +205,8 @@ fn bindimportnames(list: *syntax.node, asts: []*syntax.node, nasts: i32,
// A closure-only import may have no declarations in this
// interface. Its canonical path must never become a leaf
// qualifier by fallback.
u.str = u.usepath;
if (u.usealias.len > 0) { u.str = u.usealias; }
else { u.str = u.usepath; };
}; };
};
};
@@ -425,8 +457,8 @@ export fn main(argc: i32, argv: **u8) i32 = {
};
};
let importhead: *node = nil;
let importtail: *node = nil;
let importhead: *syntax.node = nil;
let importtail: *syntax.node = nil;
importi = 0;
for (importi < nimports) {
let ibuf: *u8;
@@ -456,20 +488,20 @@ export fn main(argc: i32, argv: **u8) i32 = {
os.write(2, nl.ptr, nl.len: u64);
return 1;
};
let il: lex;
lexinit(&il, strings.dup(pathstr(importfiles[importi])), ibuf, ilen);
let ips: parser;
parserinit(&ips, &il);
let il: syntax.lex;
syntax.lexinit(&il, strings.dup(pathstr(importfiles[importi])), ibuf, ilen);
let ips: syntax.parser;
syntax.parserinit(&ips, &il);
let imod: str = pathstr(importpaths[importi]);
ips.pathmod = imod;
ips.curmod = imod;
if (testsupport != nil) { ips.testmodule = pathstr(testsupport); };
let imported: *node = parsefile(&ips);
let imported: *syntax.node = syntax.parsefile(&ips);
if (il.errs > 0 || ips.errs > 0) { return 1; };
importasts[importi] = imported;
if (!bindimportnames(imported.list, importasts, importi + 1,
if (!bindimportnames(imported.list, importasts, importpaths, importi + 1,
nil, testsupport)) { return 1; };
let d: *node = imported.list;
let d: *syntax.node = imported.list;
if (d != nil) {
if (importhead == nil) { importhead = d; }
else { importtail.next = d; };
@@ -487,25 +519,27 @@ export fn main(argc: i32, argv: **u8) i32 = {
os.write(2, m.ptr, m.len: u64);
return 1;
};
let l: lex;
lexinit(&l, strings.dup(pathstr(src)), buf, blen);
let ps: parser;
parserinit(&ps, &l);
let l: syntax.lex;
syntax.lexinit(&l, strings.dup(pathstr(src)), buf, blen);
let ps: syntax.parser;
syntax.parserinit(&ps, &l);
if (testsupport != nil) { ps.testmodule = pathstr(testsupport); };
// Entry compilation classifies an ordinary command package. The explicit
// marker carries only that parser fact for non-entry command test variants.
ps.commandpackage = commandpackage != 0 || entrymode != 0;
let f: *node = parsefile(&ps);
let f: *syntax.node = syntax.parsefile(&ps);
// Gate cgen on parse-stage errors. Mirrors cmd/w6c/main.c's
// `if (l.errs || p.errs) return 1;` — broken AST otherwise reaches
// cgen and emits junk asm with a zero exit (silent miscompile).
if (l.errs > 0 || ps.errs > 0) { return 1; };
let use: *node = f.list;
let use: *syntax.node = f.list;
for (use != nil) {
if (use.kind == syntax.nkind.N_USE && use.usepath.len != 0) {
let source: str = use.usesource;
if (source.len == 0) { source = use.usepath; };
mapi = 0;
for (mapi < nmaps) {
if (syntax.streq(use.usepath,
if (syntax.streq(source,
pathstr(importmaps[mapi].source))) {
use.usepath = pathstr(importmaps[mapi].path);
importmaps[mapi].seen = true;
@@ -529,11 +563,13 @@ export fn main(argc: i32, argv: **u8) i32 = {
// by an earlier interface, so bind once more against the complete set.
importi = 0;
for (importi < nimports) {
if (!bindimportnames(importasts[importi].list, importasts, nimports,
if (!bindimportnames(importasts[importi].list, importasts, importpaths,
nimports,
nil, testsupport)) { return 1; };
importi += 1;
};
if (!bindimportnames(f.list, importasts, nimports, f, testsupport)) {
if (!bindimportnames(f.list, importasts, importpaths, nimports, f,
testsupport)) {
return 1;
};
if (testtarget != nil) {

View File

@@ -586,8 +586,15 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool,
};
if (!aistagged) {
widensz = slotsize(c, ptype);
let tagged: *syntax.node = resolvetagged(c, ptype);
let t: i32 = taggedvariantindex(c, tagged, arg);
// The parameter may come from an imported signature whose
// qualifier is owned by that interface source, not by the
// caller currently being emitted. The checker has already
// resolved and stamped its canonical type; use that identity
// directly instead of re-resolving source-local alias text in
// the caller's scope. Mirrors cstage's Type-keyed
// cg_tag_for_variant path.
let t: i32 = taggedvariantindext(c,
ptype.type_: *syntax.tinfo, arg);
// tagged→tagged subset sources synthesize
// no scalar tag here (the widen store
// remaps); only a CONCRETE source with no

View File

@@ -173,12 +173,11 @@ fn declmod(file: *syntax.node, d: *syntax.node) str = {
return empty;
};
// usepath — map a source-file default qualifier (the imported package's
// declared name) to the full canonical import path it binds, for
// module-qualified resolution and codegen hint (M1 #22). Only an import owned
// by the referencing file is visible; a matching
// alias carried by a transitive interface is deliberately ignored.
fn usepathfor(file: *syntax.node, modtag: str, source: i32, alias: str) str = {
// 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
// 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; };
@@ -198,7 +197,7 @@ fn usepathfor(file: *syntax.node, modtag: str, source: i32, alias: str) str = {
if (um.len == 0) { same = true; };
} else { if (syntax.streq(um, modtag)) { same = true; }; };
if (same) {
u.used = 1;
if (mark) { u.used = 1; };
if (u.usepath.len != 0) { return u.usepath; };
return u.str;
};
@@ -209,6 +208,10 @@ fn usepathfor(file: *syntax.node, modtag: str, source: i32, alias: str) str = {
return empty;
};
fn usepathfor(file: *syntax.node, modtag: str, source: i32, alias: str) str = {
return findusepath(file, modtag, source, alias, true);
};
// 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 = {
@@ -250,31 +253,6 @@ fn srcimports(file: *syntax.node, modtag: str, source: i32, 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 u: *syntax.node = c.file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.sourceid == c.cursource) {
let um: str = declmod(c.file, u);
let same: bool = false;
if (c.curmod.len == 0) { same = um.len == 0; }
else { same = syntax.streq(um, c.curmod); };
let path: str = u.usepath;
if (path.len == 0) { path = u.str; };
if (same && syntax.streq(path, mod)) {
u.used = 1;
return true;
};
};
u = u.next;
};
return false;
};
fn lookupvisible(c: *checker, name: str) *syntax.sym = {
let found: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, name);
let builtin: *syntax.sym = nil;
@@ -282,10 +260,10 @@ fn lookupvisible(c: *checker, name: str) *syntax.sym = {
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, c.cursource, name).len != 0) {
// Flat scope installation may coalesce equal qualifiers from distinct
// files. A bare mention only locates the marker; the DOT resolution owns
// usage marking.
if (findusepath(c.file, c.curmod, c.cursource, name, false).len != 0) {
let q: *syntax.scope = c.cur;
for (q != nil) {
let u: *syntax.sym = q.first;
@@ -298,17 +276,6 @@ fn lookupvisible(c: *checker, name: str) *syntax.sym = {
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;
};
@@ -323,19 +290,6 @@ fn lookupvisibletype(c: *checker, name: str) *syntax.sym = {
};
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)
&& (b.decl == nil || b.decl.imported == 0
|| b.decl.exported != 0)) { return b; };
b = b.snext;
};
p = p.parent;
};
return nil;
};
@@ -7332,6 +7286,22 @@ fn hascvariadic(params: *syntax.node) bool = {
return false;
};
// Normalize a Hare-style variadic parameter before either signature stamping
// or parameter installation. Doing this in the signature pass means an inline
// aggregate element is walked and validated once, under its declaring package,
// rather than being revisited through a newly created slice in Pass 2.
fn normalizevariadicparam(p: *syntax.node) void = {
if (p == nil || p.kind != syntax.nkind.N_PARAM
|| p.op != syntax.tkind.TK_ELLIPSIS || p.lhs == nil
|| p.lhs.kind == syntax.nkind.N_TSLICE) { return; };
let sl: *syntax.node = syntax.newnode(syntax.nkind.N_TSLICE, "", 0, 0);
sl.lhs = p.lhs;
// op marks this compiler normalization so typeeqast and the interface
// writer can peel exactly this wrapper back to the surface `T...` form.
sl.op = syntax.tkind.TK_ELLIPSIS;
p.lhs = sl;
};
// TODO(#11): cstage check.c (post-#32) errors `param '%s' redeclared`
// when two params share a name. The fn body's scope IS fresh here
// (resolvefnbody opens it before calling us), so guarding scopedefine's
@@ -7350,21 +7320,7 @@ fn installparams(c: *checker, params: *syntax.node) void = {
// `tp->type = type_slice(c->a, pt)` and harec
// check_func_type. Surface-fidelity preserved: wwdump
// -a runs parser only and never reaches this mutation.
if (p.op == syntax.tkind.TK_ELLIPSIS) {
if (p.lhs != nil && p.lhs.kind != syntax.nkind.N_TSLICE) {
let sl: *syntax.node = syntax.newnode(syntax.nkind.N_TSLICE, "", 0, 0);
sl.lhs = p.lhs;
// op marks the wrapper as THIS normalization, not
// surface syntax, so typeeqast can peel exactly it
// when comparing against an unnormalized fn TYPE
// expr (`let f: fn(args: i64...) void = sum` — the
// decl side reads []i64 here, the let side i64).
// Param-lhs position never carries a tagged spread
// marker, so the op reads stay disjoint.
sl.op = syntax.tkind.TK_ELLIPSIS;
p.lhs = sl;
};
};
normalizevariadicparam(p);
let nm: str = p.str;
if (nm.len > 0) {
checkmoduleshadow(c, nm, "param");
@@ -7390,7 +7346,9 @@ fn resolvefnbody(c: *checker, fnnode: *syntax.node) void = {
let p: *syntax.node = fnnode.list;
for (p != nil) {
if (p.kind == syntax.nkind.N_PARAM) {
if (p.lhs != nil) { resolvewalk(c, p.lhs); };
if (p.lhs != nil && p.lhs.type_ == nil) {
resolvewalk(c, p.lhs);
};
};
p = p.next;
};
@@ -7550,6 +7508,42 @@ fn topdeclkind(d: *syntax.node) bool = {
|| d.kind == syntax.nkind.N_LET);
};
// Record only qualified syntax in each owning source before name resolution.
// This preserves source-position import diagnostics and never lets a failed
// bare lookup consume an ordinary import.
fn markimportusesnode(c: *checker, n: *syntax.node, owner: str,
source: i32) void = {
if (n == nil) { return; };
if (n.kind == syntax.nkind.N_TNAME) {
let (head, leaf) = strings.rcut(n.str, ".");
if (head.len > 0) {
findusepath(c.file, owner, source, head, true);
};
} else { if (n.kind == syntax.nkind.N_DOT && n.lhs != nil
&& n.lhs.kind == syntax.nkind.N_IDENT) {
findusepath(c.file, owner, source, n.lhs.str, true);
}; };
let p: *syntax.node = n.attr;
for (p != nil) { markimportusesnode(c, p, owner, source); p = p.next; };
markimportusesnode(c, n.lhs, owner, source);
markimportusesnode(c, n.rhs, owner, source);
markimportusesnode(c, n.cond, owner, source);
markimportusesnode(c, n.body, owner, source);
markimportusesnode(c, n.els, owner, source);
p = n.list;
for (p != nil) { markimportusesnode(c, p, owner, source); p = p.next; };
};
fn markimportuses(c: *checker, file: *syntax.node) void = {
let d: *syntax.node = file.list;
for (d != nil) {
if (d.kind != syntax.nkind.N_USE) {
markimportusesnode(c, d, declmod(file, d), d.sourceid);
};
d = d.next;
};
};
fn checkimportredeclarations(c: *checker, file: *syntax.node) void = {
if (c.sepmode == 0) { return; };
let u: *syntax.node = file.list;
@@ -7577,7 +7571,8 @@ fn checkimportusageandcollisions(c: *checker, file: *syntax.node) void = {
let u: *syntax.node = file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.imported == 0 && u.used == 0) {
let path: str = u.usepath;
let path: str = u.usesource;
if (path.len == 0) { path = u.usepath; };
if (path.len == 0) { path = u.str; };
let (prefix, suffix) = strings.rcut(path, ".");
let leaf: str = suffix;
@@ -7600,7 +7595,8 @@ fn checkimportusageandcollisions(c: *checker, file: *syntax.node) void = {
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.imported == 0
&& syntax.streq(d.str, u.str)) {
let path: str = u.usepath;
let path: str = u.usesource;
if (path.len == 0) { path = u.usepath; };
if (path.len == 0) { path = u.str; };
importdiagprefix(d); cerr(d.str);
cerr(" already declared through import of package ");
@@ -7659,10 +7655,11 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
// earlier module-reset section neither supplies nor uses its binding.
let su: *syntax.node = file.list;
for (su != nil) {
if (c.testtarget.len > 0 && su.kind == syntax.nkind.N_USE
if (su.kind == syntax.nkind.N_USE
&& su.imported == 0 && su.sourceid == file.sourceid
&& (syntax.streq(su.usepath, c.testtarget)
|| syntax.streq(su.usepath, c.testmodule))) {
&& (syntax.streq(su.usepath, c.testmodule)
|| (c.testtarget.len > 0
&& syntax.streq(su.usepath, c.testtarget)))) {
su.used = 1i32;
};
if (su.kind == syntax.nkind.N_USE && su.imported == 0
@@ -7675,15 +7672,18 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
if (!present) {
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, file.file, file.line, file.col);
usenode.str = c.testmodule;
usenode.usesource = c.testmodule;
usenode.usepath = c.testmodule;
usenode.pkgname = file.pkgname;
usenode.sourceid = file.sourceid;
if (c.testtarget.len > 0) { usenode.used = 1i32; };
usenode.used = 1i32;
usenode.next = file.list;
file.list = usenode;
};
};
markimportuses(c, file);
checkimportredeclarations(c, file);
checkimportusageandcollisions(c, file);
// Pass 1: install all top-level names.
let d: *syntax.node = file.list;
@@ -7956,6 +7956,39 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
};
};
// Resolve every function signature in its declaring package before any
// body can consume it. Cstage's declaration pass builds all function
// types before its body-check pass. Without the same split here, a call
// from an earlier package section to a later declaration can first visit
// the later function's bare same-package return/parameter names while the
// caller's module is current. The historical implicit-import fallback
// happened to hide that owner error when the caller imported the callee;
// ordinary imports must not. The later body walk is idempotent over these
// checker-stamped type nodes. Variadics are normalized before stamping and
// Pass 2 skips those same roots, so inline enum/struct validators still run
// exactly once.
d = file.list;
for (d != nil) {
if (d.kind == syntax.nkind.N_FNDECL) {
c.curmod = declmod(file, d);
c.cursource = d.sourceid;
if (d.lhs != nil && d.lhs.type_ == nil) {
resolvewalk(c, d.lhs);
};
let fp: *syntax.node = d.list;
for (fp != nil) {
if (fp.kind == syntax.nkind.N_PARAM) {
normalizevariadicparam(fp);
if (fp.lhs != nil && fp.lhs.type_ == nil) {
resolvewalk(c, fp.lhs);
};
};
fp = fp.next;
};
};
d = d.next;
};
// Pass 2: walk decl bodies/types and resolve identifiers.
// Track the per-decl module bareword so bare-leaf lookups inside
// the body prefer same-module entries over alphabetically-earlier
@@ -7984,7 +8017,9 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
cerr(": error: C-style variadic '...' requires a bodiless declaration\n");
c.errs += 1;
};
if (d.lhs != nil) { resolvewalk(c, d.lhs); }; // return type
if (d.lhs != nil && d.lhs.type_ == nil) {
resolvewalk(c, d.lhs); // return type
};
resolvefnbody(c, d);
case syntax.nkind.N_DEF:
// #11: a module-level `def xs: [_]T = arrlit;` must infer
@@ -8058,8 +8093,6 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
};
d = d.next;
};
checkimportusageandcollisions(c, file);
// Pass 3 (#15, A.6.2.1e): post-checker invariant gate. Walks each
// decl with its curmod set so asserttyped's gate lookups resolve
// against the same module context exprtype saw during pass 2.

View File

@@ -104,24 +104,41 @@ fn wwiusepath(c: *checker, owner: str, source: i32, alias: str) str = {
return empty;
};
fn wwidirectmodvisible(c: *checker, owner: str, source: i32, mod: str) bool = {
if (mod.len == 0) { return false; };
let u: *syntax.node = c.file.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE && u.sourceid == source) {
let same: bool = false;
if (owner.len == 0) {
same = u.imported == 0;
} else {
same = u.imported != 0 && wwimodeq(u.nmod, owner);
};
let path: str = u.str;
if (u.usepath.len > 0) { path = u.usepath; };
if (same && syntax.streq(path, mod)) { return true; };
};
u = u.next;
// Canonical export spelling depends only on path identity, never on the
// source alias or imported declared package name.
fn wwicanonicalalias(fd: i32, path: str) void = {
wputs(fd, "__wwi_");
let i: i32 = 0;
for (i < path.len) {
let hi: u8 = path[i] >> 4u8;
let lo: u8 = path[i] & 15u8;
if (hi < 10u8) { wputb(fd, hi + 48u8); }
else { wputb(fd, hi - 10u8 + 97u8); };
if (lo < 10u8) { wputb(fd, lo + 48u8); }
else { wputb(fd, lo - 10u8 + 97u8); };
i += 1;
};
return false;
};
fn wwiname(c: *checker, fd: i32, owner: str, source: i32, name: str) void = {
let dotidx: i32 = -1;
let i: i32 = 0;
for (i < name.len) { if (name[i] == 46u8) { dotidx = i; }; i += 1; };
if (dotidx >= 0) {
let head: str;
head.ptr = name.ptr;
head.len = dotidx;
let path: str = wwiusepath(c, owner, source, head);
if (path.len > 0) {
wwicanonicalalias(fd, path);
let tail: str;
tail.ptr = name.ptr + (dotidx: u64);
tail.len = name.len - dotidx;
wputs(fd, tail);
return;
};
};
wputs(fd, name);
};
fn wwitypesym(c: *checker, owner: str, source: i32, nm: str) *syntax.sym = {
@@ -145,22 +162,6 @@ fn wwitypesym(c: *checker, owner: str, source: i32, nm: str) *syntax.sym = {
};
} else {
s = syntax.scopelookuptype(c.cur, owner, nm);
if (s == nil) {
let p: *syntax.scope = c.cur;
for (p != nil && s == nil) {
let b: *syntax.sym = p.first;
for (b != nil) {
if (b.skind == syntax.skind.SK_TYPE
&& syntax.streq(b.name, nm)
&& wwidirectmodvisible(c, owner, source, b.mod)) {
s = b;
break;
};
b = b.snext;
};
p = p.parent;
};
};
};
if (s == nil) { return nil; };
if (s.skind != syntax.skind.SK_TYPE) { return nil; };
@@ -307,15 +308,20 @@ fn wwirune(fd: i32, cp: u64) void = {
wputb(fd, '\'');
};
fn wwiexpr(fd: i32, e: *syntax.node) void = {
fn wwiexpr(c: *checker, fd: i32, owner: str, source: i32,
e: *syntax.node) void = {
if (e == nil) { return; };
if (e.kind == syntax.nkind.N_INTLIT) {
wputs(fd, strconv.u64tos(e.uval, strconv.base.DEC));
if (e.tsuffix.len > 0) { wputs(fd, e.tsuffix); };
} else { if (e.kind == syntax.nkind.N_IDENT || e.kind == syntax.nkind.N_TNAME) {
wputs(fd, e.str);
wwiname(c, fd, owner, source, e.str);
} else { if (e.kind == syntax.nkind.N_DOT) {
wwiexpr(fd, e.lhs);
if (e.lhs != nil && e.lhs.kind == syntax.nkind.N_IDENT) {
let path: str = wwiusepath(c, owner, source, e.lhs.str);
if (path.len > 0) { wwicanonicalalias(fd, path); }
else { wwiexpr(c, fd, owner, source, e.lhs); };
} else { wwiexpr(c, fd, owner, source, e.lhs); };
wputb(fd, '.');
wputs(fd, e.str);
} else { if (e.kind == syntax.nkind.N_TRUE) {
@@ -334,20 +340,20 @@ fn wwiexpr(fd: i32, e: *syntax.node) void = {
wwirune(fd, e.uval);
} else { if (e.kind == syntax.nkind.N_BIN) {
wputb(fd, '(');
wwiexpr(fd, e.lhs);
wwiexpr(c, fd, owner, source, e.lhs);
wputb(fd, 32u8);
wputs(fd, syntax.tokname(e.op));
wputb(fd, 32u8);
wwiexpr(fd, e.rhs);
wwiexpr(c, fd, owner, source, e.rhs);
wputb(fd, ')');
} else { if (e.kind == syntax.nkind.N_UN) {
wputs(fd, syntax.tokname(e.op));
wwiexpr(fd, e.lhs);
wwiexpr(c, fd, owner, source, e.lhs);
} else { if (e.kind == syntax.nkind.N_CAST) {
wputb(fd, '(');
wwiexpr(fd, e.lhs);
wwiexpr(c, fd, owner, source, e.lhs);
wputs(fd, ": ");
wwitype(fd, e.rhs);
wwitype(c, fd, owner, source, e.rhs);
wputb(fd, ')');
} else {
wputs(2, "wwi: unhandled const-expr node kind\n");
@@ -355,7 +361,8 @@ fn wwiexpr(fd: i32, e: *syntax.node) void = {
};};};};};};};};};};};
};
fn wwiparam(fd: i32, p: *syntax.node) void = {
fn wwiparam(c: *checker, fd: i32, owner: str, source: i32,
p: *syntax.node) void = {
if (syntax.streq(p.str, "...")) { // C-style FFI `...`
wputs(fd, "...");
return;
@@ -368,52 +375,57 @@ fn wwiparam(fd: i32, p: *syntax.node) void = {
// in place to `[]T` (lhs becomes N_TSLICE); cstage leaves lhs == T.
// Peel the inserted slice so both stages emit the surface `T...`.
if (p.op == syntax.tkind.TK_ELLIPSIS && p.lhs != nil && p.lhs.kind == syntax.nkind.N_TSLICE) {
wwitype(fd, p.lhs.lhs);
wwitype(c, fd, owner, source, p.lhs.lhs);
} else {
wwitype(fd, p.lhs);
wwitype(c, fd, owner, source, p.lhs);
};
if (p.op == syntax.tkind.TK_ELLIPSIS) { // Hare `T...` variadic
wputs(fd, "...");
};
};
fn wwitype(fd: i32, t: *syntax.node) void = {
fn wwitype(c: *checker, fd: i32, owner: str, source: i32,
t: *syntax.node) void = {
if (t == nil) { // absent return type spells void
wputs(fd, "void");
return;
};
// rule-10: unwrap the wwstage-only N_TPARAM tuple-element wrapper
// (ast.ww:101) so the unparse matches cstage's pristine type-AST.
if (t.kind == syntax.nkind.N_TPARAM) { wwitype(fd, t.lhs); return; };
if (t.kind == syntax.nkind.N_TPARAM) {
wwitype(c, fd, owner, source, t.lhs); return;
};
if (t.kind == syntax.nkind.N_TNAME) {
if (t.str.len > 0) { wputs(fd, t.str); } else { wputs(fd, "void"); };
if (t.str.len > 0) { wwiname(c, fd, owner, source, t.str); }
else { wputs(fd, "void"); };
} else { if (t.kind == syntax.nkind.N_TPTR) {
wputb(fd, '*');
wwitype(fd, t.lhs);
wwitype(c, fd, owner, source, t.lhs);
} else { if (t.kind == syntax.nkind.N_TSLICE) {
wputs(fd, "[]");
wwitype(fd, t.lhs);
wwitype(c, fd, owner, source, t.lhs);
} else { if (t.kind == syntax.nkind.N_TARRAY) {
wputb(fd, '[');
if (t.rhs != nil) { wwiexpr(fd, t.rhs); } else { wputb(fd, '_'); };
if (t.rhs != nil) { wwiexpr(c, fd, owner, source, t.rhs); }
else { wputb(fd, '_'); };
wputb(fd, ']');
wwitype(fd, t.lhs);
wwitype(c, fd, owner, source, t.lhs);
} else { if (t.kind == syntax.nkind.N_TBANG) {
wputb(fd, '!');
wwitype(fd, t.lhs);
wwitype(c, fd, owner, source, t.lhs);
} else { if (t.kind == syntax.nkind.N_TCHAN) {
wputs(fd, "chan ");
wwitype(fd, t.lhs);
wwitype(c, fd, owner, source, t.lhs);
} else { if (t.kind == syntax.nkind.N_TFN) {
wputs(fd, "fn(");
let p: *syntax.node = t.list;
for (p != nil) {
if (p != t.list) { wputs(fd, ", "); };
wwiparam(fd, p);
wwiparam(c, fd, owner, source, p);
p = p.next;
};
wputs(fd, ") ");
wwitype(fd, t.lhs);
wwitype(c, fd, owner, source, t.lhs);
} else { if (t.kind == syntax.nkind.N_TSTRUCT) {
// re-emit `@packed` so the flag round-trips through
// sep-compile (harec unparse/type.ha:122-126).
@@ -429,7 +441,7 @@ fn wwitype(fd: i32, t: *syntax.node) void = {
wputs(fd, f.str);
wputs(fd, ": ");
};
wwitype(fd, f.lhs);
wwitype(c, fd, owner, source, f.lhs);
f = f.next;
};
wputs(fd, " }");
@@ -438,7 +450,7 @@ fn wwitype(fd: i32, t: *syntax.node) void = {
let e: *syntax.node = t.list;
for (e != nil) {
if (e != t.list) { wputs(fd, ", "); };
wwitype(fd, e);
wwitype(c, fd, owner, source, e);
e = e.next;
};
wputb(fd, ')');
@@ -451,14 +463,14 @@ fn wwitype(fd: i32, t: *syntax.node) void = {
// the consumer's type-store flattens
// (ref/hare/hare/unparse/type.ha:290-300).
if (e.op == syntax.tkind.TK_ELLIPSIS) { wputs(fd, "..."); };
wwitype(fd, e);
wwitype(c, fd, owner, source, e);
e = e.next;
};
wputb(fd, ')');
} else { if (t.kind == syntax.nkind.N_TENUM) {
wputs(fd, "enum ");
if (t.lhs != nil) {
wwitype(fd, t.lhs);
wwitype(c, fd, owner, source, t.lhs);
wputb(fd, 32u8);
};
wputs(fd, "{ ");
@@ -468,7 +480,7 @@ fn wwitype(fd: i32, t: *syntax.node) void = {
wputs(fd, m.str);
if (m.lhs != nil) {
wputs(fd, " = ");
wwiexpr(fd, m.lhs);
wwiexpr(c, fd, owner, source, m.lhs);
};
m = m.next;
};
@@ -489,7 +501,7 @@ fn wwiattrrelevant(nm: str) bool = {
return syntax.streq(nm, "symbol") || syntax.streq(nm, "test");
};
fn wwiattrs(fd: i32, d: *syntax.node) void = {
fn wwiattrs(c: *checker, fd: i32, owner: str, d: *syntax.node) void = {
let a: *syntax.node = d.attr;
for (a != nil) {
if (a.kind == syntax.nkind.N_ATTR && wwiattrrelevant(a.str)) {
@@ -500,7 +512,7 @@ fn wwiattrs(fd: i32, d: *syntax.node) void = {
let arg: *syntax.node = a.list;
for (arg != nil) {
if (arg != a.list) { wputs(fd, ", "); };
wwiexpr(fd, arg);
wwiexpr(c, fd, owner, d.sourceid, arg);
arg = arg.next;
};
wputb(fd, ')');
@@ -511,9 +523,9 @@ fn wwiattrs(fd: i32, d: *syntax.node) void = {
};
};
fn wwidecl(fd: i32, d: *syntax.node) void = {
fn wwidecl(c: *checker, fd: i32, owner: str, d: *syntax.node) void = {
if (d.kind == syntax.nkind.N_FNDECL) {
wwiattrs(fd, d);
wwiattrs(c, fd, owner, d);
if (d.exported != 0) { wputs(fd, "export fn "); }
else { wputs(fd, "fn "); };
wputs(fd, d.str);
@@ -521,25 +533,25 @@ fn wwidecl(fd: i32, d: *syntax.node) void = {
let p: *syntax.node = d.list;
for (p != nil) {
if (p != d.list) { wputs(fd, ", "); };
wwiparam(fd, p);
wwiparam(c, fd, owner, d.sourceid, p);
p = p.next;
};
wputs(fd, ") ");
wwitype(fd, d.lhs);
wwitype(c, fd, owner, d.sourceid, d.lhs);
wputs(fd, ";\n");
} else { if (d.kind == syntax.nkind.N_TYPEDECL) {
if (d.exported != 0) { wputs(fd, "export type "); }
else { wputs(fd, "type "); };
wputs(fd, d.str);
wputs(fd, " = ");
wwitype(fd, d.lhs);
wwitype(c, fd, owner, d.sourceid, d.lhs);
wputs(fd, ";\n");
} else { if (d.kind == syntax.nkind.N_DEF) {
if (d.exported != 0) { wputs(fd, "export def "); }
else { wputs(fd, "def "); };
wputs(fd, d.str);
wputs(fd, ": ");
wwitype(fd, d.lhs);
wwitype(c, fd, owner, d.sourceid, d.lhs);
// An aggregate initializer (N_STRUCTLIT/N_ARRLIT) is a DATA-global
// (#52): emit a value-LESS prototype `export def X: T;`. The
// defining package's own .o emits the struct/array DATA; the
@@ -554,7 +566,7 @@ fn wwidecl(fd: i32, d: *syntax.node) void = {
wputs(fd, ";\n");
} else {
wputs(fd, " = ");
wwiexpr(fd, d.rhs);
wwiexpr(c, fd, owner, d.sourceid, d.rhs);
wputs(fd, ";\n");
};
} else { if (d.kind == syntax.nkind.N_LET) {
@@ -566,7 +578,7 @@ fn wwidecl(fd: i32, d: *syntax.node) void = {
wputs(2, "wwi: exported let has no declared type\n");
os.exit(1);
};
wwitype(fd, d.lhs);
wwitype(c, fd, owner, d.sourceid, d.lhs);
wputs(fd, ";\n");
};};};};
};
@@ -671,16 +683,6 @@ fn wwifactvaluesym(c: *checker, owner: str, source: i32,
};
p = p.parent;
};
p = c.top;
for (p != nil) {
let s: *syntax.sym = p.first;
for (s != nil) {
if (s.skind == syntax.skind.SK_DEF && syntax.streq(s.name, name)
&& wwidirectmodvisible(c, owner, source, s.mod)) { return s; };
s = s.snext;
};
p = p.parent;
};
return nil;
};
@@ -929,7 +931,8 @@ fn wwiemitimports(fd: i32, file: *syntax.node, owner: str, source: i32,
let i: i32 = 0;
for (i < nuse) {
if (previous.len == 0 || !syntax.streq(previous, paths[i])) {
wputs(fd, "import "); wputs(fd, paths[i]); wputs(fd, ";\n");
wputs(fd, "import "); wwicanonicalalias(fd, paths[i]);
wputb(fd, 32u8); wputs(fd, paths[i]); wputs(fd, ";\n");
previous = paths[i];
};
i += 1;
@@ -995,7 +998,8 @@ fn wwiemitprimarysection(c: *checker, fd: i32, file: *syntax.node,
let i: i32 = 0;
for (i < fs.nprivate) {
if (fs.privatenodes[i].sourceid == source) {
wwidecl(fd, fs.privatenodes[i]);
let primaryowner: str;
wwidecl(c, fd, primaryowner, fs.privatenodes[i]);
};
i += 1;
};
@@ -1004,13 +1008,19 @@ fn wwiemitprimarysection(c: *checker, fd: i32, file: *syntax.node,
for (d != nil) {
if (wwiprimary(d) && d.sourceid == source
&& d.kind == syntax.nkind.N_FNDECL && d.exported == 0
&& wwihasattr(d, "test")) { wwidecl(fd, d); };
&& wwihasattr(d, "test")) {
let primaryowner: str;
wwidecl(c, fd, primaryowner, d);
};
d = d.next;
};
};
i = 0;
for (i < nexports) {
if (exports[i].sourceid == source) { wwidecl(fd, exports[i]); };
if (exports[i].sourceid == source) {
let primaryowner: str;
wwidecl(c, fd, primaryowner, exports[i]);
};
i += 1;
};
};
@@ -1134,23 +1144,13 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
if (lastmod.len == 0 || !syntax.streq(lastmod, mod)
|| lastsource != source) {
wputs(fd, "//ww:module "); wputs(fd, mod); wputs(fd, "\n");
let factpkg: str = fs.factnodes[fi].pkgname;
if (factpkg.len == 0) {
let dotidx: i32 = -1;
let mi: i32 = 0;
for (mi < mod.len) { if (mod[mi] == 46u8) { dotidx = mi; }; mi += 1; };
factpkg = mod;
if (dotidx >= 0) {
factpkg.ptr = mod.ptr + ((dotidx + 1): u64);
factpkg.len = mod.len - dotidx - 1;
};
};
wputs(fd, "package "); wputs(fd, factpkg); wputs(fd, ";\n");
wputs(fd, "package "); wwicanonicalalias(fd, mod);
wputs(fd, ";\n");
wwiemitimports(fd, file, mod, source, true);
lastmod = mod;
lastsource = source;
};
wwidecl(fd, fs.factnodes[fi]);
wwidecl(c, fd, mod, fs.factnodes[fi]);
fi += 1;
};

View File

@@ -2853,7 +2853,11 @@ fn sepresolvesourceimport(g: *sepgraph, context: i32, name: *u8,
};
fn sepusecmp(a: *syntax.node, b: *syntax.node) i32 = {
let r: i32 = strings.compare(a.usepath, b.usepath): i32;
let apath: str = a.usesource;
if (apath.len == 0) { apath = a.usepath; };
let bpath: str = b.usesource;
if (bpath.len == 0) { bpath = b.usepath; };
let r: i32 = strings.compare(apath, bpath): i32;
if (r != 0) { return r; };
r = strings.compare(a.file, b.file): i32;
if (r != 0) { return r; };
@@ -2977,12 +2981,14 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32,
ui = 0;
for (ui < nuse) {
u = uses[ui];
let idp: *u8 = u.usepath.ptr;
let idn: u64 = u.usepath.len: u64;
if (reservedimport(u.usepath)) {
let sourcepath: str = u.usesource;
if (sourcepath.len == 0) { sourcepath = u.usepath; };
let idp: *u8 = sourcepath.ptr;
let idn: u64 = sourcepath.len: u64;
if (reservedimport(sourcepath)) {
cerrpos(u.file, u.line, u.col);
cerr(": error: package path ");
cerr(u.usepath); cerr(" is reserved\n");
cerr(sourcepath); cerr(" is reserved\n");
return -1;
};
let nm: []u8;
@@ -3085,7 +3091,7 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32,
let childcontext: i32 = sepchildcontextfor(g, context,
resolved.entry, resolved.sourceroot);
if (childcontext < 0
|| !sepbindadd(bindings, 'D': u8, u.usepath, di,
|| !sepbindadd(bindings, 'D': u8, sourcepath, di,
u.file, u.line, u.col)
|| !sepadddep(g, pi, di)
|| !sepchildrenadd(children, di, childcontext)) {
@@ -3108,7 +3114,7 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32,
cerr("\n");
return -1;
} else {
if (!sepbindadd(bindings, 'I': u8, u.usepath, -1,
if (!sepbindadd(bindings, 'I': u8, sourcepath, -1,
u.file, u.line, u.col)) {
return -1;
};
@@ -4360,14 +4366,14 @@ fn validatecommandoutputpath(out: *u8) i32 = {
fn workdirstamptext(istest: i32, emitasm: i32) str = {
if (istest != 0) {
if (emitasm != 0) {
return "ww workdir fmt 13 mode test asm 1\n";
return "ww workdir fmt 14 mode test asm 1\n";
};
return "ww workdir fmt 13 mode test asm 0\n";
return "ww workdir fmt 14 mode test asm 0\n";
};
if (emitasm != 0) {
return "ww workdir fmt 14 mode build asm 1\n";
return "ww workdir fmt 15 mode build asm 1\n";
};
return "ww workdir fmt 14 mode build asm 0\n";
return "ww workdir fmt 15 mode build asm 0\n";
};
fn stampmatches(path: *u8, want: str) bool = {

View File

@@ -97,27 +97,27 @@ export fn main(argc: i32, argv: **u8) i32 = {
return 1;
};
let l: lex;
lexinit(&l, argstr(path), buf.ptr, sz: u64);
let l: syntax.lex;
syntax.lexinit(&l, argstr(path), buf.ptr, sz: u64);
if (mode == 116) { // '-t'
for (true) {
let t: tok;
lexnext(&l, &t);
tokprint(1i32, &t);
if (t.kind == tkind.TK_EOF) { break; };
if (t.kind == tkind.TK_ERR) { break; };
let t: syntax.tok;
syntax.lexnext(&l, &t);
syntax.tokprint(1i32, &t);
if (t.kind == syntax.tkind.TK_EOF) { break; };
if (t.kind == syntax.tkind.TK_ERR) { break; };
};
} else { if (mode == 97) { // '-a'
let ps: parser;
parserinit(&ps, &l);
let f: *node = parsefile(&ps);
astprint(1i32, f);
let ps: syntax.parser;
syntax.parserinit(&ps, &l);
let f: *syntax.node = syntax.parsefile(&ps);
syntax.astprint(1i32, f);
if (ps.errs > 0) { return 1; };
} else { if (mode == 114) { // '-r' — name resolve report
let ps: parser;
parserinit(&ps, &l);
let f: *node = parsefile(&ps);
let ps: syntax.parser;
syntax.parserinit(&ps, &l);
let f: *syntax.node = syntax.parsefile(&ps);
// #52: gate the resolve report on parse-stage errors. Without
// this a parse-errored decl is silently dropped from the AST
// and the report is printed with rc=0. Mirrors w6c main.ww:162
@@ -138,9 +138,9 @@ export fn main(argc: i32, argv: **u8) i32 = {
os.write(1, " resolved\n".ptr, 10u64);
if (checkrc != 0) { return 1; };
} else { if (mode == 99) { // '-c' — codegen / emit asm
let ps: parser;
parserinit(&ps, &l);
let f: *node = parsefile(&ps);
let ps: syntax.parser;
syntax.parserinit(&ps, &l);
let f: *syntax.node = syntax.parsefile(&ps);
// #52: gate cgen on parse-stage errors BEFORE check/cgen.
// A parse-errored decl is silently dropped from the AST; the
// remaining file would otherwise emit asm with rc=0 (silent