]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-94607: Fix subclassing generics (GH-94610)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Sat, 9 Jul 2022 05:20:43 +0000 (22:20 -0700)
committerGitHub <noreply@github.com>
Sat, 9 Jul 2022 05:20:43 +0000 (22:20 -0700)
Co-authored-by: Serhiy Storchaka <3659035+serhiy-storchaka@users.noreply.github.com>
(cherry picked from commit 6442a9dd212fa18343db21849cf05c0181662c1f)

Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
Lib/test/test_typing.py
Lib/typing.py
Misc/NEWS.d/next/Library/2022-07-06-16-01-08.gh-issue-94607.Q6RYfz.rst [new file with mode: 0644]
Objects/genericaliasobject.c

index 634be9062d63394072e665ac971974ba0b1e5fe0..2fd57822726e56c9254f40070e7d17e01e61d556 100644 (file)
@@ -3644,6 +3644,35 @@ class GenericTests(BaseTestCase):
                     class Foo(obj):
                         pass
 
+    def test_complex_subclasses(self):
+        T_co = TypeVar("T_co", covariant=True)
+
+        class Base(Generic[T_co]):
+            ...
+
+        T = TypeVar("T")
+
+        # see gh-94607: this fails in that bug
+        class Sub(Base, Generic[T]):
+            ...
+
+    def test_parameter_detection(self):
+        self.assertEqual(List[T].__parameters__, (T,))
+        self.assertEqual(List[List[T]].__parameters__, (T,))
+        class A:
+            __parameters__ = (T,)
+        # Bare classes should be skipped
+        for a in (List, list):
+            for b in (A, int, TypeVar, TypeVarTuple, ParamSpec, types.GenericAlias, types.UnionType):
+                with self.subTest(generic=a, sub=b):
+                    with self.assertRaisesRegex(TypeError, '.* is not a generic class'):
+                        a[b][str]
+        # Duck-typing anything that looks like it has __parameters__.
+        # These tests are optional and failure is okay.
+        self.assertEqual(List[A()].__parameters__, (T,))
+        # C version of GenericAlias
+        self.assertEqual(list[A()].__parameters__, (T,))
+
 class ClassVarTests(BaseTestCase):
 
     def test_basics(self):
index 1ebc3ce1f64bd61e5fa1ca488a93a0926d8e3625..31269273a5c0d324c0fc07a9deb3d1ebd7df0c96 100644 (file)
@@ -250,6 +250,9 @@ def _collect_parameters(args):
     """
     parameters = []
     for t in args:
+        # We don't want __parameters__ descriptor of a bare Python class.
+        if isinstance(t, type):
+            continue
         if hasattr(t, '__typing_subst__'):
             if t not in parameters:
                 parameters.append(t)
diff --git a/Misc/NEWS.d/next/Library/2022-07-06-16-01-08.gh-issue-94607.Q6RYfz.rst b/Misc/NEWS.d/next/Library/2022-07-06-16-01-08.gh-issue-94607.Q6RYfz.rst
new file mode 100644 (file)
index 0000000..3bbb917
--- /dev/null
@@ -0,0 +1,2 @@
+Fix subclassing complex generics with type variables in :mod:`typing`. Previously an error message saying ``Some type variables ... are not listed in Generic[...]`` was shown.
+:mod:`typing` no longer populates ``__parameters__`` with the ``__parameters__`` of a Python class.
index b2636d5475dbb952d4bca5b55862c5f1a487e16c..19f011fd3a7437f2d00aa98767b4860e089230fe 100644 (file)
@@ -219,6 +219,10 @@ _Py_make_parameters(PyObject *args)
     for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
         PyObject *t = PyTuple_GET_ITEM(args, iarg);
         PyObject *subst;
+        // We don't want __parameters__ descriptor of a bare Python class.
+        if (PyType_Check(t)) {
+            continue;
+        }
         if (_PyObject_LookupAttr(t, &_Py_ID(__typing_subst__), &subst) < 0) {
             Py_DECREF(parameters);
             return NULL;