Skip to content

Commit bbf1979

Browse files
tomasr8JelleZijlstraAlexWaygood
authored
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>
1 parent 3a7f17c commit bbf1979

File tree

3 files changed

+144
-2
lines changed

3 files changed

+144
-2
lines changed

Lib/test/test_genericalias.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,76 @@ def test_del_iter(self):
474474
iter_x = iter(t)
475475
del iter_x
476476

477+
def test_paramspec_specialization(self):
478+
# gh-124445
479+
T = TypeVar("T")
480+
U = TypeVar("U")
481+
type X[**P] = Callable[P, int]
482+
483+
generic = X[[T]]
484+
self.assertEqual(generic.__args__, ([T],))
485+
self.assertEqual(generic.__parameters__, (T,))
486+
specialized = generic[str]
487+
self.assertEqual(specialized.__args__, ([str],))
488+
self.assertEqual(specialized.__parameters__, ())
489+
490+
generic = X[(T,)]
491+
self.assertEqual(generic.__args__, (T,))
492+
self.assertEqual(generic.__parameters__, (T,))
493+
specialized = generic[str]
494+
self.assertEqual(specialized.__args__, (str,))
495+
self.assertEqual(specialized.__parameters__, ())
496+
497+
generic = X[[T, U]]
498+
self.assertEqual(generic.__args__, ([T, U],))
499+
self.assertEqual(generic.__parameters__, (T, U))
500+
specialized = generic[str, int]
501+
self.assertEqual(specialized.__args__, ([str, int],))
502+
self.assertEqual(specialized.__parameters__, ())
503+
504+
generic = X[(T, U)]
505+
self.assertEqual(generic.__args__, (T, U))
506+
self.assertEqual(generic.__parameters__, (T, U))
507+
specialized = generic[str, int]
508+
self.assertEqual(specialized.__args__, (str, int))
509+
self.assertEqual(specialized.__parameters__, ())
510+
511+
def test_nested_paramspec_specialization(self):
512+
# gh-124445
513+
type X[**P, T] = Callable[P, T]
514+
515+
x_list = X[[int, str], float]
516+
self.assertEqual(x_list.__args__, ([int, str], float))
517+
self.assertEqual(x_list.__parameters__, ())
518+
519+
x_tuple = X[(int, str), float]
520+
self.assertEqual(x_tuple.__args__, ((int, str), float))
521+
self.assertEqual(x_tuple.__parameters__, ())
522+
523+
U = TypeVar("U")
524+
V = TypeVar("V")
525+
526+
multiple_params_list = X[[int, U], V]
527+
self.assertEqual(multiple_params_list.__args__, ([int, U], V))
528+
self.assertEqual(multiple_params_list.__parameters__, (U, V))
529+
multiple_params_list_specialized = multiple_params_list[str, float]
530+
self.assertEqual(multiple_params_list_specialized.__args__, ([int, str], float))
531+
self.assertEqual(multiple_params_list_specialized.__parameters__, ())
532+
533+
multiple_params_tuple = X[(int, U), V]
534+
self.assertEqual(multiple_params_tuple.__args__, ((int, U), V))
535+
self.assertEqual(multiple_params_tuple.__parameters__, (U, V))
536+
multiple_params_tuple_specialized = multiple_params_tuple[str, float]
537+
self.assertEqual(multiple_params_tuple_specialized.__args__, ((int, str), float))
538+
self.assertEqual(multiple_params_tuple_specialized.__parameters__, ())
539+
540+
deeply_nested = X[[U, [V], int], V]
541+
self.assertEqual(deeply_nested.__args__, ([U, [V], int], V))
542+
self.assertEqual(deeply_nested.__parameters__, (U, V))
543+
deeply_nested_specialized = deeply_nested[str, float]
544+
self.assertEqual(deeply_nested_specialized.__args__, ([str, [float], int], float))
545+
self.assertEqual(deeply_nested_specialized.__parameters__, ())
546+
477547

478548
class TypeIterationTests(unittest.TestCase):
479549
_UNITERABLE_TYPES = (list, tuple)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix specialization of generic aliases that are generic over a
2+
:class:`typing.ParamSpec` and have been specialized with a
3+
nested type variable.

