Skip to content

Commit 4e52941

Browse files
committed
PEP 702 (@deprecated): descriptors
1 parent 05a9e79 commit 4e52941

File tree

4 files changed

+87
-6
lines changed

4 files changed

+87
-6
lines changed

mypy/checker.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4425,7 +4425,7 @@ def check_member_assignment(
44254425
msg=self.msg,
44264426
chk=self,
44274427
)
4428-
get_type = analyze_descriptor_access(attribute_type, mx)
4428+
get_type = analyze_descriptor_access(attribute_type, mx, assignment=True)
44294429
if not attribute_type.type.has_readable_member("__set__"):
44304430
# If there is no __set__, we type-check that the assigned value matches
44314431
# the return type of __get__. This doesn't match the python semantics,
@@ -4492,6 +4492,12 @@ def check_member_assignment(
44924492
callable_name=callable_name,
44934493
)
44944494

4495+
# Search for possible deprecations:
4496+
mx.chk.check_deprecated(dunder_set, mx.context)
4497+
mx.chk.warn_deprecated_overload_item(
4498+
dunder_set, mx.context, inferred_dunder_set_type, attribute_type
4499+
)
4500+
44954501
# In the following cases, a message already will have been recorded in check_call.
44964502
if (not isinstance(inferred_dunder_set_type, CallableType)) or (
44974503
len(inferred_dunder_set_type.arg_types) < 2
@@ -7688,6 +7694,22 @@ def warn_deprecated(self, node: SymbolNode | None, context: Context) -> None:
76887694
warn = self.msg.fail if self.options.report_deprecated_as_error else self.msg.note
76897695
warn(deprecated, context, code=codes.DEPRECATED)
76907696

7697+
def warn_deprecated_overload_item(
7698+
self, node: SymbolNode | None,
7699+
context: Context,
7700+
target: CallableType,
7701+
instance: Instance | None = None,
7702+
) -> None:
7703+
"""Warn if the overload item corresponding to the given callable is deprecated."""
7704+
if isinstance(node, OverloadedFuncDef):
7705+
for item in node.items:
7706+
if isinstance(item, Decorator):
7707+
candidate = item.func.type
7708+
if instance is not None:
7709+
candidate = bind_self(candidate, instance)
7710+
if candidate == target:
7711+
self.warn_deprecated(item.func, context)
7712+
76917713

76927714
class CollectArgTypeVarTypes(TypeTraverserVisitor):
76937715
"""Collects the non-nested argument types in a set."""

mypy/checkexpr.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,10 +1482,8 @@ def check_call_expr_with_callee_type(
14821482
object_type=object_type,
14831483
)
14841484
proper_callee = get_proper_type(callee_type)
1485-
if isinstance(e.callee, NameExpr) and isinstance(e.callee.node, OverloadedFuncDef):
1486-
for item in e.callee.node.items:
1487-
if isinstance(item, Decorator) and (item.func.type == callee_type):
1488-
self.chk.check_deprecated(item.func, e)
1485+
if isinstance(e.callee, NameExpr):
1486+
self.chk.warn_deprecated_overload_item(e.callee.node, e, callee_type)
14891487
if isinstance(e.callee, RefExpr) and isinstance(proper_callee, CallableType):
14901488
# Cache it for find_isinstance_check()
14911489
if proper_callee.type_guard is not None:

mypy/checkmember.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,9 @@ def check_final_member(name: str, info: TypeInfo, msg: MessageBuilder, ctx: Cont
638638
msg.cant_assign_to_final(name, attr_assign=True, ctx=ctx)
639639

640640

641-
def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type:
641+
def analyze_descriptor_access(
642+
descriptor_type: Type, mx: MemberContext, *, assignment: bool = False
643+
) -> Type:
642644
"""Type check descriptor access.
643645
644646
Arguments:
@@ -719,6 +721,12 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type:
719721
callable_name=callable_name,
720722
)
721723

724+
if not assignment:
725+
mx.chk.check_deprecated(dunder_get, mx.context)
726+
mx.chk.warn_deprecated_overload_item(
727+
dunder_get, mx.context, inferred_dunder_get_type, descriptor_type
728+
)
729+
722730
inferred_dunder_get_type = get_proper_type(inferred_dunder_get_type)
723731
if isinstance(inferred_dunder_get_type, AnyType):
724732
# check_call failed, and will have reported an error

test-data/unit/check-deprecated.test

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,59 @@ C().g = "x" # N: function __main__.C.g is deprecated: use g2 instead \
490490
[builtins fixtures/property.pyi]
491491

492492

493+
[case testDeprecatedDescriptor]
494+
495+
from typing import Optional, Type, Union
496+
from typing_extensions import deprecated, overload
497+
498+
@deprecated("use E1 instead")
499+
class D1:
500+
def __get__(self, obj: Optional[C], objtype: Type[C]) -> Union[D1, int]: ...
501+
502+
class D2:
503+
@deprecated("use E2.__get__ instead")
504+
def __get__(self, obj: Optional[C], objtype: Type[C]) -> Union[D2, int]: ...
505+
506+
@deprecated("use E2.__set__ instead")
507+
def __set__(self, obj: C, value: int) -> None: ...
508+
509+
class D3:
510+
@overload
511+
@deprecated("use E3.__get__ instead")
512+
def __get__(self, obj: None, objtype: Type[C]) -> D3: ...
513+
@overload
514+
@deprecated("use E3.__get__ instead")
515+
def __get__(self, obj: C, objtype: Type[C]) -> int: ...
516+
def __get__(self, obj: Optional[C], objtype: Type[C]) -> Union[D3, int]: ...
517+
518+
@overload
519+
def __set__(self, obj: C, value: int) -> None: ...
520+
@overload
521+
@deprecated("use E3.__set__ instead")
522+
def __set__(self, obj: C, value: str) -> None: ...
523+
def __set__(self, obj: C, value: Union[int, str]) -> None: ...
524+
525+
class C:
526+
d1 = D1() # N: class __main__.D1 is deprecated: use E1 instead
527+
d2 = D2()
528+
d3 = D3()
529+
530+
c: C
531+
C.d1
532+
c.d1
533+
c.d1 = 1
534+
535+
C.d2 # N: function __main__.D2.__get__ is deprecated: use E2.__get__ instead
536+
c.d2 # N: function __main__.D2.__get__ is deprecated: use E2.__get__ instead
537+
c.d2 = 1 # N: function __main__.D2.__set__ is deprecated: use E2.__set__ instead
538+
539+
C.d3 # N: overload def (self: __main__.D3, obj: None, objtype: type[__main__.C]) -> __main__.D3 of function __main__.D3.__get__ is deprecated: use E3.__get__ instead
540+
c.d3 # N: overload def (self: __main__.D3, obj: __main__.C, objtype: type[__main__.C]) -> builtins.int of function __main__.D3.__get__ is deprecated: use E3.__get__ instead
541+
c.d3 = 1
542+
c.d3 = "x" # N: overload def (self: __main__.D3, obj: __main__.C, value: builtins.str) of function __main__.D3.__set__ is deprecated: use E3.__set__ instead
543+
[builtins fixtures/property.pyi]
544+
545+
493546
[case testDeprecatedOverloadedFunction]
494547

495548
from typing import Union

0 commit comments

Comments
 (0)