Skip to content

Commit a07abb6

Browse files
authored
PEP 702 (@deprecated): handle "combined" overloads (#19626)
This change is taken from #18682. The new code and the tests are unmodified. I only had to remove two now unnecessary calls of `warn_deprecated` which were introduced after opening #18682.
1 parent 94eb6b7 commit a07abb6

File tree

2 files changed

+89
-17
lines changed

2 files changed

+89
-17
lines changed

mypy/checkexpr.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2722,6 +2722,7 @@ def check_overload_call(
27222722
# for example, when we have a fallback alternative that accepts an unrestricted
27232723
# typevar. See https://github.com/python/mypy/issues/4063 for related discussion.
27242724
erased_targets: list[CallableType] | None = None
2725+
inferred_types: list[Type] | None = None
27252726
unioned_result: tuple[Type, Type] | None = None
27262727

27272728
# Determine whether we need to encourage union math. This should be generally safe,
@@ -2749,13 +2750,14 @@ def check_overload_call(
27492750
# Record if we succeeded. Next we need to see if maybe normal procedure
27502751
# gives a narrower type.
27512752
if unioned_return:
2752-
returns, inferred_types = zip(*unioned_return)
2753+
returns = [u[0] for u in unioned_return]
2754+
inferred_types = [u[1] for u in unioned_return]
27532755
# Note that we use `combine_function_signatures` instead of just returning
27542756
# a union of inferred callables because for example a call
27552757
# Union[int -> int, str -> str](Union[int, str]) is invalid and
27562758
# we don't want to introduce internal inconsistencies.
27572759
unioned_result = (
2758-
make_simplified_union(list(returns), context.line, context.column),
2760+
make_simplified_union(returns, context.line, context.column),
27592761
self.combine_function_signatures(get_proper_types(inferred_types)),
27602762
)
27612763

@@ -2770,19 +2772,26 @@ def check_overload_call(
27702772
object_type,
27712773
context,
27722774
)
2773-
# If any of checks succeed, stop early.
2775+
# If any of checks succeed, perform deprecation tests and stop early.
27742776
if inferred_result is not None and unioned_result is not None:
27752777
# Both unioned and direct checks succeeded, choose the more precise type.
27762778
if (
27772779
is_subtype(inferred_result[0], unioned_result[0])
27782780
and not isinstance(get_proper_type(inferred_result[0]), AnyType)
27792781
and not none_type_var_overlap
27802782
):
2781-
return inferred_result
2782-
return unioned_result
2783-
elif unioned_result is not None:
2783+
unioned_result = None
2784+
else:
2785+
inferred_result = None
2786+
if unioned_result is not None:
2787+
if inferred_types is not None:
2788+
for inferred_type in inferred_types:
2789+
if isinstance(c := get_proper_type(inferred_type), CallableType):
2790+
self.chk.warn_deprecated(c.definition, context)
27842791
return unioned_result
2785-
elif inferred_result is not None:
2792+
if inferred_result is not None:
2793+
if isinstance(c := get_proper_type(inferred_result[1]), CallableType):
2794+
self.chk.warn_deprecated(c.definition, context)
27862795
return inferred_result
27872796

27882797
# Step 4: Failure. At this point, we know there is no match. We fall back to trying
@@ -2936,8 +2945,6 @@ def infer_overload_return_type(
29362945
# check for ambiguity due to 'Any' below.
29372946
if not args_contain_any:
29382947
self.chk.store_types(m)
2939-
if isinstance(infer_type, ProperType) and isinstance(infer_type, CallableType):
2940-
self.chk.warn_deprecated(infer_type.definition, context)
29412948
return ret_type, infer_type
29422949
p_infer_type = get_proper_type(infer_type)
29432950
if isinstance(p_infer_type, CallableType):
@@ -2974,11 +2981,6 @@ def infer_overload_return_type(
29742981
else:
29752982
# Success! No ambiguity; return the first match.
29762983
self.chk.store_types(type_maps[0])
2977-
inferred_callable = inferred_types[0]
2978-
if isinstance(inferred_callable, ProperType) and isinstance(
2979-
inferred_callable, CallableType
2980-
):
2981-
self.chk.warn_deprecated(inferred_callable.definition, context)
29822984
return return_types[0], inferred_types[0]
29832985

29842986
def overload_erased_call_targets(

test-data/unit/check-deprecated.test

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -671,9 +671,11 @@ C().g = "x" # E: function __main__.C.g is deprecated: use g2 instead \
671671
[case testDeprecatedDescriptor]
672672
# flags: --enable-error-code=deprecated
673673

674-
from typing import Any, Optional, Union, overload
674+
from typing import Any, Generic, Optional, overload, TypeVar, Union
675675
from typing_extensions import deprecated
676676

677+
T = TypeVar("T")
678+
677679
@deprecated("use E1 instead")
678680
class D1:
679681
def __get__(self, obj: Optional[C], objtype: Any) -> Union[D1, int]: ...
@@ -701,10 +703,19 @@ class D3:
701703
def __set__(self, obj: C, value: str) -> None: ...
702704
def __set__(self, obj: C, value: Union[int, str]) -> None: ...
703705

706+
class D4(Generic[T]):
707+
@overload
708+
def __get__(self, obj: None, objtype: Any) -> T: ...
709+
@overload
710+
@deprecated("deprecated instance access")
711+
def __get__(self, obj: C, objtype: Any) -> T: ...
712+
def __get__(self, obj: Optional[C], objtype: Any) -> T: ...
713+
704714
class C:
705715
d1 = D1() # E: class __main__.D1 is deprecated: use E1 instead
706716
d2 = D2()
707717
d3 = D3()
718+
d4 = D4[int]()
708719

709720
c: C
710721
C.d1
@@ -719,15 +730,21 @@ C.d3 # E: overload def (self: __main__.D3, obj: None, objtype: Any) -> __main__
719730
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
720731
c.d3 = 1
721732
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
733+
734+
C.d4
735+
c.d4 # E: overload def (self: __main__.D4[T`1], obj: __main__.C, objtype: Any) -> T`1 of function __main__.D4.__get__ is deprecated: deprecated instance access
722736
[builtins fixtures/property.pyi]
723737

724738

725739
[case testDeprecatedOverloadedFunction]
726740
# flags: --enable-error-code=deprecated
727741

728-
from typing import Union, overload
742+
from typing import Any, overload, Union
729743
from typing_extensions import deprecated
730744

745+
int_or_str: Union[int, str]
746+
any: Any
747+
731748
@overload
732749
def f(x: int) -> int: ...
733750
@overload
@@ -738,6 +755,8 @@ def f(x: Union[int, str]) -> Union[int, str]: ...
738755
f # E: function __main__.f is deprecated: use f2 instead
739756
f(1) # E: function __main__.f is deprecated: use f2 instead
740757
f("x") # E: function __main__.f is deprecated: use f2 instead
758+
f(int_or_str) # E: function __main__.f is deprecated: use f2 instead
759+
f(any) # E: function __main__.f is deprecated: use f2 instead
741760
f(1.0) # E: function __main__.f is deprecated: use f2 instead \
742761
# E: No overload variant of "f" matches argument type "float" \
743762
# N: Possible overload variants: \
@@ -754,6 +773,8 @@ def g(x: Union[int, str]) -> Union[int, str]: ...
754773
g
755774
g(1) # E: overload def (x: builtins.int) -> builtins.int of function __main__.g is deprecated: work with str instead
756775
g("x")
776+
g(int_or_str) # E: overload def (x: builtins.int) -> builtins.int of function __main__.g is deprecated: work with str instead
777+
g(any)
757778
g(1.0) # E: No overload variant of "g" matches argument type "float" \
758779
# N: Possible overload variants: \
759780
# N: def g(x: int) -> int \
@@ -769,13 +790,62 @@ def h(x: Union[int, str]) -> Union[int, str]: ...
769790
h
770791
h(1)
771792
h("x") # E: overload def (x: builtins.str) -> builtins.str of function __main__.h is deprecated: work with int instead
793+
h(int_or_str) # E: overload def (x: builtins.str) -> builtins.str of function __main__.h is deprecated: work with int instead
794+
h(any)
772795
h(1.0) # E: No overload variant of "h" matches argument type "float" \
773796
# N: Possible overload variants: \
774797
# N: def h(x: int) -> int \
775798
# N: def h(x: str) -> str
776799

777-
[builtins fixtures/tuple.pyi]
800+
@overload
801+
def i(x: int) -> int: ...
802+
@overload
803+
@deprecated("work with int instead")
804+
def i(x: str) -> str: ...
805+
@overload
806+
def i(x: Any) -> Any: ...
807+
def i(x: Union[int, str]) -> Union[int, str]: ...
778808

809+
i
810+
i(1)
811+
i("x") # E: overload def (x: builtins.str) -> builtins.str of function __main__.i is deprecated: work with int instead
812+
i(int_or_str) # E: overload def (x: builtins.str) -> builtins.str of function __main__.i is deprecated: work with int instead
813+
i(any)
814+
i(1.0)
815+
816+
@overload
817+
def j(x: int) -> int: ...
818+
@overload
819+
def j(x: str) -> str: ...
820+
@overload
821+
@deprecated("work with int or str instead")
822+
def j(x: Any) -> Any: ...
823+
def j(x: Union[int, str]) -> Union[int, str]: ...
824+
825+
j
826+
j(1)
827+
j("x")
828+
j(int_or_str)
829+
j(any)
830+
j(1.0) # E: overload def (x: Any) -> Any of function __main__.j is deprecated: work with int or str instead
831+
832+
@overload
833+
@deprecated("work with str instead")
834+
def k(x: int) -> int: ...
835+
@overload
836+
def k(x: str) -> str: ...
837+
@overload
838+
@deprecated("work with str instead")
839+
def k(x: object) -> Any: ...
840+
def k(x: object) -> Union[int, str]: ...
841+
842+
k
843+
k(1) # E: overload def (x: builtins.int) -> builtins.int of function __main__.k is deprecated: work with str instead
844+
k("x")
845+
k(int_or_str) # E: overload def (x: builtins.int) -> builtins.int of function __main__.k is deprecated: work with str instead
846+
k(any)
847+
k(1.0) # E: overload def (x: builtins.object) -> Any of function __main__.k is deprecated: work with str instead
848+
[builtins fixtures/tuple.pyi]
779849

780850
[case testDeprecatedImportedOverloadedFunction]
781851
# flags: --enable-error-code=deprecated

0 commit comments

Comments
 (0)