diff --git a/src/server/analysis.odin b/src/server/analysis.odin index 2269d1e8..e8b64e44 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -794,6 +794,7 @@ CallArg :: struct { is_nil: bool, bad_expr: bool, has_symbol: bool, + is_constant: bool, is_poly_type: bool, } @@ -854,6 +855,7 @@ expand_call_args :: proc(ast_context: ^AstContext, call: ^ast.Call_Expr) -> ([]C if symbol, ok := resolve_call_arg_type_expression(ast_context, call_arg.value_expr); ok { call_arg.symbol = symbol call_arg.has_symbol = true + call_arg.is_constant = symbol.type == .Constant && !(.Mutable in symbol.flags) if _, ok := symbol.value.(SymbolPolyTypeValue); ok { call_arg.is_poly_type = true append(results, call_arg) @@ -903,6 +905,240 @@ expand_call_args :: proc(ast_context: ^AstContext, call: ^ast.Call_Expr) -> ([]C return results[:], all_valid } +untyped_basic_match_score :: proc(value: SymbolUntypedValue, expected: Symbol) -> (score: int, compatible: bool, handled: bool) { + + basic := expected.value.(SymbolBasicValue) or_return + name := basic.ident.name + + handled = true + + switch name { + case "bool", "b8", "b16", "b32", "b64": + if value.type == .Bool { + score = 1 if name != "bool" else 0 + } else { + return + } + case "int", "i8", "i16", "i32", "i64", "i128", + "uint", "u8", "u16", "u32", "u64", "u128", "uintptr", + "i16le", "i32le", "i64le", "i128le", "u16le", "u32le", "u64le", "u128le", + "i16be", "i32be", "i64be", "i128be", "u16be", "u32be", "u64be", "u128be", + "rune": + if value.type == .Integer { + score = 1 if name != "int" else 0 + + n := strconv.parse_i128(value.tok.text) or_return + valid := true + switch name { + case "u8": valid = n >= i128(min(u8)) && n <= i128(max(u8)) + case "i8": valid = n >= i128(min(i8)) && n <= i128(max(i8)) + case "u16": valid = n >= i128(min(u16)) && n <= i128(max(u16)) + case "i16": valid = n >= i128(min(i16)) && n <= i128(max(i16)) + case "u32": valid = n >= i128(min(u32)) && n <= i128(max(u32)) + case "i32": valid = n >= i128(min(i32)) && n <= i128(max(i32)) + case "u64": valid = n >= i128(min(u64)) && n <= i128(max(u64)) + case "i64": valid = n >= i128(min(i64)) && n <= i128(max(i64)) + } + if !valid { + return + } + } else if value.type == .Rune { + score = 1 if name != "rune" else 0 + } else { + return + } + case "f16", "f32", "f64", + "f16le", "f32le", "f64le", + "f16be", "f32be", "f64be": + if value.type == .Float { + score = 1 if name != "f64" else 0 + } else if value.type == .Integer { + score = 2 + } else { + return + } + case "string", "cstring": + if value.type == .String { + score = 1 if name != "string" else 0 + } else { + return + } + case "complex32", "complex64", "complex128", + "quaternion64", "quaternion128", "quaternion256": + if value.type == .Complex { + score = 1 if strings.has_prefix(name, "quaternion") else 0 + } else if value.type == .Quaternion { + score = 1 if name != "quaternion64" else 0 + } else { + return + } + case: + return + } + + return score, true, true +} + +proc_field_type_for_call :: proc(field: ^ast.Field) -> (type: ^ast.Expr, ok: bool) #optional_ok { + if field == nil { + return nil, false + } + if field.type != nil { + if ellipsis, ok := field.type.derived.(^ast.Ellipsis); ok { + return ellipsis.expr, true + } + return field.type, false + } + return field.default_value, false +} + +proc_field_from_list_at :: proc(fields: []^ast.Field, index: int) -> (field: ^ast.Field, ok:bool) #optional_ok { + current := 0 + for field in fields { + count := max(1, len(field.names)) + if index < current+count { + return field, true + } + current += count + } + return nil, false +} + +get_proc_return_type_from_index :: proc(fields: []^ast.Field, index: int) -> (type: ^ast.Expr, ok: bool) #optional_ok { + field := proc_field_from_list_at(fields, index) or_return + return proc_field_type_for_call(field) +} + +proc_has_variadic_arg :: proc(procedure: SymbolProcedureValue) -> bool { + for field in procedure.arg_types { + if field.type != nil { + if _, ok := field.type.derived.(^ast.Ellipsis); ok { + return true + } + } + } + return false +} + +proc_total_arg_count :: proc(procedure: SymbolProcedureValue) -> int { + total := 0 + for field in procedure.arg_types { + count := max(1, len(field.names)) + total += count + } + return total +} + +proc_has_value_poly_arg :: proc(procedure: SymbolProcedureValue) -> bool { + for field in procedure.orig_arg_types { + for name in field.names { + _ = name.derived.(^ast.Poly_Type) or_continue + if field.type == nil { + return true + } + if _, is_type_parameter := field.type.derived.(^ast.Typeid_Type); !is_type_parameter { + return true + } + } + } + return false +} + +proc_arg_is_value_poly :: proc(procedure: SymbolProcedureValue, index: int) -> bool { + if index < 0 || index >= len(procedure.orig_arg_types) { + return false + } + for name in procedure.orig_arg_types[index].names { + _ = name.derived.(^ast.Poly_Type) or_continue + if procedure.orig_arg_types[index].type == nil { + return true + } + if _, is_type_parameter := procedure.orig_arg_types[index].type.derived.(^ast.Typeid_Type); !is_type_parameter { + return true + } + } + return false +} + +proc_has_type_poly_arg :: proc(procedure: SymbolProcedureValue) -> bool { + for field in procedure.orig_arg_types { + if expr_contains_poly(field.type) { + return true + } + } + return false +} + +proc_unconstrained_poly_arg_count :: proc(procedure: SymbolProcedureValue) -> int { + count := 0 + for field in procedure.orig_arg_types { + if !expr_contains_poly(field.type) { + continue + } + if field.type != nil { + if poly, is_poly := field.type.derived.(^ast.Poly_Type); + is_poly && poly.specialization != nil { + continue + } + } + count += 1 + } + return count +} + +proc_symbols_compatible :: proc(ast_context: ^AstContext, actual, expected: Symbol) -> bool { + + a := actual.value.(SymbolProcedureValue) or_return + b := expected.value.(SymbolProcedureValue) or_return + + a_args := get_proc_arg_count(a) + b_args := get_proc_arg_count(b) + if a_args != b_args { + return false + } + + for i in 0.. total_arg_count { + continue } } - for proc_arg, arg_index in procedure.arg_types { - for name in proc_arg.names { - // Since poly args are usually replaced, we give them a slightly worse score here - // That way if an overload has an exact type match, it'll do better - // We add 1 point per named arg that is poly - orig_arg := procedure.orig_arg_types[arg_index].type - if orig_arg == nil { - orig_arg = procedure.orig_arg_types[arg_index].default_value - } - if expr_contains_poly(orig_arg) { - candidate.score += 1 - } - if i >= len(call_args) { - i += 1 - continue - } - - call_arg := call_args[i] + // Fewer synthesized defaults is a closer match + provided := min(len(call_args), total_arg_count) + candidate.score += (total_arg_count - provided) * 100 + if is_variadic && len(call_args) == total_arg_count-1 { + candidate.score += 1 + } + } + for proc_arg, arg_index in procedure.arg_types { + for name in proc_arg.names { + orig_arg := procedure.orig_arg_types[arg_index].type + if orig_arg == nil { + orig_arg = procedure.orig_arg_types[arg_index].default_value + } + if i >= len(call_args) { i += 1 + continue + } - ast_context.use_locals = true + call_arg := call_args[i] + i += 1 - arg_symbol: Symbol - ok: bool + ast_context.use_locals = true - if call_arg.bad_expr { - continue - } + arg_symbol: Symbol + ok: bool - if call_arg.is_poly_type { - continue - } + if call_arg.bad_expr { + continue + } - if !call_arg.has_symbol && call_arg.implicit_selector == nil && !call_arg.is_nil { - continue - } + if call_arg.is_poly_type { + continue + } + + if !call_arg.has_symbol && call_arg.implicit_selector == nil && !call_arg.is_nil { + continue + } + + proc_arg := proc_arg - proc_arg := proc_arg + if call_arg.named { + proc_arg = get_proc_arg_type_from_name(procedure, call_arg.name) or_continue args + } + + if proc_arg_is_value_poly(procedure, arg_index) && !call_arg.is_constant { + continue args + } + + expected_expr, expected_is_variadic := proc_field_type_for_call(proc_arg) + if expected_is_variadic && ast_context.call != nil && ast_context.call.ellipsis.pos.line != 0 { + expected_expr = proc_arg.type + } + arg_symbol = resolve_call_arg_type_expression(ast_context, expected_expr) or_continue args + + // TODO: check intrinsics for parapoly types? + if _, is_poly := arg_symbol.value.(SymbolPolyTypeValue); is_poly { + candidate.score += 1 + continue + } - if call_arg.named { - proc_arg, ok = get_proc_arg_type_from_name(procedure, call_arg.name) - if !ok { - break next_fn + if call_arg.implicit_selector != nil { + if call_arg.implicit_selector.field.name == "_" { + continue + } + if value, ok := arg_symbol.value.(SymbolEnumValue); ok { + found: bool + for name in value.names { + if call_arg.implicit_selector.field.name == name { + found = true + break + } + } + if found { + continue } } + continue args + } - if proc_arg.type != nil { - arg_symbol, ok = resolve_call_arg_type_expression(ast_context, proc_arg.type) + if call_arg.is_nil { + if is_valid_nil_symbol(arg_symbol) { + continue } else { - arg_symbol, ok = resolve_call_arg_type_expression(ast_context, proc_arg.default_value) - } - - if !ok { - break next_fn + continue args } + } - // TODO: check intrinsics for parapoly types? - if _, is_poly := arg_symbol.value.(SymbolPolyTypeValue); is_poly { - candidate.score += 1 + if untyped, is_untyped := call_arg.symbol.value.(SymbolUntypedValue); is_untyped { + literal_score, compatible, handled := untyped_basic_match_score(untyped, arg_symbol) + if handled { + if !compatible { + continue args + } + candidate.score += literal_score continue } + } - if call_arg.implicit_selector != nil { - if call_arg.implicit_selector.field.name == "_" { - continue + + if !is_symbol_same_typed(ast_context, call_arg.symbol, arg_symbol, proc_arg.flags) { + if _, actual_is_proc := call_arg.symbol.value.(SymbolProcedureValue); actual_is_proc { + if _, expected_is_proc := arg_symbol.value.(SymbolProcedureValue); expected_is_proc { + if proc_symbols_compatible(ast_context, call_arg.symbol, arg_symbol) { + continue + } } - if value, ok := arg_symbol.value.(SymbolEnumValue); ok { - found: bool - for name in value.names { - if call_arg.implicit_selector.field.name == name { + } + found := false + // Are we a union variant + if value, ok := arg_symbol.value.(SymbolUnionValue); ok { + for variant in value.types { + if symbol, ok := resolve_type_expression(ast_context, variant); ok { + if is_symbol_same_typed(ast_context, call_arg.symbol, symbol, proc_arg.flags) { + // matching union types are a low priority + candidate.score = 1000000 found = true break } } - if found { - continue - } - } - break next_fn - } - - if call_arg.is_nil { - if is_valid_nil_symbol(arg_symbol) { - continue - } else { - break next_fn } } - - if !is_symbol_same_typed(ast_context, call_arg.symbol, arg_symbol, proc_arg.flags) { - found := false - // Are we a union variant - if value, ok := arg_symbol.value.(SymbolUnionValue); ok { - for variant in value.types { - if symbol, ok := resolve_type_expression(ast_context, variant); ok { - if is_symbol_same_typed(ast_context, call_arg.symbol, symbol, proc_arg.flags) { - // matching union types are a low priority - candidate.score = 1000000 - found = true - break - } - } + // Do we contain a using that matches + if value, ok := call_arg.symbol.value.(SymbolStructValue); ok { + using_score := 1000000 + for k in value.usings { + symbol := resolve_type_expression(ast_context, value.types[k]) or_continue + + // foo :: proc (bar: ^Bar) — level 1 (arg_symbol) + // baz: struct {using bar: ^Bar} — level 1 (symbol) + // foo(&baz) — level 1 (call_arg.symbol) + if is_symbol_same_typed(ast_context, symbol, arg_symbol, proc_arg.flags) { + using_score = min(k, using_score) + found = true + continue } - } - // Do we contain a using that matches - if value, ok := call_arg.symbol.value.(SymbolStructValue); ok { - using_score := 1000000 - for k in value.usings { - symbol := resolve_type_expression(ast_context, value.types[k]) or_continue - - // foo :: proc (bar: ^Bar) — level 1 (arg_symbol) - // baz: struct {using bar: ^Bar} — level 1 (symbol) - // foo(&baz) — level 1 (call_arg.symbol) + // foo :: proc (bar: ^Bar) — level 1 (arg_symbol) + // baz: struct {using bar: Bar} — level 0 (symbol) + // foo(&baz) — level 1 (call_arg.symbol) + if call_arg.symbol.pointers != symbol.pointers { + symbol.pointers = call_arg.symbol.pointers if is_symbol_same_typed(ast_context, symbol, arg_symbol, proc_arg.flags) { using_score = min(k, using_score) found = true - continue - } - - // foo :: proc (bar: ^Bar) — level 1 (arg_symbol) - // baz: struct {using bar: Bar} — level 0 (symbol) - // foo(&baz) — level 1 (call_arg.symbol) - if call_arg.symbol.pointers != symbol.pointers { - symbol.pointers = call_arg.symbol.pointers - if is_symbol_same_typed(ast_context, symbol, arg_symbol, proc_arg.flags) { - using_score = min(k, using_score) - found = true - } } } - candidate.score = using_score } + candidate.score = using_score + } - if !found { - // If still not found, resolve to the base type and see if it matches - bypass_distinct := expr_contains_poly(orig_arg) - resolved_call_arg := resolve_base_symbol(ast_context, call_arg.symbol, bypass_distinct) - resolved_expected_arg := resolve_base_symbol(ast_context, arg_symbol) - resolved_call_arg.pointers = call_arg.symbol.pointers - resolved_expected_arg.pointers = arg_symbol.pointers - if !is_symbol_same_typed( - ast_context, - resolved_call_arg, - resolved_expected_arg, - proc_arg.flags, - ) { - break next_fn - } - - candidate.score += 1 + if !found { + // If still not found, resolve to the base type and see if it matches + bypass_distinct := expr_contains_poly(orig_arg) + resolved_call_arg := resolve_base_symbol(ast_context, call_arg.symbol, bypass_distinct) + resolved_expected_arg := resolve_base_symbol(ast_context, arg_symbol) + resolved_call_arg.pointers = call_arg.symbol.pointers + resolved_expected_arg.pointers = arg_symbol.pointers + if !is_symbol_same_typed( + ast_context, + resolved_call_arg, + resolved_expected_arg, + proc_arg.flags, + ) { + continue args } + + candidate.score += 1 } } } + } - append(&candidates, candidate) + // Keep polymorphic tie-breaks small + // Match quality and arity must remain dominant + if proc_has_value_poly_arg(procedure) { + candidate.score -= 2 + } else if proc_has_type_poly_arg(procedure) { + candidate.score += proc_unconstrained_poly_arg_count(procedure) + // Concrete > specialized generic > unconstrained generic + candidate.score += 1 } + + append(&candidates, candidate) } } diff --git a/tests/proc_group_inference_test.odin b/tests/proc_group_inference_test.odin new file mode 100644 index 00000000..66b68f84 --- /dev/null +++ b/tests/proc_group_inference_test.odin @@ -0,0 +1,648 @@ +package tests + +// Procedure group overload resolution tests, +// mirroring the Odin compiler tests from: +// tests/internal/test_proc_group_type_inference.odin + +import "core:testing" +import "src:common" +import test "src:testing" + +// Declarations in the embedded sources start at column 0 on purpose +// so expected locations are trivial: character = 0, end = len(name) +@(private = "file") +overload_location :: proc(line: int, name: string) -> common.Location { + return {range = {{line, 0}, {line, len(name)}}} +} + +@(test) +test_proc_group_default_arg_precedence_zero_args :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +My_Bit_Set :: bit_set[enum{A, B, C}] +proc_one_default :: proc(a: My_Bit_Set={.A}) -> int { return 1 } +proc_two_defaults :: proc(a: My_Bit_Set={.B}, b: My_Bit_Set={.C}) -> int { return 2 } +group :: proc{proc_one_default, proc_two_defaults} + +main :: proc() { + grou{*}p() +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(2, "proc_one_default")}) +} + +@(test) +test_proc_group_default_arg_precedence_typed_bitset :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +My_Bit_Set :: bit_set[enum{A, B, C}] +proc_one_default :: proc(a: My_Bit_Set={.A}) -> int { return 1 } +proc_two_defaults :: proc(a: My_Bit_Set={.B}, b: My_Bit_Set={.C}) -> int { return 2 } +group :: proc{proc_one_default, proc_two_defaults} + +main :: proc() { + grou{*}p(My_Bit_Set{.A}) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(2, "proc_one_default")}) +} + +@(test) +test_proc_group_default_arg_precedence_untyped_bitset :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +My_Bit_Set :: bit_set[enum{A, B, C}] +proc_one_default :: proc(a: My_Bit_Set={.A}) -> int { return 1 } +proc_two_defaults :: proc(a: My_Bit_Set={.B}, b: My_Bit_Set={.C}) -> int { return 2 } +group :: proc{proc_one_default, proc_two_defaults} + +main :: proc() { + grou{*}p({.A}) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(2, "proc_one_default")}) +} + +@(test) +test_proc_group_default_arg_precedence_two_args :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +My_Bit_Set :: bit_set[enum{A, B, C}] +proc_one_default :: proc(a: My_Bit_Set={.A}) -> int { return 1 } +proc_two_defaults :: proc(a: My_Bit_Set={.B}, b: My_Bit_Set={.C}) -> int { return 2 } +group :: proc{proc_one_default, proc_two_defaults} + +main :: proc() { + grou{*}p({.B}, {.C}) +} +`, + config = {enable_overload_resolution = true}, + } + + // only proc_two_defaults takes two arguments + test.expect_definition_locations(t, &source, {overload_location(3, "proc_two_defaults")}) +} + +@(test) +test_proc_group_default_arg_precedence_zero_args_reversed :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +My_Bit_Set :: bit_set[enum{A, B, C}] +proc_one_default :: proc(a: My_Bit_Set={.A}) -> int { return 1 } +proc_two_defaults :: proc(a: My_Bit_Set={.B}, b: My_Bit_Set={.C}) -> int { return 2 } +group :: proc{proc_two_defaults, proc_one_default} + +main :: proc() { + grou{*}p() +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(2, "proc_one_default")}) +} + +@(test) +test_proc_group_default_arg_precedence_exact_vs_defaulted :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_exact :: proc(x: int) -> int { return 1 } +proc_defaulted :: proc(x: int, y := 0) -> int { return 2 } +group :: proc{proc_exact, proc_defaulted} + +main :: proc() { + grou{*}p(1) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_exact")}) +} + +@(test) +test_proc_group_default_arg_precedence_fewer_defaults :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_fewer :: proc(x: int, y := 0) -> int { return 1 } +proc_more :: proc(x: int, y := 0, z := 0) -> int { return 2 } +group :: proc{proc_fewer, proc_more} + +main :: proc() { + grou{*}p(1) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_fewer")}) +} + +@(test) +test_proc_group_default_arg_precedence_fewer_defaults_reversed :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_fewer :: proc(x: int, y := 0) -> int { return 1 } +proc_more :: proc(x: int, y := 0, z := 0) -> int { return 2 } +group :: proc{proc_more, proc_fewer} + +main :: proc() { + grou{*}p(1) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_fewer")}) +} + +@(test) +test_proc_group_arity_precedence_non_variadic :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_exact :: proc(x: int) -> int { return 1 } +proc_variadic :: proc(x: int, r: ..int) -> int { return 2 } +group :: proc{proc_exact, proc_variadic} + +main :: proc() { + grou{*}p(1) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_exact")}) +} + +@(test) +test_proc_group_arity_precedence_variadic :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_exact :: proc(x: int) -> int { return 1 } +proc_variadic :: proc(x: int, r: ..int) -> int { return 2 } +group :: proc{proc_exact, proc_variadic} + +main :: proc() { + grou{*}p(1, 2, 3) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(2, "proc_variadic")}) +} + +@(test) +test_proc_group_arity_precedence_defaulted_sibling :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +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_defaulted :: 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_defaulted, proc_rune} + +main :: proc() { + v: f32 + grou{*}p(v) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(3, "proc_f32")}) +} + +@(test) +test_proc_group_untyped_constant_default_type_int_vs_i64 :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_int :: proc(x: int) -> int { return 1 } +proc_i64 :: proc(x: i64) -> int { return 2 } +group :: proc{proc_int, proc_i64} + +main :: proc() { + grou{*}p(1) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_int")}) +} + +@(test) +test_proc_group_untyped_constant_default_type_int_vs_i64_reversed :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_int :: proc(x: int) -> int { return 1 } +proc_i64 :: proc(x: i64) -> int { return 2 } +group :: proc{proc_i64, proc_int} + +main :: proc() { + grou{*}p(1) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_int")}) +} + +@(test) +test_proc_group_untyped_constant_default_type_int_i64_vs_f64 :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_i64 :: proc(x: i64) -> int { return 1 } +proc_f64 :: proc(x: f64) -> int { return 2 } +group :: proc{proc_i64, proc_f64} + +main :: proc() { + grou{*}p(1) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_i64")}) +} + +@(test) +test_proc_group_untyped_constant_default_type_float_f32_vs_f64 :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_f32 :: proc(x: f32) -> int { return 1 } +proc_f64 :: proc(x: f64) -> int { return 2 } +group :: proc{proc_f32, proc_f64} + +main :: proc() { + grou{*}p(1.5) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(2, "proc_f64")}) +} + +@(test) +test_proc_group_untyped_constant_default_type_rune_vs_int :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_rune :: proc(x: rune) -> int { return 1 } +proc_int :: proc(x: int) -> int { return 2 } +group :: proc{proc_rune, proc_int} + +main :: proc() { + grou{*}p('x') +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_rune")}) +} + +@(test) +test_proc_group_untyped_constant_default_type_rune_vs_int_reversed :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_rune :: proc(x: rune) -> int { return 1 } +proc_int :: proc(x: int) -> int { return 2 } +group :: proc{proc_int, proc_rune} + +main :: proc() { + grou{*}p('x') +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_rune")}) +} + +@(test) +test_proc_group_untyped_constant_default_type_string_vs_cstring :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_string :: proc(x: string) -> int { return 1 } +proc_cstring :: proc(x: cstring) -> int { return 2 } +group :: proc{proc_string, proc_cstring} + +main :: proc() { + grou{*}p("hi") +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_string")}) +} + +@(test) +test_proc_group_untyped_constant_default_type_string_vs_cstring_reversed :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_string :: proc(x: string) -> int { return 1 } +proc_cstring :: proc(x: cstring) -> int { return 2 } +group :: proc{proc_cstring, proc_string} + +main :: proc() { + grou{*}p("hi") +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_string")}) +} + +@(test) +test_proc_group_untyped_constant_default_type_bool_vs_b32 :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_bool :: proc(x: bool) -> int { return 1 } +proc_b32 :: proc(x: b32) -> int { return 2 } +group :: proc{proc_bool, proc_b32} + +main :: proc() { + grou{*}p(true) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_bool")}) +} + +@(test) +test_proc_group_untyped_constant_default_type_bool_vs_b32_reversed :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_bool :: proc(x: bool) -> int { return 1 } +proc_b32 :: proc(x: b32) -> int { return 2 } +group :: proc{proc_b32, proc_bool} + +main :: proc() { + grou{*}p(true) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_bool")}) +} + +@(test) +test_proc_group_untyped_constant_default_type_int_u8_vs_i64_overflow :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_u8 :: proc(x: u8) -> int { return 1 } +proc_i64 :: proc(x: i64) -> int { return 2 } +group :: proc{proc_u8, proc_i64} + +main :: proc() { + grou{*}p(100000) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(2, "proc_i64")}) +} + +@(test) +test_proc_group_polymorphic_precedence_concrete_vs_generic :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_concrete :: proc(x: int) -> int { return 1 } +proc_generic :: proc(x: $T) -> int { return 2 } +group :: proc{proc_concrete, proc_generic} + +main :: proc() { + grou{*}p(1) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_concrete")}) +} + +@(test) +test_proc_group_polymorphic_precedence_concrete_vs_generic_typed_arg :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_concrete :: proc(x: int) -> int { return 1 } +proc_generic :: proc(x: $T) -> int { return 2 } +group :: proc{proc_concrete, proc_generic} + +main :: proc() { + v: int = 1 + grou{*}p(v) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_concrete")}) +} + +@(test) +test_proc_group_polymorphic_precedence_generic_only_viable :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_concrete :: proc(x: string) -> int { return 1 } +proc_generic :: proc(x: $T) -> int { return 2 } +group :: proc{proc_concrete, proc_generic} + +main :: proc() { + grou{*}p(1) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(2, "proc_generic")}) +} + +@(test) +test_proc_group_polymorphic_precedence_value_poly_literal :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_static :: proc($S: string) -> int { return 1 } +proc_dynamic :: proc(s: string) -> int { return 2 } +group :: proc{proc_static, proc_dynamic} + +main :: proc() { + grou{*}p("literal") +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_static")}) +} + +@(test) +test_proc_group_polymorphic_precedence_value_poly_runtime_value :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_static :: proc($S: string) -> int { return 1 } +proc_dynamic :: proc(s: string) -> int { return 2 } +group :: proc{proc_static, proc_dynamic} + +main :: proc() { + s := "runtime" + grou{*}p(s) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(2, "proc_dynamic")}) +} + +@(test) +test_proc_group_polymorphic_precedence_three_tiers :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +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} + +main :: proc() { + grou{*}p("literal") +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(3, "proc_static")}) +} + +@(test) +test_proc_group_polymorphic_precedence_specialized_vs_generic :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_slice :: proc(x: $T/[]$E) -> int { return 1 } +proc_generic :: proc(x: $T) -> int { return 2 } +group :: proc{proc_slice, proc_generic} + +main :: proc() { + s := []int{1} + grou{*}p(s) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_slice")}) +} + +@(test) +test_proc_group_polymorphic_precedence_specialized_vs_generic_reversed :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_slice :: proc(x: $T/[]$E) -> int { return 1 } +proc_generic :: proc(x: $T) -> int { return 2 } +group :: proc{proc_generic, proc_slice} + +main :: proc() { + s := []int{1} + grou{*}p(s) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(1, "proc_slice")}) +} + +@(test) +test_proc_group_polymorphic_precedence_proc_typed_param :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +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} + +main :: proc() { + grou{*}p(1, f_poly) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(2, "foo_concrete")}) +} + +@(test) +test_proc_group_type_inference_literals_for_various_parameters :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +Bit_Set :: bit_set[enum{A, B, C}] +proc_0 :: proc() -> int { return 0 } +proc_1 :: proc(Bit_Set) -> int { return 1 } +proc_2 :: proc(int, Bit_Set) -> int { return 2 } +proc_3 :: proc(f32, Bit_Set) -> int { return 3 } +group :: proc{proc_0, proc_1, proc_2, proc_3} + +main :: proc() { + grou{*}p(9, {.A}) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(4, "proc_2")}) +} + +@(test) +test_proc_group_type_inference_literals_with_default_args :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +Bit_Set :: bit_set[enum{A, B, C}] +proc_nil :: proc() {} +proc_default_arg :: proc(a: Bit_Set = {.A}) -> Bit_Set { return a } +group :: proc{proc_nil, proc_default_arg} + +main :: proc() { + grou{*}p({.A}) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(3, "proc_default_arg")}) +} + +@(test) +test_proc_group_type_inference_literals_for_various_types :: proc(t: ^testing.T) { + source := test.Source { + main = `package test +proc_nil :: proc() {} +proc_array :: proc(a: [3]f32) -> [3]f32 { return a } +group_array :: proc{proc_nil, proc_array} + +main :: proc() { + grou{*}p_array({1.1, 2.2, 3.3}) +} +`, + config = {enable_overload_resolution = true}, + } + + test.expect_definition_locations(t, &source, {overload_location(2, "proc_array")}) +}