From 52072e652b529b03455e9d7bbcf153ecfb7698eb Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 29 Dec 2025 20:26:39 -0800 Subject: [PATCH 1/2] Evaluate argument expressions in runtime evaluation order Fixes #20411 --- mypy/checkexpr.py | 43 +++++++++++++++--------------- test-data/unit/check-python38.test | 9 +++++++ 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 9990caaeb7a1..a7b0a3709bb6 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1958,28 +1958,29 @@ def infer_arg_types_in_context( Returns the inferred types of *actual arguments*. """ - res: list[Type | None] = [None] * len(args) - - for i, actuals in enumerate(formal_to_actual): + # Precompute arg_context so that we type check argument expresions in evaluation order + arg_context: list[Type | None] = [None] * len(args) + for fi, actuals in enumerate(formal_to_actual): for ai in actuals: - if not arg_kinds[ai].is_star(): - arg_type = callee.arg_types[i] - # When the outer context for a function call is known to be recursive, - # we solve type constraints inferred from arguments using unions instead - # of joins. This is a bit arbitrary, but in practice it works for most - # cases. A cleaner alternative would be to switch to single bin type - # inference, but this is a lot of work. - old = self.infer_more_unions_for_recursive_type(arg_type) - res[ai] = self.accept(args[ai], arg_type) - # We need to manually restore union inference state, ugh. - type_state.infer_unions = old - - # Fill in the rest of the argument types. - for i, t in enumerate(res): - if not t: - res[i] = self.accept(args[i]) - assert all(tp is not None for tp in res) - return cast(list[Type], res) + if arg_kinds[ai].is_star(): + continue + arg_context[ai] = callee.arg_types[fi] + + res = [] + for arg, ctx in zip(args, arg_context): + if ctx is not None: + # When the outer context for a function call is known to be recursive, + # we solve type constraints inferred from arguments using unions instead + # of joins. This is a bit arbitrary, but in practice it works for most + # cases. A cleaner alternative would be to switch to single bin type + # inference, but this is a lot of work. + old = self.infer_more_unions_for_recursive_type(ctx) + res.append(self.accept(arg, ctx)) + # We need to manually restore union inference state, ugh. + type_state.infer_unions = old + else: + res.append(self.accept(arg)) + return res def infer_function_type_arguments_using_context( self, callable: CallableType, error_context: Context diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index c32dec94a62d..6b2725dab4b4 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -738,6 +738,15 @@ if typeguard(x=(n := cast(object, "hi"))): [out] main:8: note: Revealed type is "builtins.int" +[case testWalrusAssignmentAcrossCallArguments] +def f(notifications: str, initial_reference: str) -> int: + return 42 + +f( + initial_reference=(request_reference := "abc"), + notifications=reveal_type(request_reference), # N: Revealed type is "builtins.str" +) + [case testNoCrashOnAssignmentExprClass] class C: [(j := i) for i in [1, 2, 3]] # E: Assignment expression within a comprehension cannot be used in a class body From 6ca7578ebf5fce78cb77990eb5475fcd11f9e932 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 29 Dec 2025 21:05:00 -0800 Subject: [PATCH 2/2] Update mypy/checkexpr.py --- mypy/checkexpr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index a7b0a3709bb6..0acb3a13d025 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1958,7 +1958,7 @@ def infer_arg_types_in_context( Returns the inferred types of *actual arguments*. """ - # Precompute arg_context so that we type check argument expresions in evaluation order + # Precompute arg_context so that we type check argument expressions in evaluation order arg_context: list[Type | None] = [None] * len(args) for fi, actuals in enumerate(formal_to_actual): for ai in actuals: