diff --git a/mypy/checker.py b/mypy/checker.py index fd4486218dc1..8f91f89100b5 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5501,12 +5501,17 @@ def visit_operator_assignment_stmt(self, s: OperatorAssignmentStmt) -> None: # Special case, some additional errors may be given for # assignments to read-only or final attributes. lvalue_type = self.expr_checker.visit_member_expr(s.lvalue, True) + # The operator is applied to the value read from the attribute, not + # to the type accepted by a setter. These can differ for properties + # and descriptors. + operator_type = self.expr_checker.visit_member_expr(s.lvalue, False) else: lvalue_type = self.expr_checker.accept(s.lvalue) - inplace, method = infer_operator_assignment_method(lvalue_type, s.op) + operator_type = lvalue_type + inplace, method = infer_operator_assignment_method(operator_type, s.op) if inplace: # There is __ifoo__, treat as x = x.__ifoo__(y) - rvalue_type, _ = self.expr_checker.check_op(method, lvalue_type, s.rvalue, s) + rvalue_type, _ = self.expr_checker.check_op(method, operator_type, s.rvalue, s) if not is_subtype(rvalue_type, lvalue_type): self.msg.incompatible_operator_assignment(s.op, s) else: diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 1db257b06e9a..5783f0999a71 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -2219,6 +2219,29 @@ class A: [builtins fixtures/float.pyi] +[case testAugmentedAssignmentUsesGetterTypeForProperty] +class P: + def __iadd__(self, other: int) -> P: ... + +class Q: + def __iadd__(self, other: int) -> int: ... + +class C: + @property + def p(self) -> P: ... + @p.setter + def p(self, value: P | str) -> None: ... + + @property + def q(self) -> Q: ... + @q.setter + def q(self, value: Q) -> None: ... + +c = C() +c.p += 1 +c.q += 1 # E: Result type of + incompatible in assignment +[builtins fixtures/property.pyi] + [case testAugmentedAssignmentIntFloatDict] from typing import Dict d = {'weight0': 65.5}