From c2a19715302d3aec7232ff53ac7e5ed269e409fe Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Sun, 26 Jan 2020 14:34:28 +0200 Subject: [PATCH] Fix joining of fixed-length tuples with mismatching lengths For example: Tuple[bool, int] + Tuple[bool] becomes Tuple[int, ...] Previously Mypy simply punted and returned `object`. The handling of fixed tuple + variadic tuple will be implemented separately. --- mypy/join.py | 13 ++++++--- mypy/test/testtypes.py | 6 +++++ test-data/unit/check-tuples.test | 46 ++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index d6a0dc1c3238..c22574884b61 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -265,6 +265,8 @@ 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] + # * If lengths do not match, return a variadic tuple: + # Tuple[bool, int] + Tuple[bool] becomes Tuple[int, ...] # # Otherwise, `t` is a fixed-length tuple but `self.s` is NOT: # * Joining with a variadic tuple returns variadic tuple: @@ -272,13 +274,16 @@ def visit_tuple_type(self, t: TupleType) -> ProperType: # * 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()): - items.append(self.join(t.items[i], self.s.items[i])) fallback = join_instances(mypy.typeops.tuple_fallback(self.s), mypy.typeops.tuple_fallback(t)) assert isinstance(fallback, Instance) - return TupleType(items, fallback) + if self.s.length() == t.length(): + items = [] # type: List[Type] + for i in range(t.length()): + items.append(self.join(t.items[i], self.s.items[i])) + return TupleType(items, fallback) + else: + return fallback else: return join_types(self.s, mypy.typeops.tuple_fallback(t)) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index b9dbb0cc60e3..957b3ad7c4ba 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -505,6 +505,12 @@ def test_tuples(self) -> None: self.assert_join(self.tuple(self.fx.a), self.tuple(self.fx.a, self.fx.a), self.var_tuple(self.fx.a)) + self.assert_join(self.tuple(self.fx.b), + self.tuple(self.fx.a, self.fx.c), + self.var_tuple(self.fx.a)) + self.assert_join(self.tuple(), + self.tuple(self.fx.a), + self.var_tuple(self.fx.a)) def test_var_tuples(self) -> None: self.assert_join(self.tuple(self.fx.a), diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 213ed545e6d2..2995e3b7fc80 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1158,6 +1158,52 @@ reveal_type(subtup if int() else vartup) # N: Revealed type is 'builtins.tuple[ [builtins fixtures/tuple.pyi] [out] +[case testTupleJoinIrregular] +from typing import Tuple + +tup1 = None # type: Tuple[bool, int] +tup2 = None # type: Tuple[bool] + +reveal_type(tup1 if int() else tup2) # N: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(tup2 if int() else tup1) # N: Revealed type is 'builtins.tuple[builtins.int]' + +reveal_type(tup1 if int() else ()) # N: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(() if int() else tup1) # N: Revealed type is 'builtins.tuple[builtins.int]' + +reveal_type(tup2 if int() else ()) # N: Revealed type is 'builtins.tuple[builtins.bool]' +reveal_type(() if int() else tup2) # N: Revealed type is 'builtins.tuple[builtins.bool]' + +[builtins fixtures/tuple.pyi] +[out] + +[case testTupleSubclassJoinIrregular] +from typing import Tuple, NamedTuple + +class NTup1(NamedTuple): + a: bool + +class NTup2(NamedTuple): + a: bool + b: bool + +class SubTuple(Tuple[bool, int, int]): ... + +tup1 = None # type: NTup1 +tup2 = None # type: NTup2 +subtup = None # type: SubTuple + +reveal_type(tup1 if int() else tup2) # N: Revealed type is 'builtins.tuple[builtins.bool]' +reveal_type(tup2 if int() else tup1) # N: Revealed type is 'builtins.tuple[builtins.bool]' + +reveal_type(tup1 if int() else subtup) # N: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(subtup if int() else tup1) # N: Revealed type is 'builtins.tuple[builtins.int]' + +reveal_type(tup2 if int() else subtup) # N: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(subtup if int() else tup2) # N: Revealed type is 'builtins.tuple[builtins.int]' + +[builtins fixtures/tuple.pyi] +[out] + [case testTupleWithUndersizedContext] a = ([1], 'x') if int():