From 8bb19cf3b7ef5f518608673f907c683b5ab143e2 Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Sun, 26 Jan 2020 18:10:13 +0200 Subject: [PATCH] Fix joining of Sequence (e.g. variadic tuple) and fixed-length tuple For example: * Tuple[int] + Tuple[bool, ...] becomes Tuple[int, ...] * List[int] + Tuple[bool, ...] becomes Sequence[int] Previously Mypy simply punted and returned `object`. --- mypy/join.py | 13 ++++- mypy/test/testtypes.py | 19 +++++++- test-data/unit/check-tuples.test | 81 ++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 3 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index a2513bd36201..d6a0dc1c3238 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -177,6 +177,8 @@ def visit_instance(self, t: Instance) -> ProperType: return join_types(t, self.s) elif isinstance(self.s, TypedDictType): return join_types(t, self.s) + elif isinstance(self.s, TupleType): + return join_types(t, self.s) elif isinstance(self.s, LiteralType): return join_types(t, self.s) else: @@ -260,6 +262,15 @@ def visit_overloaded(self, t: Overloaded) -> ProperType: return join_types(t.fallback, s) def visit_tuple_type(self, t: TupleType) -> ProperType: + # When given two fixed-length tuples: + # * If they have the same length, join their subtypes item-wise: + # Tuple[int, bool] + Tuple[bool, bool] becomes Tuple[int, bool] + # + # Otherwise, `t` is a fixed-length tuple but `self.s` is NOT: + # * Joining with a variadic tuple returns variadic tuple: + # Tuple[int, bool] + Tuple[bool, ...] becomes Tuple[int, ...] + # * Joining with any Sequence also returns a Sequence: + # Tuple[int, bool] + List[bool] becomes Sequence[int] if isinstance(self.s, TupleType) and self.s.length() == t.length(): items = [] # type: List[Type] for i in range(t.length()): @@ -269,7 +280,7 @@ def visit_tuple_type(self, t: TupleType) -> ProperType: assert isinstance(fallback, Instance) return TupleType(items, fallback) else: - return self.default(self.s) + return join_types(self.s, mypy.typeops.tuple_fallback(t)) def visit_typeddict_type(self, t: TypedDictType) -> ProperType: if isinstance(self.s, TypedDictType): diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 4609e0dd1a02..b9dbb0cc60e3 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -501,10 +501,21 @@ def test_tuples(self) -> None: self.assert_join(self.tuple(self.fx.a, self.fx.a), self.fx.std_tuple, - self.fx.o) + self.var_tuple(self.fx.anyt)) self.assert_join(self.tuple(self.fx.a), self.tuple(self.fx.a, self.fx.a), - self.fx.o) + self.var_tuple(self.fx.a)) + + def test_var_tuples(self) -> None: + self.assert_join(self.tuple(self.fx.a), + self.var_tuple(self.fx.a), + self.var_tuple(self.fx.a)) + self.assert_join(self.var_tuple(self.fx.a), + self.tuple(self.fx.a), + self.var_tuple(self.fx.a)) + self.assert_join(self.var_tuple(self.fx.a), + self.tuple(), + self.var_tuple(self.fx.a)) def test_function_types(self) -> None: self.assert_join(self.callable(self.fx.a, self.fx.b), @@ -760,6 +771,10 @@ def assert_simple_join(self, s: Type, t: Type, join: Type) -> None: def tuple(self, *a: Type) -> TupleType: return TupleType(list(a), self.fx.std_tuple) + def var_tuple(self, t: Type) -> Instance: + """Construct a variable-length tuple type""" + return Instance(self.fx.std_tuplei, [t]) + def callable(self, *a: Type) -> CallableType: """callable(a1, ..., an, r) constructs a callable with argument types a1, ... an and return type r. diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 5a792a77d856..213ed545e6d2 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1077,6 +1077,87 @@ x, y = g(z) # E: Argument 1 to "g" has incompatible type "int"; expected "Tuple[ [builtins fixtures/tuple.pyi] [out] +[case testFixedTupleJoinVarTuple] +from typing import Tuple + +class A: pass +class B(A): pass + +fixtup = None # type: Tuple[B, B] + +vartup_b = None # type: Tuple[B, ...] +reveal_type(fixtup if int() else vartup_b) # N: Revealed type is 'builtins.tuple[__main__.B]' +reveal_type(vartup_b if int() else fixtup) # N: Revealed type is 'builtins.tuple[__main__.B]' + +vartup_a = None # type: Tuple[A, ...] +reveal_type(fixtup if int() else vartup_a) # N: Revealed type is 'builtins.tuple[__main__.A]' +reveal_type(vartup_a if int() else fixtup) # N: Revealed type is 'builtins.tuple[__main__.A]' + + +[builtins fixtures/tuple.pyi] +[out] + +[case testFixedTupleJoinList] +from typing import Tuple, List + +class A: pass +class B(A): pass + +fixtup = None # type: Tuple[B, B] + +lst_b = None # type: List[B] +reveal_type(fixtup if int() else lst_b) # N: Revealed type is 'typing.Sequence[__main__.B]' +reveal_type(lst_b if int() else fixtup) # N: Revealed type is 'typing.Sequence[__main__.B]' + +lst_a = None # type: List[A] +reveal_type(fixtup if int() else lst_a) # N: Revealed type is 'typing.Sequence[__main__.A]' +reveal_type(lst_a if int() else fixtup) # N: Revealed type is 'typing.Sequence[__main__.A]' + +[builtins fixtures/tuple.pyi] +[out] + +[case testEmptyTupleJoin] +from typing import Tuple, List + +class A: pass + +empty = () + +fixtup = None # type: Tuple[A] +reveal_type(fixtup if int() else empty) # N: Revealed type is 'builtins.tuple[__main__.A]' +reveal_type(empty if int() else fixtup) # N: Revealed type is 'builtins.tuple[__main__.A]' + +vartup = None # type: Tuple[A, ...] +reveal_type(empty if int() else vartup) # N: Revealed type is 'builtins.tuple[__main__.A]' +reveal_type(vartup if int() else empty) # N: Revealed type is 'builtins.tuple[__main__.A]' + +lst = None # type: List[A] +reveal_type(empty if int() else lst) # N: Revealed type is 'typing.Sequence[__main__.A*]' +reveal_type(lst if int() else empty) # N: Revealed type is 'typing.Sequence[__main__.A*]' + +[builtins fixtures/tuple.pyi] +[out] + +[case testTupleSubclassJoin] +from typing import Tuple, NamedTuple + +class NTup(NamedTuple): + a: bool + b: bool + +class SubTuple(Tuple[bool]): ... +class SubVarTuple(Tuple[int, ...]): ... + +ntup = None # type: NTup +subtup = None # type: SubTuple +vartup = None # type: SubVarTuple + +reveal_type(ntup if int() else vartup) # N: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(subtup if int() else vartup) # N: Revealed type is 'builtins.tuple[builtins.int]' + +[builtins fixtures/tuple.pyi] +[out] + [case testTupleWithUndersizedContext] a = ([1], 'x') if int():