Skip to content

Commit 3186281

Browse files
author
Kamil Turek
authored
Check __setattr__ when property is not settable (#9196)
* Create test for __setattr__ usage with property * Check __setattr__ before calling read_only_property * Look through MRO for setattr definition * Create test case with setattr and incompatible type assignment
1 parent eb50379 commit 3186281

File tree

3 files changed

+44
-2
lines changed

3 files changed

+44
-2
lines changed

mypy/checkmember.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,8 @@ def analyze_var(name: str,
553553
return mx.chk.handle_partial_var_type(typ, mx.is_lvalue, var, mx.context)
554554
if mx.is_lvalue and var.is_property and not var.is_settable_property:
555555
# TODO allow setting attributes in subclass (although it is probably an error)
556-
mx.msg.read_only_property(name, itype.type, mx.context)
556+
if info.get('__setattr__') is None:
557+
mx.msg.read_only_property(name, itype.type, mx.context)
557558
if mx.is_lvalue and var.is_classvar:
558559
mx.msg.cant_assign_to_classvar(name, mx.context)
559560
t = get_proper_type(expand_type_by_instance(typ, itype))
@@ -563,7 +564,8 @@ def analyze_var(name: str,
563564
if mx.is_lvalue:
564565
if var.is_property:
565566
if not var.is_settable_property:
566-
mx.msg.read_only_property(name, itype.type, mx.context)
567+
if info.get('__setattr__') is None:
568+
mx.msg.read_only_property(name, itype.type, mx.context)
567569
else:
568570
mx.msg.cant_assign_to_method(mx.context)
569571

mypy/test/testcheck.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
'check-reports.test',
9191
'check-errorcodes.test',
9292
'check-annotated.test',
93+
'check-setattr.test',
9394
'check-parameter-specification.test',
9495
]
9596

test-data/unit/check-setattr.test

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[case testSetAttrWithProperty]
2+
from typing import Any
3+
class Foo:
4+
_a = 1
5+
def __setattr__(self, name: str, value: Any) -> None:
6+
self._a = value
7+
@property
8+
def a(self) -> int:
9+
return self._a
10+
f = Foo()
11+
f.a = 2
12+
[builtins fixtures/property.pyi]
13+
14+
[case testInheritedSetAttrWithProperty]
15+
from typing import Any
16+
class Foo:
17+
_a = 1
18+
def __setattr__(self, name: str, value: Any) -> None:
19+
self._a = value
20+
class Bar(Foo):
21+
@property
22+
def a(self) -> int:
23+
return self._a
24+
f = Bar()
25+
f.a = 2
26+
[builtins fixtures/property.pyi]
27+
28+
[case testSetAttrWithIncompatibleType]
29+
from typing import Any
30+
class Foo:
31+
_a = 1
32+
def __setattr__(self, name: str, value: Any) -> None:
33+
self._a = value
34+
@property
35+
def a(self) -> int:
36+
return self._a
37+
f = Foo()
38+
f.a = 'hello' # E: Incompatible types in assignment (expression has type "str", variable has type "int")
39+
[builtins fixtures/property.pyi]

0 commit comments

Comments
 (0)