From db46204e388ebc63df20fcb8cb3e4bc835a3f2a1 Mon Sep 17 00:00:00 2001 From: Louis Novy <101842021+louisnovy@users.noreply.github.com> Date: Tue, 29 Sep 2026 19:22:01 -0400 Subject: [PATCH] Catch calls of a deferred out proc in short circuits during check --- src/check_expr.cpp | 21 +++++++++++++++++++ src/check_stmt.cpp | 3 +++ tests/issues/run.bat | 1 + tests/issues/run.sh | 6 ++++++ tests/issues/test_issue_5105_5569.odin | 29 ++++++++++++++++++++++++++ 5 files changed, 60 insertions(+) create mode 100644 tests/issues/test_issue_5105_5569.odin diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 248e9f146..dc173e8a1 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -9081,6 +9081,17 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c gb_zero_item(operand); operand->expr = call; + if ((call->viral_state_flags & ViralStateFlag_ContainsDeferredProcedure) == 0) { + // NOTE: which procedure of a group is called is only known once its arguments are checked + Entity *e = entity_of_node(call->CallExpr.proc); + if (e != nullptr && e->kind == Entity_Procedure && e->Procedure.deferred_procedure.entity != nullptr) { + call->viral_state_flags |= ViralStateFlag_ContainsDeferredProcedure; + if (c->decl) { + c->decl->defer_used += 1; + } + } + } + if (result_type == t_invalid) { operand->mode = Addressing_Invalid; operand->type = t_invalid; @@ -10110,6 +10121,13 @@ gb_internal ExprKind check_ternary_if_expr(CheckerContext *c, Operand *o, Ast *n return kind; } + if (te->x->viral_state_flags & ViralStateFlag_ContainsDeferredProcedure) { + error(te->x, "Procedure calls that have an associated deferred procedure are not allowed within ternary expressions"); + } + if (te->y->viral_state_flags & ViralStateFlag_ContainsDeferredProcedure) { + error(te->y, "Procedure calls that have an associated deferred procedure are not allowed within ternary expressions"); + } + if (x.mode == Addressing_Type || y.mode == Addressing_Type) { Ast *type_expr = (x.mode == Addressing_Type) ? x.expr : y.expr; gbString type_string = expr_to_string(type_expr); @@ -10282,6 +10300,9 @@ gb_internal ExprKind check_or_else_expr(CheckerContext *c, Operand *o, Ast *node bool y_is_diverging = false; check_expr_base(c, &y, default_value, left_type); + if (default_value->viral_state_flags & ViralStateFlag_ContainsDeferredProcedure) { + error(default_value, "Procedure calls that have an associated deferred procedure are not allowed on the right-hand side of 'or_else'"); + } switch (y.mode) { case Addressing_NoValue: if (is_diverging_expr(y.expr)) { diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 713c19239..ef7c02a2c 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1315,6 +1315,9 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags } else { check_expr_with_type_hint(ctx, &y, expr, x.type); } + if (expr->viral_state_flags & ViralStateFlag_ContainsDeferredProcedure) { + error(expr, "Procedure calls that have an associated deferred procedure are not allowed within case clauses"); + } if (x.mode == Addressing_Invalid || y.mode == Addressing_Invalid) { diff --git a/tests/issues/run.bat b/tests/issues/run.bat index a672af123..a120d2c93 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -25,6 +25,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin build ..\test_issue_5043.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_5097.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_5097-2.odin %COMMON% || exit /b +..\..\..\odin check ..\test_issue_5105_5569.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "4" || exit /b ..\..\..\odin build ..\test_issue_5265.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_5318.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_5573.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 68b10fcdc..9018f9c16 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -32,6 +32,12 @@ fi $ODIN build ../test_issue_5043.odin $COMMON $ODIN build ../test_issue_5097.odin $COMMON $ODIN build ../test_issue_5097-2.odin $COMMON +if [[ $($ODIN check ../test_issue_5105_5569.odin $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 4 ]]; then + echo "SUCCESSFUL 1/1" +else + echo "SUCCESSFUL 0/1" + exit 1 +fi $ODIN build ../test_issue_5265.odin $COMMON $ODIN test ../test_issue_5318.odin $COMMON if [[ $($ODIN build ../test_issue_5573.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]]; then diff --git a/tests/issues/test_issue_5105_5569.odin b/tests/issues/test_issue_5105_5569.odin new file mode 100644 index 000000000..27603183c --- /dev/null +++ b/tests/issues/test_issue_5105_5569.odin @@ -0,0 +1,29 @@ +// Tests issues #5105 https://github.com/odin-lang/Odin/issues/5105 +// and #5569 https://github.com/odin-lang/Odin/issues/5569 +package test_issues + +import "core:sync" + +// A procedure call with a deferred procedure is not allowed where it may not be evaluated. +// The associated deferred procedure would be called at the end of the scope regardless. + +@(deferred_out = finish) +start :: proc(x: int) -> int { return x } +finish :: proc(_: int) {} + +maybe :: proc(x: int) -> (int, bool) { return x, x > 0 } + +m: sync.Mutex + +main :: proc() { + flag := true + + _ = start(1) if flag else 2 // Error + _ = maybe(1) or_else start(2) // Error + switch { + case flag, start(3) > 0: // Error + } + if flag && sync.guard(&m) {} // Error, `sync.guard` is a procedure group + + _ = start(4) + 1 +}