From b29ad88591fba2c2d5bf06de7c81e60008f69b7c Mon Sep 17 00:00:00 2001 From: Jared Hance Date: Wed, 20 Jul 2022 10:06:19 -0700 Subject: [PATCH] Implement more cases for typevartuple subtyping. --- mypy/subtypes.py | 21 +++++++++- mypy/test/testsubtypes.py | 85 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index de223b5cf95c..64f1de2c6828 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -319,7 +319,14 @@ def check_mixed( return False if isinstance(unpacked_type, AnyType): return True - assert False + if isinstance(unpacked_type, TupleType): + if len(unpacked_type.items) != len(compare_to): + return False + for t1, t2 in zip(unpacked_type.items, compare_to): + if not is_equivalent(t1, t2): + return False + return True + return False # Case 1: Both are unpacks, in this case we check what is being # unpacked is the same. @@ -327,11 +334,21 @@ def check_mixed( if not is_equivalent(left_unpacked, right_unpacked): return False - # Case 2: Only one of the types is an unpack. + # Case 2: Only one of the types is an unpack. The equivalence + # case is mostly the same but we check some additional + # things when unpacking on the right. elif left_unpacked is not None and right_unpacked is None: if not check_mixed(left_unpacked, right_middle): return False elif left_unpacked is None and right_unpacked is not None: + if ( + isinstance(right_unpacked, Instance) + and right_unpacked.type.fullname == "builtins.tuple" + ): + return all( + is_equivalent(l, right_unpacked.args[0]) + for l in left_middle + ) if not check_mixed(right_unpacked, left_middle): return False diff --git a/mypy/test/testsubtypes.py b/mypy/test/testsubtypes.py index c0c49c82c637..5b556a1dc16e 100644 --- a/mypy/test/testsubtypes.py +++ b/mypy/test/testsubtypes.py @@ -2,7 +2,7 @@ from mypy.nodes import CONTRAVARIANT, INVARIANT, COVARIANT from mypy.subtypes import is_subtype from mypy.test.typefixture import TypeFixture, InterfaceTypeFixture -from mypy.types import Type, Instance, UnpackType +from mypy.types import Type, Instance, UnpackType, TupleType class SubtypingSuite(Suite): @@ -217,6 +217,89 @@ def test_type_var_tuple(self) -> None: Instance(self.fx.gvi, [self.fx.anyt]), ) + def test_type_var_tuple_with_prefix_suffix(self) -> None: + self.assert_subtype( + Instance(self.fx.gvi, [self.fx.a, UnpackType(self.fx.ss)]), + Instance(self.fx.gvi, [self.fx.a, UnpackType(self.fx.ss)]), + ) + self.assert_subtype( + Instance(self.fx.gvi, [self.fx.a, self.fx.b, UnpackType(self.fx.ss)]), + Instance(self.fx.gvi, [self.fx.a, self.fx.b, UnpackType(self.fx.ss)]), + ) + self.assert_not_subtype( + Instance(self.fx.gvi, [self.fx.a, UnpackType(self.fx.ss)]), + Instance(self.fx.gvi, [self.fx.b, UnpackType(self.fx.ss)]), + ) + self.assert_not_subtype( + Instance(self.fx.gvi, [self.fx.a, UnpackType(self.fx.ss)]), + Instance(self.fx.gvi, [self.fx.a, self.fx.b, UnpackType(self.fx.ss)]), + ) + + self.assert_subtype( + Instance(self.fx.gvi, [UnpackType(self.fx.ss), self.fx.a]), + Instance(self.fx.gvi, [UnpackType(self.fx.ss), self.fx.a]), + ) + self.assert_not_subtype( + Instance(self.fx.gvi, [UnpackType(self.fx.ss), self.fx.a]), + Instance(self.fx.gvi, [UnpackType(self.fx.ss), self.fx.b]), + ) + self.assert_not_subtype( + Instance(self.fx.gvi, [UnpackType(self.fx.ss), self.fx.a]), + Instance(self.fx.gvi, [UnpackType(self.fx.ss), self.fx.a, self.fx.b]), + ) + + self.assert_subtype( + Instance(self.fx.gvi, [self.fx.a, self.fx.b, UnpackType(self.fx.ss), self.fx.c]), + Instance(self.fx.gvi, [self.fx.a, self.fx.b, UnpackType(self.fx.ss), self.fx.c]), + ) + self.assert_not_subtype( + Instance(self.fx.gvi, [self.fx.a, self.fx.b, UnpackType(self.fx.ss), self.fx.c]), + Instance(self.fx.gvi, [self.fx.a, UnpackType(self.fx.ss), self.fx.b, self.fx.c]), + ) + + def test_type_var_tuple_unpacked_tuple(self) -> None: + self.assert_subtype( + Instance(self.fx.gvi, [ + UnpackType(TupleType( + [self.fx.a, self.fx.b], fallback=Instance(self.fx.std_tuplei, [self.fx.o]), + )) + ]), + Instance(self.fx.gvi, [self.fx.a, self.fx.b]), + ) + self.assert_subtype( + Instance(self.fx.gvi, [ + UnpackType(TupleType( + [self.fx.a, self.fx.b], fallback=Instance(self.fx.std_tuplei, [self.fx.o]), + )) + ]), + Instance(self.fx.gvi, [self.fx.anyt, self.fx.anyt]), + ) + self.assert_not_subtype( + Instance(self.fx.gvi, [ + UnpackType(TupleType( + [self.fx.a, self.fx.b], fallback=Instance(self.fx.std_tuplei, [self.fx.o]), + )) + ]), + Instance(self.fx.gvi, [self.fx.a]), + ) + self.assert_not_subtype( + Instance(self.fx.gvi, [ + UnpackType(TupleType( + [self.fx.a, self.fx.b], fallback=Instance(self.fx.std_tuplei, [self.fx.o]), + )) + ]), + # Order flipped here. + Instance(self.fx.gvi, [self.fx.b, self.fx.a]), + ) + + def test_type_var_tuple_unpacked_variable_length_tuple(self) -> None: + self.assert_strict_subtype( + Instance(self.fx.gvi, [self.fx.a, self.fx.a]), + Instance(self.fx.gvi, [ + UnpackType(Instance(self.fx.std_tuplei, [self.fx.a])), + ]), + ) + # IDEA: Maybe add these test cases (they are tested pretty well in type # checker tests already): # * more interface subtyping test cases