]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.9] bpo-39679: Fix `singledispatchmethod` `classmethod`/`staticmethod` bug (GH...
authorAlex Waygood <Alex.Waygood@Gmail.com>
Thu, 28 Oct 2021 16:02:04 +0000 (17:02 +0100)
committerGitHub <noreply@github.com>
Thu, 28 Oct 2021 16:02:04 +0000 (18:02 +0200)
This commit fixes a bug in the 3.9 branch where stacking
`@functools.singledispatchmethod` on top of `@classmethod` or `@staticmethod`
caused an exception to be raised if the method was registered using
type-annotations rather than `@method.register(int)`. Tests for this scenario
were added to the 3.11 and 3.10 branches in #29034 and #29072; this commit
also backports those tests to the 3.9 branch.

Co-authored-by: Yurii Karabas <1998uriyyo@gmail.com>
Co-authored-by: Ɓukasz Langa <lukasz@langa.pl>
Lib/functools.py
Lib/test/test_functools.py
Misc/NEWS.d/next/Library/2021-10-20-10-07-44.bpo-39679.nVYJJ3.rst [new file with mode: 0644]

index 97744a869563d6cca420581a6e0ff5c11a9b06a8..5054e281ad281496c4911437040fc41aef62a3cd 100644 (file)
@@ -906,6 +906,12 @@ class singledispatchmethod:
 
         Registers a new implementation for the given *cls* on a *generic_method*.
         """
+        # bpo-39679: in Python <= 3.9, classmethods and staticmethods don't
+        # inherit __annotations__ of the wrapped function (fixed in 3.10+ as
+        # a side-effect of bpo-43682) but we need that for annotation-derived
+        # singledispatches. So we add that just-in-time here.
+        if isinstance(cls, (staticmethod, classmethod)):
+            cls.__annotations__ = getattr(cls.__func__, '__annotations__', {})
         return self.dispatcher.register(cls, func=method)
 
     def __get__(self, obj, cls=None):
index 987020ea007fa09faecc3367f598bc8a52ba8f0c..96e93ed8eab344f6ebcfe18c19dbe30d029c0038 100644 (file)
@@ -2427,6 +2427,48 @@ class TestSingleDispatch(unittest.TestCase):
         self.assertEqual(a.t(''), "str")
         self.assertEqual(a.t(0.0), "base")
 
+    def test_staticmethod_type_ann_register(self):
+        class A:
+            @functools.singledispatchmethod
+            @staticmethod
+            def t(arg):
+                return arg
+            @t.register
+            @staticmethod
+            def _(arg: int):
+                return isinstance(arg, int)
+            @t.register
+            @staticmethod
+            def _(arg: str):
+                return isinstance(arg, str)
+        a = A()
+
+        self.assertTrue(A.t(0))
+        self.assertTrue(A.t(''))
+        self.assertEqual(A.t(0.0), 0.0)
+
+    def test_classmethod_type_ann_register(self):
+        class A:
+            def __init__(self, arg):
+                self.arg = arg
+
+            @functools.singledispatchmethod
+            @classmethod
+            def t(cls, arg):
+                return cls("base")
+            @t.register
+            @classmethod
+            def _(cls, arg: int):
+                return cls("int")
+            @t.register
+            @classmethod
+            def _(cls, arg: str):
+                return cls("str")
+
+        self.assertEqual(A.t(0).arg, "int")
+        self.assertEqual(A.t('').arg, "str")
+        self.assertEqual(A.t(0.0).arg, "base")
+
     def test_invalid_registrations(self):
         msg_prefix = "Invalid first argument to `register()`: "
         msg_suffix = (
diff --git a/Misc/NEWS.d/next/Library/2021-10-20-10-07-44.bpo-39679.nVYJJ3.rst b/Misc/NEWS.d/next/Library/2021-10-20-10-07-44.bpo-39679.nVYJJ3.rst
new file mode 100644 (file)
index 0000000..b0656aa
--- /dev/null
@@ -0,0 +1,3 @@
+Fix bug in :class:`functools.singledispatchmethod` that caused it to fail
+when attempting to register a :func:`classmethod` or :func:`staticmethod`
+using type annotations. Patch contributed by Alex Waygood.