fix(stdlib/S2.1c): preseed top-level UFCS aliases [additive]
This commit is contained in:
@@ -353,6 +353,18 @@ fn findDecl(root: *const ast.Node, name: []const u8, comptime kind: std.meta.Tag
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn firstBareCallCallee(func: *const ast.Node, name: []const u8) ?*const ast.Node {
|
||||||
|
if (func.data != .fn_decl) return null;
|
||||||
|
const body = func.data.fn_decl.body;
|
||||||
|
if (body.data != .block) return null;
|
||||||
|
for (body.data.block.stmts) |stmt| {
|
||||||
|
if (stmt.data != .call) continue;
|
||||||
|
const callee = stmt.data.call.callee;
|
||||||
|
if (callee.data == .identifier and std.mem.eql(u8, callee.data.identifier.name, name)) return callee;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
fn expectTypeRefOwnTag(
|
fn expectTypeRefOwnTag(
|
||||||
rp: *const resolver.ResolvedProgram,
|
rp: *const resolver.ResolvedProgram,
|
||||||
node: *const ast.Node,
|
node: *const ast.Node,
|
||||||
@@ -711,8 +723,9 @@ test "resolver: resolve — namespace-qualified + generic-struct/type-fn/protoco
|
|||||||
// into foreign_class_refs — NOT type_refs. (2) A `Type.CONST` field access whose
|
// into foreign_class_refs — NOT type_refs. (2) A `Type.CONST` field access whose
|
||||||
// base resolves to a struct carrying that const member (`Phys.GRAVITY`) fills
|
// base resolves to a struct carrying that const member (`Phys.GRAVITY`) fills
|
||||||
// struct_const_refs, keyed by the field_access node. (3) A UFCS alias
|
// struct_const_refs, keyed by the field_access node. (3) A UFCS alias
|
||||||
// (`plus :: ufcs adder`) is keyed by its decl node AND its rewrite call site
|
// (`plus :: ufcs adder`) is keyed by its decl node AND its rewrite call sites
|
||||||
// (`plus(1, 2)`) is keyed by the callee, both resolving to the target's author.
|
// (including one before the alias decl) are keyed by the callee, both resolving
|
||||||
|
// to the target's author.
|
||||||
// This fixture exercises ALL TEN domains at once, proving the full-population
|
// This fixture exercises ALL TEN domains at once, proving the full-population
|
||||||
// acceptance: every ResolvedProgram side table is non-empty and node-keyed.
|
// acceptance: every ResolvedProgram side table is non-empty and node-keyed.
|
||||||
test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten domains populated" {
|
test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten domains populated" {
|
||||||
@@ -738,6 +751,7 @@ test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten doma
|
|||||||
\\Cmp :: protocol(T: Type) { get :: () -> T; }
|
\\Cmp :: protocol(T: Type) { get :: () -> T; }
|
||||||
\\LIMIT :: 5;
|
\\LIMIT :: 5;
|
||||||
\\adder :: (a: s64, b: s64) -> s64 { a + b }
|
\\adder :: (a: s64, b: s64) -> s64 { a + b }
|
||||||
|
\\call_alias_before :: () -> s64 { plus(3, 4) }
|
||||||
\\plus :: ufcs adder;
|
\\plus :: ufcs adder;
|
||||||
\\use_phys :: (p: Phys) -> s64 { p.mass }
|
\\use_phys :: (p: Phys) -> s64 { p.mass }
|
||||||
\\use_obj :: (o: Obj) -> s64 { 0 }
|
\\use_obj :: (o: Obj) -> s64 { 0 }
|
||||||
@@ -746,7 +760,7 @@ test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten doma
|
|||||||
\\use_cmp :: (c: Cmp(s64)) -> s64 { 0 }
|
\\use_cmp :: (c: Cmp(s64)) -> s64 { 0 }
|
||||||
\\read_const :: () -> s64 { Phys.GRAVITY }
|
\\read_const :: () -> s64 { Phys.GRAVITY }
|
||||||
\\read_ns :: () -> s64 { g.helper_fn() }
|
\\read_ns :: () -> s64 { g.helper_fn() }
|
||||||
\\call_alias :: () -> s64 { plus(1, 2) }
|
\\call_alias_after :: () -> s64 { plus(1, 2) }
|
||||||
\\main :: () -> s32 {
|
\\main :: () -> s32 {
|
||||||
\\ n := helper();
|
\\ n := helper();
|
||||||
\\ m := LIMIT;
|
\\ m := LIMIT;
|
||||||
@@ -824,20 +838,29 @@ test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten doma
|
|||||||
try std.testing.expect(plus_ref.authors.own != null);
|
try std.testing.expect(plus_ref.authors.own != null);
|
||||||
try std.testing.expectEqual(fn_tag, std.meta.activeTag(plus_ref.authors.own.?.raw));
|
try std.testing.expectEqual(fn_tag, std.meta.activeTag(plus_ref.authors.own.?.raw));
|
||||||
|
|
||||||
// (3b) UFCS rewrite site: the `plus(1, 2)` callee identifier is keyed in
|
// (3b) UFCS rewrite sites: both the forward `plus(3, 4)` site and the later
|
||||||
// ufcs_refs (NOT callable_refs) and resolves to the SAME target author.
|
// `plus(1, 2)` site are keyed in ufcs_refs (NOT callable_refs) and
|
||||||
var saw_site = false;
|
// resolve to the SAME target author.
|
||||||
|
const before_alias = findFn(prog.root, "call_alias_before") orelse return error.MissingBeforeAliasFn;
|
||||||
|
const before_alias_callee = firstBareCallCallee(before_alias, "plus") orelse return error.MissingBeforeAliasCallee;
|
||||||
|
const before_ref = rp.ufcs_refs.get(before_alias_callee) orelse return error.BeforeAliasUfcsNotKeyed;
|
||||||
|
try std.testing.expect(before_ref == .authors);
|
||||||
|
try std.testing.expect(before_ref.authors.own != null);
|
||||||
|
try std.testing.expectEqual(fn_tag, std.meta.activeTag(before_ref.authors.own.?.raw));
|
||||||
|
try std.testing.expect(rp.callable_refs.get(before_alias_callee) == null);
|
||||||
|
|
||||||
|
var saw_after_site = false;
|
||||||
var uit = rp.ufcs_refs.iterator();
|
var uit = rp.ufcs_refs.iterator();
|
||||||
while (uit.next()) |e| {
|
while (uit.next()) |e| {
|
||||||
const k = e.key_ptr.*;
|
const k = e.key_ptr.*;
|
||||||
if (k.data == .identifier and std.mem.eql(u8, k.data.identifier.name, "plus")) {
|
if (k != before_alias_callee and k.data == .identifier and std.mem.eql(u8, k.data.identifier.name, "plus")) {
|
||||||
try std.testing.expect(e.value_ptr.* == .authors);
|
try std.testing.expect(e.value_ptr.* == .authors);
|
||||||
try std.testing.expect(e.value_ptr.authors.own != null);
|
try std.testing.expect(e.value_ptr.authors.own != null);
|
||||||
try std.testing.expectEqual(fn_tag, std.meta.activeTag(e.value_ptr.authors.own.?.raw));
|
try std.testing.expectEqual(fn_tag, std.meta.activeTag(e.value_ptr.authors.own.?.raw));
|
||||||
saw_site = true;
|
saw_after_site = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try std.testing.expect(saw_site);
|
try std.testing.expect(saw_after_site);
|
||||||
|
|
||||||
// A UFCS-rewritten callee is NOT also recorded as an ordinary callable head.
|
// A UFCS-rewritten callee is NOT also recorded as an ordinary callable head.
|
||||||
var cit = rp.callable_refs.iterator();
|
var cit = rp.callable_refs.iterator();
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ pub fn resolve(
|
|||||||
.ufcs_aliases = std.StringHashMap([]const u8).init(alloc),
|
.ufcs_aliases = std.StringHashMap([]const u8).init(alloc),
|
||||||
};
|
};
|
||||||
defer pass.ufcs_aliases.deinit();
|
defer pass.ufcs_aliases.deinit();
|
||||||
|
pass.seedTopLevelUfcsAliases(root);
|
||||||
pass.visit(root, .{ .source = main_file, .scope = null });
|
pass.visit(root, .{ .source = main_file, .scope = null });
|
||||||
return pass.out;
|
return pass.out;
|
||||||
}
|
}
|
||||||
@@ -352,6 +353,10 @@ const Frame = struct {
|
|||||||
const Ctx = struct {
|
const Ctx = struct {
|
||||||
source: []const u8,
|
source: []const u8,
|
||||||
scope: ?*const Frame,
|
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
|
/// 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
|
/// names must resolve in their DEFINING module) overrides the ambient source
|
||||||
/// for this subtree; an unstamped node inherits its parent's.
|
/// for this subtree; an unstamped node inherits its parent's.
|
||||||
fn visit(self: *ResolvePass, node: *const ast.Node, ctx: Ctx) void {
|
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) {
|
switch (node.data) {
|
||||||
// ── declarations that open a generic-param scope ──
|
// ── declarations that open a generic-param scope ──
|
||||||
.fn_decl => |*fd| {
|
.fn_decl => |*fd| {
|
||||||
@@ -585,7 +594,8 @@ const ResolvePass = struct {
|
|||||||
// ── structural recursion (no classification of their own) ──
|
// ── structural recursion (no classification of their own) ──
|
||||||
.root => |*r| {
|
.root => |*r| {
|
||||||
// each top-level decl carries its own ambient source stamp.
|
// 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),
|
.block => |*b| self.visitAll(b.stmts, here),
|
||||||
.binary_op => |*b| {
|
.binary_op => |*b| {
|
||||||
@@ -746,12 +756,37 @@ const ResolvePass = struct {
|
|||||||
// name so its rewrite call sites resolve to the same target. The map is
|
// name so its rewrite call sites resolve to the same target. The map is
|
||||||
// global / traversal-ordered, mirroring lowering's flat `ufcs_alias_map`.
|
// global / traversal-ordered, mirroring lowering's flat `ufcs_alias_map`.
|
||||||
.ufcs_alias => |ua| {
|
.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);
|
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 {
|
fn visitAll(self: *ResolvePass, nodes: anytype, ctx: Ctx) void {
|
||||||
for (nodes) |n| self.visit(n, ctx);
|
for (nodes) |n| self.visit(n, ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user