refactor(ir): move protocol/impl registration into ProtocolResolver (A4.2 registration increment)
Move the registration functions behind the protocols.zig facade, per PLAN-ARCH
A4.2 ("then registration", keeping IR emission in Lowering):
- registerProtocolDecl (protocol struct + dispatch method table + vtable type),
- registerImplBlock (concrete impl -> <Target>.<method> in fn_ast_map + default-
method synthesis),
- registerParamImpl (parameterised impl -> param_impl_map / param_impl_pack_map
+ the same-file duplicate diagnostic),
- synthesizeDefaultMethod (facade-private; its only caller moved too).
Moved verbatim with self. -> self.l. facade rewrites. Emission stays in
Lowering: the registry calls self.l.declareFunction (the extern-stub primitive)
but the thunk/value builders (createProtocolThunk / buildProtocolValue /
tryUserConversion / getOrCreateThunks) are NOT moved.
Lowering keeps registerProtocolDecl as a thin pub wrapper (scan pass + 7
unit-test callers); registerImplBlock / registerParamImpl /
synthesizeDefaultMethod deleted (no fallback), the 2 scan call sites routed
through protocolResolver(). New pub: declareFunction (8 callers, emission infra),
ParamImplEntry / PackParamImplEntry (the registry constructs them; stay as
Lowering nested types). State maps remain on Lowering; the facade reads/writes
self.l.* (migrate once planning lands).
protocols.test.zig +2: registerImplBlock records Circle.draw in fn_ast_map (and
packArgConformsTo then sees it); registerParamImpl flags a same-file duplicate
impl Into(s64) for IntCell (the 0412-class, unit level).
zig build, zig build test, tests/run_examples.sh (357/0) all green — no .ir
churn; the 0410/0411/0412 rejection diagnostics are byte-for-byte preserved.
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
const std = @import("std");
|
||||
const ast = @import("../ast.zig");
|
||||
const Node = ast.Node;
|
||||
const errors = @import("../errors.zig");
|
||||
|
||||
const ir_mod = @import("ir.zig");
|
||||
const TypeId = ir_mod.TypeId;
|
||||
@@ -19,6 +20,18 @@ fn protoMethodReq(name: []const u8) ast.ProtocolMethodDecl {
|
||||
return .{ .name = name, .params = &.{}, .param_names = &.{}, .return_type = null, .default_body = null };
|
||||
}
|
||||
|
||||
fn mk(alloc: std.mem.Allocator, data: ast.Node.Data) *Node {
|
||||
const n = alloc.create(Node) catch unreachable;
|
||||
n.* = .{ .span = .{ .start = 0, .end = 0 }, .data = data };
|
||||
return n;
|
||||
}
|
||||
fn typeExpr(alloc: std.mem.Allocator, name: []const u8) *Node {
|
||||
return mk(alloc, .{ .type_expr = .{ .name = name, .is_generic = false } });
|
||||
}
|
||||
fn emptyBody(alloc: std.mem.Allocator) *Node {
|
||||
return mk(alloc, .{ .block = .{ .stmts = &.{} } });
|
||||
}
|
||||
|
||||
test "protocols: getProtocolInfo resolves registered protocol structs only" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
@@ -105,3 +118,69 @@ test "protocols: packArgConformsTo at the impl-declaration level (non-parameteri
|
||||
// An unregistered protocol name conforms to nothing.
|
||||
try std.testing.expect(!pr.packArgConformsTo("Nope", circle));
|
||||
}
|
||||
|
||||
test "protocols: registerImplBlock records <Target>.<method> in fn_ast_map" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
defer module.deinit();
|
||||
var l = Lowering.init(&module);
|
||||
const pr = ProtocolResolver{ .l = &l };
|
||||
|
||||
// Drawable :: protocol { draw :: (); } + impl Drawable for Circle { draw :: (){} }
|
||||
const proto_methods = [_]ast.ProtocolMethodDecl{protoMethodReq("draw")};
|
||||
const pd = ast.ProtocolDecl{ .name = "Drawable", .methods = &proto_methods };
|
||||
l.registerProtocolDecl(&pd);
|
||||
|
||||
const draw_node = mk(alloc, .{ .fn_decl = .{ .name = "draw", .params = &.{}, .return_type = null, .body = emptyBody(alloc) } });
|
||||
const methods = [_]*Node{draw_node};
|
||||
const ib = ast.ImplBlock{ .protocol_name = "Drawable", .target_type = "Circle", .methods = &methods };
|
||||
const decl = mk(alloc, .{ .impl_block = ib });
|
||||
|
||||
// Not registered before; the non-parameterised impl registers `Circle.draw`.
|
||||
try std.testing.expect(!l.program_index.fn_ast_map.contains("Circle.draw"));
|
||||
pr.registerImplBlock(&ib, false, decl);
|
||||
try std.testing.expect(l.program_index.fn_ast_map.contains("Circle.draw"));
|
||||
// And it now conforms (same fn_ast_map entry packArgConformsTo checks).
|
||||
const circle = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Circle"), .fields = &.{} } });
|
||||
try std.testing.expect(pr.packArgConformsTo("Drawable", circle));
|
||||
}
|
||||
|
||||
test "protocols: registerParamImpl flags a same-file duplicate impl" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
defer module.deinit();
|
||||
var diags = errors.DiagnosticList.init(alloc, "", "test.sx");
|
||||
defer diags.deinit();
|
||||
var l = Lowering.init(&module);
|
||||
l.diagnostics = &diags;
|
||||
l.current_source_file = "test.sx"; // both impls share a defining module
|
||||
|
||||
const pr = ProtocolResolver{ .l = &l };
|
||||
_ = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("IntCell"), .fields = &.{} } });
|
||||
|
||||
// impl Into(s64) for IntCell { ... } — a parameterised-protocol impl.
|
||||
const args = [_]*Node{typeExpr(alloc, "s64")};
|
||||
const conv = mk(alloc, .{ .fn_decl = .{ .name = "convert", .params = &.{}, .return_type = null, .body = emptyBody(alloc) } });
|
||||
const methods = [_]*Node{conv};
|
||||
const ib = ast.ImplBlock{
|
||||
.protocol_name = "Into",
|
||||
.target_type = "IntCell",
|
||||
.methods = &methods,
|
||||
.protocol_type_args = &args,
|
||||
};
|
||||
const decl = mk(alloc, .{ .impl_block = ib });
|
||||
|
||||
// First registration is fine; the second (same key, same module) is a dup.
|
||||
pr.registerImplBlock(&ib, false, decl);
|
||||
pr.registerImplBlock(&ib, false, decl);
|
||||
|
||||
var dup_reported = false;
|
||||
for (diags.items.items) |d| {
|
||||
if (d.level == .err and std.mem.indexOf(u8, d.message, "duplicate impl 'Into'") != null) dup_reported = true;
|
||||
}
|
||||
try std.testing.expect(dup_reported);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user