From b4668691aa7f0d4e8af8512f24122681604062d4 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 23 Aug 2024 23:01:04 +0100 Subject: [PATCH 1/2] Fix another crash scenario on recursive tuple types --- mypy/typeops.py | 9 +++++++-- mypy/types.py | 13 +++++++++++-- test-data/unit/check-recursive-types.test | 12 ++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 036d782be89c..d22448a715e5 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -114,7 +114,11 @@ def tuple_fallback(typ: TupleType) -> Instance: else: items.append(item) return Instance( - info, [make_simplified_union(items)], extra_attrs=typ.partial_fallback.extra_attrs + info, + # Note: flattening recursive unions is dangerous, since it can fool recursive + # types optimization in subtypes.py and go into infinite recursion. + [make_simplified_union(items, handle_recursive=False)], + extra_attrs=typ.partial_fallback.extra_attrs, ) @@ -440,6 +444,7 @@ def make_simplified_union( *, keep_erased: bool = False, contract_literals: bool = True, + handle_recursive: bool = True, ) -> ProperType: """Build union type with redundant union items removed. @@ -465,7 +470,7 @@ def make_simplified_union( to_union(). """ # Step 1: expand all nested unions - items = flatten_nested_unions(items) + items = flatten_nested_unions(items, handle_recursive=handle_recursive) # Step 2: fast path for single item if len(items) == 1: diff --git a/mypy/types.py b/mypy/types.py index 618228c361f3..b6fc870318d8 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -3629,7 +3629,10 @@ def extend_args_for_prefix_and_suffix( def flatten_nested_unions( - types: Sequence[Type], handle_type_alias_type: bool = True + types: Sequence[Type], + *, + handle_type_alias_type: bool = True, + handle_recursive: bool = True, ) -> list[Type]: """Flatten nested unions in a type list.""" if not isinstance(types, list): @@ -3643,7 +3646,13 @@ def flatten_nested_unions( flat_items: list[Type] = [] for t in typelist: - tp = get_proper_type(t) if handle_type_alias_type else t + if handle_type_alias_type: + if not handle_recursive and isinstance(t, TypeAliasType) and t.is_recursive: + tp: Type = t + else: + tp = get_proper_type(t) + else: + tp = t if isinstance(tp, ProperType) and isinstance(tp, UnionType): flat_items.extend( flatten_nested_unions(tp.items, handle_type_alias_type=handle_type_alias_type) diff --git a/test-data/unit/check-recursive-types.test b/test-data/unit/check-recursive-types.test index d5c8acd1bc15..ac1ea0c0035a 100644 --- a/test-data/unit/check-recursive-types.test +++ b/test-data/unit/check-recursive-types.test @@ -994,3 +994,15 @@ class T2(Tuple[T1, "T4", "T4"]): ... class T3(Tuple[str, "T4", "T4"]): ... T4 = Union[T2, T3] [builtins fixtures/tuple.pyi] + +[case testRecursiveTupleFallback5] +from typing import Protocol, Tuple, Union + +class Proto(Protocol): + def __len__(self) -> int: ... + +A = Union[Proto, Tuple[A]] +ta: Tuple[A] +p: Proto +p = ta +[builtins fixtures/tuple.pyi] From d74422f8f0d00fdb72ae77098353763dd9cb351a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:06: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/types.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index b6fc870318d8..78244d0f9cf4 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -3629,10 +3629,7 @@ def extend_args_for_prefix_and_suffix( def flatten_nested_unions( - types: Sequence[Type], - *, - handle_type_alias_type: bool = True, - handle_recursive: bool = True, + types: Sequence[Type], *, handle_type_alias_type: bool = True, handle_recursive: bool = True ) -> list[Type]: """Flatten nested unions in a type list.""" if not isinstance(types, list):