Skip to content

Commit 019551b

Browse files
committed
Address CR
1 parent a2187de commit 019551b

File tree

6 files changed

+127
-14
lines changed

6 files changed

+127
-14
lines changed

mypy/checker.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -647,19 +647,20 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
647647
# HACK: Infer the type of the property.
648648
assert isinstance(defn.items[0], Decorator)
649649
self.visit_decorator(defn.items[0])
650-
assert isinstance(defn.items[1], Decorator)
651-
self.visit_func_def(defn.items[1].func)
652-
setter_type = self.function_type(defn.items[1].func)
653-
assert isinstance(setter_type, CallableType)
654-
if len(setter_type.arg_types) != 2:
655-
self.fail("Invalid property setter signature", defn.items[1].func)
656-
any_type = AnyType(TypeOfAny.from_error)
657-
setter_type = setter_type.copy_modified(
658-
arg_types=[any_type, any_type],
659-
arg_kinds=[ARG_POS, ARG_POS],
660-
arg_names=[None, None],
661-
)
662-
defn.items[0].var.setter_type = setter_type
650+
if defn.items[0].var.is_settable_property:
651+
assert isinstance(defn.items[1], Decorator)
652+
self.visit_func_def(defn.items[1].func)
653+
setter_type = self.function_type(defn.items[1].func)
654+
assert isinstance(setter_type, CallableType)
655+
if len(setter_type.arg_types) != 2:
656+
self.fail("Invalid property setter signature", defn.items[1].func)
657+
any_type = AnyType(TypeOfAny.from_error)
658+
setter_type = setter_type.copy_modified(
659+
arg_types=[any_type, any_type],
660+
arg_kinds=[ARG_POS, ARG_POS],
661+
arg_names=[None, None],
662+
)
663+
defn.items[0].var.setter_type = setter_type
663664
for fdef in defn.items:
664665
assert isinstance(fdef, Decorator)
665666
if defn.is_property:

mypy/server/astdiff.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,11 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb
245245
impl = node
246246
elif isinstance(node, OverloadedFuncDef) and node.impl:
247247
impl = node.impl.func if isinstance(node.impl, Decorator) else node.impl
248+
setter_type = None
249+
if isinstance(node, OverloadedFuncDef) and node.items:
250+
first_item = node.items[0]
251+
if isinstance(first_item, Decorator) and first_item.func.is_property:
252+
setter_type = snapshot_optional_type(first_item.var.setter_type)
248253
is_trivial_body = impl.is_trivial_body if impl else False
249254
dataclass_transform_spec = find_dataclass_transform_spec(node)
250255
return (
@@ -258,6 +263,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb
258263
is_trivial_body,
259264
dataclass_transform_spec.serialize() if dataclass_transform_spec is not None else None,
260265
node.deprecated if isinstance(node, FuncDef) else None,
266+
setter_type, # multi-part properties are stored as OverloadedFuncDef
261267
)
262268
elif isinstance(node, Var):
263269
return ("Var", common, snapshot_optional_type(node.type), node.is_final)

test-data/unit/check-classes.test

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8410,3 +8410,33 @@ reveal_type(a.f) # N: Revealed type is "builtins.int"
84108410
a.f = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "str")
84118411
reveal_type(a.f) # N: Revealed type is "builtins.int"
84128412
[builtins fixtures/property.pyi]
8413+
8414+
[case testPropertySetterTypeGeneric]
8415+
from typing import TypeVar, Generic, List
8416+
8417+
T = TypeVar("T")
8418+
8419+
class B(Generic[T]):
8420+
@property
8421+
def foo(self) -> int: ...
8422+
@foo.setter
8423+
def foo(self, x: T) -> None: ...
8424+
8425+
class C(B[List[T]]): ...
8426+
8427+
a = C[str]()
8428+
a.foo = ["foo", "bar"]
8429+
reveal_type(a.foo) # N: Revealed type is "builtins.int"
8430+
a.foo = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "List[str]")
8431+
reveal_type(a.foo) # N: Revealed type is "builtins.int"
8432+
[builtins fixtures/property.pyi]
8433+
8434+
[case testPropertyDeleterNoSetterOK]
8435+
class C:
8436+
@property
8437+
def x(self) -> int:
8438+
return 0
8439+
@x.deleter
8440+
def x(self) -> None:
8441+
pass
8442+
[builtins fixtures/property.pyi]

test-data/unit/check-incremental.test

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6745,3 +6745,29 @@ from typing_extensions import TypeAlias
67456745
IntOrStr: TypeAlias = int | str
67466746
assert isinstance(1, IntOrStr)
67476747
[builtins fixtures/type.pyi]
6748+
6749+
[case testPropertySetterTypeIncremental]
6750+
import b
6751+
[file a.py]
6752+
class A:
6753+
@property
6754+
def f(self) -> int:
6755+
return 1
6756+
@f.setter
6757+
def f(self, x: str) -> None:
6758+
pass
6759+
[file b.py]
6760+
from a import A
6761+
[file b.py.2]
6762+
from a import A
6763+
a = A()
6764+
a.f = '' # OK
6765+
reveal_type(a.f)
6766+
a.f = 1
6767+
reveal_type(a.f)
6768+
[builtins fixtures/property.pyi]
6769+
[out]
6770+
[out2]
6771+
tmp/b.py:4: note: Revealed type is "builtins.int"
6772+
tmp/b.py:5: error: Incompatible types in assignment (expression has type "int", variable has type "str")
6773+
tmp/b.py:6: note: Revealed type is "builtins.int"

test-data/unit/fine-grained.test

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11161,3 +11161,53 @@ main:6: error: class a.D is deprecated: use D2 instead
1116111161
main:7: error: class a.D is deprecated: use D2 instead
1116211162
b.py:1: error: class a.C is deprecated: use C2 instead
1116311163
b.py:2: error: class a.D is deprecated: use D2 instead
11164+
11165+
[case testPropertySetterTypeFineGrained]
11166+
from a import A
11167+
a = A()
11168+
a.f = ''
11169+
[file a.py]
11170+
class A:
11171+
@property
11172+
def f(self) -> int:
11173+
return 1
11174+
@f.setter
11175+
def f(self, x: str) -> None:
11176+
pass
11177+
[file a.py.2]
11178+
class A:
11179+
@property
11180+
def f(self) -> int:
11181+
return 1
11182+
@f.setter
11183+
def f(self, x: int) -> None:
11184+
pass
11185+
[builtins fixtures/property.pyi]
11186+
[out]
11187+
==
11188+
main:3: error: Incompatible types in assignment (expression has type "str", variable has type "int")
11189+
11190+
[case testPropertyDeleteSetterFineGrained]
11191+
from a import A
11192+
a = A()
11193+
a.f = 1
11194+
[file a.py]
11195+
class A:
11196+
@property
11197+
def f(self) -> int:
11198+
return 1
11199+
@f.setter
11200+
def f(self, x: int) -> None:
11201+
pass
11202+
[file a.py.2]
11203+
class A:
11204+
@property
11205+
def f(self) -> int:
11206+
return 1
11207+
@f.deleter
11208+
def f(self) -> None:
11209+
pass
11210+
[builtins fixtures/property.pyi]
11211+
[out]
11212+
==
11213+
main:3: error: Property "f" defined in "A" is read-only

test-data/unit/fixtures/property.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class function: pass
1313
property = object() # Dummy definition
1414
class classmethod: pass
1515

16-
class list: pass
16+
class list(typing.Generic[_T]): pass
1717
class dict: pass
1818
class int: pass
1919
class float: pass

0 commit comments

Comments
 (0)