diff --git a/mypy/maptype.py b/mypy/maptype.py index 59ecb2bc9993..d9bb6da8080a 100644 --- a/mypy/maptype.py +++ b/mypy/maptype.py @@ -16,25 +16,6 @@ def map_instance_to_supertype(instance: Instance, superclass: TypeInfo) -> Insta # Fast path: `instance` already belongs to `superclass`. return instance - if superclass.fullname == "builtins.tuple" and instance.type.tuple_type: - if has_type_vars(instance.type.tuple_type): - # We special case mapping generic tuple types to tuple base, because for - # such tuples fallback can't be calculated before applying type arguments. - alias = instance.type.special_alias - assert alias is not None - if not alias._is_recursive: - # Unfortunately we can't support this for generic recursive tuples. - # If we skip this special casing we will fall back to tuple[Any, ...]. - tuple_type = expand_type_by_instance(instance.type.tuple_type, instance) - if isinstance(tuple_type, TupleType): - # Make the import here to avoid cyclic imports. - import mypy.typeops - - return mypy.typeops.tuple_fallback(tuple_type) - elif isinstance(tuple_type, Instance): - # This can happen after normalizing variadic tuples. - return tuple_type - if not superclass.type_vars: # Fast path: `superclass` has no type variables to map to. return Instance(superclass, []) @@ -93,6 +74,28 @@ def map_instance_to_direct_supertypes(instance: Instance, supertype: TypeInfo) - for b in typ.bases: if b.type == supertype: + + if supertype.fullname == "builtins.tuple" and instance.type.tuple_type: + if has_type_vars(instance.type.tuple_type): + # We special case mapping generic tuple types to tuple base, because for + # such tuples fallback can't be calculated before applying type arguments. + alias = instance.type.special_alias + assert alias is not None + if not alias._is_recursive: + # Unfortunately we can't support this for generic recursive tuples. + # If we skip this special casing we will fall back to tuple[Any, ...]. + tuple_type = expand_type_by_instance(instance.type.tuple_type, instance) + if isinstance(tuple_type, TupleType): + # Make the import here to avoid cyclic imports. + import mypy.typeops + + result.append(mypy.typeops.tuple_fallback(tuple_type)) + continue + elif isinstance(tuple_type, Instance): + # This can happen after normalizing variadic tuples. + result.append(tuple_type) + continue + t = expand_type_by_instance(b, instance) assert isinstance(t, Instance) result.append(t) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index db5625619262..0c0399d91ecc 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -899,6 +899,7 @@ def analyze_type_with_type_info( ctx, self.options, use_standard_error=True, + empty_tuple_index=empty_tuple_index, ) return tup.copy_modified( items=self.anal_array(tup.items, allow_unpack=True), fallback=instance diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 45bb58e1a597..8d3f3cd80531 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -2803,3 +2803,38 @@ c2: S2[int, str] = S2((1, "", 2)) # E: Argument 1 to "S2" has incompatible type t: S2[Unpack[tuple[int, ...]]] = S2((1, 2)) t_bad: S2[Unpack[tuple[int, ...]]] = S2((1, "")) # E: Argument 1 to "S2" has incompatible type "tuple[int, str]"; expected "tuple[int, ...]" [builtins fixtures/tuple.pyi] + +[case testVariadicTupleSubclassEmptyIndex] +from typing import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class Shape(tuple[Unpack[Ts]]): ... + +class Shape0(Shape[()]): ... + +x: Shape[()] +y: Shape0 + +reveal_type(x) # N: Revealed type is "tuple[(), fallback=__main__.Shape[()]]" +reveal_type(y) # N: Revealed type is "tuple[(), fallback=__main__.Shape0]" + +def test(t: tuple[int, ...]) -> None: ... +test(x) +test(y) +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleSubclassVariadicIndex] +from typing import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class Shape(tuple[Unpack[Ts]]): ... + +class ShapeN(Shape[Unpack[tuple[int, ...]]]): ... + +x: Shape[Unpack[tuple[int, ...]]] +y: ShapeN + +def test(t: tuple[int, ...]) -> None: ... +test(x) +test(y) +[builtins fixtures/tuple.pyi]