Skip to content

Commit d640155

Browse files
authored
Fix joining of Sequence (e.g. variadic tuple) and fixed-length tuple (#8335)
For example: * Tuple[int] + Tuple[bool, ...] becomes Tuple[int, ...] * List[int] + Tuple[bool, ...] becomes Sequence[int] Previously Mypy simply punted and returned `object`. This solves the other part of issue #4975. Fixes issue #8074.
1 parent 1f9d87e commit d640155

File tree

3 files changed

+110
-3
lines changed

3 files changed

+110
-3
lines changed

mypy/join.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ def visit_instance(self, t: Instance) -> ProperType:
177177
return join_types(t, self.s)
178178
elif isinstance(self.s, TypedDictType):
179179
return join_types(t, self.s)
180+
elif isinstance(self.s, TupleType):
181+
return join_types(t, self.s)
180182
elif isinstance(self.s, LiteralType):
181183
return join_types(t, self.s)
182184
else:
@@ -260,6 +262,15 @@ def visit_overloaded(self, t: Overloaded) -> ProperType:
260262
return join_types(t.fallback, s)
261263

262264
def visit_tuple_type(self, t: TupleType) -> ProperType:
265+
# When given two fixed-length tuples:
266+
# * If they have the same length, join their subtypes item-wise:
267+
# Tuple[int, bool] + Tuple[bool, bool] becomes Tuple[int, bool]
268+
#
269+
# Otherwise, `t` is a fixed-length tuple but `self.s` is NOT:
270+
# * Joining with a variadic tuple returns variadic tuple:
271+
# Tuple[int, bool] + Tuple[bool, ...] becomes Tuple[int, ...]
272+
# * Joining with any Sequence also returns a Sequence:
273+
# Tuple[int, bool] + List[bool] becomes Sequence[int]
263274
if isinstance(self.s, TupleType) and self.s.length() == t.length():
264275
items = [] # type: List[Type]
265276
for i in range(t.length()):
@@ -269,7 +280,7 @@ def visit_tuple_type(self, t: TupleType) -> ProperType:
269280
assert isinstance(fallback, Instance)
270281
return TupleType(items, fallback)
271282
else:
272-
return self.default(self.s)
283+
return join_types(self.s, mypy.typeops.tuple_fallback(t))
273284

274285
def visit_typeddict_type(self, t: TypedDictType) -> ProperType:
275286
if isinstance(self.s, TypedDictType):

mypy/test/testtypes.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,10 +501,21 @@ def test_tuples(self) -> None:
501501

502502
self.assert_join(self.tuple(self.fx.a, self.fx.a),
503503
self.fx.std_tuple,
504-
self.fx.o)
504+
self.var_tuple(self.fx.anyt))
505505
self.assert_join(self.tuple(self.fx.a),
506506
self.tuple(self.fx.a, self.fx.a),
507-
self.fx.o)
507+
self.var_tuple(self.fx.a))
508+
509+
def test_var_tuples(self) -> None:
510+
self.assert_join(self.tuple(self.fx.a),
511+
self.var_tuple(self.fx.a),
512+
self.var_tuple(self.fx.a))
513+
self.assert_join(self.var_tuple(self.fx.a),
514+
self.tuple(self.fx.a),
515+
self.var_tuple(self.fx.a))
516+
self.assert_join(self.var_tuple(self.fx.a),
517+
self.tuple(),
518+
self.var_tuple(self.fx.a))
508519

509520
def test_function_types(self) -> None:
510521
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:
760771
def tuple(self, *a: Type) -> TupleType:
761772
return TupleType(list(a), self.fx.std_tuple)
762773

