mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-27 07:21:31 +00:00
Merge pull request #7227 from kalsprite/poly
Improve procedure group overload scoring
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -34,15 +34,170 @@ test_type_inference_on_literals_with_default_args :: proc(t: ^testing.T) {
|
||||
}
|
||||
{
|
||||
// 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}) -> Bit_Set { return a }
|
||||
proc_2 :: proc(a: Bit_Set={.B}, b: Bit_Set={.C}) -> int { return 2 }
|
||||
group :: proc{proc_1, proc_2}
|
||||
proc_2 :: proc(a, b: Bit_Set) -> Bit_Set { return b }
|
||||
group :: proc{proc_1, proc_2}
|
||||
|
||||
testing.expect_value(t, group(), Bit_Set{.A})
|
||||
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{.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)
|
||||
}
|
||||
}
|
||||
|
||||
@test
|
||||
|
||||
Reference in New Issue
Block a user