Skip to content

Commit

Permalink
gh-124445: Allow specializing generic ParamSpec aliases (#124512)
Browse files Browse the repository at this point in the history
Co-authored-by: Jelle Zijlstra <[email protected]>
Co-authored-by: Alex Waygood <[email protected]>
  • Loading branch information
3 people authored Mar 3, 2025
1 parent 3a7f17c commit bbf1979
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 2 deletions.
70 changes: 70 additions & 0 deletions Lib/test/test_genericalias.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
73 changes: 71 additions & 2 deletions Objects/genericaliasobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -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);
Expand All @@ -215,6 +238,7 @@ _Py_make_parameters(PyObject *args)
if (_PyTuple_Resize(&parameters, len) < 0) {
Py_DECREF(subparams);
Py_DECREF(parameters);
Py_XDECREF(tuple_args);
return NULL;
}
}
Expand All @@ -229,9 +253,11 @@ _Py_make_parameters(PyObject *args)
if (iparam < len) {
if (_PyTuple_Resize(&parameters, iparam) < 0) {
Py_XDECREF(parameters);
Py_XDECREF(tuple_args);
return NULL;
}
}
Py_XDECREF(tuple_args);
return parameters;
}

Expand Down Expand Up @@ -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++) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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;
}
}
Expand All @@ -473,6 +541,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
}

Py_DECREF(item);
Py_XDECREF(tuple_args);
return newargs;
}

Expand Down

0 comments on commit bbf1979

Please sign in to comment.