774+
def var_tuple(self, t: Type) -> Instance:
775+
"""Construct a variable-length tuple type"""
776+
return Instance(self.fx.std_tuplei, [t])
777+
763778
def callable(self, *a: Type) -> CallableType:
764779
"""callable(a1, ..., an, r) constructs a callable with argument types
765780
a1, ... an and return type r.

test-data/unit/check-tuples.test

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,6 +1077,87 @@ x, y = g(z) # E: Argument 1 to "g" has incompatible type "int"; expected "Tuple[
10771077
[builtins fixtures/tuple.pyi]
10781078
[out]
10791079

1080+
[case testFixedTupleJoinVarTuple]
1081+
from typing import Tuple
1082+
1083+
class A: pass
1084+
class B(A): pass
1085+
1086+
fixtup = None # type: Tuple[B, B]
1087+
1088+
vartup_b = None # type: Tuple[B, ...]
1089+
reveal_type(fixtup if int() else vartup_b) # N: Revealed type is 'builtins.tuple[__main__.B]'
1090+
reveal_type(vartup_b if int() else fixtup) # N: Revealed type is 'builtins.tuple[__main__.B]'
1091+
1092+
vartup_a = None # type: Tuple[A, ...]
1093+
reveal_type(fixtup if int() else vartup_a) # N: Revealed type is 'builtins.tuple[__main__.A]'
1094+
reveal_type(vartup_a if int() else fixtup) # N: Revealed type is 'builtins.tuple[__main__.A]'
1095+
1096+
1097+
[builtins fixtures/tuple.pyi]
1098+
[out]
1099+
1100+
[case testFixedTupleJoinList]
1101+
from typing import Tuple, List
1102+
1103+
class A: pass
1104+
class B(A): pass
1105+
1106+
fixtup = None # type: Tuple[B, B]
1107+
1108+
lst_b = None # type: List[B]
1109+
reveal_type(fixtup if int() else lst_b) # N: Revealed type is 'typing.Sequence[__main__.B]'
1110+
reveal_type(lst_b if int() else fixtup) # N: Revealed type is 'typing.Sequence[__main__.B]'
1111+
1112+
lst_a = None # type: List[A]
1113+
reveal_type(fixtup if int() else lst_a) # N: Revealed type is 'typing.Sequence[__main__.A]'
1114+
reveal_type(lst_a if int() else fixtup) # N: Revealed type is 'typing.Sequence[__main__.A]'
1115+
1116+
[builtins fixtures/tuple.pyi]
1117+
[out]
1118+
1119+
[case testEmptyTupleJoin]
1120+
from typing import Tuple, List
1121+
1122+
class A: pass
1123+
1124+
empty = ()
1125+
1126+
fixtup = None # type: Tuple[A]
1127+
reveal_type(fixtup if int() else empty) # N: Revealed type is 'builtins.tuple[__main__.A]'
1128+
reveal_type(empty if int() else fixtup) # N: Revealed type is 'builtins.tuple[__main__.A]'
1129+
1130+
vartup = None # type: Tuple[A, ...]
1131+
reveal_type(empty if int() else vartup) # N: Revealed type is 'builtins.tuple[__main__.A]'
1132+
reveal_type(vartup if int() else empty) # N: Revealed type is 'builtins.tuple[__main__.A]'
1133+
1134+
lst = None # type: List[A]
1135+
reveal_type(empty if int() else lst) # N: Revealed type is 'typing.Sequence[__main__.A*]'
1136+
reveal_type(lst if int() else empty) # N: Revealed type is 'typing.Sequence[__main__.A*]'
1137+
1138+
[builtins fixtures/tuple.pyi]
1139+
[out]
1140+
1141+
[case testTupleSubclassJoin]
1142+
from typing import Tuple, NamedTuple
1143+
1144+
class NTup(NamedTuple):
1145+
a: bool
1146+
b: bool
1147+
1148+
class SubTuple(Tuple[bool]): ...
1149+
class SubVarTuple(Tuple[int, ...]): ...
1150+
1151+
ntup = None # type: NTup
1152+
subtup = None # type: SubTuple
1153+
vartup = None # type: SubVarTuple
1154+
1155+
reveal_type(ntup if int() else vartup) # N: Revealed type is 'builtins.tuple[builtins.int]'
1156+
reveal_type(subtup if int() else vartup) # N: Revealed type is 'builtins.tuple[builtins.int]'
1157+
1158+
[builtins fixtures/tuple.pyi]
1159+
[out]
1160+
10801161
[case testTupleWithUndersizedContext]
10811162
a = ([1], 'x')
10821163
if int():

0 commit comments

Comments
 (0)