From bb5479cba4cf01f200fb832e860a5cb20a82bee6 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 25 Apr 2026 00:18:37 -0700 Subject: [PATCH 1/2] Narrow more sequence parents --- mypy/checker.py | 25 +++++++++----- test-data/unit/check-isinstance.test | 51 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 59571954e0f7..f673babb0992 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -7143,14 +7143,23 @@ def replay_lookup(new_parent_type: ProperType) -> Type | None: if int_literals is not None: def replay_lookup(new_parent_type: ProperType) -> Type | None: - if not isinstance(new_parent_type, TupleType): - return None - try: - assert int_literals is not None - member_types = [new_parent_type.items[key] for key in int_literals] - except IndexError: - return None - return make_simplified_union(member_types) + if isinstance(new_parent_type, TupleType): + try: + assert int_literals is not None + member_types = [ + new_parent_type.items[key] for key in int_literals + ] + except IndexError: + return None + return make_simplified_union(member_types) + if ( + isinstance(new_parent_type, Instance) + and new_parent_type.type.fullname + in ("builtins.list", "builtins.tuple") + and new_parent_type.args + ): + return new_parent_type.args[0] + return None else: return output diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index c5695864d6b1..855010c19824 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -53,6 +53,57 @@ def f(x: Union[int, str, List]) -> None: reveal_type(x) # N: Revealed type is "builtins.int | builtins.str | builtins.list[Any]" [builtins fixtures/isinstancelist.pyi] +[case testIsinstanceNarrowsSequenceParentFromElement] +# flags: --warn-unreachable +from __future__ import annotations + +def f1(x: list[str] | list[int]) -> None: + if isinstance(x[0], str): + reveal_type(x) # N: Revealed type is "builtins.list[builtins.str]" + reveal_type(x[0]) # N: Revealed type is "builtins.str" + else: + reveal_type(x) # N: Revealed type is "builtins.list[builtins.int]" + reveal_type(x[0]) # N: Revealed type is "builtins.int" + +def f2(x: tuple[str, ...] | tuple[int, ...]) -> None: + if isinstance(x[0], str): + reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.str, ...]" + reveal_type(x[0]) # N: Revealed type is "builtins.str" + else: + reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" + reveal_type(x[0]) # N: Revealed type is "builtins.int" + +def f3(x: tuple[str, str] | tuple[int, int]) -> None: + if isinstance(x[0], str): + reveal_type(x) # N: Revealed type is "tuple[builtins.str, builtins.str]" + else: + reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int]" + +class A: ... +class B: ... +class C(A, B): ... + +def f4(x: list[A] | list[B]): + # Technically this is unsound in the presence of multiple inheritance, + # but we already do this kind of narrowing for tuples + if isinstance(x[0], A): + reveal_type(x) # N: Revealed type is "builtins.list[__main__.A]" + else: + reveal_type(x) # N: Revealed type is "builtins.list[__main__.B]" + +def f5(x: tuple[A, ...] | tuple[B, ...]): + if isinstance(x[0], A): + reveal_type(x) # N: Revealed type is "builtins.tuple[__main__.A, ...]" + else: + reveal_type(x) # N: Revealed type is "builtins.tuple[__main__.B, ...]" + +def f6(x: tuple[A, A] | tuple[B, B]): + if isinstance(x[0], A): + reveal_type(x) # N: Revealed type is "tuple[__main__.A, __main__.A]" + else: + reveal_type(x) # N: Revealed type is "tuple[__main__.B, __main__.B]" +[builtins fixtures/tuple.pyi] + [case testClassAttributeInitialization] # flags: --warn-unreachable class A: From 2213e61ce2133b1ad5706ce3f4385315e17e85ac Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 25 Apr 2026 12:45:49 -0700 Subject: [PATCH 2/2] more test --- test-data/unit/check-isinstance.test | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 855010c19824..029224e12216 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -102,6 +102,20 @@ def f6(x: tuple[A, A] | tuple[B, B]): reveal_type(x) # N: Revealed type is "tuple[__main__.A, __main__.A]" else: reveal_type(x) # N: Revealed type is "tuple[__main__.B, __main__.B]" + +class AA(A): ... + +def f7(x: list[A] | list[AA]): + if isinstance(x[0], AA): + reveal_type(x) # N: Revealed type is "builtins.list[__main__.A] | builtins.list[__main__.AA]" + else: + reveal_type(x) # N: Revealed type is "builtins.list[__main__.A] | builtins.list[__main__.AA]" + +def f8(x: list[str] | list[str | None]): + if x[0] is not None: + reveal_type(x) # N: Revealed type is "builtins.list[builtins.str] | builtins.list[builtins.str | None]" + else: + reveal_type(x) # N: Revealed type is "builtins.list[builtins.str | None]" [builtins fixtures/tuple.pyi] [case testClassAttributeInitialization]