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(&parameters, 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(&parameters, 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

Reply via email to