fix(stdlib/S2.1c): preseed top-level UFCS aliases [additive]

This commit is contained in:
agra
2026-06-09 14:50:51 +03:00
parent 59681e0a09
commit 3b1415a287
2 changed files with 70 additions and 12 deletions

View File

@@ -334,6 +334,7 @@ pub fn resolve(
.ufcs_aliases = std.StringHashMap([]const u8).init(alloc),
};
defer pass.ufcs_aliases.deinit();
pass.seedTopLevelUfcsAliases(root);
pass.visit(root, .{ .source = main_file, .scope = null });
return pass.out;
}
@@ -352,6 +353,10 @@ const Frame = struct {
const Ctx = struct {
source: []const u8,
scope: ?*const Frame,
/// True only while visiting declarations that were already covered by the
/// top-level UFCS alias pre-scan. Their decl nodes still get recorded into
/// `ufcs_refs`, but the alias map keeps the scanDecls-style final state.
preseeded_decl: bool = false,
};
/// A resolved generic-param reference: the matched param (its address is its
@@ -467,7 +472,11 @@ const ResolvePass = struct {
/// names must resolve in their DEFINING module) overrides the ambient source
/// for this subtree; an unstamped node inherits its parent's.
fn visit(self: *ResolvePass, node: *const ast.Node, ctx: Ctx) void {
const here = Ctx{ .source = node.source_file orelse ctx.source, .scope = ctx.scope };
const here = Ctx{
.source = node.source_file orelse ctx.source,
.scope = ctx.scope,
.preseeded_decl = ctx.preseeded_decl,
};
switch (node.data) {
// ── declarations that open a generic-param scope ──
.fn_decl => |*fd| {
@@ -585,7 +594,8 @@ const ResolvePass = struct {
// ── structural recursion (no classification of their own) ──
.root => |*r| {
// each top-level decl carries its own ambient source stamp.
self.visitAll(r.decls, here);
const decl_ctx = Ctx{ .source = here.source, .scope = here.scope, .preseeded_decl = true };
self.visitAll(r.decls, decl_ctx);
},
.block => |*b| self.visitAll(b.stmts, here),
.binary_op => |*b| {
@@ -746,12 +756,37 @@ const ResolvePass = struct {
// name so its rewrite call sites resolve to the same target. The map is
// global / traversal-ordered, mirroring lowering's flat `ufcs_alias_map`.
.ufcs_alias => |ua| {
self.ufcs_aliases.put(ua.name, ua.target) catch @panic("resolve: OOM");
if (!here.preseeded_decl) {
self.ufcs_aliases.put(ua.name, ua.target) catch @panic("resolve: OOM");
}
self.recordAuthorsInto(&self.out.ufcs_refs, node, ua.target, here.source);
},
}
}
/// Mirror lowering's declaration pre-scan for UFCS aliases: top-level roots
/// and namespace declaration lists are scanned before function bodies are
/// visited, so a call can resolve through an alias declared later in the file.
/// Function/lambda/block bodies are intentionally not entered here; local
/// aliases keep normal statement-order behavior on the owning walk.
fn seedTopLevelUfcsAliases(self: *ResolvePass, node: *const ast.Node) void {
switch (node.data) {
.root => |*r| self.seedTopLevelUfcsAliasDecls(r.decls),
.namespace_decl => |*ns| self.seedTopLevelUfcsAliasDecls(ns.decls),
else => {},
}
}
fn seedTopLevelUfcsAliasDecls(self: *ResolvePass, decls: []const *ast.Node) void {
for (decls) |decl| switch (decl.data) {
.ufcs_alias => |ua| {
self.ufcs_aliases.put(ua.name, ua.target) catch @panic("resolve: OOM");
},
.namespace_decl => self.seedTopLevelUfcsAliases(decl),
else => {},
};
}
fn visitAll(self: *ResolvePass, nodes: anytype, ctx: Ctx) void {
for (nodes) |n| self.visit(n, ctx);
}