From c63a4fa7582ceb68978f2c89947f1eaa1dcdfe4a Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 6 Jul 2022 23:53:28 +0800 Subject: [PATCH 01/11] Fix subclassing generics --- Lib/typing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index 25ae19f71fe94a..384fbd03e51b84 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -253,7 +253,8 @@ def _collect_parameters(args): if hasattr(t, '__typing_subst__'): if t not in parameters: parameters.append(t) - else: + # We need to avoid bare Python types as those aren't really generic. + elif not type(t) is type: for x in getattr(t, '__parameters__', ()): if x not in parameters: parameters.append(x) From be88cf8d976106240d8df5655fd49713a5a06b7b Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:01:09 +0000 Subject: [PATCH 02/11] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2022-07-06-16-01-08.gh-issue-94607.Q6RYfz.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-07-06-16-01-08.gh-issue-94607.Q6RYfz.rst 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 index 00000000000000..4ca76808ac0743 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-07-06-16-01-08.gh-issue-94607.Q6RYfz.rst @@ -0,0 +1 @@ +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. From 8eb205157b2c88332cd84290bc9204c524ac2f21 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Thu, 7 Jul 2022 00:01:57 +0800 Subject: [PATCH 03/11] fix comment --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index 384fbd03e51b84..60ae9003eb3f9d 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -253,7 +253,7 @@ def _collect_parameters(args): if hasattr(t, '__typing_subst__'): if t not in parameters: parameters.append(t) - # We need to avoid bare Python types as those aren't really generic. + # We need to avoid bare Python types as those aren't really objects. elif not type(t) is type: for x in getattr(t, '__parameters__', ()): if x not in parameters: From 5cb5c45dd24746a2b3215b81568daed7ea9de4e0 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Thu, 7 Jul 2022 00:06:49 +0800 Subject: [PATCH 04/11] Add tests --- Lib/test/test_typing.py | 13 +++++++++++++ Lib/typing.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 6850b881a188d4..8ad94323375286 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3650,6 +3650,19 @@ def test_subclass_special_form(self): 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]): + ... + + class ClassVarTests(BaseTestCase): def test_basics(self): diff --git a/Lib/typing.py b/Lib/typing.py index 60ae9003eb3f9d..39a1fd635576ec 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -254,7 +254,7 @@ def _collect_parameters(args): if t not in parameters: parameters.append(t) # We need to avoid bare Python types as those aren't really objects. - elif not type(t) is type: + elif type(t) is not type: for x in getattr(t, '__parameters__', ()): if x not in parameters: parameters.append(x) From a9c38813bbbf95e8e9d26b7a2625990a338431de Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Thu, 7 Jul 2022 00:49:20 +0800 Subject: [PATCH 05/11] use more restrictive checks --- Lib/typing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 39a1fd635576ec..9d11c69699dbf6 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -253,8 +253,8 @@ def _collect_parameters(args): if hasattr(t, '__typing_subst__'): if t not in parameters: parameters.append(t) - # We need to avoid bare Python types as those aren't really objects. - elif type(t) is not type: + # Ensures we aren't grabbing from something non-generic. E.g. a bare Python class isn't generic. + elif isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)): for x in getattr(t, '__parameters__', ()): if x not in parameters: parameters.append(x) From d44dd50de9dda05ca31555a474c2e763f9a05c4f Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Thu, 7 Jul 2022 00:54:05 +0800 Subject: [PATCH 06/11] fix comment --- Lib/typing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index 9d11c69699dbf6..c36e7041a34dd0 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -253,7 +253,8 @@ def _collect_parameters(args): if hasattr(t, '__typing_subst__'): if t not in parameters: parameters.append(t) - # Ensures we aren't grabbing from something non-generic. E.g. a bare Python class isn't generic. + # Ensures we aren't grabbing from something non-generic. + # E.g. a bare Python class isn't generic. elif isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)): for x in getattr(t, '__parameters__', ()): if x not in parameters: From bc475be15b4baae6166cc43d55935e7314ed6de0 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Thu, 7 Jul 2022 12:48:30 +0800 Subject: [PATCH 07/11] explicitly skip, add tests --- Lib/test/test_typing.py | 11 +++++++++++ Lib/typing.py | 7 ++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 8ad94323375286..f0159accbcd52e 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3662,6 +3662,17 @@ class Base(Generic[T_co]): 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 + self.assertEqual(List[A].__parameters__, ()) + # Duck-typing anything that looks like it has __parameters__. + # This test is optional and failure is okay. + self.assertEqual(List[A()].__parameters__, (T,)) + class ClassVarTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index c36e7041a34dd0..b79d090cce6adc 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -250,12 +250,13 @@ def _collect_parameters(args): """ parameters = [] for t in args: + # A bare Python class isn't generic. + if isinstance(t, type): + continue if hasattr(t, '__typing_subst__'): if t not in parameters: parameters.append(t) - # Ensures we aren't grabbing from something non-generic. - # E.g. a bare Python class isn't generic. - elif isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)): + else: for x in getattr(t, '__parameters__', ()): if x not in parameters: parameters.append(x) From 35e00d6c68a869e8f39df4f13734a105ba2f7b2c Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Thu, 7 Jul 2022 17:26:41 +0800 Subject: [PATCH 08/11] Fix C version, add more tests --- Lib/test/test_typing.py | 6 +++++- Objects/genericaliasobject.c | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index f0159accbcd52e..7da225feb23555 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3668,7 +3668,11 @@ def test_parameter_detection(self): class A: __parameters__ = (T,) # Bare classes should be skipped - self.assertEqual(List[A].__parameters__, ()) + 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__. # This test is optional and failure is okay. self.assertEqual(List[A()].__parameters__, (T,)) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index b2636d5475dbb9..87ed613c17590b 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -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; + // A bare Python class isn't generic. + if (PyType_Check(t)) { + continue; + } if (_PyObject_LookupAttr(t, &_Py_ID(__typing_subst__), &subst) < 0) { Py_DECREF(parameters); return NULL; From 4c7f11b4db1257aafe2f843c6b3765b67904885c Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Thu, 7 Jul 2022 20:19:12 +0800 Subject: [PATCH 09/11] Update 2022-07-06-16-01-08.gh-issue-94607.Q6RYfz.rst --- .../next/Library/2022-07-06-16-01-08.gh-issue-94607.Q6RYfz.rst | 1 + 1 file changed, 1 insertion(+) 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 index 4ca76808ac0743..3bbb9172f26195 100644 --- 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 @@ -1 +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. From a2fdbb4e77cee2b45b4dcfae5143a425bba955c6 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 8 Jul 2022 17:24:05 +0800 Subject: [PATCH 10/11] test C version --- Lib/test/test_typing.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 7da225feb23555..3f4101485c6e08 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3668,15 +3668,16 @@ def test_parameter_detection(self): class A: __parameters__ = (T,) # Bare classes should be skipped - for a in List, list: + 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__. - # This test is optional and failure is okay. + # 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): From 246a8b05c30d3bdccea7065074e65415d94745b0 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sat, 9 Jul 2022 00:01:52 +0800 Subject: [PATCH 11/11] Make comments more accurate --- Lib/typing.py | 2 +- Objects/genericaliasobject.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index b79d090cce6adc..66c26e416bd0ba 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -250,7 +250,7 @@ def _collect_parameters(args): """ parameters = [] for t in args: - # A bare Python class isn't generic. + # We don't want __parameters__ descriptor of a bare Python class. if isinstance(t, type): continue if hasattr(t, '__typing_subst__'): diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 87ed613c17590b..19f011fd3a7437 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -219,7 +219,7 @@ _Py_make_parameters(PyObject *args) for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { PyObject *t = PyTuple_GET_ITEM(args, iarg); PyObject *subst; - // A bare Python class isn't generic. + // We don't want __parameters__ descriptor of a bare Python class. if (PyType_Check(t)) { continue; }