Move the pure protocol/impl conformance lookups into one module,
src/ir/protocols.zig, behind a *Lowering facade (ProtocolResolver), mirroring
GenericResolver / CallResolver. Per PLAN-ARCH A4.2 ("move pure lookup first;
keep emission in Lowering"), this increment moves only the read-only queries:
- getProtocolInfo (is a type a registered protocol + its method table),
- hasImplPlain (have the (protocol, type) thunks been materialized),
- packArgConformsTo (impl-declaration-level conformance for ..xs: P).
Registration (registerProtocolDecl / registerImplBlock / registerParamImpl) and
all IR emission (createProtocolThunk / buildProtocolValue / tryUserConversion /
getOrCreateThunks) stay in Lowering for the later increments. The state maps
(protocol_thunk_map / param_impl_map on Lowering, protocol_decl_map /
protocol_ast_map in ProgramIndex) stay put; the facade reads them via self.l.* —
no map migration.
Lowering keeps getProtocolInfo as a thin pub wrapper (~9 callers incl.
calls.zig); hasImplPlain + packArgConformsTo are deleted (no fallback), their 3
call sites (computeHasImpl x2, the pack-conformance check x1) routed through
self.protocolResolver(). formatTypeName widened to pub (the lookups use it);
protocolResolver() accessor added.
protocols.test.zig (wired into the barrel) drives ProtocolResolver directly:
getProtocolInfo (registered vs builtin/plain-struct + wrapper delegation),
hasImplPlain (thunk-map materialization), packArgConformsTo (non-parameterised
requires <ty>.<m> in fn_ast_map; trivially-true for an erased protocol value;
false for unknown protocol).
zig build, zig build test, tests/run_examples.sh (357/0) all green — no .ir
snapshot churn; the 0410/0411/0412 rejection anchors still pass.
108 lines
5.0 KiB
Zig
108 lines
5.0 KiB
Zig
// Tests for protocols.zig — the protocol/impl LOOKUP owner (`ProtocolResolver`).
|
|
// Reached via `ir.ProtocolResolver{ .l = &lowering }`, mirroring calls.test.zig /
|
|
// generics.test.zig. Covers the pure conformance queries moved out of `Lowering`
|
|
// in A4.2 sub-step 2 (lookup increment); registration + emission stay in
|
|
// `Lowering`, so their plan tests land with later increments.
|
|
|
|
const std = @import("std");
|
|
const ast = @import("../ast.zig");
|
|
const Node = ast.Node;
|
|
|
|
const ir_mod = @import("ir.zig");
|
|
const TypeId = ir_mod.TypeId;
|
|
const FuncId = ir_mod.FuncId;
|
|
const Lowering = ir_mod.Lowering;
|
|
const ProtocolResolver = ir_mod.ProtocolResolver;
|
|
|
|
fn protoMethodReq(name: []const u8) ast.ProtocolMethodDecl {
|
|
// A required (no default body) method, no params, void return.
|
|
return .{ .name = name, .params = &.{}, .param_names = &.{}, .return_type = null, .default_body = null };
|
|
}
|
|
|
|
test "protocols: getProtocolInfo resolves registered protocol structs only" {
|
|
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 };
|
|
|
|
const methods = [_]ast.ProtocolMethodDecl{protoMethodReq("draw")};
|
|
const pd = ast.ProtocolDecl{ .name = "Drawable", .methods = &methods };
|
|
l.registerProtocolDecl(&pd);
|
|
|
|
// The registered protocol struct resolves to its decl info.
|
|
const drawable_ty = module.types.findByName(module.types.internString("Drawable")).?;
|
|
const info = pr.getProtocolInfo(drawable_ty).?;
|
|
try std.testing.expectEqualStrings("Drawable", info.name);
|
|
try std.testing.expectEqual(@as(usize, 1), info.methods.len);
|
|
|
|
// A builtin and an unrelated plain struct are not protocols.
|
|
try std.testing.expect(pr.getProtocolInfo(.s32) == null);
|
|
const plain = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Point"), .fields = &.{} } });
|
|
try std.testing.expect(pr.getProtocolInfo(plain) == null);
|
|
|
|
// The Lowering wrapper delegates to the same result.
|
|
try std.testing.expect(l.getProtocolInfo(drawable_ty) != null);
|
|
}
|
|
|
|
test "protocols: hasImplPlain reflects materialized thunks for a (protocol, type) pair" {
|
|
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 };
|
|
|
|
const circle = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Circle"), .fields = &.{} } });
|
|
|
|
// No thunks yet → not materialized.
|
|
try std.testing.expect(!pr.hasImplPlain("Drawable", circle));
|
|
|
|
// Materialize the (Drawable, Circle) thunk slot the way `getOrCreateThunks`
|
|
// does — key "Proto\x00<formatTypeName>". hasImplPlain must then see it.
|
|
const key = std.fmt.allocPrint(alloc, "Drawable\x00{s}", .{l.formatTypeName(circle)}) catch unreachable;
|
|
l.protocol_thunk_map.put(key, &[_]FuncId{}) catch unreachable;
|
|
try std.testing.expect(pr.hasImplPlain("Drawable", circle));
|
|
|
|
// A different protocol over the same type is still unmaterialized.
|
|
try std.testing.expect(!pr.hasImplPlain("Hash", circle));
|
|
}
|
|
|
|
test "protocols: packArgConformsTo at the impl-declaration level (non-parameterised)" {
|
|
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 };
|
|
|
|
// Shape :: protocol { draw :: (); } (non-parameterised, one required method)
|
|
const methods = [_]ast.ProtocolMethodDecl{protoMethodReq("draw")};
|
|
const pd = ast.ProtocolDecl{ .name = "Shape", .methods = &methods };
|
|
l.registerProtocolDecl(&pd);
|
|
|
|
const circle = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Circle"), .fields = &.{} } });
|
|
|
|
// No `Circle.draw` registered → does NOT conform.
|
|
try std.testing.expect(!pr.packArgConformsTo("Shape", circle));
|
|
|
|
// Register the impl method `Circle.draw` (how registerImplBlock records a
|
|
// non-parameterised impl) → now conforms.
|
|
const body = alloc.create(Node) catch unreachable;
|
|
body.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .block = .{ .stmts = &.{} } } };
|
|
const draw_fd = ast.FnDecl{ .name = "Circle.draw", .params = &.{}, .return_type = null, .body = body };
|
|
l.program_index.fn_ast_map.put("Circle.draw", &draw_fd) catch unreachable;
|
|
try std.testing.expect(pr.packArgConformsTo("Shape", circle));
|
|
|
|
// An arg already erased to the protocol struct itself trivially conforms.
|
|
const shape_ty = module.types.findByName(module.types.internString("Shape")).?;
|
|
try std.testing.expect(pr.packArgConformsTo("Shape", shape_ty));
|
|
|
|
// An unregistered protocol name conforms to nothing.
|
|
try std.testing.expect(!pr.packArgConformsTo("Nope", circle));
|
|
}
|