diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 89a7dc446..3d0a25bb7 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7603,24 +7603,37 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, array_add(&proc_entities, data.gen_entity); index = proc_entities.count-1; - // Order candidates: value-polymorphic > concrete > type-polymorphic + // Order candidates: + // value-polymorphic > concrete > specialized generic > unconstrained generic // // `proc($S: string)` specialises on a compile-time *value* - // `proc(x: $T)` specialises on a *type* and is a fallback, so it should - // lose to an exact concrete overload + // `proc(x: $T)` specialises on a *type* and is a fallback, so it should lose + // to an exact concrete overload + // `proc(x: $T/[]$E)` constrains that type, so it is the closer of the two // - // Both are small tie-breaks on purpose: assign_score_function(1) is + // These are small tie-breaks on purpose: assign_score_function(1) is // ~a full perfect-match unit and would swamp argument match quality. bool has_polymorphic_constant = false; + bool has_specialized_generic = false; if (pt->Proc.params != nullptr) { for (Entity *param : pt->Proc.params->Tuple.variables) { - if (param != nullptr && param->kind == Entity_Constant) { + if (param == nullptr) { + continue; + } + if (param->kind == Entity_Constant) { has_polymorphic_constant = true; - break; + } + Type *bt = base_type(param->type); + if (bt != nullptr && bt->kind == Type_Generic && bt->Generic.specialized != nullptr) { + has_specialized_generic = true; } } } - item.score += has_polymorphic_constant ? +1 : -1; + if (has_polymorphic_constant) { + item.score += 2; + } else { + item.score += has_specialized_generic ? -1 : -2; + } } max_matched_features = gb_max(max_matched_features, matched_target_features(&pt->Proc)); diff --git a/tests/internal/test_proc_group_type_inference.odin b/tests/internal/test_proc_group_type_inference.odin index 907d6c36e..4a77dc1a6 100644 --- a/tests/internal/test_proc_group_type_inference.odin +++ b/tests/internal/test_proc_group_type_inference.odin @@ -179,32 +179,26 @@ test_proc_group_polymorphic_precedence :: proc(t: ^testing.T) { testing.expect_value(t, group("literal"), 1) } - // NOTE: the two cases below are still reported as ambiguous. Both are in the - // polymorphic instantiation machinery rather than in candidate scoring, and are - // expected to be resolved by https://github.com/odin-lang/Odin/pull/7208 - // - // A more specialised generic should beat a less specialised one: - // - // { - // proc_slice :: proc(x: $T/[]$E) -> int { return 1 } - // proc_generic :: proc(x: $T) -> int { return 2 } - // group :: proc{proc_slice, proc_generic} - // - // s := []int{1} - // testing.expect_value(t, group(s), 1) - // } - // - // Passing a polymorphic procedure to a group whose members take procedure-typed - // parameters: only foo_concrete can accept f_poly once instantiated. - // - // { - // f_poly :: proc(x: $T) -> T { return x } - // foo_concrete :: proc(x: int, g: proc(int) -> int) -> int { return 1 } - // foo_impossible :: proc(x: int, g: proc(int, int) -> string) -> int { return 2 } - // group :: proc{foo_concrete, foo_impossible} - // - // testing.expect_value(t, group(1, f_poly), 1) - // } + // a specialised generic beats an unconstrained one + { + proc_slice :: proc(x: $T/[]$E) -> int { return 1 } + proc_generic :: proc(x: $T) -> int { return 2 } + group :: proc{proc_slice, proc_generic} + + s := []int{1} + testing.expect_value(t, group(s), 1) + } + + // passing a polymorphic procedure to a group whose members take procedure-typed + // parameters: only foo_concrete can accept f_poly once instantiated + { + f_poly :: proc(x: $T) -> T { return x } + foo_concrete :: proc(x: int, g: proc(int) -> int) -> int { return 1 } + foo_impossible :: proc(x: int, g: proc(int, int) -> string) -> int { return 2 } + group :: proc{foo_concrete, foo_impossible} + + testing.expect_value(t, group(1, f_poly), 1) + } } @test