From df6719cb89d9c36866099ca703d06bde530226e2 Mon Sep 17 00:00:00 2001 From: Brody Date: Mon, 3 Aug 2026 18:13:12 +1000 Subject: [PATCH 1/7] fix: remove broken special case for poly proc as default value --- src/check_expr.cpp | 11 ----------- tests/issues/run.bat | 1 + tests/issues/run.sh | 1 + tests/issues/test_issue_6753.odin | 27 +++++++++++++++++++++++++++ 4 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 tests/issues/test_issue_6753.odin diff --git a/src/check_expr.cpp b/src/check_expr.cpp index eea6db2b4..115b13992 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1023,17 +1023,6 @@ gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *o return false; } - // Handle polymorphic procedure used as default parameter - if (operand->mode == Addressing_Value && is_type_proc(type) && is_type_proc(operand->type)) { - Entity *e = entity_from_expr(operand->expr); - if (e != nullptr && e->kind == Entity_Procedure && is_type_polymorphic(e->type) && !is_type_polymorphic(type)) { - // Special case: Allow a polymorphic procedure to be used as default value for concrete proc type - // during the initial check. It will be properly instantiated when actually used. - if (score_) *score_ = assign_score_function(1); - return true; - } - } - i64 score = check_distance_between_types(c, operand, type, allow_array_programming); if (score >= 0) { if (score_) *score_ = assign_score_function(score, is_variadic); diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 8a85ba90a..269c91a0b 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -37,6 +37,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin test ..\test_pr_6470.odin -define:TEST_EXPECT_FAILURE=true %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b ..\..\..\odin test ..\test_pr_6476.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_6484.odin -no-entry-point %COMMON% || exit /b +..\..\..\odin test ..\test_issue_6753.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_6874.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b ..\..\..\odin check ..\test_issue_6979.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin build ..\test_issue_7037.odin %COMMON% -o:none || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index ce97a570a..9bcc53dfe 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -74,6 +74,7 @@ else exit 1 fi $ODIN check ../test_issue_6484.odin -no-entry-point $COMMON +$ODIN test ../test_issue_6753.odin -no-entry-point $COMMON if [[ $($ODIN check ../test_issue_6874.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then echo "SUCCESSFUL 1/1" else diff --git a/tests/issues/test_issue_6753.odin b/tests/issues/test_issue_6753.odin new file mode 100644 index 000000000..7f6c7208c --- /dev/null +++ b/tests/issues/test_issue_6753.odin @@ -0,0 +1,27 @@ +// test issue for #6753 https://github.com/odin-lang/odin/issues/6753 +package test_issues +import "core:testing" + +identity :: proc(x: $T) -> T { return x } + +foo :: proc(x: int, poly: proc(int) -> int = identity) -> int { + return foo(identity(x)) +} + +// failing as returns before specialized +@test +test_issue_6753 :: proc(t: ^testing.T) { + p: proc(int) -> int = identity + + testing.expect(t, p != nil) + + if p != nil { + testing.expect(t, p(123) == 123) + } +} + +// failing as returns before specialized +@test +test_issue_default_poly_parameter_6753 :: proc(t: ^testing.T) { + testing.expect(t, foo(123) == 123) +} \ No newline at end of file From 6888e843861a35376679c05d92366a7ad22264c2 Mon Sep 17 00:00:00 2001 From: Brody Date: Mon, 3 Aug 2026 18:34:53 +1000 Subject: [PATCH 2/7] fix: Added null check before setting operand type to invalid --- src/check_type.cpp | 8 ++++++-- tests/issues/test_issue_6753.odin | 10 +++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index be06ca108..ed916ceb2 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2143,8 +2143,12 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para // This is just to add the error message to determine_type_from_polymorphic which // depends on valid position information op.expr = _params; - op.mode = Addressing_Invalid; - op.type = t_invalid; + + // NOTE(taylbr): Can still have valid type with null expr. Needed for resolving + if (op.mode == Addressing_Invalid || op.type == nullptr) { + op.mode = Addressing_Invalid; + op.type = t_invalid; + } } if (is_type_polymorphic_type) { type = determine_type_from_polymorphic(ctx, type, op); diff --git a/tests/issues/test_issue_6753.odin b/tests/issues/test_issue_6753.odin index 7f6c7208c..d4c668c6d 100644 --- a/tests/issues/test_issue_6753.odin +++ b/tests/issues/test_issue_6753.odin @@ -12,16 +12,12 @@ foo :: proc(x: int, poly: proc(int) -> int = identity) -> int { @test test_issue_6753 :: proc(t: ^testing.T) { p: proc(int) -> int = identity - - testing.expect(t, p != nil) - - if p != nil { - testing.expect(t, p(123) == 123) - } + testing.expect(t, p != nil, "polymorphic procedure was not instantiated") + testing.expect_value(t, p(123), 123) } // failing as returns before specialized @test test_issue_default_poly_parameter_6753 :: proc(t: ^testing.T) { - testing.expect(t, foo(123) == 123) + testing.expect_value(t, foo(123), 123) } \ No newline at end of file From cce1bdd8c751344f01cd17b90a7ceca9dcac2cfe Mon Sep 17 00:00:00 2001 From: Brody Date: Mon, 3 Aug 2026 21:42:39 +1000 Subject: [PATCH 3/7] fix: return true for already specialised poly proc --- src/check_expr.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 115b13992..8256d9474 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -410,6 +410,15 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E } if (!src->Proc.is_polymorphic || src->Proc.is_poly_specialized) { + // NOTE: polymorphic procedure check not idempotent without this + if (src->Proc.is_poly_specialized && base_entity->Procedure.generated_from_polymorphic) { + if (are_types_identical(src, dst)) { + if (poly_proc_data) { + poly_proc_data->gen_entity = base_entity; + } + return true; + } + } return false; } From 87b514890cb2fd3524e6bd9e5e7e12e3a248ca91 Mon Sep 17 00:00:00 2001 From: Brody Date: Mon, 3 Aug 2026 22:07:18 +1000 Subject: [PATCH 4/7] fix: instantiated procs keep their own scope Was this check here for a reason? Investigate --- src/check_expr.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 8256d9474..609173981 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -466,9 +466,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E scope->flags |= ScopeFlag_Proc; nctx.scope = scope; nctx.allow_polymorphic_types = true; - if (nctx.polymorphic_scope == nullptr) { - nctx.polymorphic_scope = scope; - } + nctx.polymorphic_scope = scope; auto *pt = &src->Proc; From 91e0e6ce473bbad09f225d6a6fcaffa0699f9ee9 Mon Sep 17 00:00:00 2001 From: Brody Date: Mon, 3 Aug 2026 22:26:24 +1000 Subject: [PATCH 5/7] fix: record resolved entity for proc-typed default values --- src/check_expr.cpp | 2 +- src/check_type.cpp | 1 + src/entity.cpp | 1 + src/llvm_backend_proc.cpp | 6 +++ tests/issues/run.bat | 1 + tests/issues/run.sh | 8 +++- tests/issues/test_issue_5573.odin | 17 +++++++++ tests/issues/test_issue_6753.odin | 63 +++++++++++++++++++++++++------ 8 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 tests/issues/test_issue_5573.odin diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 609173981..a91664bb0 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -412,7 +412,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E if (!src->Proc.is_polymorphic || src->Proc.is_poly_specialized) { // NOTE: polymorphic procedure check not idempotent without this if (src->Proc.is_poly_specialized && base_entity->Procedure.generated_from_polymorphic) { - if (are_types_identical(src, dst)) { + if (are_types_identical(src, dst)) { if (poly_proc_data) { poly_proc_data->gen_entity = base_entity; } diff --git a/src/check_type.cpp b/src/check_type.cpp index ed916ceb2..97f609dd8 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1791,6 +1791,7 @@ gb_internal ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_ if (e->kind == Entity_Procedure) { param_value.kind = ParameterValue_Constant; param_value.value = exact_value_procedure(e->identifier); + param_value.proc_entity = e; add_entity_use(ctx, e->identifier, e); } else { if (e->flags & EntityFlag_Param) { diff --git a/src/entity.cpp b/src/entity.cpp index 31f90023b..d3170c823 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -111,6 +111,7 @@ enum ParameterValueKind { struct ParameterValue { ParameterValueKind kind; Ast *original_ast_expr; + Entity *proc_entity; union { ExactValue value; Ast *ast_value; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 9f1b5dd3f..1cff61ba9 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -4669,6 +4669,12 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, ParameterValue const ¶m_value, TypeProc *procedure_type, Ast* call_expression) { switch (param_value.kind) { case ParameterValue_Constant: + if (param_value.proc_entity != nullptr && is_type_proc(parameter_type)) { + lbValue v = lb_find_procedure_value_from_entity(p->module, param_value.proc_entity); + if (v.value != nullptr) { + return lb_emit_conv(p, v, parameter_type); + } + } if (is_type_constant_type(parameter_type)) { auto res = lb_const_value(p->module, parameter_type, param_value.value); return res; diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 269c91a0b..6d562ccbc 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -26,6 +26,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin build ..\test_issue_5097.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_5097-2.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_5265.odin %COMMON% || exit /b +..\..\..\odin build ..\test_issue_5573.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b ..\..\..\odin test ..\test_issue_5699.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_6068.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_6101.odin %COMMON% || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 9bcc53dfe..49b825bb8 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -32,6 +32,12 @@ $ODIN build ../test_issue_5043.odin $COMMON $ODIN build ../test_issue_5097.odin $COMMON $ODIN build ../test_issue_5097-2.odin $COMMON $ODIN build ../test_issue_5265.odin $COMMON +if [[ $($ODIN build ../test_issue_5573.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then + echo "SUCCESSFUL 1/1" +else + echo "SUCCESSFUL 0/1" + exit 1 +fi $ODIN test ../test_issue_5699.odin $COMMON $ODIN test ../test_issue_6068.odin $COMMON $ODIN test ../test_issue_6101.odin $COMMON @@ -74,7 +80,7 @@ else exit 1 fi $ODIN check ../test_issue_6484.odin -no-entry-point $COMMON -$ODIN test ../test_issue_6753.odin -no-entry-point $COMMON +$ODIN test ../test_issue_6753.odin $COMMON if [[ $($ODIN check ../test_issue_6874.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then echo "SUCCESSFUL 1/1" else diff --git a/tests/issues/test_issue_5573.odin b/tests/issues/test_issue_5573.odin new file mode 100644 index 000000000..dd154a89a --- /dev/null +++ b/tests/issues/test_issue_5573.odin @@ -0,0 +1,17 @@ +// Tests issue #5573 https://github.com/odin-lang/Odin/issues/5573 +package test_issues + +poly :: proc(x: $T) -> string { + return "poly" +} + +takes_concrete :: proc(f: proc(a: int, b: f32, c: rawptr) -> ^int) { +} + +main :: proc() { + // should error - wrong arity, wrong parameter types, wrong return type + mismatched: proc(a: int, b: f32, c: rawptr) -> ^int = poly + + // should error - same, as a procedure argument + takes_concrete(poly) +} diff --git a/tests/issues/test_issue_6753.odin b/tests/issues/test_issue_6753.odin index d4c668c6d..a14b2dc01 100644 --- a/tests/issues/test_issue_6753.odin +++ b/tests/issues/test_issue_6753.odin @@ -1,23 +1,64 @@ // test issue for #6753 https://github.com/odin-lang/odin/issues/6753 package test_issues import "core:testing" +import "core:fmt" -identity :: proc(x: $T) -> T { return x } - -foo :: proc(x: int, poly: proc(int) -> int = identity) -> int { - return foo(identity(x)) +foo_concrete :: proc(x: int, g: proc(int) -> int) -> int { + return g(x) +} +foo_impossible :: proc(x: int, g: proc(int, int) -> string) -> string { + return "impossible" +} +foo_group :: proc { + foo_concrete, + foo_impossible, +} + +f_poly :: proc(x: $T) -> T { return x } +foo_poly :: proc(x: $T, g: proc(T) -> T = f_poly) -> T { + return g(x) } -// failing as returns before specialized @test -test_issue_6753 :: proc(t: ^testing.T) { - p: proc(int) -> int = identity +test_issue_6753_ambiguous_poly_argumentment :: proc (t: ^testing.T) { + testing.expect_value(t, foo_group(1, f_poly), 1) // should be no ambiguity whether foo_concrete or foo_impossible +} + +@test +test_issue_6753_default_poly_proc :: proc (t: ^testing.T) { + testing.expect_value(t, foo_poly(1), 1) +} + +@test +test_issue_6753_parapoly_proc_variable :: proc(t: ^testing.T) { + p: proc(int) -> int = f_poly testing.expect(t, p != nil, "polymorphic procedure was not instantiated") testing.expect_value(t, p(123), 123) } -// failing as returns before specialized +// -- Fixing above led to some new bugs surfacing -- @test -test_issue_default_poly_parameter_6753 :: proc(t: ^testing.T) { - testing.expect_value(t, foo(123), 123) -} \ No newline at end of file +test_issue_6753_parapoly_proc_as_argument :: proc(t: ^testing.T) { + testing.expect(t, foo_concrete(123, f_poly) == 123, "failed to pass poly proc as argument") +} + +@test +test_issue_6753_parapoly_with_default_proc_same_generic_type_T :: proc(t: ^testing.T) { + testing.expect_value(t, foo_poly(123), 123) + testing.expect_value(t, foo_poly(123, f_poly), 123) +} + +// all together now +describe :: proc(x: $T) -> string { return fmt.tprintf("#%v", x) } +describe_bytes :: proc(x: []byte) -> string { return "bytes" } + +bar_poly :: proc(x: $T, g: proc(x: T) -> string = describe) -> string { return g(x) } +bar_bytes :: proc(x: []byte, g: proc(x: []byte) -> string) -> string { return g(x) } +bar_group :: proc { bar_poly, bar_bytes } + +@test +test_issue_6753_parapoly_default_in_group :: proc(t: ^testing.T) { + testing.expect_value(t, bar_group(123, describe), "#123") + testing.expect_value(t, bar_group("hi"), "#hi") + testing.expect_value(t, bar_group([]byte{1, 2}, describe_bytes), "bytes") +} From 90f6c9d0dbb7739aa3e8a0a59576112473065a56 Mon Sep 17 00:00:00 2001 From: Brody Date: Wed, 5 Aug 2026 00:17:48 +1000 Subject: [PATCH 6/7] whitespace --- tests/issues/test_issue_5573.odin | 1 + tests/issues/test_issue_6753.odin | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/issues/test_issue_5573.odin b/tests/issues/test_issue_5573.odin index dd154a89a..150f95863 100644 --- a/tests/issues/test_issue_5573.odin +++ b/tests/issues/test_issue_5573.odin @@ -11,6 +11,7 @@ takes_concrete :: proc(f: proc(a: int, b: f32, c: rawptr) -> ^int) { main :: proc() { // should error - wrong arity, wrong parameter types, wrong return type mismatched: proc(a: int, b: f32, c: rawptr) -> ^int = poly + _ = mismatched // should error - same, as a procedure argument takes_concrete(poly) diff --git a/tests/issues/test_issue_6753.odin b/tests/issues/test_issue_6753.odin index a14b2dc01..26531ae31 100644 --- a/tests/issues/test_issue_6753.odin +++ b/tests/issues/test_issue_6753.odin @@ -20,18 +20,18 @@ foo_poly :: proc(x: $T, g: proc(T) -> T = f_poly) -> T { } @test -test_issue_6753_ambiguous_poly_argumentment :: proc (t: ^testing.T) { - testing.expect_value(t, foo_group(1, f_poly), 1) // should be no ambiguity whether foo_concrete or foo_impossible +test_issue_6753_ambiguous_poly_argument :: proc (t: ^testing.T) { + testing.expect_value(t, foo_group(1, f_poly), 1) // should be no ambiguity whether foo_concrete or foo_impossible } @test test_issue_6753_default_poly_proc :: proc (t: ^testing.T) { - testing.expect_value(t, foo_poly(1), 1) + testing.expect_value(t, foo_poly(1), 1) } @test test_issue_6753_parapoly_proc_variable :: proc(t: ^testing.T) { - p: proc(int) -> int = f_poly + p: proc(int) -> int = f_poly testing.expect(t, p != nil, "polymorphic procedure was not instantiated") testing.expect_value(t, p(123), 123) } @@ -58,7 +58,7 @@ bar_group :: proc { bar_poly, bar_bytes } @test test_issue_6753_parapoly_default_in_group :: proc(t: ^testing.T) { - testing.expect_value(t, bar_group(123, describe), "#123") - testing.expect_value(t, bar_group("hi"), "#hi") - testing.expect_value(t, bar_group([]byte{1, 2}, describe_bytes), "bytes") + testing.expect_value(t, bar_group(123, describe), "#123") + testing.expect_value(t, bar_group("hi"), "#hi") + testing.expect_value(t, bar_group([]byte{1, 2}, describe_bytes), "bytes") } From 21fd0c4645467c3259ccf7fb3b755b2d66cdcba9 Mon Sep 17 00:00:00 2001 From: Brody Date: Wed, 5 Aug 2026 00:40:13 +1000 Subject: [PATCH 7/7] fix: bad merge --- tests/issues/run.sh | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 62213e0c9..0d41652c2 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -81,15 +81,8 @@ else echo "SUCCESSFUL 0/1" exit 1 fi -$ODIN check ../test_issue_6484.odin -no-entry-point $COMMON -$ODIN test ../test_issue_6753.odin $COMMON -if [[ $($ODIN check ../test_issue_6874.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then $ODIN check ../test_issue_6484.odin -no-entry-point $COMMON_CHECK - echo "SUCCESSFUL 1/1" -else - echo "SUCCESSFUL 0/1" - exit 1 -fi +$ODIN test ../test_issue_6753.odin $COMMON if [[ $($ODIN check ../test_issue_6874.odin $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then echo "SUCCESSFUL 1/1" else