From 73ae04d69cccc39ae1e25811e665dc4d5cdf7f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=8F=E6=9D=A5?= Date: Thu, 22 Oct 2026 16:03:49 +0800 Subject: [PATCH] Fix in-place operator lookup on asymmetric properties Augmented assignment now looks up the in-place operator method on the type returned by the getter rather than the type accepted by the setter. This matters for properties and descriptors whose getter and setter types differ. Fixes #22273. Assisted-by: Codex:GPT-5 --- mypy/checker.py | 9 +++++++-- test-data/unit/check-statements.test | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) 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}