diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 80c425f2b..3d0a25bb7 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -735,35 +735,38 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand if (operand->mode == Addressing_Constant) { if (check_representable_as_constant(c, operand->value, dst, nullptr)) { if (is_type_typed(dst) && src->kind == Type_Basic) { + // NOTE: prefer the untyped constant's default type (int, f64, string, bool, + // rune) over other members of the same family, so a call like g(1) picks + // `int` over `i64` instead of scoring them identically and going ambiguous. switch (src->Basic.kind) { case Basic_UntypedBool: if (is_type_boolean(dst)) { - return 1; + return are_types_identical(dst, default_type(src)) ? 1 : 2; } break; case Basic_UntypedRune: if (is_type_integer(dst) || is_type_rune(dst)) { - return 1; + return are_types_identical(dst, default_type(src)) ? 1 : 2; } break; case Basic_UntypedInteger: if (is_type_integer(dst) || is_type_rune(dst)) { - return 1; + return are_types_identical(dst, default_type(src)) ? 1 : 2; } break; case Basic_UntypedString: if (is_type_string(dst)) { - return 1; + return are_types_identical(dst, default_type(src)) ? 1 : 2; } break; case Basic_UntypedFloat: if (is_type_float(dst)) { - return 1; + return are_types_identical(dst, default_type(src)) ? 1 : 2; } break; case Basic_UntypedComplex: if (is_type_complex(dst)) { - return 1; + return are_types_identical(dst, default_type(src)) ? 1 : 2; } if (is_type_quaternion(dst)) { return 2; @@ -771,12 +774,13 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand break; case Basic_UntypedQuaternion: if (is_type_quaternion(dst)) { - return 1; + return are_types_identical(dst, default_type(src)) ? 1 : 2; } break; } } - return 2; + // A cross-family constant conversion ranks below a same-family one. + return 3; } return -1; } @@ -7008,6 +7012,11 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A error(o->expr, "'..' in a variadic procedure can only have one variadic argument at the end"); } if (data) { + // A synthesised default argument is not evidence of a better match: it + // contributes assign_score_function(1) as a dummy bonus and is then scored + // again as a perfect-match argument. Discount both, plus 1 to break the + // resulting tie, so an exact-arity overload wins. + score -= dummy_argument_count * (assign_score_function(0) + assign_score_function(1) + 1); data->score = score; data->result_type = final_proc_type->Proc.results; data->gen_entity = gen_entity; @@ -7037,6 +7046,11 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } if (data) { + // A synthesised default argument is not evidence of a better match: it + // contributes assign_score_function(1) as a dummy bonus and is then scored + // again as a perfect-match argument. Discount both, plus 1 to break the + // resulting tie, so an exact-arity overload wins. + score -= dummy_argument_count * (assign_score_function(0) + assign_score_function(1) + 1); data->score = score; data->result_type = final_proc_type->Proc.results; data->gen_entity = gen_entity; @@ -7589,8 +7603,37 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, array_add(&proc_entities, data.gen_entity); index = proc_entities.count-1; - // prefer non-polymorphic procedures over polymorphic - item.score += assign_score_function(1); + // 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/[]$E)` constrains that type, so it is the closer of the two + // + // 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) { + continue; + } + if (param->kind == Entity_Constant) { + has_polymorphic_constant = true; + } + Type *bt = base_type(param->type); + if (bt != nullptr && bt->kind == Type_Generic && bt->Generic.specialized != nullptr) { + has_specialized_generic = true; + } + } + } + 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 32498c836..4a77dc1a6 100644 --- a/tests/internal/test_proc_group_type_inference.odin +++ b/tests/internal/test_proc_group_type_inference.odin @@ -34,15 +34,170 @@ test_type_inference_on_literals_with_default_args :: proc(t: ^testing.T) { testing.expect_value(t, group({.A}), Bit_Set{.A}) } { + // NOTE: the overloads differ in arity so that only one is ever viable, i.e. this + // checks what was inferred rather than which overload won. See + // test_proc_group_default_arg_precedence for the latter. Bit_Set :: bit_set[enum{A, B, C}] - proc_1 :: proc(a: Bit_Set={.A}) -> int { return 1 } - proc_2 :: proc(a: Bit_Set={.B}, b: Bit_Set={.C}) -> int { return 2 } + proc_1 :: proc(a: Bit_Set={.A}) -> Bit_Set { return a } + proc_2 :: proc(a, b: Bit_Set) -> Bit_Set { return b } group :: proc{proc_1, proc_2} - testing.expect_value(t, group(), 2) - testing.expect_value(t, group(Bit_Set{.A}), 2) - testing.expect_value(t, group({.A}), 2) - testing.expect_value(t, group({.B}, {.C}), 2) + testing.expect_value(t, group(), Bit_Set{.A}) + testing.expect_value(t, group(Bit_Set{.B}), Bit_Set{.B}) + testing.expect_value(t, group({.B}), Bit_Set{.B}) + testing.expect_value(t, group({.B}, {.C}), Bit_Set{.C}) + } +} + +@test +test_proc_group_default_arg_precedence :: proc(t: ^testing.T) { + // An overload that needs fewer default arguments synthesised is the closer match, so + // proc_1 wins whenever both are viable. + Bit_Set :: bit_set[enum{A, B, C}] + proc_1 :: proc(a: Bit_Set={.A}) -> int { return 1 } + proc_2 :: proc(a: Bit_Set={.B}, b: Bit_Set={.C}) -> int { return 2 } + group :: proc{proc_1, proc_2} + + testing.expect_value(t, group(), 1) // proc_1 synthesises 1, proc_2 synthesises 2 + testing.expect_value(t, group(Bit_Set{.A}), 1) // proc_1 synthesises 0, proc_2 synthesises 1 + testing.expect_value(t, group({.A}), 1) + testing.expect_value(t, group({.B}, {.C}), 2) // only proc_2 takes two arguments +} + +@test +test_proc_group_arity_precedence :: proc(t: ^testing.T) { + { + // a non-variadic overload is the closer match when the variadic part is empty + proc_exact :: proc(x: int) -> int { return 1 } + proc_variadic :: proc(x: int, r: ..int) -> int { return 2 } + group :: proc{proc_exact, proc_variadic} + + testing.expect_value(t, group(1), 1) + testing.expect_value(t, group(1, 2, 3), 2) + } + { + // adding a defaulted sibling must not steal an exact match from an existing member + proc_int :: proc(x: int) -> int { return 1 } + proc_string :: proc(x: string) -> int { return 2 } + proc_f32 :: proc(x: f32) -> int { return 3 } + proc_f32_d :: proc(x: f32, y: int=0) -> int { return 4 } + proc_rune :: proc(x: rune) -> int { return 5 } + group :: proc{proc_int, proc_string, proc_f32, proc_f32_d, proc_rune} + + v: f32 + testing.expect_value(t, group(v), 3) + } +} + +@test +test_proc_group_untyped_constant_default_type :: proc(t: ^testing.T) { + // An untyped constant prefers its default type over other members of the same family, + // and any same-family type over a cross-family one. + { + proc_int :: proc(int) -> int { return 1 } + proc_i64 :: proc(i64) -> int { return 2 } + group :: proc{proc_int, proc_i64} + testing.expect_value(t, group(1), 1) + } + { + // neither is the default type, but the integer family still beats the float one + proc_i64 :: proc(i64) -> int { return 1 } + proc_f64 :: proc(f64) -> int { return 2 } + group :: proc{proc_i64, proc_f64} + testing.expect_value(t, group(1), 1) + } + { + proc_f32 :: proc(f32) -> int { return 1 } + proc_f64 :: proc(f64) -> int { return 2 } + group :: proc{proc_f32, proc_f64} + testing.expect_value(t, group(1.5), 2) + } + { + proc_rune :: proc(rune) -> int { return 1 } + proc_int :: proc(int) -> int { return 2 } + group :: proc{proc_rune, proc_int} + testing.expect_value(t, group('x'), 1) + } + { + proc_string :: proc(string) -> int { return 1 } + proc_cstring :: proc(cstring) -> int { return 2 } + group :: proc{proc_string, proc_cstring} + testing.expect_value(t, group("hi"), 1) + } + { + proc_bool :: proc(bool) -> int { return 1 } + proc_b32 :: proc(b32) -> int { return 2 } + group :: proc{proc_bool, proc_b32} + testing.expect_value(t, group(true), 1) + } + { + // a value that does not fit the default type selects the overload that can hold it + proc_u8 :: proc(u8) -> int { return 1 } + proc_i64 :: proc(i64) -> int { return 2 } + group :: proc{proc_u8, proc_i64} + testing.expect_value(t, group(100000), 2) + } +} + +@test +test_proc_group_polymorphic_precedence :: proc(t: ^testing.T) { + // Candidates are ordered value-polymorphic > concrete > type-polymorphic: `proc($S: T)` + // specializes on a compile-time value and is the most specific, `proc(x: $T)` + // specializes on a type and is a fallback. + { + proc_concrete :: proc(x: int) -> int { return 1 } + proc_generic :: proc(x: $T) -> int { return 2 } + group :: proc{proc_concrete, proc_generic} + + testing.expect_value(t, group(1), 1) + v: int = 1 + testing.expect_value(t, group(v), 1) + } + { + // the generic is the only viable overload + proc_concrete :: proc(x: string) -> int { return 1 } + proc_generic :: proc(x: $T) -> int { return 2 } + group :: proc{proc_concrete, proc_generic} + testing.expect_value(t, group(1), 2) + } + { + proc_static :: proc($S: string) -> int { return 1 } + proc_dynamic :: proc(s: string) -> int { return 2 } + group :: proc{proc_static, proc_dynamic} + + testing.expect_value(t, group("literal"), 1) + s := "runtime" + testing.expect_value(t, group(s), 2) // not a constant, only proc_dynamic is viable + } + { + // all three tiers present, and the result must not depend on declaration order + proc_generic :: proc(x: $T) -> int { return 3 } + proc_concrete :: proc(s: string) -> int { return 2 } + proc_static :: proc($S: string) -> int { return 1 } + group :: proc{proc_generic, proc_concrete, proc_static} + + testing.expect_value(t, group("literal"), 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) } }