test(ir): cover buildTypeBindings strategy-2 inference (A4.1 coverage closeout)

Adds the one deferred A4.1 coverage item: a focused unit test for
GenericResolver.buildTypeBindings inferring a type param from value args
(strategy 2) with widest-match — add(1,2) => T=s64, and add(1.0,2) / add(1,2.0)
=> T=f64 regardless of argument order.

Previously this inference path was guarded only by the 0200 .ir snapshot; the
unit test pins it directly against the new generics.zig API. Test-only.

zig build test and tests/run_examples.sh (357/0) green.
This commit is contained in:
agra
2026-06-02 21:34:58 +03:00
parent 3ca68189c0
commit 7ab5d7bee9

View File

@@ -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 = &params, .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").?);
}
}