Skip to content

Commit 28f0600

Browse files
authored
Support properties with generic setters (#19298)
This is yet another followup for `checkmember` work. This handles a niche use case, but it is still used in the wild, and this restores parity between logic for descriptors with `__set__()` and properties with custom setters.
1 parent 5081c59 commit 28f0600

File tree

2 files changed

+36
-1
lines changed

2 files changed

+36
-1
lines changed

mypy/checkmember.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,13 @@ def expand_and_bind_callable(
968968
# TODO: a decorated property can result in Overloaded here.
969969
assert isinstance(expanded, CallableType)
970970
if var.is_settable_property and mx.is_lvalue and var.setter_type is not None:
971-
# TODO: use check_call() to infer better type, same as for __set__().
971+
if expanded.variables:
972+
type_ctx = mx.rvalue or TempNode(AnyType(TypeOfAny.special_form), context=mx.context)
973+
_, inferred_expanded = mx.chk.expr_checker.check_call(
974+
expanded, [type_ctx], [ARG_POS], mx.context
975+
)
976+
expanded = get_proper_type(inferred_expanded)
977+
assert isinstance(expanded, CallableType)
972978
if not expanded.arg_types:
973979
# This can happen when accessing invalid property from its own body,
974980
# error will be reported elsewhere.

test-data/unit/check-generics.test

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3628,3 +3628,32 @@ def draw_none(
36283628
takes_int_str_none(c2)
36293629
takes_int_str_none(c3)
36303630
[builtins fixtures/tuple.pyi]
3631+
3632+
[case testPropertyWithGenericSetter]
3633+
from typing import TypeVar
3634+
3635+
class B: ...
3636+
class C(B): ...
3637+
T = TypeVar("T", bound=B)
3638+
3639+
class Test:
3640+
@property
3641+
def foo(self) -> list[C]: ...
3642+
@foo.setter
3643+
def foo(self, val: list[T]) -> None: ...
3644+
3645+
t1: Test
3646+
t2: Test
3647+
3648+
lb: list[B]
3649+
lc: list[C]
3650+
li: list[int]
3651+
3652+
t1.foo = lb
3653+
t1.foo = lc
3654+
t1.foo = li # E: Value of type variable "T" of "foo" of "Test" cannot be "int"
3655+
3656+
t2.foo = [B()]
3657+
t2.foo = [C()]
3658+
t2.foo = [1] # E: Value of type variable "T" of "foo" of "Test" cannot be "int"
3659+
[builtins fixtures/property.pyi]

0 commit comments

Comments
 (0)