bpo-41559: Change PEP 612 implementation to pure Python (#25449)

diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py
index 87302ac..dddf8a2 100644
--- a/Lib/_collections_abc.py
+++ b/Lib/_collections_abc.py
@@ -443,6 +443,18 @@ def __create_ga(cls, origin, args):
             ga_args = args
         return super().__new__(cls, origin, ga_args)
 
+    @property
+    def __parameters__(self):
+        params = []
+        for arg in self.__args__:
+            # Looks like a genericalias
+            if hasattr(arg, "__parameters__") and isinstance(arg.__parameters__, tuple):
+                params.extend(arg.__parameters__)
+            else:
+                if _is_typevarlike(arg):
+                    params.append(arg)
+        return tuple(dict.fromkeys(params))
+
     def __repr__(self):
         if _has_special_args(self.__args__):
             return super().__repr__()
@@ -458,16 +470,50 @@ def __reduce__(self):
 
     def __getitem__(self, item):
         # Called during TypeVar substitution, returns the custom subclass
-        # rather than the default types.GenericAlias object.
-        ga = super().__getitem__(item)
-        args = ga.__args__
-        # args[0] occurs due to things like Z[[int, str, bool]] from PEP 612
-        if not isinstance(ga.__args__[0], tuple):
-            t_result = ga.__args__[-1]
-            t_args = ga.__args__[:-1]
-            args = (t_args, t_result)
-        return _CallableGenericAlias(Callable, args)
+        # rather than the default types.GenericAlias object.  Most of the
+        # code is copied from typing's _GenericAlias and the builtin
+        # types.GenericAlias.
 
+        # A special case in PEP 612 where if X = Callable[P, int],
+        # then X[int, str] == X[[int, str]].
+        param_len = len(self.__parameters__)
+        if param_len == 0:
+            raise TypeError(f'There are no type or parameter specification'
+                            f'variables left in {self}')
+        if (param_len == 1
+                and isinstance(item, (tuple, list))
+                and len(item) > 1) or not isinstance(item, tuple):
+            item = (item,)
+        item_len = len(item)
+        if item_len != param_len:
+            raise TypeError(f'Too {"many" if item_len > param_len else "few"}'
+                            f' arguments for {self};'
+                            f' actual {item_len}, expected {param_len}')
+        subst = dict(zip(self.__parameters__, item))
+        new_args = []
+        for arg in self.__args__:
+            if _is_typevarlike(arg):
+                arg = subst[arg]
+            # Looks like a GenericAlias
+            elif hasattr(arg, '__parameters__') and isinstance(arg.__parameters__, tuple):
+                subparams = arg.__parameters__
+                if subparams:
+                    subargs = tuple(subst[x] for x in subparams)
+                    arg = arg[subargs]
+            new_args.append(arg)
+
+        # args[0] occurs due to things like Z[[int, str, bool]] from PEP 612
+        if not isinstance(new_args[0], (tuple, list)):
+            t_result = new_args[-1]
+            t_args = new_args[:-1]
+            new_args = (t_args, t_result)
+        return _CallableGenericAlias(Callable, tuple(new_args))
+
+def _is_typevarlike(arg):
+    obj = type(arg)
+    # looks like a TypeVar/ParamSpec
+    return (obj.__module__ == 'typing'
+            and obj.__name__ in {'ParamSpec', 'TypeVar'})
 
 def _has_special_args(args):
     """Checks if args[0] matches either ``...``, ``ParamSpec`` or
diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py
index fd024dc..9f92739 100644
--- a/Lib/test/test_genericalias.py
+++ b/Lib/test/test_genericalias.py
@@ -353,6 +353,12 @@ def test_abc_callable(self):
             self.assertEqual(repr(C4[dict]).split(".")[-1], "Callable[[int, dict], str]")
             self.assertEqual(C4[dict], Callable[[int, dict], str])
 
+            # substitute a nested GenericAlias (both typing and the builtin
+            # version)
+            C5 = Callable[[typing.List[T], tuple[K, T], V], int]
+            self.assertEqual(C5[int, str, float],
+                             Callable[[typing.List[int], tuple[str, int], float], int])
+
         with self.subTest("Testing type erasure"):
             class C1(Callable):
                 def __call__(self):
@@ -391,5 +397,16 @@ def __call__(self):
             self.assertEqual(repr(C1), "collections.abc.Callable"
                                        "[typing.Concatenate[int, ~P], int]")
 
+        with self.subTest("Testing TypeErrors"):
+            with self.assertRaisesRegex(TypeError, "variables left in"):
+                alias[int]
+            P = typing.ParamSpec('P')
+            C1 = Callable[P, T]
+            with self.assertRaisesRegex(TypeError, "many arguments for"):
+                C1[int, str, str]
+            with self.assertRaisesRegex(TypeError, "few arguments for"):
+                C1[int]
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2021-04-17-10-49-57.bpo-41559.caIwt9.rst b/Misc/NEWS.d/next/Library/2021-04-17-10-49-57.bpo-41559.caIwt9.rst
new file mode 100644
index 0000000..11db423
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-04-17-10-49-57.bpo-41559.caIwt9.rst
@@ -0,0 +1,6 @@
+:pep:`612` is now implemented purely in Python; builtin ``types.GenericAlias``
+objects no longer include ``typing.ParamSpec`` in ``__parameters__``
+(with the exception of ``collections.abc.Callable``\ 's ``GenericAlias``).
+This means previously invalid uses of ``ParamSpec`` (such as
+``list[P]``) which worked in earlier versions of Python 3.10 alpha,
+will now raise ``TypeError`` during substitution.
diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c
index 8fae83b..756a7ce 100644
--- a/Objects/genericaliasobject.c
+++ b/Objects/genericaliasobject.c
@@ -156,25 +156,13 @@ ga_repr(PyObject *self)
     return NULL;
 }
 
-/* Checks if a variable number of names are from typing.py.
-*  If any one of the names are found, return 1, else 0.
-**/
-static inline int
-is_typing_name(PyObject *obj, int num, ...)
+// isinstance(obj, TypeVar) without importing typing.py.
+// Returns -1 for errors.
+static int
+is_typevar(PyObject *obj)
 {
-    va_list names;
-    va_start(names, num);
-
     PyTypeObject *type = Py_TYPE(obj);
-    int hit = 0;
-    for (int i = 0; i < num; ++i) {
-        if (!strcmp(type->tp_name, va_arg(names, const char *))) {
-            hit = 1;
-            break;
-        }
-    }
-    va_end(names);
-    if (!hit) {
+    if (strcmp(type->tp_name, "TypeVar") != 0) {
         return 0;
     }
     PyObject *module = PyObject_GetAttrString((PyObject *)type, "__module__");
@@ -184,24 +172,9 @@ is_typing_name(PyObject *obj, int num, ...)
     int res = PyUnicode_Check(module)
         && _PyUnicode_EqualToASCIIString(module, "typing");
     Py_DECREF(module);
-    
     return res;
 }
 
-// isinstance(obj, (TypeVar, ParamSpec)) without importing typing.py.
-// Returns -1 for errors.
-static inline int
-is_typevarlike(PyObject *obj)
-{
-    return is_typing_name(obj, 2, "TypeVar", "ParamSpec");
-}
-
-static inline int
-is_paramspec(PyObject *obj)
-{
-    return is_typing_name(obj, 1, "ParamSpec");
-}
-
 // Index of item in self[:len], or -1 if not found (self is a tuple)
 static Py_ssize_t
 tuple_index(PyObject *self, Py_ssize_t len, PyObject *item)
@@ -236,7 +209,7 @@ make_parameters(PyObject *args)
     Py_ssize_t iparam = 0;
     for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
         PyObject *t = PyTuple_GET_ITEM(args, iarg);
-        int typevar = is_typevarlike(t);
+        int typevar = is_typevar(t);
         if (typevar < 0) {
             Py_DECREF(parameters);
             return NULL;
@@ -306,14 +279,7 @@ subs_tvars(PyObject *obj, PyObject *params, PyObject **argitems)
             if (iparam >= 0) {
                 arg = argitems[iparam];
             }
-            // convert all the lists inside args to tuples to help
-            // with caching in other libaries
-            if (PyList_CheckExact(arg)) {
-                arg = PyList_AsTuple(arg);
-            }
-            else {
-                Py_INCREF(arg);
-            }
+            Py_INCREF(arg);
             PyTuple_SET_ITEM(subargs, i, arg);
         }
 
@@ -348,19 +314,11 @@ ga_getitem(PyObject *self, PyObject *item)
     int is_tuple = PyTuple_Check(item);
     Py_ssize_t nitems = is_tuple ? PyTuple_GET_SIZE(item) : 1;
     PyObject **argitems = is_tuple ? &PyTuple_GET_ITEM(item, 0) : &item;
-    // A special case in PEP 612 where if X = Callable[P, int], 
-    // then X[int, str] == X[[int, str]].
-    if (nparams == 1 && nitems > 1 && is_tuple &&
-        is_paramspec(PyTuple_GET_ITEM(alias->parameters, 0))) {
-        argitems = &item;
-    }
-    else {
-        if (nitems != nparams) {
-            return PyErr_Format(PyExc_TypeError,
-                "Too %s arguments for %R",
-                nitems > nparams ? "many" : "few",
-                self);
-        }
+    if (nitems != nparams) {
+        return PyErr_Format(PyExc_TypeError,
+                            "Too %s arguments for %R",
+                            nitems > nparams ? "many" : "few",
+                            self);
     }
     /* Replace all type variables (specified by alias->parameters)
        with corresponding values specified by argitems.
@@ -375,7 +333,7 @@ ga_getitem(PyObject *self, PyObject *item)
     }
     for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
         PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
-        int typevar = is_typevarlike(arg);
+        int typevar = is_typevar(arg);
         if (typevar < 0) {
             Py_DECREF(newargs);
             return NULL;
@@ -384,13 +342,7 @@ ga_getitem(PyObject *self, PyObject *item)
             Py_ssize_t iparam = tuple_index(alias->parameters, nparams, arg);
             assert(iparam >= 0);
             arg = argitems[iparam];
-            // convert lists to tuples to help with caching in other libaries.
-            if (PyList_CheckExact(arg)) {
-                arg = PyList_AsTuple(arg);
-            }
-            else {
-                Py_INCREF(arg);
-            }
+            Py_INCREF(arg);
         }
         else {
             arg = subs_tvars(arg, alias->parameters, argitems);