Skip to content

Commit decd092

Browse files
committed
Use an alternative approach to fix more crash scenarios
1 parent 1ca0a7b commit decd092

6 files changed

Lines changed: 40 additions & 23 deletions

File tree

mypy/expandtype.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -182,16 +182,9 @@ class ExpandTypeVisitor(TrivialSyntheticTypeTranslator):
182182

183183
variables: Mapping[TypeVarId, Type] # TypeVar id -> TypeVar value
184184

185-
def __init__(
186-
self, variables: Mapping[TypeVarId, Type], skip_normalization: bool = False
187-
) -> None:
185+
def __init__(self, variables: Mapping[TypeVarId, Type]) -> None:
188186
super().__init__()
189187
self.variables = variables
190-
# This flag will skip normalizations that are semantically not needed, but
191-
# require calling get_proper_type(). This is needed during very early stages
192-
# to avoid infinite recursion when detecting invalid/divergent recursive
193-
# type aliases.
194-
self.skip_normalization = skip_normalization
195188

196189
def visit_unbound_type(self, t: UnboundType) -> Type:
197190
return t
@@ -235,7 +228,9 @@ def visit_instance(self, t: Instance) -> Type:
235228
if t.type.fullname == "builtins.tuple":
236229
# Normalize Tuple[*Tuple[X, ...], ...] -> Tuple[X, ...]
237230
arg = args[0]
238-
if not self.skip_normalization and isinstance(arg, UnpackType):
231+
if isinstance(arg, UnpackType) and not (
232+
isinstance(arg.type, TypeAliasType) and arg.type.is_recursive
233+
):
239234
unpacked = get_proper_type(arg.type)
240235
if isinstance(unpacked, Instance):
241236
assert unpacked.type.fullname == "builtins.tuple"
@@ -540,7 +535,9 @@ def visit_tuple_type(self, t: TupleType) -> Type:
540535
if len(items) == 1:
541536
# Normalize Tuple[*Tuple[X, ...]] -> Tuple[X, ...]
542537
item = items[0]
543-
if not self.skip_normalization and isinstance(item, UnpackType):
538+
if isinstance(item, UnpackType) and not (
539+
isinstance(item.type, TypeAliasType) and item.type.is_recursive
540+
):
544541
unpacked = get_proper_type(item.type)
545542
if isinstance(unpacked, Instance):
546543
# expand_type() may be called during semantic analysis, before invalid unpacks are fixed.

mypy/semanal_typeargs.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,10 @@ def visit_type_alias_type(self, t: TypeAliasType) -> None:
108108
self.seen_aliases.discard(t)
109109

110110
def visit_tuple_type(self, t: TupleType) -> None:
111-
t.items = flatten_nested_tuples(t.items)
111+
# Unfortunately, universal normalization of tuples is not possible in presence of
112+
# recursive aliases, see testNoCrashOnNonNormalRecursiveTuple for an example.
113+
# TODO: update the places where we handle tuples to always expect non-normal ones.
114+
t.items = flatten_nested_tuples(t.items, handle_recursive=False)
112115
for i, it in enumerate(t.items):
113116
if self.check_non_paramspec(it, "tuple", t):
114117
t.items[i] = AnyType(TypeOfAny.from_error)

mypy/typeanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2503,7 +2503,7 @@ def visit_type_alias_type(self, t: TypeAliasType) -> Type:
25032503
return t
25042504
new_nodes = self.seen_nodes | {t.alias}
25052505
visitor = DivergingAliasDetector(new_nodes)
2506-
_ = t.expand_once(skip_normalization=True).accept(visitor)
2506+
_ = get_proper_type(t).accept(visitor)
25072507
if visitor.diverging:
25082508
self.diverging = True
25092509
return t

mypy/types.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,11 @@ def __init__(
350350
self.args = args
351351
self.type_ref: str | None = None
352352

353-
def expand_once(self, skip_normalization: bool = False) -> Type:
353+
def _expand_once(self) -> Type:
354354
"""Expand to the target type exactly once.
355355
356356
This doesn't do full expansion, i.e. the result can contain another
357-
(or even this same) type alias. Use this helper only when needed,
357+
(or even this same) type alias. Use this internal helper only when really needed,
358358
its public wrapper mypy.types.get_proper_type() is preferred.
359359
"""
360360
assert self.alias is not None
@@ -381,8 +381,7 @@ def expand_once(self, skip_normalization: bool = False) -> Type:
381381
):
382382
mapping[tvar.id] = sub
383383

