// 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". 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)); }