From ab99c46baea5546cb666c140b3e99fafa9e159d4 Mon Sep 17 00:00:00 2001 From: Bhuvansh855 Date: Thu, 18 Jun 2026 13:33:17 +0530 Subject: [PATCH 1/2] Fix type narrowing for variable swaps after isinstance checks --- mypy/checker.py | 14 ++++++++++++++ test-data/unit/check-isinstance.test | 18 ++++++++++++++++++ test-data/unit/check-narrowing.test | 18 ++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 33705c98e10c3..6ba65436bfd7b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4175,7 +4175,21 @@ def check_assignment_to_multiple_lvalues( lr_pairs.append((star_lv.expr, rv_list)) lr_pairs.extend(zip(right_lvs, right_rvs)) + captured_pairs: list[tuple[Lvalue, Expression]] = [] + for lv, rv in lr_pairs: + if isinstance(rv, NameExpr): + narrowed = self.binder.get(rv) + + if narrowed is not None: + captured_pairs.append( + (lv, self.temp_node(narrowed, context=rv)) + ) + continue + + captured_pairs.append((lv, rv)) + + for lv, rv in captured_pairs: self.check_assignment(lv, rv, infer_lvalue_type) else: self.check_multi_assignment(lvalues, rvalue, context, infer_lvalue_type) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index acd81839fcdc7..ccd1d624f3711 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -3377,3 +3377,21 @@ def f2(x: A | None, t: type[A]): else: reveal_type(x) # N: Revealed type is "None" [builtins fixtures/isinstancelist.pyi] + +[case testTupleSwapAfterIsinstance] +class Base: pass +class Sub1(Base): pass +class Sub2(Base): pass + +def f(a: Base, b: Base) -> None: + if isinstance(b, Sub1) and isinstance(a, Sub2): + t = (b, a) + + reveal_type(t) # N: Revealed type is "tuple[__main__.Sub1, __main__.Sub2]" + + a, b = t + + reveal_type(a) # N: Revealed type is "__main__.Sub1" + reveal_type(b) # N: Revealed type is "__main__.Sub2" + +[builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 7bab7baa6cebd..39c161222397f 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -4281,3 +4281,21 @@ def func(y: H) -> H: else: return y [builtins fixtures/primitives.pyi] + +[case testIsInstanceVariableSwapNarrowing] +class Base: + pass + +class Sub1(Base): + pass + +class Sub2(Base): + pass + +def f(a: Base, b: Base) -> None: + if isinstance(b, Sub1) and isinstance(a, Sub2): + a, b = b, a + reveal_type(a) # N: Revealed type is "__main__.Sub1" + reveal_type(b) # N: Revealed type is "__main__.Sub2" + +[builtins fixtures/isinstancelist.pyi] From fd822a084cbf01e4c102cdb9620c6d56608688d2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 08:07:26 +0000 Subject: [PATCH 2/2] [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, 1 insertion(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6ba65436bfd7b..32d9dbc8f1b32 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4182,9 +4182,7 @@ def check_assignment_to_multiple_lvalues( narrowed = self.binder.get(rv) if narrowed is not None: - captured_pairs.append( - (lv, self.temp_node(narrowed, context=rv)) - ) + captured_pairs.append((lv, self.temp_node(narrowed, context=rv))) continue captured_pairs.append((lv, rv))