From ed1cbcc27f328b0ae4becdde856187345d71946c Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Wed, 25 Sep 2024 13:44:04 +0200 Subject: [PATCH 01/16] Allow specializing Generic ParamSpec aliases --- Objects/genericaliasobject.c | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 64b4e2645cbaee..76080bac09e188 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -180,18 +180,31 @@ tuple_extend(PyObject **dst, Py_ssize_t dstindex, PyObject * _Py_make_parameters(PyObject *args) { - Py_ssize_t nargs = PyTuple_GET_SIZE(args); + Py_ssize_t nargs = PySequence_Length(args); Py_ssize_t len = nargs; PyObject *parameters = PyTuple_New(len); if (parameters == NULL) return NULL; Py_ssize_t iparam = 0; for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { - PyObject *t = PyTuple_GET_ITEM(args, iarg); + PyObject *t = PySequence_GetItem(args, iarg); // We don't want __parameters__ descriptor of a bare Python class. if (PyType_Check(t)) { continue; } + // Recursively call _Py_make_parameters for lists/tuples and + // add the results to the current parameters. + if (PyTuple_Check(t) || PyList_Check(t)) { + PyObject *subargs = _Py_make_parameters(t); + if (subargs == NULL) { + Py_DECREF(parameters); + return NULL; + } + iparam += tuple_extend(¶meters, iparam, &PyTuple_GET_ITEM(subargs, 0), + PyTuple_GET_SIZE(subargs)); + Py_DECREF(subargs); + continue; + } int rc = PyObject_HasAttrWithError(t, &_Py_ID(__typing_subst__)); if (rc < 0) { Py_DECREF(parameters); @@ -416,21 +429,34 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje t = list[T]; t[int] -> newargs = [int] t = dict[str, T]; t[int] -> newargs = [str, int] t = dict[T, list[S]]; t[str, int] -> newargs = [str, list[int]] + t = list[[T]]; t[str] -> newargs = [[str]] */ - Py_ssize_t nargs = PyTuple_GET_SIZE(args); + Py_ssize_t nargs = PySequence_Length(args); PyObject *newargs = PyTuple_New(nargs); if (newargs == NULL) { Py_DECREF(item); return NULL; } for (Py_ssize_t iarg = 0, jarg = 0; iarg < nargs; iarg++) { - PyObject *arg = PyTuple_GET_ITEM(args, iarg); + PyObject *arg = PySequence_GetItem(args, iarg); if (PyType_Check(arg)) { PyTuple_SET_ITEM(newargs, jarg, Py_NewRef(arg)); jarg++; continue; } - + // Recursively substitute params in lists/tuples. + if (PyTuple_Check(arg) || PyList_Check(arg)) { + PyObject *subargs = _Py_subs_parameters(self, arg, parameters, item); + if (subargs == NULL) { + Py_DECREF(newargs); + Py_DECREF(item); + return NULL; + } + Py_SETREF(subargs, PySequence_List(subargs)); + PyTuple_SET_ITEM(newargs, jarg, Py_NewRef(subargs)); + jarg++; + continue; + } int unpack = _is_unpacked_typevartuple(arg); if (unpack < 0) { Py_DECREF(newargs); From 24c194167a2ea0608dcb57a52c1a4a80020cc372 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Wed, 25 Sep 2024 13:45:10 +0200 Subject: [PATCH 02/16] Add news entry --- .../2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst new file mode 100644 index 00000000000000..8cc90808659817 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst @@ -0,0 +1 @@ +Allow specializing Generic ParamSpec aliases From de37e9b763fcdf3f7b69082b6e4d1b55b2336e9a Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Wed, 25 Sep 2024 13:54:03 +0200 Subject: [PATCH 03/16] Add tests --- Lib/test/test_genericalias.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 12564b423493aa..b5ba9da4b5f59a 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -474,6 +474,17 @@ def test_del_iter(self): iter_x = iter(t) del iter_x + def test_paramspec_specialization(self): + # gh-124445 + T = TypeVar("T") + type X[**P] = Callable[P, int] + generic = X[[T]] + self.assertEqual(generic.__args__, ([T],)) + self.assertEqual(generic.__parameters__, (T,)) + specialized = generic[str] + self.assertEqual(specialized.__args__, ([str],)) + self.assertEqual(specialized.__parameters__, ()) + class TypeIterationTests(unittest.TestCase): _UNITERABLE_TYPES = (list, tuple) From 34a498a4f38a6cfc1c04a7babaacf3a6b1470ca3 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Wed, 25 Sep 2024 14:04:50 +0200 Subject: [PATCH 04/16] Check for NULLs --- Objects/genericaliasobject.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 76080bac09e188..f6481aaddc8eb0 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -452,7 +452,14 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje Py_DECREF(item); return NULL; } - Py_SETREF(subargs, PySequence_List(subargs)); + PyObject *subargs_list = PySequence_List(subargs); + Py_DECREF(subargs); + if (subargs_list == NULL) { + Py_DECREF(newargs); + Py_DECREF(item); + return NULL; + } + Py_SETREF(subargs, subargs_list); PyTuple_SET_ITEM(newargs, jarg, Py_NewRef(subargs)); jarg++; continue; From c7ab733979df65eb0952e7bbd849cd8b7f1d72dd Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Wed, 25 Sep 2024 17:39:14 +0200 Subject: [PATCH 05/16] Fix refleaks --- Objects/genericaliasobject.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index f6481aaddc8eb0..1a9d03e5f96af6 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -180,6 +180,7 @@ tuple_extend(PyObject **dst, Py_ssize_t dstindex, PyObject * _Py_make_parameters(PyObject *args) { + assert (PyTuple_Check(args) || PyList_Check(args)); Py_ssize_t nargs = PySequence_Length(args); Py_ssize_t len = nargs; PyObject *parameters = PyTuple_New(len); @@ -187,24 +188,12 @@ _Py_make_parameters(PyObject *args) return NULL; Py_ssize_t iparam = 0; for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { - PyObject *t = PySequence_GetItem(args, iarg); + PyObject *t = PyTuple_Check(args) ? + PyTuple_GET_ITEM(args, iarg) : PyList_GET_ITEM(args, iarg); // We don't want __parameters__ descriptor of a bare Python class. if (PyType_Check(t)) { continue; } - // Recursively call _Py_make_parameters for lists/tuples and - // add the results to the current parameters. - if (PyTuple_Check(t) || PyList_Check(t)) { - PyObject *subargs = _Py_make_parameters(t); - if (subargs == NULL) { - Py_DECREF(parameters); - return NULL; - } - iparam += tuple_extend(¶meters, iparam, &PyTuple_GET_ITEM(subargs, 0), - PyTuple_GET_SIZE(subargs)); - Py_DECREF(subargs); - continue; - } int rc = PyObject_HasAttrWithError(t, &_Py_ID(__typing_subst__)); if (rc < 0) { Py_DECREF(parameters); @@ -220,6 +209,15 @@ _Py_make_parameters(PyObject *args) Py_DECREF(parameters); return NULL; } + if (!subparams && (PyTuple_Check(t) || PyList_Check(t))) { + // Recursively call _Py_make_parameters for lists/tuples and + // add the results to the current parameters. + subparams = _Py_make_parameters(t); + if (subparams == NULL) { + Py_DECREF(parameters); + return NULL; + } + } if (subparams && PyTuple_Check(subparams)) { Py_ssize_t len2 = PyTuple_GET_SIZE(subparams); Py_ssize_t needed = len2 - 1 - (iarg - iparam); @@ -431,6 +429,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje t = dict[T, list[S]]; t[str, int] -> newargs = [str, list[int]] t = list[[T]]; t[str] -> newargs = [[str]] */ + assert (PyTuple_Check(args) || PyList_Check(args)); Py_ssize_t nargs = PySequence_Length(args); PyObject *newargs = PyTuple_New(nargs); if (newargs == NULL) { @@ -438,7 +437,8 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje return NULL; } for (Py_ssize_t iarg = 0, jarg = 0; iarg < nargs; iarg++) { - PyObject *arg = PySequence_GetItem(args, iarg); + PyObject *arg = PyTuple_Check(args) ? + PyTuple_GET_ITEM(args, iarg) : PyList_GET_ITEM(args, iarg); if (PyType_Check(arg)) { PyTuple_SET_ITEM(newargs, jarg, Py_NewRef(arg)); jarg++; @@ -460,7 +460,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje return NULL; } Py_SETREF(subargs, subargs_list); - PyTuple_SET_ITEM(newargs, jarg, Py_NewRef(subargs)); + PyTuple_SET_ITEM(newargs, jarg, subargs); jarg++; continue; } From e900004483dea96ebd462afdcdd3426ee05e5811 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Wed, 25 Sep 2024 17:39:40 +0200 Subject: [PATCH 06/16] Add tests --- Lib/test/test_genericalias.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index b5ba9da4b5f59a..a0046f90cadfe8 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -477,7 +477,9 @@ def test_del_iter(self): def test_paramspec_specialization(self): # gh-124445 T = TypeVar("T") + U = TypeVar("U") type X[**P] = Callable[P, int] + generic = X[[T]] self.assertEqual(generic.__args__, ([T],)) self.assertEqual(generic.__parameters__, (T,)) @@ -485,6 +487,27 @@ def test_paramspec_specialization(self): self.assertEqual(specialized.__args__, ([str],)) self.assertEqual(specialized.__parameters__, ()) + generic = X[(T,)] + self.assertEqual(generic.__args__, (T,)) + self.assertEqual(generic.__parameters__, (T,)) + specialized = generic[str] + self.assertEqual(specialized.__args__, (str,)) + self.assertEqual(specialized.__parameters__, ()) + + generic = X[[T, U]] + self.assertEqual(generic.__args__, ([T, U],)) + self.assertEqual(generic.__parameters__, (T, U)) + specialized = generic[str, int] + self.assertEqual(specialized.__args__, ([str, int],)) + self.assertEqual(specialized.__parameters__, ()) + + generic = X[(T, U)] + self.assertEqual(generic.__args__, (T, U)) + self.assertEqual(generic.__parameters__, (T, U)) + specialized = generic[str, int] + self.assertEqual(specialized.__args__, (str, int)) + self.assertEqual(specialized.__parameters__, ()) + class TypeIterationTests(unittest.TestCase): _UNITERABLE_TYPES = (list, tuple) From 8b0d8abf4ef24a6f1cdd904ca6eaab5f3e100d9c Mon Sep 17 00:00:00 2001 From: Tomas R Date: Wed, 25 Sep 2024 22:05:34 +0200 Subject: [PATCH 07/16] Fix segfault Co-authored-by: Jelle Zijlstra --- Objects/genericaliasobject.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 1a9d03e5f96af6..11e17a902ce2a0 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -459,8 +459,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje Py_DECREF(item); return NULL; } - Py_SETREF(subargs, subargs_list); - PyTuple_SET_ITEM(newargs, jarg, subargs); + PyTuple_SET_ITEM(newargs, jarg, subargs_list); jarg++; continue; } From 055969720d9ed7a521b31e75db2c78f08e397104 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Wed, 25 Sep 2024 22:16:14 +0200 Subject: [PATCH 08/16] Optimize type checks --- Objects/genericaliasobject.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 11e17a902ce2a0..8ffb01ebff8e91 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -187,9 +187,9 @@ _Py_make_parameters(PyObject *args) if (parameters == NULL) return NULL; Py_ssize_t iparam = 0; + const int is_args_tuple = PyTuple_Check(args); for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { - PyObject *t = PyTuple_Check(args) ? - PyTuple_GET_ITEM(args, iarg) : PyList_GET_ITEM(args, iarg); + PyObject *t = is_args_tuple ? PyTuple_GET_ITEM(args, iarg) : PyList_GET_ITEM(args, iarg); // We don't want __parameters__ descriptor of a bare Python class. if (PyType_Check(t)) { continue; @@ -430,6 +430,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje t = list[[T]]; t[str] -> newargs = [[str]] */ assert (PyTuple_Check(args) || PyList_Check(args)); + const int is_args_tuple = PyTuple_Check(args); Py_ssize_t nargs = PySequence_Length(args); PyObject *newargs = PyTuple_New(nargs); if (newargs == NULL) { @@ -437,7 +438,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje return NULL; } for (Py_ssize_t iarg = 0, jarg = 0; iarg < nargs; iarg++) { - PyObject *arg = PyTuple_Check(args) ? + PyObject *arg = is_args_tuple ? PyTuple_GET_ITEM(args, iarg) : PyList_GET_ITEM(args, iarg); if (PyType_Check(arg)) { PyTuple_SET_ITEM(newargs, jarg, Py_NewRef(arg)); From d3b0d5a953d398420d979db2d30aeb75a54bae28 Mon Sep 17 00:00:00 2001 From: Tomas R Date: Sun, 29 Sep 2024 16:16:18 +0200 Subject: [PATCH 09/16] Fix spacing Co-authored-by: Jelle Zijlstra --- Objects/genericaliasobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 8ffb01ebff8e91..e0362eb92ae139 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -180,7 +180,7 @@ tuple_extend(PyObject **dst, Py_ssize_t dstindex, PyObject * _Py_make_parameters(PyObject *args) { - assert (PyTuple_Check(args) || PyList_Check(args)); + assert(PyTuple_Check(args) || PyList_Check(args)); Py_ssize_t nargs = PySequence_Length(args); Py_ssize_t len = nargs; PyObject *parameters = PyTuple_New(len); From 3bf757f2f035f56e05ac93197f34f988f51c03b6 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 29 Sep 2024 16:20:05 +0200 Subject: [PATCH 10/16] Use bool instead of int --- Objects/genericaliasobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index e0362eb92ae139..97f208219741ec 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -187,7 +187,7 @@ _Py_make_parameters(PyObject *args) if (parameters == NULL) return NULL; Py_ssize_t iparam = 0; - const int is_args_tuple = PyTuple_Check(args); + const bool is_args_tuple = PyTuple_Check(args); for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { PyObject *t = is_args_tuple ? PyTuple_GET_ITEM(args, iarg) : PyList_GET_ITEM(args, iarg); // We don't want __parameters__ descriptor of a bare Python class. @@ -430,7 +430,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje t = list[[T]]; t[str] -> newargs = [[str]] */ assert (PyTuple_Check(args) || PyList_Check(args)); - const int is_args_tuple = PyTuple_Check(args); + const bool is_args_tuple = PyTuple_Check(args); Py_ssize_t nargs = PySequence_Length(args); PyObject *newargs = PyTuple_New(nargs); if (newargs == NULL) { From 62eea5cf711791777d03d81457f01e1b3e7c393c Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 29 Sep 2024 16:25:44 +0200 Subject: [PATCH 11/16] Do not use PySequence_Length --- Objects/genericaliasobject.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 97f208219741ec..61c519433dd12c 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -181,13 +181,13 @@ PyObject * _Py_make_parameters(PyObject *args) { assert(PyTuple_Check(args) || PyList_Check(args)); - Py_ssize_t nargs = PySequence_Length(args); + const bool is_args_tuple = PyTuple_Check(args); + Py_ssize_t nargs = is_args_tuple ? PyTuple_GET_SIZE(args) : PyList_GET_SIZE(args); Py_ssize_t len = nargs; PyObject *parameters = PyTuple_New(len); if (parameters == NULL) return NULL; Py_ssize_t iparam = 0; - const bool is_args_tuple = PyTuple_Check(args); for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { PyObject *t = is_args_tuple ? PyTuple_GET_ITEM(args, iarg) : PyList_GET_ITEM(args, iarg); // We don't want __parameters__ descriptor of a bare Python class. @@ -431,7 +431,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje */ assert (PyTuple_Check(args) || PyList_Check(args)); const bool is_args_tuple = PyTuple_Check(args); - Py_ssize_t nargs = PySequence_Length(args); + Py_ssize_t nargs = is_args_tuple ? PyTuple_GET_SIZE(args) : PyList_GET_SIZE(args); PyObject *newargs = PyTuple_New(nargs); if (newargs == NULL) { Py_DECREF(item); From 7a0f815cffe87efaa365e82ebf3b5c46a43ebd36 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 29 Sep 2024 16:47:28 +0200 Subject: [PATCH 12/16] Add more tests --- Lib/test/test_genericalias.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index a0046f90cadfe8..3af277da8d7f27 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -508,6 +508,18 @@ def test_paramspec_specialization(self): self.assertEqual(specialized.__args__, (str, int)) self.assertEqual(specialized.__parameters__, ()) + def test_nested_paramspec_specialization(self): + # gh-124445 + type X[**P, T] = Callable[P, T] + + x_list = X[[int, str], float] + self.assertEqual(x_list.__args__, ([int, str], float)) + self.assertEqual(x_list.__parameters__, ()) + + x_tuple = X[(int, str), float] + self.assertEqual(x_tuple.__args__, ((int, str), float)) + self.assertEqual(x_tuple.__parameters__, ()) + class TypeIterationTests(unittest.TestCase): _UNITERABLE_TYPES = (list, tuple) From d2e5b4c12ebfbaf514d2e6066e7895343ca82fc2 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Mon, 30 Sep 2024 18:10:11 +0200 Subject: [PATCH 13/16] Convert lists to tuples before processing --- Objects/genericaliasobject.c | 45 +++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 61c519433dd12c..f733f150d67a2a 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -181,15 +181,24 @@ PyObject * _Py_make_parameters(PyObject *args) { assert(PyTuple_Check(args) || PyList_Check(args)); - const bool is_args_tuple = PyTuple_Check(args); - Py_ssize_t nargs = is_args_tuple ? PyTuple_GET_SIZE(args) : PyList_GET_SIZE(args); + const bool is_args_list = PyList_Check(args); + PyObject *tuple_args = NULL; + if (is_args_list) { + args = tuple_args = PySequence_Tuple(args); + if (args == NULL) { + return NULL; + } + } + Py_ssize_t nargs = PyTuple_GET_SIZE(args); Py_ssize_t len = nargs; PyObject *parameters = PyTuple_New(len); - if (parameters == NULL) + if (parameters == NULL) { + Py_XDECREF(tuple_args); return NULL; + } Py_ssize_t iparam = 0; for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { - PyObject *t = is_args_tuple ? PyTuple_GET_ITEM(args, iarg) : PyList_GET_ITEM(args, iarg); + PyObject *t = PyTuple_GET_ITEM(args, iarg); // We don't want __parameters__ descriptor of a bare Python class. if (PyType_Check(t)) { continue; @@ -197,6 +206,7 @@ _Py_make_parameters(PyObject *args) int rc = PyObject_HasAttrWithError(t, &_Py_ID(__typing_subst__)); if (rc < 0) { Py_DECREF(parameters); + Py_XDECREF(tuple_args); return NULL; } if (rc) { @@ -207,6 +217,7 @@ _Py_make_parameters(PyObject *args) if (PyObject_GetOptionalAttr(t, &_Py_ID(__parameters__), &subparams) < 0) { Py_DECREF(parameters); + Py_XDECREF(tuple_args); return NULL; } if (!subparams && (PyTuple_Check(t) || PyList_Check(t))) { @@ -215,6 +226,7 @@ _Py_make_parameters(PyObject *args) subparams = _Py_make_parameters(t); if (subparams == NULL) { Py_DECREF(parameters); + Py_XDECREF(tuple_args); return NULL; } } @@ -226,6 +238,7 @@ _Py_make_parameters(PyObject *args) if (_PyTuple_Resize(¶meters, len) < 0) { Py_DECREF(subparams); Py_DECREF(parameters); + Py_XDECREF(tuple_args); return NULL; } } @@ -240,9 +253,11 @@ _Py_make_parameters(PyObject *args) if (iparam < len) { if (_PyTuple_Resize(¶meters, iparam) < 0) { Py_XDECREF(parameters); + Py_XDECREF(tuple_args); return NULL; } } + Py_XDECREF(tuple_args); return parameters; } @@ -430,16 +445,23 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje t = list[[T]]; t[str] -> newargs = [[str]] */ assert (PyTuple_Check(args) || PyList_Check(args)); - const bool is_args_tuple = PyTuple_Check(args); - Py_ssize_t nargs = is_args_tuple ? PyTuple_GET_SIZE(args) : PyList_GET_SIZE(args); + const bool is_args_list = PyList_Check(args); + PyObject *tuple_args = NULL; + if (is_args_list) { + args = tuple_args = PySequence_Tuple(args); + if (args == NULL) { + return NULL; + } + } + Py_ssize_t nargs = PyTuple_GET_SIZE(args); PyObject *newargs = PyTuple_New(nargs); if (newargs == NULL) { Py_DECREF(item); + Py_XDECREF(tuple_args); return NULL; } for (Py_ssize_t iarg = 0, jarg = 0; iarg < nargs; iarg++) { - PyObject *arg = is_args_tuple ? - PyTuple_GET_ITEM(args, iarg) : PyList_GET_ITEM(args, iarg); + PyObject *arg = PyTuple_GET_ITEM(args, iarg); if (PyType_Check(arg)) { PyTuple_SET_ITEM(newargs, jarg, Py_NewRef(arg)); jarg++; @@ -451,6 +473,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje if (subargs == NULL) { Py_DECREF(newargs); Py_DECREF(item); + Py_XDECREF(tuple_args); return NULL; } PyObject *subargs_list = PySequence_List(subargs); @@ -458,6 +481,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje if (subargs_list == NULL) { Py_DECREF(newargs); Py_DECREF(item); + Py_XDECREF(tuple_args); return NULL; } PyTuple_SET_ITEM(newargs, jarg, subargs_list); @@ -468,12 +492,14 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje if (unpack < 0) { Py_DECREF(newargs); Py_DECREF(item); + Py_XDECREF(tuple_args); return NULL; } PyObject *subst; if (PyObject_GetOptionalAttr(arg, &_Py_ID(__typing_subst__), &subst) < 0) { Py_DECREF(newargs); Py_DECREF(item); + Py_XDECREF(tuple_args); return NULL; } if (subst) { @@ -488,6 +514,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje if (arg == NULL) { Py_DECREF(newargs); Py_DECREF(item); + Py_XDECREF(tuple_args); return NULL; } if (unpack) { @@ -496,6 +523,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje Py_DECREF(arg); if (jarg < 0) { Py_DECREF(item); + Py_XDECREF(tuple_args); return NULL; } } @@ -506,6 +534,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje } Py_DECREF(item); + Py_XDECREF(tuple_args); return newargs; } From 50d2d0c66d3aef0b240c05a61a557211a4141d5e Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 10 Nov 2024 20:56:17 +0100 Subject: [PATCH 14/16] Preserve the type (list/tuple) of nested arguments --- Objects/genericaliasobject.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index f733f150d67a2a..24ea0441d81101 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -476,15 +476,22 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje Py_XDECREF(tuple_args); return NULL; } - PyObject *subargs_list = PySequence_List(subargs); - Py_DECREF(subargs); - if (subargs_list == NULL) { - Py_DECREF(newargs); - Py_DECREF(item); - Py_XDECREF(tuple_args); - return NULL; + if (PyTuple_Check(arg)) { + PyTuple_SET_ITEM(newargs, jarg, subargs); + } + else { + // _Py_subs_parameters returns a tuple. If the original arg was a list, + // convert subargs to a list as well. + PyObject *subargs_list = PySequence_List(subargs); + Py_DECREF(subargs); + if (subargs_list == NULL) { + Py_DECREF(newargs); + Py_DECREF(item); + Py_XDECREF(tuple_args); + return NULL; + } + PyTuple_SET_ITEM(newargs, jarg, subargs_list); } - PyTuple_SET_ITEM(newargs, jarg, subargs_list); jarg++; continue; } From 92dbb2d9032b774b6e4d5d758bec7622e213a319 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 10 Nov 2024 20:56:36 +0100 Subject: [PATCH 15/16] Add tests --- Lib/test/test_genericalias.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 77c658ca02d3f8..61547d98ba6666 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -520,6 +520,30 @@ def test_nested_paramspec_specialization(self): self.assertEqual(x_tuple.__args__, ((int, str), float)) self.assertEqual(x_tuple.__parameters__, ()) + U = TypeVar("U") + V = TypeVar("V") + + multiple_params_list = X[[int, U], V] + self.assertEqual(multiple_params_list.__args__, ([int, U], V)) + self.assertEqual(multiple_params_list.__parameters__, (U, V)) + multiple_params_list_specialized = multiple_params_list[str, float] + self.assertEqual(multiple_params_list_specialized.__args__, ([int, str], float)) + self.assertEqual(multiple_params_list_specialized.__parameters__, ()) + + multiple_params_tuple = X[(int, U), V] + self.assertEqual(multiple_params_tuple.__args__, ((int, U), V)) + self.assertEqual(multiple_params_tuple.__parameters__, (U, V)) + multiple_params_tuple_specialized = multiple_params_tuple[str, float] + self.assertEqual(multiple_params_tuple_specialized.__args__, ((int, str), float)) + self.assertEqual(multiple_params_tuple_specialized.__parameters__, ()) + + deeply_nested = X[[U, [V], int], V] + self.assertEqual(deeply_nested.__args__, ([U, [V], int], V)) + self.assertEqual(deeply_nested.__parameters__, (U, V)) + deeply_nested_specialized = deeply_nested[str, float] + self.assertEqual(deeply_nested_specialized.__args__, ([str, [float], int], float)) + self.assertEqual(deeply_nested_specialized.__parameters__, ()) + class TypeIterationTests(unittest.TestCase): _UNITERABLE_TYPES = (list, tuple) From 1b24fd2566502adee0c628161935c9e15b2bc71d Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 10 Nov 2024 20:58:12 +0100 Subject: [PATCH 16/16] Improve news entry --- .../2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst index 8cc90808659817..b67e797c5cf1d9 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst @@ -1 +1,3 @@ -Allow specializing Generic ParamSpec aliases +Fix specialization of generic aliases that are generic over a +:class:`typing.ParamSpec` and have been specialized with a +nested type variable.