summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Waygood <Alex.Waygood@Gmail.com>2021-10-28 17:02:04 +0100
committerGitHub <noreply@github.com>2021-10-28 18:02:04 +0200
commit97388c204b557f30e48a2b2ef826868702204cf2 (patch)
treec6e20a9bb0e3da86ebd1777350a1694afafa3779
parent8365a5b5abe51cbe4151d89a5d0a993273320067 (diff)
downloadcpython-git-97388c204b557f30e48a2b2ef826868702204cf2.tar.gz
[3.9] bpo-39679: Fix `singledispatchmethod` `classmethod`/`staticmethod` bug (GH-29087)
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>
-rw-r--r--Lib/functools.py6
-rw-r--r--Lib/test/test_functools.py42
-rw-r--r--Misc/NEWS.d/next/Library/2021-10-20-10-07-44.bpo-39679.nVYJJ3.rst3
3 files changed, 51 insertions, 0 deletions
diff --git a/Lib/functools.py b/Lib/functools.py
index 97744a8695..5054e281ad 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -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):
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index 987020ea00..96e93ed8ea 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -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
index 0000000000..b0656aac51
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-10-20-10-07-44.bpo-39679.nVYJJ3.rst
@@ -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.