From 30a524591e7463d34ae7e90afbd647cfc7cd23aa Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 8 Jan 2026 16:09:04 +0100 Subject: [PATCH 1/2] Fix `TestSingleDispatch.test_method_signatures` to put annotations on the right params See https://github.com/python/cpython/pull/130309/changes#r2663516538. These functions don't annotate parameters for values used in single-dispatching (`item`), they annotate parameters one later (`arg`). This being legal relies on a bug -- GH-84644, which is that `singledispatch` doesn't verify the annotation is on the "first" parameter. I think these functions should look like ```diff @functools.singledispatchmethod - def func(self, item, arg: int) -> str: + def func(self, item: int, arg) -> str: return str(item) @func.register - def _(self, item, arg: bytes) -> str: + def _(self, item: bytes, arg) -> str: return str(item) ``` (and signature tests updated accordingly) In practice, it doesn't matter how you annotate `func` here, because it's a default callback. But what matters is that any function passed to `register()` annotates the target parameter (always first positional in `singledispatch` and always second positional in `singledispatchmethod` (unless staticmethod, then the first) etc.) for the value to single-dispatch on; not some other, unrelated parameter (or return type, as presented in GH-84644). Let's have a class with the same registree signature as these from the test: ```py class A: @functools.singledispatchmethod def func(self, item, arg: int) -> str: return 'argint' @func.register def _(self, item, arg: bytes) -> str: return 'argbytes' a = A() print(a.func(b'', 1)) ``` For this code, the output would be `argbytes`, even though `arg` is an integer `1`. --- Lib/test/test_functools.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 090926fd8d8b61..d53248c3f711f5 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -3448,44 +3448,44 @@ def _(item: int, arg: bytes) -> str: def test_method_signatures(self): class A: - def m(self, item, arg: int) -> str: + def m(self, item: int, arg) -> str: return str(item) @classmethod - def cm(cls, item, arg: int) -> str: + def cm(cls, item: int, arg) -> str: return str(item) @functools.singledispatchmethod - def func(self, item, arg: int) -> str: + def func(self, item: int, arg) -> str: return str(item) @func.register - def _(self, item, arg: bytes) -> str: + def _(self, item: bytes, arg) -> str: return str(item) @functools.singledispatchmethod @classmethod - def cls_func(cls, item, arg: int) -> str: + def cls_func(cls, item: int, arg) -> str: return str(arg) @func.register @classmethod - def _(cls, item, arg: bytes) -> str: + def _(cls, item: bytes, arg) -> str: return str(item) @functools.singledispatchmethod @staticmethod - def static_func(item, arg: int) -> str: + def static_func(item: int, arg) -> str: return str(arg) @func.register @staticmethod - def _(item, arg: bytes) -> str: + def _(item: bytes, arg) -> str: return str(item) self.assertEqual(str(Signature.from_callable(A.func)), - '(self, item, arg: int) -> str') + '(self, item: int, arg) -> str') self.assertEqual(str(Signature.from_callable(A().func)), - '(self, item, arg: int) -> str') + '(self, item: int, arg) -> str') self.assertEqual(str(Signature.from_callable(A.cls_func)), - '(cls, item, arg: int) -> str') + '(cls, item: int, arg) -> str') self.assertEqual(str(Signature.from_callable(A.static_func)), - '(item, arg: int) -> str') + '(item: int, arg) -> str') def test_method_non_descriptor(self): class Callable: From 85201c349c2a92e114c239277e77db3ef286c3d7 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 11 Jan 2026 17:22:21 +0100 Subject: [PATCH 2/2] Keep tests analogous to `test_signatures` Co-authored-by: Serhiy Storchaka --- Lib/test/test_functools.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index d53248c3f711f5..1b5ace0fbbba3f 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -3448,44 +3448,39 @@ def _(item: int, arg: bytes) -> str: def test_method_signatures(self): class A: - def m(self, item: int, arg) -> str: - return str(item) - @classmethod - def cm(cls, item: int, arg) -> str: - return str(item) @functools.singledispatchmethod - def func(self, item: int, arg) -> str: + def func(self, item, arg: int) -> str: return str(item) @func.register - def _(self, item: bytes, arg) -> str: + def _(self, item: int, arg: bytes) -> str: return str(item) @functools.singledispatchmethod @classmethod - def cls_func(cls, item: int, arg) -> str: + def cls_func(cls, item, arg: int) -> str: return str(arg) @func.register @classmethod - def _(cls, item: bytes, arg) -> str: + def _(cls, item: int, arg: bytes) -> str: return str(item) @functools.singledispatchmethod @staticmethod - def static_func(item: int, arg) -> str: + def static_func(item, arg: int) -> str: return str(arg) @func.register @staticmethod - def _(item: bytes, arg) -> str: + def _(item: int, arg: bytes) -> str: return str(item) self.assertEqual(str(Signature.from_callable(A.func)), - '(self, item: int, arg) -> str') + '(self, item, arg: int) -> str') self.assertEqual(str(Signature.from_callable(A().func)), - '(self, item: int, arg) -> str') + '(self, item, arg: int) -> str') self.assertEqual(str(Signature.from_callable(A.cls_func)), - '(cls, item: int, arg) -> str') + '(cls, item, arg: int) -> str') self.assertEqual(str(Signature.from_callable(A.static_func)), - '(item: int, arg) -> str') + '(item, arg: int) -> str') def test_method_non_descriptor(self): class Callable: