From 22cf5476d352229834b12f15eb6b848ecd27db2e Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Thu, 29 Aug 2019 16:51:28 +0300 Subject: [PATCH 1/5] Add fixed length tuple concatenation --- mypy/checkexpr.py | 13 +++++++++++++ test-data/unit/check-tuples.test | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f5def57a9b1f..f3a17c90e76b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1886,6 +1886,13 @@ def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Ty column=typ.column, )) + def concat_tuples(self, left: TupleType, right: TupleType) -> TupleType: + """Concatenate two fixed length tuples.""" + result = TupleType(items=left.items + right.items, + fallback=self.named_type('builtins.tuple')) + result.partial_fallback = tuple_fallback(result) + return result + def visit_int_expr(self, e: IntExpr) -> Type: """Type check an integer literal (trivial).""" return self.infer_literal_expr_type(e.value, 'builtins.int') @@ -1945,6 +1952,12 @@ def visit_op_expr(self, e: OpExpr) -> Type: return self.strfrm_checker.check_str_interpolation(e.left, e.right) left_type = self.accept(e.left) + proper_left_type = get_proper_type(left_type) + if isinstance(proper_left_type, TupleType) and e.op == '+': + proper_right_type = get_proper_type(self.accept(e.right)) + if isinstance(proper_right_type, TupleType): + return self.concat_tuples(proper_left_type, proper_right_type) + if e.op in nodes.op_methods: method = self.get_operator_method(e.op) result, method_type = self.check_op(method, left_type, e.right, e, diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index e7f240e91926..bc569cb3b451 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1233,3 +1233,9 @@ reveal_type(tup[2]) # N: Revealed type is 'Union[Any, builtins.int*]' \ reveal_type(tup[:]) # N: Revealed type is 'Union[Tuple[builtins.int, builtins.str], builtins.list[builtins.int*]]' [builtins fixtures/tuple.pyi] + +[case testFixedLengthTupleConcatenation] +a = (1, "foo", 3) +b = ("bar", 7) + +reveal_type(a + b) # N: Revealed type is 'Tuple[builtins.int, builtins.str, builtins.int, builtins.str, builtins.int]' \ No newline at end of file From 239b43d7c52663860a8cde99446b4179d3b5f6d1 Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Fri, 30 Aug 2019 14:24:15 +0300 Subject: [PATCH 2/5] Remove fallback computation --- mypy/checkexpr.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f3a17c90e76b..f99d73746399 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1888,10 +1888,8 @@ def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Ty def concat_tuples(self, left: TupleType, right: TupleType) -> TupleType: """Concatenate two fixed length tuples.""" - result = TupleType(items=left.items + right.items, - fallback=self.named_type('builtins.tuple')) - result.partial_fallback = tuple_fallback(result) - return result + return TupleType(items=left.items + right.items, + fallback=self.named_type('builtins.tuple')) def visit_int_expr(self, e: IntExpr) -> Type: """Type check an integer literal (trivial).""" From 3d6d045791b9a9ae16d9086890a64169cc8737cb Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Fri, 30 Aug 2019 17:14:52 +0300 Subject: [PATCH 3/5] Add method override checking --- mypy/checkexpr.py | 10 +++++++--- test-data/unit/check-tuples.test | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f99d73746399..d48605a85fa7 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1952,9 +1952,13 @@ def visit_op_expr(self, e: OpExpr) -> Type: proper_left_type = get_proper_type(left_type) if isinstance(proper_left_type, TupleType) and e.op == '+': - proper_right_type = get_proper_type(self.accept(e.right)) - if isinstance(proper_right_type, TupleType): - return self.concat_tuples(proper_left_type, proper_right_type) + left_add_method = proper_left_type.partial_fallback.type.get('__add__') + if left_add_method and left_add_method.fullname == 'builtins.tuple.__add__': + proper_right_type = get_proper_type(self.accept(e.right)) + if isinstance(proper_right_type, TupleType): + right_radd_method = proper_right_type.partial_fallback.type.get('__radd__') + if right_radd_method is None: + return self.concat_tuples(proper_left_type, proper_right_type) if e.op in nodes.op_methods: method = self.get_operator_method(e.op) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index bc569cb3b451..2fb9ab54d901 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1238,4 +1238,6 @@ reveal_type(tup[:]) # N: Revealed type is 'Union[Tuple[builtins.int, builtins.s a = (1, "foo", 3) b = ("bar", 7) -reveal_type(a + b) # N: Revealed type is 'Tuple[builtins.int, builtins.str, builtins.int, builtins.str, builtins.int]' \ No newline at end of file +reveal_type(a + b) # N: Revealed type is 'Tuple[builtins.int, builtins.str, builtins.int, builtins.str, builtins.int]' + +[builtins fixtures/tuple.pyi] \ No newline at end of file From 62f5d59c9a33c95eceb929b011f5facecb3ddb06 Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Fri, 30 Aug 2019 20:11:00 +0300 Subject: [PATCH 4/5] Add __add__ to tuple fixture --- test-data/unit/fixtures/tuple.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test-data/unit/fixtures/tuple.pyi b/test-data/unit/fixtures/tuple.pyi index 4e6c421a1615..6e000a7699fd 100644 --- a/test-data/unit/fixtures/tuple.pyi +++ b/test-data/unit/fixtures/tuple.pyi @@ -1,6 +1,6 @@ # Builtins stub used in tuple-related test cases. -from typing import Iterable, Iterator, TypeVar, Generic, Sequence, Any, overload +from typing import Iterable, Iterator, TypeVar, Generic, Sequence, Any, overload, Tuple Tco = TypeVar('Tco', covariant=True) @@ -15,6 +15,7 @@ class tuple(Sequence[Tco], Generic[Tco]): def __contains__(self, item: object) -> bool: pass def __getitem__(self, x: int) -> Tco: pass def __rmul__(self, n: int) -> tuple: pass + def __add__(self, x: Tuple[Tco, ...]) -> Tuple[Tco, ...]: pass def count(self, obj: Any) -> int: pass class function: pass class ellipsis: pass From a5a5fa5b99ad976ced3e544fac8fbec6469341b4 Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Mon, 2 Sep 2019 20:14:12 +0300 Subject: [PATCH 5/5] Correct test output --- test-data/unit/check-tuples.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 2fb9ab54d901..99a3157337cb 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -738,7 +738,7 @@ class C: pass a = None # type: A -(a, a) + a # E: Unsupported left operand type for + ("Tuple[A, A]") +(a, a) + a # E: Unsupported operand types for + ("Tuple[A, A]" and "A") a + (a, a) # E: Unsupported operand types for + ("A" and "Tuple[A, A]") f((a, a)) # E: Argument 1 to "f" has incompatible type "Tuple[A, A]"; expected "A" (a, a).foo # E: "Tuple[A, A]" has no attribute "foo"