Skip to content

Commit 4d0bd6b

Browse files
committed
Fix instance vs tuple subtyping edge case
1 parent 44f82ef commit 4d0bd6b

File tree

2 files changed

+34
-28
lines changed

2 files changed

+34
-28
lines changed

mypy/subtypes.py

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -474,21 +474,17 @@ def visit_instance(self, left: Instance) -> bool:
474474
return self._is_subtype(left, unpacked)
475475
if left.type.has_base(right.partial_fallback.type.fullname):
476476
if not self.proper_subtype:
477-
# Special case to consider Foo[*tuple[Any, ...]] (i.e. bare Foo) a
478-
# subtype of Foo[<whatever>], when Foo is user defined variadic tuple type.
477+
# Special cases to consider:
478+
# * Plain tuple[Any, ...] instance is a subtype of all tuple types.
479+
# * Foo[*tuple[Any, ...]] (normalized) instance is a subtype of all
480+
# tuples with fallback to Foo (e.g. for variadic NamedTuples).
479481
mapped = map_instance_to_supertype(left, right.partial_fallback.type)
480-
for arg in map(get_proper_type, mapped.args):
481-
if isinstance(arg, UnpackType):
482-
unpacked = get_proper_type(arg.type)
483-
if not isinstance(unpacked, Instance):
484-
break
485-
assert unpacked.type.fullname == "builtins.tuple"
486-
if not isinstance(get_proper_type(unpacked.args[0]), AnyType):
487-
break
488-
elif not isinstance(arg, AnyType):
489-
break
490-
else:
491-
return True
482+
if is_erased_instance(mapped):
483+
if (
484+
mapped.type.fullname == "builtins.tuple"
485+
or mapped.type.has_type_var_tuple_type
486+
):
487+
return True
492488
return False
493489
if isinstance(right, TypeVarTupleType):
494490
# tuple[Any, ...] is like Any in the world of tuples (see special case above).
@@ -556,19 +552,8 @@ def visit_instance(self, left: Instance) -> bool:
556552
right_args = (
557553
right_prefix + (TupleType(list(right_middle), fallback),) + right_suffix
558554
)
559-
if not self.proper_subtype and t.args:
560-
for arg in map(get_proper_type, t.args):
561-
if isinstance(arg, UnpackType):
562-
unpacked = get_proper_type(arg.type)
563-
if not isinstance(unpacked, Instance):
564-
break
565-
assert unpacked.type.fullname == "builtins.tuple"
566-
if not isinstance(get_proper_type(unpacked.args[0]), AnyType):
567-
break
568-
elif not isinstance(arg, AnyType):
569-
break
570-
else:
571-
return True
555+
if not self.proper_subtype and is_erased_instance(t):
556+
return True
572557
if len(left_args) != len(right_args):
573558
return False
574559
type_params = zip(left_args, right_args, right.type.defn.type_vars)
@@ -2151,3 +2136,21 @@ def erase_return_self_types(typ: Type, self_type: Instance) -> Type:
21512136
]
21522137
)
21532138
return typ
2139+
2140+
2141+
def is_erased_instance(t: Instance) -> bool:
2142+
"""Is this an instance where all args are Any types?"""
2143+
if not t.args:
2144+
return False
2145+
for arg in map(get_proper_type, t.args):
2146+
if isinstance(arg, UnpackType):
2147+
unpacked = get_proper_type(arg.type)
2148+
if not isinstance(unpacked, Instance):
2149+
return False
2150+
assert unpacked.type.fullname == "builtins.tuple"
2151+
if not isinstance(get_proper_type(unpacked.args[0]), AnyType):
2152+
return False
2153+
elif not isinstance(arg, AnyType):
2154+
return False
2155+
else:
2156+
return True

mypy/test/testsubtypes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from mypy.subtypes import is_subtype
55
from mypy.test.helpers import Suite
66
from mypy.test.typefixture import InterfaceTypeFixture, TypeFixture
7-
from mypy.types import Instance, Type, UninhabitedType, UnpackType
7+
from mypy.types import Instance, TupleType, Type, UninhabitedType, UnpackType
88

99

1010
class SubtypingSuite(Suite):
@@ -274,6 +274,9 @@ def test_type_var_tuple_unpacked_variable_length_tuple(self) -> None:
274274
Instance(self.fx.gvi, [UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))]),
275275
)
276276

277+
def test_fallback_not_subtype_of_tuple(self) -> None:
278+
self.assert_not_subtype(self.fx.a, TupleType([self.fx.b], fallback=self.fx.a))
279+
277280
# IDEA: Maybe add these test cases (they are tested pretty well in type
278281
# checker tests already):
279282
# * more interface subtyping test cases

0 commit comments

Comments
 (0)