diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6e0915179f90..54d3d573403f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -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, @@ -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)), ) @@ -2767,7 +2769,7 @@ 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 ( @@ -2775,11 +2777,18 @@ def check_overload_call( 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 @@ -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): @@ -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( diff --git a/test-data/unit/check-deprecated.test b/test-data/unit/check-deprecated.test index e1173ac425ba..607e9d767956 100644 --- a/test-data/unit/check-deprecated.test +++ b/test-data/unit/check-deprecated.test @@ -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]: ... @@ -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 @@ -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 @@ -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: \ @@ -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 \ @@ -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