gh-124445: Allow specializing generic ParamSpec aliases (#124512)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
@@ -474,6 +474,76 @@ class BaseTest(unittest.TestCase):
|
||||
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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user