https://github.com/python/cpython/commit/bbf197913ca984b79fee642a39bd61a997e24dd3 commit: bbf197913ca984b79fee642a39bd61a997e24dd3 branch: main author: Tomas R. <tomas.ro...@gmail.com> committer: JelleZijlstra <jelle.zijls...@gmail.com> date: 2025-03-03T14:58:33-08:00 summary:
gh-124445: Allow specializing generic ParamSpec aliases (#124512) Co-authored-by: Jelle Zijlstra <jelle.zijls...@gmail.com> Co-authored-by: Alex Waygood <alex.wayg...@gmail.com> files: A Misc/NEWS.d/next/Core_and_Builtins/2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst M Lib/test/test_genericalias.py M Objects/genericaliasobject.c diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 3048d038c782d4..61547d98ba6666 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -474,6 +474,76 @@ def test_del_iter(self): iter_x = iter(t) del iter_x + 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,)) + specialized = generic[str] + 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__, ()) + + 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__, ()) + + 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) 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..b67e797c5cf1d9 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-25-13-45-01.gh-issue-124445.zfsD7q.rst @@ -0,0 +1,3 @@ +Fix specialization of generic aliases that are generic over a +:class:`typing.ParamSpec` and have been specialized with a +nested type variable. diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 64b4e2645cbaee..24ea0441d81101 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -180,11 +180,22 @@ tuple_extend(PyObject **dst, Py_ssize_t dstindex, PyObject * _Py_make_parameters(PyObject *args) { + assert(PyTuple_Check(args) || PyList_Check(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 = PyTuple_GET_ITEM(args, iarg); @@ -195,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) { @@ -205,8 +217,19 @@ _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))) { + // 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); + Py_XDECREF(tuple_args); + return NULL; + } + } if (subparams && PyTuple_Check(subparams)) { Py_ssize_t len2 = PyTuple_GET_SIZE(subparams); Py_ssize_t needed = len2 - 1 - (iarg - iparam); @@ -215,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; } } @@ -229,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; } @@ -416,11 +442,22 @@ _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]] */ + assert (PyTuple_Check(args) || PyList_Check(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++) { @@ -430,17 +467,46 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje 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); + 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); + } + jarg++; + continue; + } int unpack = _is_unpacked_typevartuple(arg); 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) { @@ -455,6 +521,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) { @@ -463,6 +530,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; } } @@ -473,6 +541,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje } Py_DECREF(item); + Py_XDECREF(tuple_args); return newargs; } _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com