Objects/genericaliasobject.c

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,22 @@ tuple_extend(PyObject **dst, Py_ssize_t dstindex,
180180
PyObject *
181181
_Py_make_parameters(PyObject *args)
182182
{
183+
assert(PyTuple_Check(args) || PyList_Check(args));
184+
const bool is_args_list = PyList_Check(args);
185+
PyObject *tuple_args = NULL;
186+
if (is_args_list) {
187+
args = tuple_args = PySequence_Tuple(args);
188+
if (args == NULL) {
189+
return NULL;
190+
}
191+
}
183192
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
184193
Py_ssize_t len = nargs;
185194
PyObject *parameters = PyTuple_New(len);
186-
if (parameters == NULL)
195+
if (parameters == NULL) {
196+
Py_XDECREF(tuple_args);
187197
return NULL;
198+
}
188199
Py_ssize_t iparam = 0;
189200
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
190201
PyObject *t = PyTuple_GET_ITEM(args, iarg);
@@ -195,6 +206,7 @@ _Py_make_parameters(PyObject *args)
195206
int rc = PyObject_HasAttrWithError(t, &_Py_ID(__typing_subst__));
196207
if (rc < 0) {
197208
Py_DECREF(parameters);
209+
Py_XDECREF(tuple_args);
198210
return NULL;
199211
}
200212
if (rc) {
@@ -205,8 +217,19 @@ _Py_make_parameters(PyObject *args)
205217
if (PyObject_GetOptionalAttr(t, &_Py_ID(__parameters__),
206218
&subparams) < 0) {
207219
Py_DECREF(parameters);
220+
Py_XDECREF(tuple_args);
208221
return NULL;
209222
}
223+
if (!subparams && (PyTuple_Check(t) || PyList_Check(t))) {
224+
// Recursively call _Py_make_parameters for lists/tuples and
225+
// add the results to the current parameters.
226+
subparams = _Py_make_parameters(t);
227+
if (subparams == NULL) {
228+
Py_DECREF(parameters);
229+
Py_XDECREF(tuple_args);
230+
return NULL;
231+
}
232+
}
210233
if (subparams && PyTuple_Check(subparams)) {
211234
Py_ssize_t len2 = PyTuple_GET_SIZE(subparams);
212235
Py_ssize_t needed = len2 - 1 - (iarg - iparam);
@@ -215,6 +238,7 @@ _Py_make_parameters(PyObject *args)
215238
if (_PyTuple_Resize(&parameters, len) < 0) {
216239
Py_DECREF(subparams);
217240
Py_DECREF(parameters);
241+
Py_XDECREF(tuple_args);
218242
return NULL;
219243
}
220244
}
@@ -229,9 +253,11 @@ _Py_make_parameters(PyObject *args)
229253
if (iparam < len) {
230254
if (_PyTuple_Resize(&parameters, iparam) < 0) {
231255
Py_XDECREF(parameters);
256+
Py_XDECREF(tuple_args);
232257
return NULL;
233258
}
234259
}
260+
Py_XDECREF(tuple_args);
235261
return parameters;
236262
}
237263

@@ -416,11 +442,22 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
416442
t = list[T]; t[int] -> newargs = [int]
417443
t = dict[str, T]; t[int] -> newargs = [str, int]
418444
t = dict[T, list[S]]; t[str, int] -> newargs = [str, list[int]]
445+
t = list[[T]]; t[str] -> newargs = [[str]]
419446
*/
447+
assert (PyTuple_Check(args) || PyList_Check(args));
448+
const bool is_args_list = PyList_Check(args);
449+
PyObject *tuple_args = NULL;
450+
if (is_args_list) {
451+
args = tuple_args = PySequence_Tuple(args);
452+
if (args == NULL) {
453+
return NULL;
454+
}
455+
}
420456
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
421457
PyObject *newargs = PyTuple_New(nargs);
422458
if (newargs == NULL) {
423459
Py_DECREF(item);
460+
Py_XDECREF(tuple_args);
424461
return NULL;
425462
}
426463
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
430467
jarg++;
431468
continue;
432469
}
433-
470+
// Recursively substitute params in lists/tuples.
471+
if (PyTuple_Check(arg) || PyList_Check(arg)) {
472+
PyObject *subargs = _Py_subs_parameters(self, arg, parameters, item);
473+
if (subargs == NULL) {
474+
Py_DECREF(newargs);
475+
Py_DECREF(item);
476+
Py_XDECREF(tuple_args);
477+
return NULL;
478+
}
479+
if (PyTuple_Check(arg)) {
480+
PyTuple_SET_ITEM(newargs, jarg, subargs);
481+
}
482+
else {
483+
// _Py_subs_parameters returns a tuple. If the original arg was a list,
484+
// convert subargs to a list as well.
485+
PyObject *subargs_list = PySequence_List(subargs);
486+
Py_DECREF(subargs);
487+
if (subargs_list == NULL) {
488+
Py_DECREF(newargs);
489+
Py_DECREF(item);
490+
Py_XDECREF(tuple_args);
491+
return NULL;
492+
}
493+
PyTuple_SET_ITEM(newargs, jarg, subargs_list);
494+
}
495+
jarg++;
496+
continue;
497+
}
434498
int unpack = _is_unpacked_typevartuple(arg);
435499
if (unpack < 0) {
436500
Py_DECREF(newargs);
437501
Py_DECREF(item);
502+
Py_XDECREF(tuple_args);
438503
return NULL;
439504
}
440505
PyObject *subst;
441506
if (PyObject_GetOptionalAttr(arg, &_Py_ID(__typing_subst__), &subst) < 0) {
442507
Py_DECREF(newargs);
443508
Py_DECREF(item);
509+
Py_XDECREF(tuple_args);
444510
return NULL;
445511
}
446512
if (subst) {
@@ -455,6 +521,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
455521
if (arg == NULL) {
456522
Py_DECREF(newargs);
457523
Py_DECREF(item);
524+
Py_XDECREF(tuple_args);
458525
return NULL;
459526
}
460527
if (unpack) {
@@ -463,6 +530,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
463530
Py_DECREF(arg);
464531
if (jarg < 0) {
465532
Py_DECREF(item);
533+
Py_XDECREF(tuple_args);
466534
return NULL;
467535
}
468536
}
@@ -473,6 +541,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
473541
}
474542

475543
Py_DECREF(item);
544+
Py_XDECREF(tuple_args);
476545
return newargs;
477546
}
478547

0 commit comments

Comments
 (0)