384-
visitor = InstantiateAliasVisitor(mapping, skip_normalization=skip_normalization)
385-
return self.alias.target.accept(visitor)
384+
return self.alias.target.accept(InstantiateAliasVisitor(mapping))
386385

387386
@property
388387
def is_recursive(self) -> bool:
@@ -3708,7 +3707,7 @@ def get_proper_type(typ: Type | None) -> ProperType | None:
37083707
if isinstance(typ, TypeGuardedType):
37093708
typ = typ.type_guard
37103709
while isinstance(typ, TypeAliasType):
3711-
typ = typ.expand_once()
3710+
typ = typ._expand_once()
37123711
# TODO: store the name of original type alias on this type, so we can show it in errors.
37133712
return cast(ProperType, typ)
37143713

@@ -4252,12 +4251,12 @@ def find_unpack_in_list(items: Sequence[Type]) -> int | None:
42524251
# Funky code here avoids mypyc narrowing the type of unpack_index.
42534252
old_index = unpack_index
42544253
assert old_index is None
4255-
# Don't return so that we can also sanity check there is only one.
4254+
# Don't return so that we can also sanity-check there is only one.
42564255
unpack_index = i
42574256
return unpack_index
42584257

42594258

4260-
def flatten_nested_tuples(types: Iterable[Type]) -> list[Type]:
4259+
def flatten_nested_tuples(types: Iterable[Type], handle_recursive: bool = True) -> list[Type]:
42614260
"""Recursively flatten TupleTypes nested with Unpack.
42624261
42634262
For example this will transform
@@ -4271,7 +4270,12 @@ def flatten_nested_tuples(types: Iterable[Type]) -> list[Type]:
42714270
res.append(typ)
42724271
continue
42734272
p_type = get_proper_type(typ.type)
4274-
if not isinstance(p_type, TupleType):
4273+
if (
4274+
not isinstance(p_type, TupleType)
4275+
or not handle_recursive
4276+
and isinstance(typ.type, TypeAliasType)
4277+
and typ.type.is_recursive
4278+
):
42754279
res.append(typ)
42764280
continue
42774281
if isinstance(typ.type, TypeAliasType):

mypy/types_utils.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,7 @@ def is_invalid_recursive_alias(seen_nodes: set[TypeAlias], target: Type) -> bool
6565
if target.alias in seen_nodes:
6666
return True
6767
assert target.alias, f"Unfixed type alias {target.type_ref}"
68-
return is_invalid_recursive_alias(
69-
seen_nodes | {target.alias}, target.expand_once(skip_normalization=True)
70-
)
68+
return is_invalid_recursive_alias(seen_nodes | {target.alias}, get_proper_type(target))
7169
assert isinstance(target, ProperType)
7270
if not isinstance(target, (UnionType, TupleType)):
7371
return False

test-data/unit/check-typevar-tuple.test

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,21 @@ A = tuple[Unpack[B[Unpack[Ts]]]] # E: Invalid recursive alias: a tuple item of
893893
B = tuple[Unpack[A[Unpack[Ts]]]]
894894
[builtins fixtures/tuple.pyi]
895895

896+
[case testNoCrashOnInvalidRecursiveUnpackOfUnion]
897+
from typing import Unpack
898+
899+
A = tuple[int, str] | list[tuple[Unpack[A]]] # E: "tuple[int, str] | list[tuple[Unpack[A]]]" cannot be unpacked (must be tuple or TypeVarTuple)
900+
[builtins fixtures/tuple.pyi]
901+
902+
[case testNoCrashOnNonNormalRecursiveTuple]
903+
from typing import Unpack
904+
905+
A = tuple[int, list[tuple[str, Unpack[A]]]]
906+
a: A
907+
x, y = a
908+
y[0] = 1 # E: Incompatible types in assignment (expression has type "int", target has type "tuple[str, Unpack[A]]")
909+
[builtins fixtures/list.pyi]
910+
896911
[case testBanTypeVarTupleNotImmediatelyInsideUnpack]
897912
from typing import TypeVarTuple, Unpack
898913

0 commit comments

Comments
 (0)