diff --git a/mypy/checkmember.py b/mypy/checkmember.py index b9221958dacb..3852a81d5133 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -550,7 +550,8 @@ def analyze_var(name: str, return mx.chk.handle_partial_var_type(typ, mx.is_lvalue, var, mx.context) if mx.is_lvalue and var.is_property and not var.is_settable_property: # TODO allow setting attributes in subclass (although it is probably an error) - mx.msg.read_only_property(name, itype.type, mx.context) + if info.get('__setattr__') is None: + mx.msg.read_only_property(name, itype.type, mx.context) if mx.is_lvalue and var.is_classvar: mx.msg.cant_assign_to_classvar(name, mx.context) t = get_proper_type(expand_type_by_instance(typ, itype)) @@ -560,7 +561,8 @@ def analyze_var(name: str, if mx.is_lvalue: if var.is_property: if not var.is_settable_property: - mx.msg.read_only_property(name, itype.type, mx.context) + if info.get('__setattr__') is None: + mx.msg.read_only_property(name, itype.type, mx.context) else: mx.msg.cant_assign_to_method(mx.context) diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 34d9b66da0c1..b138d180dc3b 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -90,6 +90,7 @@ 'check-reports.test', 'check-errorcodes.test', 'check-annotated.test', + 'check-setattr.test', 'check-parameter-specification.test', ] diff --git a/test-data/unit/check-setattr.test b/test-data/unit/check-setattr.test new file mode 100644 index 000000000000..7f7134b8eb11 --- /dev/null +++ b/test-data/unit/check-setattr.test @@ -0,0 +1,39 @@ +[case testSetAttrWithProperty] +from typing import Any +class Foo: + _a = 1 + def __setattr__(self, name: str, value: Any) -> None: + self._a = value + @property + def a(self) -> int: + return self._a +f = Foo() +f.a = 2 +[builtins fixtures/property.pyi] + +[case testInheritedSetAttrWithProperty] +from typing import Any +class Foo: + _a = 1 + def __setattr__(self, name: str, value: Any) -> None: + self._a = value +class Bar(Foo): + @property + def a(self) -> int: + return self._a +f = Bar() +f.a = 2 +[builtins fixtures/property.pyi] + +[case testSetAttrWithIncompatibleType] +from typing import Any +class Foo: + _a = 1 + def __setattr__(self, name: str, value: Any) -> None: + self._a = value + @property + def a(self) -> int: + return self._a +f = Foo() +f.a = 'hello' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[builtins fixtures/property.pyi]