From 386b12b8563b4c2e9f1799287f5d5f13504fe373 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 1 Nov 2022 09:33:09 +0200 Subject: [PATCH] [3.10] gh-98852: Fix subscription of types.GenericAlias instances (GH-98920) Fix subscription of types.GenericAlias instances containing bare generic types: for example tuple[A, T][int], where A is a generic type, and T is a type variable. --- Lib/_collections_abc.py | 5 ++ Lib/test/test_typing.py | 55 +++++++++++++++++++ ...2-10-31-21-01-35.gh-issue-98852.MYaRN6.rst | 3 + Objects/genericaliasobject.c | 8 +++ 4 files changed, 71 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-10-31-21-01-35.gh-issue-98852.MYaRN6.rst diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 40417dc1d3133d..72fd633cf9ac2f 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -441,6 +441,8 @@ def __new__(cls, origin, args): def __parameters__(self): params = [] for arg in self.__args__: + if isinstance(arg, type) and not isinstance(arg, GenericAlias): + continue # Looks like a genericalias if hasattr(arg, "__parameters__") and isinstance(arg.__parameters__, tuple): params.extend(arg.__parameters__) @@ -486,6 +488,9 @@ def __getitem__(self, item): subst = dict(zip(self.__parameters__, item)) new_args = [] for arg in self.__args__: + if isinstance(arg, type) and not isinstance(arg, GenericAlias): + new_args.append(arg) + continue if _is_typevarlike(arg): if _is_param_expr(arg): arg = subst[arg] diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 34f944416070cd..6c53154686c914 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2498,6 +2498,61 @@ def test_subclass_special_form(self): class Foo(obj): pass + def test_complex_subclasses(self): + T_co = TypeVar("T_co", covariant=True) + + class Base(Generic[T_co]): + ... + + T = TypeVar("T") + + # see gh-94607: this fails in that bug + class Sub(Base, Generic[T]): + ... + + def test_parameter_detection(self): + self.assertEqual(List[T].__parameters__, (T,)) + self.assertEqual(List[List[T]].__parameters__, (T,)) + class A: + __parameters__ = (T,) + # Bare classes should be skipped + for a in (List, list): + for b in (int, TypeVar, ParamSpec, types.GenericAlias, types.UnionType): + with self.subTest(generic=a, sub=b): + with self.assertRaisesRegex(TypeError, + '.* is not a generic class|' + 'no type variables left'): + a[b][str] + # Duck-typing anything that looks like it has __parameters__. + # C version of GenericAlias + self.assertEqual(list[A()].__parameters__, (T,)) + + def test_non_generic_subscript(self): + T = TypeVar('T') + class G(Generic[T]): + pass + + for s in (int, G, List, list, + TypeVar, ParamSpec, + types.GenericAlias, types.UnionType): + + for t in Tuple, tuple: + with self.subTest(tuple=t, sub=s): + self.assertEqual(t[s, T][int], t[s, int]) + self.assertEqual(t[T, s][int], t[int, s]) + a = t[s] + with self.assertRaises(TypeError): + a[int] + + for c in Callable, collections.abc.Callable: + with self.subTest(callable=c, sub=s): + self.assertEqual(c[[s], T][int], c[[s], int]) + self.assertEqual(c[[T], s][int], c[[int], s]) + a = c[[s], s] + with self.assertRaises(TypeError): + a[int] + + class ClassVarTests(BaseTestCase): def test_basics(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-10-31-21-01-35.gh-issue-98852.MYaRN6.rst b/Misc/NEWS.d/next/Core and Builtins/2022-10-31-21-01-35.gh-issue-98852.MYaRN6.rst new file mode 100644 index 00000000000000..25c473717ca2c3 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-10-31-21-01-35.gh-issue-98852.MYaRN6.rst @@ -0,0 +1,3 @@ +Fix subscription of :class:`types.GenericAlias` instances containing bare +generic types: for example ``tuple[A, T][int]``, +where ``A`` is a generic type, and ``T`` is a type variable. diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index f52bc974f4d810..9edb6d23725d71 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -209,6 +209,9 @@ _Py_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); + if (PyType_Check(t)) { + continue; + } int typevar = is_typevar(t); if (typevar < 0) { Py_DECREF(parameters); @@ -260,6 +263,11 @@ _Py_make_parameters(PyObject *args) static PyObject * subs_tvars(PyObject *obj, PyObject *params, PyObject **argitems) { + if (PyType_Check(obj)) { + Py_INCREF(obj); + return obj; + } + _Py_IDENTIFIER(__parameters__); PyObject *subparams; if (_PyObject_LookupAttrId(obj, &PyId___parameters__, &subparams) < 0) {