From 5c880df17b81f63c99376c6a3da86eee696fb29e Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Wed, 6 Aug 2025 07:05:05 +0200 Subject: [PATCH 1/5] add tests (taken from #18682) --- test-data/unit/check-deprecated.test | 76 ++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 3 deletions(-) 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 From b2f50ff8c91161a567a24104c6d6be965eda312d Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Sat, 9 Aug 2025 09:57:40 +0200 Subject: [PATCH 2/5] add code (taken from #18682) --- mypy/checkexpr.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6e0915179f90..a783d048cad6 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2746,7 +2746,8 @@ 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 = tuple(u[0] for u in unioned_return) + inferred_types = tuple(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 @@ -2767,7 +2768,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 +2776,17 @@ 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: + 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 From f7b689c1bc5b91eca7be5edbab88ab991ae52f41 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Sat, 9 Aug 2025 10:00:20 +0200 Subject: [PATCH 3/5] remove now unnecessary code added in the meantime --- mypy/checkexpr.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index a783d048cad6..82f09da0ffc6 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2940,8 +2940,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): @@ -2978,11 +2976,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( From 16c768fc29005a4782477935a54f63e9a08fba76 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Sun, 10 Aug 2025 08:36:52 +0200 Subject: [PATCH 4/5] turn `inferred_types` and `results` into lists --- mypy/checkexpr.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 82f09da0ffc6..4d9ffafb065e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2746,14 +2746,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 = tuple(u[0] for u in unioned_return) - inferred_types = tuple(u[1] for u in 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)), ) From ee9bd9da973cea056b10a4748c4d60d8d2c89b08 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Sun, 10 Aug 2025 08:43:00 +0200 Subject: [PATCH 5/5] make 101 % sure that `inferred_types` is defined before used --- mypy/checkexpr.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4d9ffafb065e..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, @@ -2780,9 +2781,10 @@ def check_overload_call( else: inferred_result = None if unioned_result 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) + 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 if inferred_result is not None: if isinstance(c := get_proper_type(inferred_result[1]), CallableType):