Skip to content

PEP 702 (@deprecated): handle "combined" overloads #19626

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2719,6 +2719,7 @@ def check_overload_call(
# for example, when we have a fallback alternative that accepts an unrestricted
# typevar. See https://github.com/python/mypy/issues/4063 for related discussion.
erased_targets: list[CallableType] | None = None
inferred_types: list[Type] | None = None
unioned_result: tuple[Type, Type] | None = None

# Determine whether we need to encourage union math. This should be generally safe,
Expand Down Expand Up @@ -2746,13 +2747,14 @@ def check_overload_call(
# Record if we succeeded. Next we need to see if maybe normal procedure
# gives a narrower type.
if unioned_return:
returns, inferred_types = zip(*unioned_return)
returns = [u[0] for u in unioned_return]
inferred_types = [u[1] for u in unioned_return]
# Note that we use `combine_function_signatures` instead of just returning
# a union of inferred callables because for example a call
# Union[int -> int, str -> str](Union[int, str]) is invalid and
# we don't want to introduce internal inconsistencies.
unioned_result = (
make_simplified_union(list(returns), context.line, context.column),
make_simplified_union(returns, context.line, context.column),
self.combine_function_signatures(get_proper_types(inferred_types)),
)

Expand All @@ -2767,19 +2769,26 @@ def check_overload_call(
object_type,
context,
)
# If any of checks succeed, stop early.
# If any of checks succeed, perform deprecation tests and stop early.
if inferred_result is not None and unioned_result is not None:
# Both unioned and direct checks succeeded, choose the more precise type.
if (
is_subtype(inferred_result[0], unioned_result[0])
and not isinstance(get_proper_type(inferred_result[0]), AnyType)
and not none_type_var_overlap
):
return inferred_result
return unioned_result
elif unioned_result is not None:
unioned_result = None
else:
inferred_result = None
if unioned_result is not None:
if inferred_types is not None:
for inferred_type in inferred_types:
if isinstance(c := get_proper_type(inferred_type), CallableType):
self.chk.warn_deprecated(c.definition, context)
return unioned_result
elif inferred_result is not None:
if inferred_result is not None:
if isinstance(c := get_proper_type(inferred_result[1]), CallableType):
self.chk.warn_deprecated(c.definition, context)
return inferred_result

# Step 4: Failure. At this point, we know there is no match. We fall back to trying
Expand Down Expand Up @@ -2933,8 +2942,6 @@ def infer_overload_return_type(
# check for ambiguity due to 'Any' below.
if not args_contain_any:
self.chk.store_types(m)
if isinstance(infer_type, ProperType) and isinstance(infer_type, CallableType):
self.chk.warn_deprecated(infer_type.definition, context)
return ret_type, infer_type
p_infer_type = get_proper_type(infer_type)
if isinstance(p_infer_type, CallableType):
Expand Down Expand Up @@ -2971,11 +2978,6 @@ def infer_overload_return_type(
else:
# Success! No ambiguity; return the first match.
self.chk.store_types(type_maps[0])
inferred_callable = inferred_types[0]
if isinstance(inferred_callable, ProperType) and isinstance(
inferred_callable, CallableType
):
self.chk.warn_deprecated(inferred_callable.definition, context)
return return_types[0], inferred_types[0]

def overload_erased_call_targets(
Expand Down
76 changes: 73 additions & 3 deletions test-data/unit/check-deprecated.test
Original file line number Diff line number Diff line change
Expand Up @@ -671,9 +671,11 @@ C().g = "x" # E: function __main__.C.g is deprecated: use g2 instead \
[case testDeprecatedDescriptor]
# flags: --enable-error-code=deprecated

from typing import Any, Optional, Union, overload
from typing import Any, Generic, Optional, overload, TypeVar, Union
from typing_extensions import deprecated

T = TypeVar("T")

@deprecated("use E1 instead")
class D1:
def __get__(self, obj: Optional[C], objtype: Any) -> Union[D1, int]: ...
Expand Down Expand Up @@ -701,10 +703,19 @@ class D3:
def __set__(self, obj: C, value: str) -> None: ...
def __set__(self, obj: C, value: Union[int, str]) -> None: ...

class D4(Generic[T]):
@overload
def __get__(self, obj: None, objtype: Any) -> T: ...
@overload
@deprecated("deprecated instance access")
def __get__(self, obj: C, objtype: Any) -> T: ...
def __get__(self, obj: Optional[C], objtype: Any) -> T: ...

class C:
d1 = D1() # E: class __main__.D1 is deprecated: use E1 instead
d2 = D2()
d3 = D3()
d4 = D4[int]()

c: C
C.d1
Expand All @@ -719,15 +730,21 @@ C.d3 # E: overload def (self: __main__.D3, obj: None, objtype: Any) -> __main__
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
c.d3 = 1
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

C.d4
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
[builtins fixtures/property.pyi]


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

from typing import Union, overload
from typing import Any, overload, Union
from typing_extensions import deprecated

int_or_str: Union[int, str]
any: Any

@overload
def f(x: int) -> int: ...
@overload
Expand All @@ -738,6 +755,8 @@ def f(x: Union[int, str]) -> Union[int, str]: ...
f # E: function __main__.f is deprecated: use f2 instead
f(1) # E: function __main__.f is deprecated: use f2 instead
f("x") # E: function __main__.f is deprecated: use f2 instead
f(int_or_str) # E: function __main__.f is deprecated: use f2 instead
f(any) # E: function __main__.f is deprecated: use f2 instead
f(1.0) # E: function __main__.f is deprecated: use f2 instead \
# E: No overload variant of "f" matches argument type "float" \
# N: Possible overload variants: \
Expand All @@ -754,6 +773,8 @@ def g(x: Union[int, str]) -> Union[int, str]: ...
g
g(1) # E: overload def (x: builtins.int) -> builtins.int of function __main__.g is deprecated: work with str instead
g("x")
g(int_or_str) # E: overload def (x: builtins.int) -> builtins.int of function __main__.g is deprecated: work with str instead
g(any)
g(1.0) # E: No overload variant of "g" matches argument type "float" \
# N: Possible overload variants: \
# N: def g(x: int) -> int \
Expand All @@ -769,13 +790,62 @@ def h(x: Union[int, str]) -> Union[int, str]: ...
h
h(1)
h("x") # E: overload def (x: builtins.str) -> builtins.str of function __main__.h is deprecated: work with int instead
h(int_or_str) # E: overload def (x: builtins.str) -> builtins.str of function __main__.h is deprecated: work with int instead
h(any)
h(1.0) # E: No overload variant of "h" matches argument type "float" \
# N: Possible overload variants: \
# N: def h(x: int) -> int \
# N: def h(x: str) -> str

[builtins fixtures/tuple.pyi]
@overload
def i(x: int) -> int: ...
@overload
@deprecated("work with int instead")
def i(x: str) -> str: ...
@overload
def i(x: Any) -> Any: ...
def i(x: Union[int, str]) -> Union[int, str]: ...

i
i(1)
i("x") # E: overload def (x: builtins.str) -> builtins.str of function __main__.i is deprecated: work with int instead
i(int_or_str) # E: overload def (x: builtins.str) -> builtins.str of function __main__.i is deprecated: work with int instead
i(any)
i(1.0)

@overload
def j(x: int) -> int: ...
@overload
def j(x: str) -> str: ...
@overload
@deprecated("work with int or str instead")
def j(x: Any) -> Any: ...
def j(x: Union[int, str]) -> Union[int, str]: ...

j
j(1)
j("x")
j(int_or_str)
j(any)
j(1.0) # E: overload def (x: Any) -> Any of function __main__.j is deprecated: work with int or str instead

@overload
@deprecated("work with str instead")
def k(x: int) -> int: ...
@overload
def k(x: str) -> str: ...
@overload
@deprecated("work with str instead")
def k(x: object) -> Any: ...
def k(x: object) -> Union[int, str]: ...

k
k(1) # E: overload def (x: builtins.int) -> builtins.int of function __main__.k is deprecated: work with str instead
k("x")
k(int_or_str) # E: overload def (x: builtins.int) -> builtins.int of function __main__.k is deprecated: work with str instead
k(any)
k(1.0) # E: overload def (x: builtins.object) -> Any of function __main__.k is deprecated: work with str instead
[builtins fixtures/tuple.pyi]

[case testDeprecatedImportedOverloadedFunction]
# flags: --enable-error-code=deprecated
Expand Down
Loading