From 0c3bd81cca9f06d57b2e8d01f3cc2e0b23212644 Mon Sep 17 00:00:00 2001
From: Christoph Tyralla <c.tyralla@bjoernsen.de>
Date: Sat, 15 Feb 2025 12:05:21 +0100
Subject: [PATCH 1/6] PEP 702 (@deprecated): improve the handling of overloaded
 functions and methods

---
 mypy/checker.py                      |  18 --
 mypy/checkexpr.py                    |  56 ++---
 mypy/checkmember.py                  |  11 +-
 mypy/server/astdiff.py               |  12 +-
 test-data/unit/check-deprecated.test |  75 ++-----
 test-data/unit/fine-grained.test     | 307 +++++++++++++++++++++++++++
 6 files changed, 368 insertions(+), 111 deletions(-)

diff --git a/mypy/checker.py b/mypy/checker.py
index 04a286beef5e..26371e27754f 100644
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -4648,9 +4648,6 @@ def check_member_assignment(
 
         # Search for possible deprecations:
         mx.chk.check_deprecated(dunder_set, mx.context)
-        mx.chk.warn_deprecated_overload_item(
-            dunder_set, mx.context, target=inferred_dunder_set_type, selftype=attribute_type
-        )
 
         # In the following cases, a message already will have been recorded in check_call.
         if (not isinstance(inferred_dunder_set_type, CallableType)) or (
@@ -7894,21 +7891,6 @@ def warn_deprecated(self, node: Node | None, context: Context) -> None:
             warn = self.msg.note if self.options.report_deprecated_as_note else self.msg.fail
             warn(deprecated, context, code=codes.DEPRECATED)
 
-    def warn_deprecated_overload_item(
-        self, node: Node | None, context: Context, *, target: Type, selftype: Type | None = None
-    ) -> None:
-        """Warn if the overload item corresponding to the given callable is deprecated."""
-        target = get_proper_type(target)
-        if isinstance(node, OverloadedFuncDef) and isinstance(target, CallableType):
-            for item in node.items:
-                if isinstance(item, Decorator) and isinstance(
-                    candidate := item.func.type, CallableType
-                ):
-                    if selftype is not None and not node.is_static:
-                        candidate = bind_self(candidate, selftype)
-                    if candidate == target:
-                        self.warn_deprecated(item.func, context)
-
     # leafs
 
     def visit_pass_stmt(self, o: PassStmt, /) -> None:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index 4078d447dab8..0e7cbb701345 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -1476,15 +1476,6 @@ def check_call_expr_with_callee_type(
             object_type=object_type,
         )
         proper_callee = get_proper_type(callee_type)
-        if isinstance(e.callee, (NameExpr, MemberExpr)):
-            node = e.callee.node
-            if node is None and member is not None and isinstance(object_type, Instance):
-                if (symbol := object_type.type.get(member)) is not None:
-                    node = symbol.node
-            self.chk.check_deprecated(node, e)
-            self.chk.warn_deprecated_overload_item(
-                node, e, target=callee_type, selftype=object_type
-            )
         if isinstance(e.callee, RefExpr) and isinstance(proper_callee, CallableType):
             # Cache it for find_isinstance_check()
             if proper_callee.type_guard is not None:
@@ -2652,6 +2643,25 @@ def check_overload_call(
         context: Context,
     ) -> tuple[Type, Type]:
         """Checks a call to an overloaded function."""
+
+        # The following hack tries to update the `definition` attribute of the given callable
+        # type's items with the related decorator symbols to allow checking for deprecations:
+        funcdef = None
+        if callable_name is not None:
+            if isinstance(inst := get_proper_type(object_type), Instance):
+                if (sym := inst.type.get(callable_name.rpartition(".")[-1])) is not None:
+                    funcdef = sym.node
+            else:
+                name_module, _, name = callable_name.rpartition(".")
+                if (
+                    (module := self.chk.modules.get(name_module)) is not None
+                    and (sym := module.names.get(name)) is not None
+                ):
+                    funcdef = sym.node
+        if isinstance(funcdef, OverloadedFuncDef):
+            for typ, defn in zip(callee.items, funcdef.items):
+                typ.definition = defn
+
         # Normalize unpacked kwargs before checking the call.
         callee = callee.with_unpacked_kwargs()
         arg_types = self.infer_arg_types_in_empty_context(args)
@@ -2714,7 +2724,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 (
@@ -2722,11 +2732,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
@@ -4077,21 +4093,11 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
         results = []
         for name, method, obj, arg in variants:
             with self.msg.filter_errors(save_filtered_errors=True) as local_errors:
-                result = self.check_method_call(op_name, obj, method, [arg], [ARG_POS], context)
+                result = self.check_method_call(name, obj, method, [arg], [ARG_POS], context)
             if local_errors.has_new_errors():
                 errors.append(local_errors.filtered_errors())
                 results.append(result)
             else:
-                if isinstance(obj, Instance) and isinstance(
-                    defn := obj.type.get_method(name), OverloadedFuncDef
-                ):
-                    for item in defn.items:
-                        if (
-                            isinstance(item, Decorator)
-                            and isinstance(typ := item.func.type, CallableType)
-                            and bind_self(typ) == result[1]
-                        ):
-                            self.chk.check_deprecated(item.func, context)
                 return result
 
         # We finish invoking above operators and no early return happens. Therefore,
diff --git a/mypy/checkmember.py b/mypy/checkmember.py
index 0994d0df400b..493f87403726 100644
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -7,6 +7,7 @@
 
 from mypy import message_registry, subtypes
 from mypy.erasetype import erase_typevars
+import mypy.errorcodes as codes
 from mypy.expandtype import (
     expand_self_type,
     expand_type_by_instance,
@@ -701,6 +702,10 @@ def analyze_descriptor_access(
         object_type=descriptor_type,
     )
 
+    deprecated_disabled = False
+    if assignment and codes.DEPRECATED in mx.chk.options.enabled_error_codes:
+        mx.chk.options.enabled_error_codes.remove(codes.DEPRECATED)
+        deprecated_disabled = True
     _, inferred_dunder_get_type = mx.chk.expr_checker.check_call(
         dunder_get_type,
         [
@@ -712,12 +717,10 @@ def analyze_descriptor_access(
         object_type=descriptor_type,
         callable_name=callable_name,
     )
-
+    if deprecated_disabled:
+        mx.chk.options.enabled_error_codes.add(codes.DEPRECATED)
     if not assignment:
         mx.chk.check_deprecated(dunder_get, mx.context)
-        mx.chk.warn_deprecated_overload_item(
-            dunder_get, mx.context, target=inferred_dunder_get_type, selftype=descriptor_type
-        )
 
     inferred_dunder_get_type = get_proper_type(inferred_dunder_get_type)
     if isinstance(inferred_dunder_get_type, AnyType):
diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py
index 1b0cc218ed16..a183d67dd52d 100644
--- a/mypy/server/astdiff.py
+++ b/mypy/server/astdiff.py
@@ -252,6 +252,16 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb
                 setter_type = snapshot_optional_type(first_item.var.setter_type)
         is_trivial_body = impl.is_trivial_body if impl else False
         dataclass_transform_spec = find_dataclass_transform_spec(node)
+
+        deprecated = None
+        if isinstance(node, FuncDef):
+            deprecated = node.deprecated
+        elif isinstance(node, OverloadedFuncDef):
+            deprecated_list = [node.deprecated] + [i.func.deprecated for i in node.items]
+            deprecated_list_cleaned = [d for d in deprecated_list if d is not None]
+            if deprecated_list_cleaned:
+                deprecated = ",".join(deprecated_list_cleaned)
+
         return (
             "Func",
             common,
@@ -262,7 +272,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb
             signature,
             is_trivial_body,
             dataclass_transform_spec.serialize() if dataclass_transform_spec is not None else None,
-            node.deprecated if isinstance(node, FuncDef) else None,
+            deprecated,
             setter_type,  # multi-part properties are stored as OverloadedFuncDef
         )
     elif isinstance(node, Var):
diff --git a/test-data/unit/check-deprecated.test b/test-data/unit/check-deprecated.test
index 6cc160fad81f..a6f4c8d24a0f 100644
--- a/test-data/unit/check-deprecated.test
+++ b/test-data/unit/check-deprecated.test
@@ -429,8 +429,8 @@ b.h("x")  # E: function __main__.A.h is deprecated: use `h2` instead
 [case testDeprecatedOverloadedClassMethods]
 # flags: --enable-error-code=deprecated
 
-from typing import Iterator, Union, overload
-from typing_extensions import deprecated
+from typing import Iterator, Union
+from typing_extensions import deprecated, overload
 
 class A:
     @overload
@@ -487,8 +487,8 @@ b.h("x")  # E: function __main__.A.h is deprecated: use `h2`  instead
 [case testDeprecatedOverloadedStaticMethods]
 # flags: --enable-error-code=deprecated
 
-from typing import Iterator, Union, overload
-from typing_extensions import deprecated
+from typing import Iterator, Union
+from typing_extensions import deprecated, overload
 
 class A:
     @overload
@@ -545,8 +545,8 @@ b.h("x")  # E: function __main__.A.h is deprecated: use `h2`  instead
 [case testDeprecatedOverloadedSpecialMethods]
 # flags: --enable-error-code=deprecated
 
-from typing import Iterator, Union, overload
-from typing_extensions import deprecated
+from typing import Iterator, Union
+from typing_extensions import deprecated, overload
 
 class A:
     @overload
@@ -671,8 +671,8 @@ 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_extensions import deprecated
+from typing import Any, Optional, Union
+from typing_extensions import deprecated, overload
 
 @deprecated("use E1 instead")
 class D1:
@@ -725,8 +725,8 @@ c.d3 = "x"  # E: overload def (self: __main__.D3, obj: __main__.C, value: builti
 [case testDeprecatedOverloadedFunction]
 # flags: --enable-error-code=deprecated
 
-from typing import Union, overload
-from typing_extensions import deprecated
+from typing import Union
+from typing_extensions import deprecated, overload
 
 @overload
 def f(x: int) -> int: ...
@@ -788,8 +788,8 @@ m.g("x")
 
 [file m.py]
 
-from typing import Union, overload
-from typing_extensions import deprecated
+from typing import Union
+from typing_extensions import deprecated, overload
 
 @overload
 @deprecated("work with str instead")
@@ -797,56 +797,5 @@ def g(x: int) -> int: ...
 @overload
 def g(x: str) -> str: ...
 def g(x: Union[int, str]) -> Union[int, str]: ...
-[builtins fixtures/tuple.pyi]
-
-[case testDeprecatedExclude]
-# flags: --enable-error-code=deprecated --deprecated-calls-exclude=m.C --deprecated-calls-exclude=m.D --deprecated-calls-exclude=m.E.f --deprecated-calls-exclude=m.E.g --deprecated-calls-exclude=m.E.__add__
-from m import C, D, E
-
-[file m.py]
-from typing import Union, overload
-from typing_extensions import deprecated
-
-@deprecated("use C2 instead")
-class C:
-    def __init__(self) -> None: ...
-
-c: C
-C()
-C.__init__(c)
-
-class D:
-    @deprecated("use D.g instead")
-    def f(self) -> None: ...
-
-    def g(self) -> None: ...
-
-D.f
-D().f
-D().f()
-
-class E:
-    @overload
-    def f(self, x: int) -> int: ...
-    @overload
-    def f(self, x: str) -> str: ...
-    @deprecated("use E.f2 instead")
-    def f(self, x: Union[int, str]) -> Union[int, str]: ...
-
-    @deprecated("use E.h instead")
-    def g(self) -> None: ...
-
-    @overload
-    @deprecated("no A + int")
-    def __add__(self, v: int) -> None: ...
-    @overload
-    def __add__(self, v: str) -> None: ...
-    def __add__(self, v: Union[int, str]) -> None: ...
-
-E().f(1)
-E().f("x")
 
-e = E()
-e.g()
-e + 1
 [builtins fixtures/tuple.pyi]
diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test
index d2b1a8a92b80..f8e8dcca31ef 100644
--- a/test-data/unit/fine-grained.test
+++ b/test-data/unit/fine-grained.test
@@ -11003,6 +11003,313 @@ b.py:1: error: class a.C is deprecated: use C2 instead
 b.py:2: error: class a.D is deprecated: use D2 instead
 
 
+[case testDeprecatedAddKeepChangeAndRemoveOverloadedFunctionDeprecation]
+# flags: --enable-error-code=deprecated
+
+from a import f
+f(1)
+f("y")
+import a
+a.f(1)
+a.f("y")
+
+[file a.py]
+from typing import overload, Union
+@overload
+def f(x: int) -> int: ...
+@overload
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[file a.py.2]
+from typing import overload, Union
+from typing_extensions import deprecated
+@overload
+def f(x: int) -> int: ...
+@overload
+@deprecated("pass int")
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[file a.py.3]
+from typing import overload, Union
+from typing_extensions import deprecated
+@overload
+def f(x: int) -> int: ...
+@overload
+@deprecated("pass int")
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[file a.py.4]
+from typing import overload, Union
+from typing_extensions import deprecated
+@overload
+def f(x: int) -> int: ...
+@overload
+@deprecated("pass int, please")
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[file a.py.5]
+from typing import overload, Union
+@overload
+def f(x: int) -> int: ...
+@overload
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[builtins fixtures/tuple.pyi]
+[out]
+==
+main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+==
+main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+==
+main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int, please
+main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int, please
+==
+
+
+[case testDeprecatedRemoveOverloadedFunctionDeprecation]
+# flags: --enable-error-code=deprecated
+
+from a import f
+f(1)
+f("y")
+import a
+a.f(1)
+a.f("y")
+
+[file a.py]
+from typing import overload, Union
+from typing_extensions import deprecated
+@overload
+def f(x: int) -> int: ...
+@overload
+@deprecated("pass int")
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[file a.py.2]
+from typing import overload, Union
+@overload
+def f(x: int) -> int: ...
+@overload
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[builtins fixtures/tuple.pyi]
+[out]
+main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+==
+
+
+[case testDeprecatedKeepOverloadedFunctionDeprecation]
+# flags: --enable-error-code=deprecated
+
+from a import f
+f(1)
+f("y")
+import a
+a.f(1)
+a.f("y")
+
+[file a.py]
+from typing import overload, Union
+from typing_extensions import deprecated
+@overload
+def f(x: int) -> int: ...
+@overload
+@deprecated("pass int")
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[file a.py.2]
+from typing import overload, Union
+from typing_extensions import deprecated
+@overload
+def f(x: int) -> int: ...
+@overload
+@deprecated("pass int")
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[builtins fixtures/tuple.pyi]
+[out]
+main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+==
+main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+
+
+[case testDeprecatedAddOverloadedFunctionDeprecationIndirectImport]
+# flags: --enable-error-code=deprecated
+
+from b import f
+f(1)
+f("y")
+import b
+b.f(1)
+b.f("y")
+
+[file b.py]
+from a import f
+
+[file a.py]
+from typing import overload, Union
+@overload
+def f(x: int) -> int: ...
+@overload
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[file a.py.2]
+from typing import overload, Union
+from typing_extensions import deprecated
+@overload
+def f(x: int) -> int: ...
+@overload
+@deprecated("pass int")
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[builtins fixtures/tuple.pyi]
+[out]
+==
+main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+
+
+[case testDeprecatedChangeOverloadedFunctionDeprecationIndirectImport]
+# flags: --enable-error-code=deprecated
+
+from b import f
+f(1)
+f("y")
+import b
+b.f(1)
+b.f("y")
+
+[file b.py]
+from a import f
+
+[file a.py]
+from typing import overload, Union
+from typing_extensions import deprecated
+@overload
+def f(x: int) -> int: ...
+@overload
+@deprecated("pass int")
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[file a.py.2]
+from typing import overload, Union
+from typing_extensions import deprecated
+@overload
+def f(x: int) -> int: ...
+@overload
+@deprecated("pass int, please")
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[builtins fixtures/tuple.pyi]
+[out]
+main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+==
+main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int, please
+main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int, please
+
+
+[case testDeprecatedRemoveOverloadedFunctionDeprecationIndirectImport]
+# flags: --enable-error-code=deprecated
+
+from b import f
+f(1)
+f("y")
+import b
+b.f(1)
+b.f("y")
+
+[file b.py]
+from a import f
+
+[file a.py]
+from typing import overload, Union
+from typing_extensions import deprecated
+@overload
+def f(x: int) -> int: ...
+@overload
+@deprecated("pass int")
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[file a.py.2]
+from typing import overload, Union
+@overload
+def f(x: int) -> int: ...
+@overload
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[builtins fixtures/tuple.pyi]
+[out]
+main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int
+==
+
+
+[case testDeprecatedOverloadedFunctionAlreadyDecorated]
+# flags: --enable-error-code=deprecated
+
+from b import f
+f(1)
+f("y")
+import b
+b.f(1)
+b.f("y")
+
+[file b.py]
+from a import f
+
+[file a.py]
+from typing import Callable, overload, Union
+
+def d(t: Callable[[str], str]) -> Callable[[str], str]: ...
+
+@overload
+def f(x: int) -> int: ...
+@overload
+@d
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[file a.py.2]
+from typing import Callable, overload, Union
+from typing_extensions import deprecated
+
+def d(t: Callable[[str], str]) -> Callable[[str], str]: ...
+
+@overload
+def f(x: int) -> int: ...
+@overload
+@deprecated("deprecated decorated overload")
+@d
+def f(x: str) -> str: ...
+def f(x: Union[int, str]) -> Union[int, str]: ...
+
+[builtins fixtures/tuple.pyi]
+[out]
+==
+main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: deprecated decorated overload
+main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: deprecated decorated overload
+
 [case testDeprecatedChangeClassDeprecationIndirectImport]
 # flags: --enable-error-code=deprecated
 from b import C

From bd9d1a91a8667d8c9fb634f3d87bff247eeaa117 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Sat, 15 Feb 2025 11:13:26 +0000
Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
---
 mypy/checkexpr.py   | 8 +++-----
 mypy/checkmember.py | 2 +-
 2 files changed, 4 insertions(+), 6 deletions(-)

diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index 0e7cbb701345..f05507ea8f98 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -132,7 +132,6 @@
     validate_instance,
 )
 from mypy.typeops import (
-    bind_self,
     callable_type,
     custom_special_method,
     erase_to_union_or_bound,
@@ -2653,10 +2652,9 @@ def check_overload_call(
                     funcdef = sym.node
             else:
                 name_module, _, name = callable_name.rpartition(".")
-                if (
-                    (module := self.chk.modules.get(name_module)) is not None
-                    and (sym := module.names.get(name)) is not None
-                ):
+                if (module := self.chk.modules.get(name_module)) is not None and (
+                    sym := module.names.get(name)
+                ) is not None:
                     funcdef = sym.node
         if isinstance(funcdef, OverloadedFuncDef):
             for typ, defn in zip(callee.items, funcdef.items):
diff --git a/mypy/checkmember.py b/mypy/checkmember.py
index 493f87403726..759ed077ac46 100644
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -5,9 +5,9 @@
 from collections.abc import Sequence
 from typing import TYPE_CHECKING, Callable, cast
 
+import mypy.errorcodes as codes
 from mypy import message_registry, subtypes
 from mypy.erasetype import erase_typevars
-import mypy.errorcodes as codes
 from mypy.expandtype import (
     expand_self_type,
     expand_type_by_instance,

From 07fca5417af6c14ce42d848e9e68935aea78f108 Mon Sep 17 00:00:00 2001
From: Christoph Tyralla <c.tyralla@bjoernsen.de>
Date: Sat, 15 Feb 2025 22:23:50 +0100
Subject: [PATCH 3/6] fix Mypyc build

---
 mypy/server/astdiff.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py
index a183d67dd52d..82b521405515 100644
--- a/mypy/server/astdiff.py
+++ b/mypy/server/astdiff.py
@@ -257,7 +257,9 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb
         if isinstance(node, FuncDef):
             deprecated = node.deprecated
         elif isinstance(node, OverloadedFuncDef):
-            deprecated_list = [node.deprecated] + [i.func.deprecated for i in node.items]
+            deprecated_list = [node.deprecated] + [
+                i.func.deprecated for i in node.items if isinstance(i, Decorator)
+            ]
             deprecated_list_cleaned = [d for d in deprecated_list if d is not None]
             if deprecated_list_cleaned:
                 deprecated = ",".join(deprecated_list_cleaned)

From 6528fc51003007ccd0c87eec18ebb5cf531d633a Mon Sep 17 00:00:00 2001
From: Christoph Tyralla <c.tyralla@bjoernsen.de>
Date: Sun, 16 Feb 2025 18:56:55 +0100
Subject: [PATCH 4/6] restore some test (test changes) that I must have removed
 accidentally

---
 test-data/unit/check-deprecated.test | 75 +++++++++++++++++++++++-----
 1 file changed, 63 insertions(+), 12 deletions(-)

diff --git a/test-data/unit/check-deprecated.test b/test-data/unit/check-deprecated.test
index a6f4c8d24a0f..6cc160fad81f 100644
--- a/test-data/unit/check-deprecated.test
+++ b/test-data/unit/check-deprecated.test
@@ -429,8 +429,8 @@ b.h("x")  # E: function __main__.A.h is deprecated: use `h2` instead
 [case testDeprecatedOverloadedClassMethods]
 # flags: --enable-error-code=deprecated
 
-from typing import Iterator, Union
-from typing_extensions import deprecated, overload
+from typing import Iterator, Union, overload
+from typing_extensions import deprecated
 
 class A:
     @overload
@@ -487,8 +487,8 @@ b.h("x")  # E: function __main__.A.h is deprecated: use `h2`  instead
 [case testDeprecatedOverloadedStaticMethods]
 # flags: --enable-error-code=deprecated
 
-from typing import Iterator, Union
-from typing_extensions import deprecated, overload
+from typing import Iterator, Union, overload
+from typing_extensions import deprecated
 
 class A:
     @overload
@@ -545,8 +545,8 @@ b.h("x")  # E: function __main__.A.h is deprecated: use `h2`  instead
 [case testDeprecatedOverloadedSpecialMethods]
 # flags: --enable-error-code=deprecated
 
-from typing import Iterator, Union
-from typing_extensions import deprecated, overload
+from typing import Iterator, Union, overload
+from typing_extensions import deprecated
 
 class A:
     @overload
@@ -671,8 +671,8 @@ 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
-from typing_extensions import deprecated, overload
+from typing import Any, Optional, Union, overload
+from typing_extensions import deprecated
 
 @deprecated("use E1 instead")
 class D1:
@@ -725,8 +725,8 @@ c.d3 = "x"  # E: overload def (self: __main__.D3, obj: __main__.C, value: builti
 [case testDeprecatedOverloadedFunction]
 # flags: --enable-error-code=deprecated
 
-from typing import Union
-from typing_extensions import deprecated, overload
+from typing import Union, overload
+from typing_extensions import deprecated
 
 @overload
 def f(x: int) -> int: ...
@@ -788,8 +788,8 @@ m.g("x")
 
 [file m.py]
 
-from typing import Union
-from typing_extensions import deprecated, overload
+from typing import Union, overload
+from typing_extensions import deprecated
 
 @overload
 @deprecated("work with str instead")
@@ -797,5 +797,56 @@ def g(x: int) -> int: ...
 @overload
 def g(x: str) -> str: ...
 def g(x: Union[int, str]) -> Union[int, str]: ...
+[builtins fixtures/tuple.pyi]
+
+[case testDeprecatedExclude]
+# flags: --enable-error-code=deprecated --deprecated-calls-exclude=m.C --deprecated-calls-exclude=m.D --deprecated-calls-exclude=m.E.f --deprecated-calls-exclude=m.E.g --deprecated-calls-exclude=m.E.__add__
+from m import C, D, E
+
+[file m.py]
+from typing import Union, overload
+from typing_extensions import deprecated
+
+@deprecated("use C2 instead")
+class C:
+    def __init__(self) -> None: ...
+
+c: C
+C()
+C.__init__(c)
+
+class D:
+    @deprecated("use D.g instead")
+    def f(self) -> None: ...
+
+    def g(self) -> None: ...
+
+D.f
+D().f
+D().f()
+
+class E:
+    @overload
+    def f(self, x: int) -> int: ...
+    @overload
+    def f(self, x: str) -> str: ...
+    @deprecated("use E.f2 instead")
+    def f(self, x: Union[int, str]) -> Union[int, str]: ...
+
+    @deprecated("use E.h instead")
+    def g(self) -> None: ...
+
+    @overload
+    @deprecated("no A + int")
+    def __add__(self, v: int) -> None: ...
+    @overload
+    def __add__(self, v: str) -> None: ...
+    def __add__(self, v: Union[int, str]) -> None: ...
+
+E().f(1)
+E().f("x")
 
+e = E()
+e.g()
+e + 1
 [builtins fixtures/tuple.pyi]

From ed847a88a73b69b07daa9c303912d8886a8c0f4a Mon Sep 17 00:00:00 2001
From: Christoph Tyralla <c.tyralla@bjoernsen.de>
Date: Sun, 16 Feb 2025 20:16:53 +0100
Subject: [PATCH 5/6] restore some test (test changes) that I stashed
 accidentally

---
 test-data/unit/check-deprecated.test | 74 +++++++++++++++++++++++++++-
 1 file changed, 72 insertions(+), 2 deletions(-)

diff --git a/test-data/unit/check-deprecated.test b/test-data/unit/check-deprecated.test
index 6cc160fad81f..a5681134bda4 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,14 +790,63 @@ 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
 
+@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)
+
 [builtins fixtures/tuple.pyi]
 
+@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)  # E: overload def (x: builtins.str) -> builtins.str of function __main__.i is deprecated: work with int or str instead
+j(1.0)
 
+@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: Union[int, str]) -> Union[int, str]: ...
+
+k
+k(1)
+k("x")
+k(int_or_str)
+k(any)  # E: overload def (x: builtins.str) -> builtins.str of function __main__.i is deprecated: work with int or str instead
+k(1.0)
 [case testDeprecatedImportedOverloadedFunction]
 # flags: --enable-error-code=deprecated
 

From e0a2c8e87df3f5c2d3322a64afc98eaad0574c2e Mon Sep 17 00:00:00 2001
From: Christoph Tyralla <c.tyralla@bjoernsen.de>
Date: Mon, 17 Feb 2025 21:46:14 +0100
Subject: [PATCH 6/6] fix `zip(*...) -> Any` problem

---
 mypy/checkexpr.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index f05507ea8f98..b84d2b35d46e 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -2701,7 +2701,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