Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 30 additions & 27 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,21 +474,17 @@ def visit_instance(self, left: Instance) -> bool:
return self._is_subtype(left, unpacked)
if left.type.has_base(right.partial_fallback.type.fullname):
if not self.proper_subtype:
# Special case to consider Foo[*tuple[Any, ...]] (i.e. bare Foo) a
# subtype of Foo[<whatever>], when Foo is user defined variadic tuple type.
# Special cases to consider:
# * Plain tuple[Any, ...] instance is a subtype of all tuple types.
# * Foo[*tuple[Any, ...]] (normalized) instance is a subtype of all
# tuples with fallback to Foo (e.g. for variadic NamedTuples).
mapped = map_instance_to_supertype(left, right.partial_fallback.type)
for arg in map(get_proper_type, mapped.args):
if isinstance(arg, UnpackType):
unpacked = get_proper_type(arg.type)
if not isinstance(unpacked, Instance):
break
assert unpacked.type.fullname == "builtins.tuple"
if not isinstance(get_proper_type(unpacked.args[0]), AnyType):
break
elif not isinstance(arg, AnyType):
break
else:
return True
if is_erased_instance(mapped):
if (
mapped.type.fullname == "builtins.tuple"
or mapped.type.has_type_var_tuple_type
):
return True
return False
if isinstance(right, TypeVarTupleType):
# tuple[Any, ...] is like Any in the world of tuples (see special case above).
Expand Down Expand Up @@ -556,19 +552,8 @@ def visit_instance(self, left: Instance) -> bool:
right_args = (
right_prefix + (TupleType(list(right_middle), fallback),) + right_suffix
)
if not self.proper_subtype and t.args:
for arg in map(get_proper_type, t.args):
if isinstance(arg, UnpackType):
unpacked = get_proper_type(arg.type)
if not isinstance(unpacked, Instance):
break
assert unpacked.type.fullname == "builtins.tuple"
if not isinstance(get_proper_type(unpacked.args[0]), AnyType):
break
elif not isinstance(arg, AnyType):
break
else:
return True
if not self.proper_subtype and is_erased_instance(t):
return True
if len(left_args) != len(right_args):
return False
type_params = zip(left_args, right_args, right.type.defn.type_vars)
Expand Down Expand Up @@ -2151,3 +2136,21 @@ def erase_return_self_types(typ: Type, self_type: Instance) -> Type:
]
)
return typ


def is_erased_instance(t: Instance) -> bool:
"""Is this an instance where all args are Any types?"""
if not t.args:
return False
for arg in t.args:
if isinstance(arg, UnpackType):
unpacked = get_proper_type(arg.type)
if not isinstance(unpacked, Instance):
return False
assert unpacked.type.fullname == "builtins.tuple"
if not isinstance(get_proper_type(unpacked.args[0]), AnyType):
return False
elif not isinstance(get_proper_type(arg), AnyType):
return False
else:
return True
5 changes: 4 additions & 1 deletion mypy/test/testsubtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from mypy.subtypes import is_subtype
from mypy.test.helpers import Suite
from mypy.test.typefixture import InterfaceTypeFixture, TypeFixture
from mypy.types import Instance, Type, UninhabitedType, UnpackType
from mypy.types import Instance, TupleType, Type, UninhabitedType, UnpackType


class SubtypingSuite(Suite):
Expand Down Expand Up @@ -274,6 +274,9 @@ def test_type_var_tuple_unpacked_variable_length_tuple(self) -> None:
Instance(self.fx.gvi, [UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))]),
)

def test_fallback_not_subtype_of_tuple(self) -> None:
self.assert_not_subtype(self.fx.a, TupleType([self.fx.b], fallback=self.fx.a))

# IDEA: Maybe add these test cases (they are tested pretty well in type
# checker tests already):
# * more interface subtyping test cases
Expand Down