Skip to content

Commit de6b291

Browse files
onlinedJukkaL
authored andcommitted
Fixed length tuple concatenation (#7409)
Fixes #224.
1 parent 6c9386b commit de6b291

File tree

3 files changed

+26
-2
lines changed

3 files changed

+26
-2
lines changed

mypy/checkexpr.py

+15
Original file line numberDiff line numberDiff line change
@@ -1880,6 +1880,11 @@ def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Ty
18801880
column=typ.column,
18811881
))
18821882

1883+
def concat_tuples(self, left: TupleType, right: TupleType) -> TupleType:
1884+
"""Concatenate two fixed length tuples."""
1885+
return TupleType(items=left.items + right.items,
1886+
fallback=self.named_type('builtins.tuple'))
1887+
18831888
def visit_int_expr(self, e: IntExpr) -> Type:
18841889
"""Type check an integer literal (trivial)."""
18851890
return self.infer_literal_expr_type(e.value, 'builtins.int')
@@ -1939,6 +1944,16 @@ def visit_op_expr(self, e: OpExpr) -> Type:
19391944
return self.strfrm_checker.check_str_interpolation(e.left, e.right)
19401945
left_type = self.accept(e.left)
19411946

1947+
proper_left_type = get_proper_type(left_type)
1948+
if isinstance(proper_left_type, TupleType) and e.op == '+':
1949+
left_add_method = proper_left_type.partial_fallback.type.get('__add__')
1950+
if left_add_method and left_add_method.fullname == 'builtins.tuple.__add__':
1951+
proper_right_type = get_proper_type(self.accept(e.right))
1952+
if isinstance(proper_right_type, TupleType):
1953+
right_radd_method = proper_right_type.partial_fallback.type.get('__radd__')
1954+
if right_radd_method is None:
1955+
return self.concat_tuples(proper_left_type, proper_right_type)
1956+
19421957
if e.op in nodes.op_methods:
19431958
method = self.get_operator_method(e.op)
19441959
result, method_type = self.check_op(method, left_type, e.right, e,

test-data/unit/check-tuples.test

+9-1
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ class C: pass
738738

739739
a = None # type: A
740740

741-
(a, a) + a # E: Unsupported left operand type for + ("Tuple[A, A]")
741+
(a, a) + a # E: Unsupported operand types for + ("Tuple[A, A]" and "A")
742742
a + (a, a) # E: Unsupported operand types for + ("A" and "Tuple[A, A]")
743743
f((a, a)) # E: Argument 1 to "f" has incompatible type "Tuple[A, A]"; expected "A"
744744
(a, a).foo # E: "Tuple[A, A]" has no attribute "foo"
@@ -1233,3 +1233,11 @@ reveal_type(tup[2]) # N: Revealed type is 'Union[Any, builtins.int*]' \
12331233
reveal_type(tup[:]) # N: Revealed type is 'Union[Tuple[builtins.int, builtins.str], builtins.list[builtins.int*]]'
12341234

12351235
[builtins fixtures/tuple.pyi]
1236+
1237+
[case testFixedLengthTupleConcatenation]
1238+
a = (1, "foo", 3)
1239+
b = ("bar", 7)
1240+
1241+
reveal_type(a + b) # N: Revealed type is 'Tuple[builtins.int, builtins.str, builtins.int, builtins.str, builtins.int]'
1242+
1243+
[builtins fixtures/tuple.pyi]

test-data/unit/fixtures/tuple.pyi

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Builtins stub used in tuple-related test cases.
22

3-
from typing import Iterable, Iterator, TypeVar, Generic, Sequence, Any, overload
3+
from typing import Iterable, Iterator, TypeVar, Generic, Sequence, Any, overload, Tuple
44

55
Tco = TypeVar('Tco', covariant=True)
66

@@ -15,6 +15,7 @@ class tuple(Sequence[Tco], Generic[Tco]):
1515
def __contains__(self, item: object) -> bool: pass
1616
def __getitem__(self, x: int) -> Tco: pass
1717
def __rmul__(self, n: int) -> tuple: pass
18+
def __add__(self, x: Tuple[Tco, ...]) -> Tuple[Tco, ...]: pass
1819
def count(self, obj: Any) -> int: pass
1920
class function: pass
2021
class ellipsis: pass

0 commit comments

Comments
 (0)