From 95eb26cfb22d130aa8d250cbfa70a35c14017886 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Mon, 27 Oct 2025 16:26:46 +0100 Subject: [PATCH 1/3] One alias is enough to trigger infinite recursion --- mypy/meet.py | 2 +- test-data/unit/check-recursive-types.test | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/mypy/meet.py b/mypy/meet.py index 63305c2bb236..a0e852f68938 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -346,7 +346,7 @@ def is_overlapping_types( seen_types = set() elif (left, right) in seen_types: return True - if isinstance(left, TypeAliasType) and isinstance(right, TypeAliasType): + if isinstance(left, TypeAliasType) or isinstance(right, TypeAliasType): seen_types.add((left, right)) left, right = get_proper_types((left, right)) diff --git a/test-data/unit/check-recursive-types.test b/test-data/unit/check-recursive-types.test index c82111322fe1..c09f1e6b90c0 100644 --- a/test-data/unit/check-recursive-types.test +++ b/test-data/unit/check-recursive-types.test @@ -1024,3 +1024,19 @@ L = list[T] A = L[A] a: A = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "A") + +[case testRecursiveAliasInstanceOverlapCheck] +# flags: --warn-unreachable +from typing_extensions import TypeAlias + +OneClass: TypeAlias = 'list[OneClass]' + +class TwoClass(list['TwoClass']): + pass + +def f(obj: OneClass) -> None: + if isinstance(obj, TwoClass): + reveal_type(obj) # N: Revealed type is "__main__.TwoClass" + else: + reveal_type(obj) # N: Revealed type is "builtins.list[...]" +[builtins fixtures/isinstancelist.pyi] From f914beff6cd6a4c6634dcd515989f36882a00d25 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Mon, 27 Oct 2025 16:34:56 +0100 Subject: [PATCH 2/3] Only do that for recursive aliases? --- mypy/meet.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mypy/meet.py b/mypy/meet.py index a0e852f68938..eb6e613b5151 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -346,7 +346,12 @@ def is_overlapping_types( seen_types = set() elif (left, right) in seen_types: return True - if isinstance(left, TypeAliasType) or isinstance(right, TypeAliasType): + if ( + isinstance(left, TypeAliasType) + and left.is_recursive + or isinstance(right, TypeAliasType) + and right.is_recursive + ): seen_types.add((left, right)) left, right = get_proper_types((left, right)) From d3b662ed393f064445fb2a7675f0b867f9a9e90c Mon Sep 17 00:00:00 2001 From: STerliakov Date: Mon, 27 Oct 2025 20:40:33 +0100 Subject: [PATCH 3/3] Reuse is_recursive_pair --- mypy/meet.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/mypy/meet.py b/mypy/meet.py index eb6e613b5151..1cb291ff90d5 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -346,12 +346,7 @@ def is_overlapping_types( seen_types = set() elif (left, right) in seen_types: return True - if ( - isinstance(left, TypeAliasType) - and left.is_recursive - or isinstance(right, TypeAliasType) - and right.is_recursive - ): + if is_recursive_pair(left, right): seen_types.add((left, right)) left, right = get_proper_types((left, right))