diff --git a/mypy/checker.py b/mypy/checker.py index 3f2a8476ed934..12a82bd40334b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5502,12 +5502,16 @@ 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 runtime reads the attribute (getter type) to resolve the + # operator; only the resulting value must satisfy the setter. + value_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) + value_type = lvalue_type + inplace, method = infer_operator_assignment_method(value_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, value_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 1db257b06e9ad..12d1151e39b56 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -356,6 +356,56 @@ a += '' # E: Argument 1 to "__iadd__" of "A" has incompatible type "str"; expect a *= 1 # E: Argument 1 to "__imul__" of "A" has incompatible type "int"; expected "str" a @= 1 # E: Argument 1 to "__imatmul__" of "A" has incompatible type "int"; expected "str" +[case testInplaceOperatorAsymmetricProperty] +from collections.abc import Iterable + +class P: + def __iadd__(self, x: int | Iterable[int]) -> 'P': + return self + +class N: + @property + def b(self) -> P: + return P() + + @b.setter + def b(self, v: P | list[int]) -> None: ... + +n = N() +n.b += 1 +[builtins fixtures/property.pyi] + +[case testInplaceOperatorAsymmetricPropertyBadResult] +class S: + def __iadd__(self, x: int) -> int: + return x + +class M: + @property + def m(self) -> S: + return S() + + @m.setter + def m(self, v: S) -> None: ... + +m = M() +m.m += 1 # E: Result type of + incompatible in assignment +[builtins fixtures/property.pyi] + +[case testInplaceOperatorReadOnlyProperty] +class P: + def __iadd__(self, x: int) -> 'P': + return self + +class N: + @property + def ro(self) -> P: + return P() + +n = N() +n.ro += 1 # E: Property "ro" defined in "N" is read-only +[builtins fixtures/property.pyi] + [case testInplaceSetitem] class A(object): def __init__(self) -> None: