Skip to content

Commit a53cf3d

Browse files
authored
PEP 702 (@deprecated): descriptors (#18090)
1 parent 6050204 commit a53cf3d

File tree

4 files changed

+113
-8
lines changed

4 files changed

+113
-8
lines changed

mypy/checker.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4426,7 +4426,7 @@ def check_member_assignment(
44264426
msg=self.msg,
44274427
chk=self,
44284428
)
4429-
get_type = analyze_descriptor_access(attribute_type, mx)
4429+
get_type = analyze_descriptor_access(attribute_type, mx, assignment=True)
44304430
if not attribute_type.type.has_readable_member("__set__"):
44314431
# If there is no __set__, we type-check that the assigned value matches
44324432
# the return type of __get__. This doesn't match the python semantics,
@@ -4493,6 +4493,12 @@ def check_member_assignment(
44934493
callable_name=callable_name,
44944494
)
44954495

4496+
# Search for possible deprecations:
4497+
mx.chk.check_deprecated(dunder_set, mx.context)
4498+
mx.chk.warn_deprecated_overload_item(
4499+
dunder_set, mx.context, target=inferred_dunder_set_type, selftype=attribute_type
4500+
)
4501+
44964502
# In the following cases, a message already will have been recorded in check_call.
44974503
if (not isinstance(inferred_dunder_set_type, CallableType)) or (
44984504
len(inferred_dunder_set_type.arg_types) < 2
@@ -7674,7 +7680,7 @@ def has_valid_attribute(self, typ: Type, name: str) -> bool:
76747680
def get_expression_type(self, node: Expression, type_context: Type | None = None) -> Type:
76757681
return self.expr_checker.accept(node, type_context=type_context)
76767682

7677-
def check_deprecated(self, node: SymbolNode | None, context: Context) -> None:
7683+
def check_deprecated(self, node: Node | None, context: Context) -> None:
76787684
"""Warn if deprecated and not directly imported with a `from` statement."""
76797685
if isinstance(node, Decorator):
76807686
node = node.func
@@ -7687,7 +7693,7 @@ def check_deprecated(self, node: SymbolNode | None, context: Context) -> None:
76877693
else:
76887694
self.warn_deprecated(node, context)
76897695

7690-
def warn_deprecated(self, node: SymbolNode | None, context: Context) -> None:
7696+
def warn_deprecated(self, node: Node | None, context: Context) -> None:
76917697
"""Warn if deprecated."""
76927698
if isinstance(node, Decorator):
76937699
node = node.func
@@ -7699,6 +7705,21 @@ def warn_deprecated(self, node: SymbolNode | None, context: Context) -> None:
76997705
warn = self.msg.note if self.options.report_deprecated_as_note else self.msg.fail
77007706
warn(deprecated, context, code=codes.DEPRECATED)
77017707

7708+
def warn_deprecated_overload_item(
7709+
self, node: Node | None, context: Context, *, target: Type, selftype: Type | None = None
7710+
) -> None:
7711+
"""Warn if the overload item corresponding to the given callable is deprecated."""
7712+
target = get_proper_type(target)
7713+
if isinstance(node, OverloadedFuncDef) and isinstance(target, CallableType):
7714+
for item in node.items:
7715+
if isinstance(item, Decorator) and isinstance(
7716+
candidate := item.func.type, CallableType
7717+
):
7718+
if selftype is not None:
7719+
candidate = bind_self(candidate, selftype)
7720+
if candidate == target:
7721+
self.warn_deprecated(item.func, context)
7722+
77027723

77037724
class CollectArgTypeVarTypes(TypeTraverserVisitor):
77047725
"""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
@@ -1483,10 +1483,8 @@ def check_call_expr_with_callee_type(
14831483
object_type=object_type,
14841484
)
14851485
proper_callee = get_proper_type(callee_type)
1486-
if isinstance(e.callee, NameExpr) and isinstance(e.callee.node, OverloadedFuncDef):
1487-
for item in e.callee.node.items:
1488-
if isinstance(item, Decorator) and (item.func.type == callee_type):
1489-
self.chk.check_deprecated(item.func, e)
1486+
if isinstance(e.callee, (NameExpr, MemberExpr)):
1487+
self.chk.warn_deprecated_overload_item(e.callee.node, e, target=callee_type)
14901488
if isinstance(e.callee, RefExpr) and isinstance(proper_callee, CallableType):
14911489
# Cache it for find_isinstance_check()
14921490
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, target=inferred_dunder_get_type, selftype=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: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,60 @@ C().g = "x" # E: function __main__.C.g is deprecated: use g2 instead \
503503
[builtins fixtures/property.pyi]
504504

505505

506+
[case testDeprecatedDescriptor]
507+
# flags: --enable-error-code=deprecated
508+
509+
from typing import Any, Optional, Union
510+
from typing_extensions import deprecated, overload
511+
512+
@deprecated("use E1 instead")
513+
class D1:
514+
def __get__(self, obj: Optional[C], objtype: Any) -> Union[D1, int]: ...
515+
516+
class D2:
517+
@deprecated("use E2.__get__ instead")
518+
def __get__(self, obj: Optional[C], objtype: Any) -> Union[D2, int]: ...
519+
520+
@deprecated("use E2.__set__ instead")
521+
def __set__(self, obj: C, value: int) -> None: ...
522+
523+
class D3:
524+
@overload
525+
@deprecated("use E3.__get__ instead")
526+
def __get__(self, obj: None, objtype: Any) -> D3: ...
527+
@overload
528+
@deprecated("use E3.__get__ instead")
529+
def __get__(self, obj: C, objtype: Any) -> int: ...
530+
def __get__(self, obj: Optional[C], objtype: Any) -> Union[D3, int]: ...
531+
532+
@overload
533+
def __set__(self, obj: C, value: int) -> None: ...
534+
@overload
535+
@deprecated("use E3.__set__ instead")
536+
def __set__(self, obj: C, value: str) -> None: ...
537+
def __set__(self, obj: C, value: Union[int, str]) -> None: ...
538+
539+
class C:
540+
d1 = D1() # E: class __main__.D1 is deprecated: use E1 instead
541+
d2 = D2()
542+
d3 = D3()
543+
544+
c: C
545+
C.d1
546+
c.d1
547+
c.d1 = 1
548+
549+
C.d2 # E: function __main__.D2.__get__ is deprecated: use E2.__get__ instead
550+
c.d2 # E: function __main__.D2.__get__ is deprecated: use E2.__get__ instead
551+
c.d2 = 1 # E: function __main__.D2.__set__ is deprecated: use E2.__set__ instead
552+
553+
C.d3 # E: overload def (self: __main__.D3, obj: None, objtype: Any) -> __main__.D3 of function __main__.D3.__get__ is deprecated: use E3.__get__ instead
554+
c.d3 # E: overload def (self: __main__.D3, obj: __main__.C, objtype: Any) -> builtins.int of function __main__.D3.__get__ is deprecated: use E3.__get__ instead
555+
c.d3 = 1
556+
c.d3 = "x" # E: overload def (self: __main__.D3, obj: __main__.C, value: builtins.str) of function __main__.D3.__set__ is deprecated: use E3.__set__ instead
557+
[builtins fixtures/property.pyi]
558+
559+
506560
[case testDeprecatedOverloadedFunction]
507561
# flags: --enable-error-code=deprecated
508562

@@ -556,3 +610,27 @@ h(1.0) # E: No overload variant of "h" matches argument type "float" \
556610
# N: def h(x: str) -> str
557611

558612
[builtins fixtures/tuple.pyi]
613+
614+
615+
[case testDeprecatedImportedOverloadedFunction]
616+
# flags: --enable-error-code=deprecated
617+
618+
import m
619+
620+
m.g
621+
m.g(1) # E: overload def (x: builtins.int) -> builtins.int of function m.g is deprecated: work with str instead
622+
m.g("x")
623+
624+
[file m.py]
625+
626+
from typing import Union
627+
from typing_extensions import deprecated, overload
628+
629+
@overload
630+
@deprecated("work with str instead")
631+
def g(x: int) -> int: ...
632+
@overload
633+
def g(x: str) -> str: ...
634+
def g(x: Union[int, str]) -> Union[int, str]: ...
635+
636+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy