ww: add file-scoped import aliases
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user