lang: fn aliases dispatch like their target (fix 0121) — scan-time registration through the shared alias-chain walk
Renamed fn aliases failed for EVERY kind (the filed pack-only scope was a same-name confound: same-name re-exports already resolved through the name-keyed fn_ast_map). scanDecls now follows ident-/ns.X-RHS const alias chains (aliasedFnDecl; 0120's hop walk extracted as followAliasChain) and registers the alias name in fn_ast_map (absent-only), so every dispatch path — early pack/comptime/generic, plain lazy-lower, plan-side typing — sees the target decl unchanged. my_print :: s.print; / my_format :: s.format; now work (the std.sx re-export shape). Regression: examples/0546 (+rich). Gates: zig build test 0, suite 588/588.
This commit is contained in:
@@ -1688,6 +1688,7 @@ pub const Lowering = struct {
|
||||
pub const buildGenericStructTemplate = lower_nominal.buildGenericStructTemplate;
|
||||
pub const qualifiedStructTemplate = lower_nominal.qualifiedStructTemplate;
|
||||
pub const aliasedStructTemplate = lower_nominal.aliasedStructTemplate;
|
||||
pub const aliasedFnDecl = lower_nominal.aliasedFnDecl;
|
||||
pub const qualifiedMemberMissing = lower_nominal.qualifiedMemberMissing;
|
||||
pub const bareVisibleStructDecl = lower_nominal.bareVisibleStructDecl;
|
||||
pub const bareVisibleStructTemplate = lower_nominal.bareVisibleStructTemplate;
|
||||
|
||||
@@ -211,8 +211,7 @@ pub fn checkRequiredEntryPoints(self: *Lowering) void {
|
||||
}
|
||||
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, null,
|
||||
"target is Android but no `#jni_main` Activity declared. " ++
|
||||
diags.addFmt(.err, null, "target is Android but no `#jni_main` Activity declared. " ++
|
||||
"The OS launches a Java-side Activity that delegates lifecycle " ++
|
||||
"callbacks into sx — declare one like:\n\n" ++
|
||||
" Bundle :: #foreign #jni_class(\"android/os/Bundle\") {{ }}\n\n" ++
|
||||
@@ -366,8 +365,7 @@ pub fn detectContextDecl(decls: []const *const Node) bool {
|
||||
for (decls) |decl| {
|
||||
const found = switch (decl.data) {
|
||||
.struct_decl => |sd| std.mem.eql(u8, sd.name, "Context"),
|
||||
.const_decl => |cd|
|
||||
std.mem.eql(u8, cd.name, "Context") and cd.value.data == .struct_decl,
|
||||
.const_decl => |cd| std.mem.eql(u8, cd.name, "Context") and cd.value.data == .struct_decl,
|
||||
.namespace_decl => |ns| detectContextDecl(ns.decls),
|
||||
else => false,
|
||||
};
|
||||
@@ -596,30 +594,48 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
}
|
||||
}
|
||||
self.putTypeAlias(self.current_source_file, cd.name, target_ty);
|
||||
} else if (cd.value.data == .identifier) {
|
||||
// Identifier-RHS alias: MyAlias :: MyInt; WideAlias :: Wide.
|
||||
// SOURCE-AWARE (E1.5). Resolve the RHS `B` AS SEEN FROM this
|
||||
// alias's OWN source via `selectNominalLeaf` (E1's source-
|
||||
// keyed nominal leaf), NEVER the global `type_alias_map` /
|
||||
// global `findByName` (last-wins across modules). Only the
|
||||
// `.resolved` outcome is written; `.pending` (B is itself a
|
||||
// forward alias not resolved yet), `.undeclared`, and
|
||||
// `.not_visible` (a same-name B authored only by a namespaced
|
||||
// import) leave A UNWRITTEN so the source-aware
|
||||
// `resolveForwardIdentifierAliases` fixpoint re-tries A once
|
||||
// the local B registers. A GLOBAL selection here would bind A
|
||||
// to a namespaced same-name B, and the per-source fixpoint
|
||||
// guard (`aliasResolvedInSource`) would then SKIP A — leaving
|
||||
// the wrong global TypeId and re-opening 0105 one layer down
|
||||
// (R1, E1.5). Same unified `putTypeAlias` writer (no-drift).
|
||||
const rhs = cd.value.data.identifier;
|
||||
} else if (cd.value.data == .identifier or cd.value.data == .field_access) {
|
||||
// FN alias (issue 0121): `print2 :: print;` /
|
||||
// `my_print :: s.print;`. When the alias chain terminates
|
||||
// at a fn decl, register the ALIAS name in `fn_ast_map`
|
||||
// pointing at the target's decl — every dispatch path
|
||||
// (early pack/comptime/generic, plain lazy-lower,
|
||||
// plan-side return typing) reads that map, so the alias
|
||||
// dispatches exactly like the target. Absent-only: a real
|
||||
// same-name fn keeps its slot (same-name re-exports are
|
||||
// a no-op — the target already owns the name).
|
||||
if (self.current_source_file orelse self.main_file) |from| {
|
||||
switch (self.selectNominalLeaf(rhs.name, from, rhs.is_raw)) {
|
||||
.resolved => |tid| self.putTypeAlias(self.current_source_file, cd.name, tid),
|
||||
// `.ambiguous` (same-name RHS authored by ≥2 flat
|
||||
// imports) leaves A unwritten like `.not_visible`;
|
||||
// the loud diagnostic fires where A is USED.
|
||||
.pending, .forward, .undeclared, .not_visible, .ambiguous => {},
|
||||
if (self.aliasedFnDecl(&decl.data.const_decl, from)) |target_fd| {
|
||||
if (!self.program_index.fn_ast_map.contains(cd.name)) {
|
||||
self.program_index.fn_ast_map.put(cd.name, target_fd) catch {};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cd.value.data == .identifier) {
|
||||
// Identifier-RHS alias: MyAlias :: MyInt; WideAlias :: Wide.
|
||||
// SOURCE-AWARE (E1.5). Resolve the RHS `B` AS SEEN FROM this
|
||||
// alias's OWN source via `selectNominalLeaf` (E1's source-
|
||||
// keyed nominal leaf), NEVER the global `type_alias_map` /
|
||||
// global `findByName` (last-wins across modules). Only the
|
||||
// `.resolved` outcome is written; `.pending` (B is itself a
|
||||
// forward alias not resolved yet), `.undeclared`, and
|
||||
// `.not_visible` (a same-name B authored only by a namespaced
|
||||
// import) leave A UNWRITTEN so the source-aware
|
||||
// `resolveForwardIdentifierAliases` fixpoint re-tries A once
|
||||
// the local B registers. A GLOBAL selection here would bind A
|
||||
// to a namespaced same-name B, and the per-source fixpoint
|
||||
// guard (`aliasResolvedInSource`) would then SKIP A — leaving
|
||||
// the wrong global TypeId and re-opening 0105 one layer down
|
||||
// (R1, E1.5). Same unified `putTypeAlias` writer (no-drift).
|
||||
const rhs = cd.value.data.identifier;
|
||||
if (self.current_source_file orelse self.main_file) |from| {
|
||||
switch (self.selectNominalLeaf(rhs.name, from, rhs.is_raw)) {
|
||||
.resolved => |tid| self.putTypeAlias(self.current_source_file, cd.name, tid),
|
||||
// `.ambiguous` (same-name RHS authored by ≥2 flat
|
||||
// imports) leaves A unwritten like `.not_visible`;
|
||||
// the loud diagnostic fires where A is USED.
|
||||
.pending, .forward, .undeclared, .not_visible, .ambiguous => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -686,7 +702,7 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
// template via the namespace edge (mirrors the annotation
|
||||
// head site `resolveParameterizedWithBindings`), not the
|
||||
// bare last-wins `struct_template_map`.
|
||||
const pt_alias: ?[]const u8 = if (pt_qualified) pt.name[0 .. std.mem.indexOfScalar(u8, pt.name, '.').?] else null;
|
||||
const pt_alias: ?[]const u8 = if (pt_qualified) pt.name[0..std.mem.indexOfScalar(u8, pt.name, '.').?] else null;
|
||||
// Generic-struct alias base: route layout selection through the
|
||||
// single choke-point (CP-1); the builtin parameterised-type
|
||||
// path (Vector etc.) stays as the non-generic fall-through.
|
||||
@@ -1669,7 +1685,9 @@ pub fn selectNominalLeaf(self: *Lowering, name: []const u8, from: []const u8, ra
|
||||
else => self.namedRefTid(fa.raw, name),
|
||||
};
|
||||
if (fa_tid) |t| {
|
||||
if (found_tid) |f| { if (t != f) return .ambiguous; } else found_tid = t;
|
||||
if (found_tid) |f| {
|
||||
if (t != f) return .ambiguous;
|
||||
} else found_tid = t;
|
||||
} else {
|
||||
flat_has_unregistered = true;
|
||||
}
|
||||
|
||||
@@ -434,35 +434,48 @@ pub fn aliasedStructTemplate(self: *Lowering, name: []const u8, from: []const u8
|
||||
}
|
||||
|
||||
/// One alias hop: a generic-struct author terminates the chain with its
|
||||
/// rebuilt source-pinned template; an alias author recurses on its RHS —
|
||||
/// bare identifier from the author's own source, `ns.X` through the
|
||||
/// author's namespace edge into the target module's own member. The depth
|
||||
/// cap breaks alias cycles (`A :: B; B :: A;`).
|
||||
/// rebuilt source-pinned template; an alias author recurses via
|
||||
/// `followAliasChain`.
|
||||
fn followToTemplate(self: *Lowering, author: resolver_mod.RawAuthor, depth: u8) ?StructTemplate {
|
||||
const terminal = followAliasChain(self, author, depth) orelse return null;
|
||||
const sd = structDeclOfRaw(terminal.raw) orelse return null;
|
||||
if (sd.type_params.len == 0) return null;
|
||||
return self.buildGenericStructTemplate(sd, terminal.source);
|
||||
}
|
||||
|
||||
/// Walk a chain of const ALIAS decls to its terminal author. Each hop
|
||||
/// resolves the RHS from the hop AUTHOR's own source — a bare identifier
|
||||
/// via the visible-author walk, `ns.X` through the author's namespace edge
|
||||
/// into the target module's own member. A non-alias author terminates the
|
||||
/// chain (callers unwrap it by domain: `structDeclOfRaw` / `fnDeclOfRaw`).
|
||||
/// The depth cap breaks alias cycles (`A :: B; B :: A;`).
|
||||
pub fn followAliasChain(self: *Lowering, author: resolver_mod.RawAuthor, depth: u8) ?resolver_mod.RawAuthor {
|
||||
if (depth == 0) return null;
|
||||
if (structDeclOfRaw(author.raw)) |sd| {
|
||||
if (sd.type_params.len == 0) return null;
|
||||
return self.buildGenericStructTemplate(sd, author.source);
|
||||
}
|
||||
const cd = constAliasOfRaw(author.raw) orelse return null;
|
||||
switch (cd.value.data) {
|
||||
.identifier => |id| {
|
||||
const next = singleVisibleAuthor(self, id.name, author.source) orelse return null;
|
||||
return followToTemplate(self, next, depth - 1);
|
||||
},
|
||||
.field_access => |fa| {
|
||||
if (fa.object.data != .identifier) return null;
|
||||
const cd = constAliasOfRaw(author.raw) orelse return author;
|
||||
const next: ?resolver_mod.RawAuthor = switch (cd.value.data) {
|
||||
.identifier => |id| singleVisibleAuthor(self, id.name, author.source),
|
||||
.field_access => |fa| blk: {
|
||||
if (fa.object.data != .identifier) break :blk null;
|
||||
const target = switch (self.namespaceAliasVerdictFrom(fa.object.data.identifier.name, author.source)) {
|
||||
.target => |t| t,
|
||||
.none, .ambiguous => return null,
|
||||
.none, .ambiguous => break :blk null,
|
||||
};
|
||||
var res = self.resolver();
|
||||
const member_set = res.collectNamespaceAuthors(target, fa.field);
|
||||
const member = member_set.own orelse return null;
|
||||
return followToTemplate(self, member, depth - 1);
|
||||
break :blk member_set.own;
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
else => null,
|
||||
};
|
||||
return followAliasChain(self, next orelse return null, depth - 1);
|
||||
}
|
||||
|
||||
/// The fn decl a const ALIAS chain terminates at, or null when `cd` is not
|
||||
/// an alias of a function. Entry for fn-alias registration (issue 0121):
|
||||
/// `cd` itself seeds the chain (it IS the first alias hop), `from` is its
|
||||
/// declaring source.
|
||||
pub fn aliasedFnDecl(self: *Lowering, cd: *const ast.ConstDecl, from: []const u8) ?*const ast.FnDecl {
|
||||
const terminal = followAliasChain(self, .{ .raw = .{ .const_decl = cd }, .source = from }, 9) orelse return null;
|
||||
return Lowering.fnDeclOfRaw(terminal.raw);
|
||||
}
|
||||
|
||||
/// The bare-VISIBLE single generic-struct author of `name` (its `StructDecl` +
|
||||
|
||||
Reference in New Issue
Block a user