Skip to content

Commit 9b96cbe

Browse files
authored
Fix edge cases in variadic tuple subclasses (#21518)
Fixes #19110 Two (mostly unrelated) things here: * Handle empty tuple index in tuple subclasses * Move tuple special-casing in map type to inner function to handle situations with indirect tuple subclasses.
1 parent 3a969d1 commit 9b96cbe

3 files changed

Lines changed: 58 additions & 19 deletions

File tree

mypy/maptype.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,6 @@ def map_instance_to_supertype(instance: Instance, superclass: TypeInfo) -> Insta
1616
# Fast path: `instance` already belongs to `superclass`.
1717
return instance
1818

19-
if superclass.fullname == "builtins.tuple" and instance.type.tuple_type:
20-
if has_type_vars(instance.type.tuple_type):
21-
# We special case mapping generic tuple types to tuple base, because for
22-
# such tuples fallback can't be calculated before applying type arguments.
23-
alias = instance.type.special_alias
24-
assert alias is not None
25-
if not alias._is_recursive:
26-
# Unfortunately we can't support this for generic recursive tuples.
27-
# If we skip this special casing we will fall back to tuple[Any, ...].
28-
tuple_type = expand_type_by_instance(instance.type.tuple_type, instance)
29-
if isinstance(tuple_type, TupleType):
30-
# Make the import here to avoid cyclic imports.
31-
import mypy.typeops
32-
33-
return mypy.typeops.tuple_fallback(tuple_type)
34-
elif isinstance(tuple_type, Instance):
35-
# This can happen after normalizing variadic tuples.
36-
return tuple_type
37-
3819
if not superclass.type_vars:
3920
# Fast path: `superclass` has no type variables to map to.
4021
return Instance(superclass, [])
@@ -93,6 +74,28 @@ def map_instance_to_direct_supertypes(instance: Instance, supertype: TypeInfo) -
9374

9475
for b in typ.bases:
9576
if b.type == supertype:
77+
78+
if supertype.fullname == "builtins.tuple" and instance.type.tuple_type:
79+
if has_type_vars(instance.type.tuple_type):
80+
# We special case mapping generic tuple types to tuple base, because for
81+
# such tuples fallback can't be calculated before applying type arguments.
82+
alias = instance.type.special_alias
83+
assert alias is not None
84+
if not alias._is_recursive:
85+
# Unfortunately we can't support this for generic recursive tuples.
86+
# If we skip this special casing we will fall back to tuple[Any, ...].
87+
tuple_type = expand_type_by_instance(instance.type.tuple_type, instance)
88+
if isinstance(tuple_type, TupleType):
89+
# Make the import here to avoid cyclic imports.
90+
import mypy.typeops
91+
92+
result.append(mypy.typeops.tuple_fallback(tuple_type))
93+
continue
94+
elif isinstance(tuple_type, Instance):
95+
# This can happen after normalizing variadic tuples.
96+
result.append(tuple_type)
97+
continue
98+
9699
t = expand_type_by_instance(b, instance)
97100
assert isinstance(t, Instance)
98101
result.append(t)

mypy/typeanal.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,7 @@ def analyze_type_with_type_info(
899899
ctx,
900900
self.options,
901901
use_standard_error=True,
902+
empty_tuple_index=empty_tuple_index,
902903
)
903904
return tup.copy_modified(
904905
items=self.anal_array(tup.items, allow_unpack=True), fallback=instance

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2803,3 +2803,38 @@ c2: S2[int, str] = S2((1, "", 2)) # E: Argument 1 to "S2" has incompatible type
28032803
t: S2[Unpack[tuple[int, ...]]] = S2((1, 2))
28042804
t_bad: S2[Unpack[tuple[int, ...]]] = S2((1, "")) # E: Argument 1 to "S2" has incompatible type "tuple[int, str]"; expected "tuple[int, ...]"
28052805
[builtins fixtures/tuple.pyi]
2806+
2807+
[case testVariadicTupleSubclassEmptyIndex]
2808+
from typing import TypeVarTuple, Unpack
2809+
2810+
Ts = TypeVarTuple("Ts")
2811+
class Shape(tuple[Unpack[Ts]]): ...
2812+
2813+
class Shape0(Shape[()]): ...
2814+
2815+
x: Shape[()]
2816+
y: Shape0
2817+
2818+
reveal_type(x) # N: Revealed type is "tuple[(), fallback=__main__.Shape[()]]"
2819+
reveal_type(y) # N: Revealed type is "tuple[(), fallback=__main__.Shape0]"
2820+
2821+
def test(t: tuple[int, ...]) -> None: ...
2822+
test(x)
2823+
test(y)
2824+
[builtins fixtures/tuple.pyi]
2825+
2826+
[case testVariadicTupleSubclassVariadicIndex]
2827+
from typing import TypeVarTuple, Unpack
2828+
2829+
Ts = TypeVarTuple("Ts")
2830+
class Shape(tuple[Unpack[Ts]]): ...
2831+
2832+
class ShapeN(Shape[Unpack[tuple[int, ...]]]): ...
2833+
2834+
x: Shape[Unpack[tuple[int, ...]]]
2835+
y: ShapeN
2836+
2837+
def test(t: tuple[int, ...]) -> None: ...
2838+
test(x)
2839+
test(y)
2840+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)