diff --git a/src/ir/generics.test.zig b/src/ir/generics.test.zig index 5548b38..f780ac3 100644 --- a/src/ir/generics.test.zig +++ b/src/ir/generics.test.zig @@ -109,3 +109,60 @@ test "generics: inferGenericReturnType binds explicit type args, resolves return // it must NOT leak the call's temporary bindings (the issue-0048/0050 class). try std.testing.expect(l.type_bindings == null); } + +test "generics: buildTypeBindings infers a type param from value args, widest wins" { + 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 gr = GenericResolver{ .l = &l }; + + // add :: (a: T, b: T) -> T with type param T — NO leading `$T: Type` decl, + // so T is inferred from the value args (strategy 2), not passed explicitly. + const tps = [_]ast.StructTypeParam{.{ .name = "T", .constraint = typeKeyword(alloc, "Type") }}; + const params = [_]ast.Param{ + .{ .name = "a", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeKeyword(alloc, "T") }, + .{ .name = "b", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeKeyword(alloc, "T") }, + }; + const body = alloc.create(Node) catch unreachable; + body.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .block = .{ .stmts = &.{} } } }; + const fd = ast.FnDecl{ .name = "add", .params = ¶ms, .return_type = typeKeyword(alloc, "T"), .body = body, .type_params = &tps }; + + const intLit = struct { + fn f(a: std.mem.Allocator, v: i64) *Node { + const n = a.create(Node) catch unreachable; + n.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .int_literal = .{ .value = v } } }; + return n; + } + }.f; + const floatLit = struct { + fn f(a: std.mem.Allocator, v: f64) *Node { + const n = a.create(Node) catch unreachable; + n.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .float_literal = .{ .value = v } } }; + return n; + } + }.f; + + // add(1, 2) — both args s64 → T = s64. + { + const args = [_]*const Node{ intLit(alloc, 1), intLit(alloc, 2) }; + var bindings = gr.buildTypeBindings(&fd, &args); + defer bindings.deinit(); + try std.testing.expectEqual(TypeId.s64, bindings.get("T").?); + } + // add(1.0, 2) — mixed f64/s64 → widest f64 wins regardless of order. + { + const args = [_]*const Node{ floatLit(alloc, 1.0), intLit(alloc, 2) }; + var bindings = gr.buildTypeBindings(&fd, &args); + defer bindings.deinit(); + try std.testing.expectEqual(TypeId.f64, bindings.get("T").?); + } + { + const args = [_]*const Node{ intLit(alloc, 1), floatLit(alloc, 2.0) }; + var bindings = gr.buildTypeBindings(&fd, &args); + defer bindings.deinit(); + try std.testing.expectEqual(TypeId.f64, bindings.get("T").?); + } +}