From 883b8debcc24b3b10209bad35109f786b00490b5 Mon Sep 17 00:00:00 2001 From: Greg Price Date: Wed, 24 Aug 2016 15:27:57 -0700 Subject: [PATCH] Respect in-place operation when operator-assigning through square brackets Fixes #2046. Previously, when checking an operator-assignment with an IndexExpr on the left-hand side, even after determining that the operation will happen in-place we'd forget about that a few lines later and attempt to type-check a direct assignment, which could erroneously fail. --- mypy/checker.py | 20 ++++++++++---------- test-data/unit/check-statements.test | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 8614148c7298..6a5b0d913017 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1557,11 +1557,11 @@ def visit_operator_assignment_stmt(self, s: OperatorAssignmentStmt) -> Type: """Type check an operator assignment statement, e.g. x += 1.""" lvalue_type = self.accept(s.lvalue) - method = infer_operator_assignment_method(lvalue_type, s.op) + inplace, method = infer_operator_assignment_method(lvalue_type, s.op) rvalue_type, method_type = self.expr_checker.check_op( method, lvalue_type, s.rvalue, s) - if isinstance(s.lvalue, IndexExpr): + if isinstance(s.lvalue, IndexExpr) and not inplace: self.check_indexed_assignment(s.lvalue, s.rvalue, s.rvalue) else: if not is_subtype(rvalue_type, lvalue_type): @@ -2630,19 +2630,19 @@ def is_more_precise_signature(t: CallableType, s: CallableType) -> bool: return is_more_precise(t.ret_type, s.ret_type) -def infer_operator_assignment_method(type: Type, operator: str) -> str: - """Return the method used for operator assignment for given value type. +def infer_operator_assignment_method(type: Type, operator: str) -> Tuple[bool, str]: + """Determine if operator assignment on given value type is in-place, and the method name. - For example, if operator is '+', return '__iadd__' or '__add__' depending - on which method is supported by the type. + For example, if operator is '+', return (True, '__iadd__') or (False, '__add__') + depending on which method is supported by the type. """ method = nodes.op_methods[operator] if isinstance(type, Instance): if operator in nodes.ops_with_inplace_method: - inplace = '__i' + method[2:] - if type.type.has_readable_member(inplace): - method = inplace - return method + inplace_method = '__i' + method[2:] + if type.type.has_readable_member(inplace_method): + return True, inplace_method + return False, method def is_valid_inferred_type(typ: Type) -> bool: diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index b0e9b26183e5..b53912190149 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -301,6 +301,22 @@ a *= '' a += '' # E: Argument 1 to "__iadd__" of "A" has incompatible type "str"; expected "int" a *= 1 # E: Argument 1 to "__imul__" of "A" has incompatible type "int"; expected "str" +[case testInplaceSetitem] +class A(object): + def __init__(self): + self.a = 0 + + def __iadd__(self, a): + # type: (int) -> A + self.a += 1 + return self + +a = A() +b = [a] +b[0] += 1 +[builtins fixtures/list.pyi] +[out] + -- Assert statement -- ----------------