From 5679a8970524a0e6822ab55fd9460d242bfca80e Mon Sep 17 00:00:00 2001 From: Parin Porecha Date: Tue, 21 Apr 2026 05:28:46 +0000 Subject: [PATCH 1/3] Fix 1.20 regression: use explicit walrus check instead of binder version guard The binder version guard introduced in PR #20622 was too broad: it disabled union fallback for any expression that modifies the binder, including generator expressions with ternary operators. This caused type narrowing via reassignment to fail for non-walrus expressions. Replace the binder_version == self.binder.version check with an explicit check for AssignmentExpr (walrus :=) in the expression tree, which is the actual problematic case. This restores type narrowing for generator expressions and other non-walrus constructs while preserving the walrus safety guard. --- mypy/checker.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 59571954e0f7..4fa21afb79b3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -379,6 +379,17 @@ def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> Literal return False +class _AssignmentExprSeeker(TraverserVisitor): + """Check if an expression tree contains a walrus operator (:=).""" + + def __init__(self) -> None: + super().__init__() + self.found = False + + def visit_assignment_expr(self, o: AssignmentExpr) -> None: + self.found = True + + class TypeChecker(NodeVisitor[None], TypeCheckerSharedApi, SplittingVisitor): """Mypy type checker. @@ -4757,7 +4768,7 @@ def infer_rvalue_with_fallback_context( # context that is ultimately used. This is however tricky with redefinitions. # For now we simply disable second accept in cases known to cause problems, # see e.g. testAssignToOptionalTupleWalrus. - binder_version = self.binder.version + has_walrus = self._has_assignment_expr(rvalue) fallback_context_used = False with ( @@ -4787,7 +4798,7 @@ def infer_rvalue_with_fallback_context( union_fallback = ( preferred_context is not None and isinstance(get_proper_type(lvalue_type), UnionType) - and binder_version == self.binder.version + and not has_walrus ) # Skip literal types, as they have special logic (for better errors). @@ -5091,6 +5102,13 @@ def visit_return_stmt(self, s: ReturnStmt) -> None: self.check_return_stmt(s) self.binder.unreachable() + @staticmethod + def _has_assignment_expr(expr: Expression) -> bool: + """Check if an expression tree contains a walrus operator (:=).""" + seeker = _AssignmentExprSeeker() + expr.accept(seeker) + return seeker.found + def infer_context_dependent( self, expr: Expression, type_ctx: Type, allow_none_func_call: bool ) -> ProperType: From 9dbb53e60a3a38851367ed7f550cdf7d99d28265 Mon Sep 17 00:00:00 2001 From: Parin Porecha Date: Tue, 21 Apr 2026 07:12:47 +0000 Subject: [PATCH 2/3] test: cover generator ternary narrowing regression --- mypy/checker.py | 14 +++++++------- test-data/unit/check-inference-context.test | 13 +++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 4fa21afb79b3..e1f541d869c6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4767,8 +4767,12 @@ def infer_rvalue_with_fallback_context( # binder.accumulate_type_assignments() and assign the types inferred for type # context that is ultimately used. This is however tricky with redefinitions. # For now we simply disable second accept in cases known to cause problems, - # see e.g. testAssignToOptionalTupleWalrus. - has_walrus = self._has_assignment_expr(rvalue) + # see e.g. testAssignToOptionalTupleWalrus. We only need to scan for walrus + # when union fallback is otherwise applicable. + union_fallback_possible = ( + preferred_context is not None and isinstance(get_proper_type(lvalue_type), UnionType) + ) + has_walrus = union_fallback_possible and self._has_assignment_expr(rvalue) fallback_context_used = False with ( @@ -4795,11 +4799,7 @@ def infer_rvalue_with_fallback_context( # Try re-inferring r.h.s. in empty context for union with explicit annotation, # and use it results in a narrower type. This helps with various practical # examples, see e.g. testOptionalTypeNarrowedByGenericCall. - union_fallback = ( - preferred_context is not None - and isinstance(get_proper_type(lvalue_type), UnionType) - and not has_walrus - ) + union_fallback = union_fallback_possible and not has_walrus # Skip literal types, as they have special logic (for better errors). try_fallback = redefinition_fallback or union_fallback or argument_redefinition_fallback diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test index eda17c820d42..49b6612fb77b 100644 --- a/test-data/unit/check-inference-context.test +++ b/test-data/unit/check-inference-context.test @@ -1514,6 +1514,19 @@ i = i if isinstance(i, int) else b reveal_type(i) # N: Revealed type is "Any | builtins.int" [builtins fixtures/isinstance.pyi] +[case testTypeNarrowingByReassignmentGeneratorTernary] +from typing import Iterable, Union + +def foo(args: Union[Iterable[Union[str, int]], str, int]) -> Iterable[str]: + if isinstance(args, (str, int)): + args = (args,) + args = ( + arg if isinstance(arg, str) else str(arg) + for arg in args + ) + return args +[builtins fixtures/isinstance.pyi] + [case testLambdaInferenceUsesNarrowedTypes] from typing import Optional, Callable From 04a86185fbed0ae14bd74a9318893e7d4c4761a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 07:14:25 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e1f541d869c6..224f3e63287c 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4769,8 +4769,8 @@ def infer_rvalue_with_fallback_context( # For now we simply disable second accept in cases known to cause problems, # see e.g. testAssignToOptionalTupleWalrus. We only need to scan for walrus # when union fallback is otherwise applicable. - union_fallback_possible = ( - preferred_context is not None and isinstance(get_proper_type(lvalue_type), UnionType) + union_fallback_possible = preferred_context is not None and isinstance( + get_proper_type(lvalue_type), UnionType ) has_walrus = union_fallback_possible and self._has_assignment_expr(rvalue)