From edb320234e7a5fd260ed1b3be38bf5e1dbae43c2 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 27 Jul 2021 20:32:20 +0300 Subject: [PATCH 1/6] bpo-44796: Add __parameters__ and __getitem__ in TypeVar and ParamSpec --- Lib/_collections_abc.py | 43 +-------- Lib/test/test_typing.py | 95 +++++++++++++++---- Lib/typing.py | 82 +++++++++------- .../2021-07-31-16-33-33.bpo-44796.-pyVhM.rst | 2 + Objects/genericaliasobject.c | 80 ++++------------ 5 files changed, 146 insertions(+), 156 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-07-31-16-33-33.bpo-44796.-pyVhM.rst diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index bff58ad4a7f6a7..e89c7a83cb0e79 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -443,18 +443,6 @@ 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__() @@ -476,31 +464,12 @@ def __getitem__(self, item): # 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 + if (len(self.__parameters__) == 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) + + new_args = super().__getitem__(item).__args__ # args[0] occurs due to things like Z[[int, str, bool]] from PEP 612 if not isinstance(new_args[0], (tuple, list)): @@ -509,12 +478,6 @@ def __getitem__(self, item): 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 ``_ConcatenateGenericAlias`` from typing.py diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 06bd49b593a77b..bf026f2c8287d5 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -253,6 +253,35 @@ def test_no_bivariant(self): with self.assertRaises(ValueError): TypeVar('T', covariant=True, contravariant=True) + def test_subscript(self): + T = TypeVar('T') + self.assertEqual(T.__parameters__, (T,)) + self.assertIs(T.__parameters__[0], T) + self.assertIs(T[int], int) + self.assertEqual(T[list[int]], list[int]) + self.assertEqual(T[List[int]], List[int]) + self.assertEqual(T[List], List) + self.assertIs(T[Any], Any) + self.assertIs(T[None], type(None)) + self.assertIs(T[T], T) + self.assertIs(T[(int,)], int) + + def test_bad_subscript(self): + T = TypeVar('T') + bad_args = ( + 42, ..., [int], (), (int, str), Union, + Generic, Generic[T], Protocol, Protocol[T], + Final, Final[int], ClassVar, ClassVar[int], + ) + for arg in bad_args: + with self.subTest(arg=arg): + with self.assertRaises(TypeError): + T[arg] + with self.assertRaises(TypeError): + List[T][arg] + with self.assertRaises(TypeError): + list[T][arg] + class UnionTests(BaseTestCase): @@ -510,6 +539,37 @@ def test_ellipsis_in_generic(self): # Shouldn't crash; see https://github.com/python/typing/issues/259 typing.List[Callable[..., str]] + def test_subscript(self): + T = TypeVar("T") + T2 = TypeVar('T2') + + C = Callable[[T], T2] + self.assertEqual(C[int, str], Callable[[int], str]) + self.assertEqual(C[int, NoReturn], Callable[[int], NoReturn]) + self.assertEqual(C[None, None], Callable[[type(None)], type(None)]) + + C = Callable[..., T] + self.assertEqual(C[str], Callable[..., str]) + + P = ParamSpec("P") + C = Callable[P, T] + self.assertEqual(C[int, str], Callable[[int], str]) + self.assertEqual(C[[int, str], float], Callable[[int, str], float]) + self.assertEqual(C[..., float], Callable[..., float]) + self.assertEqual(C[P, float], Callable[P, float]) + self.assertEqual(C[Concatenate[int, P], float], + Callable[Concatenate[int, P], float]) + + C = Callable[P, str] + self.assertEqual(C[int], Callable[[int], str]) + self.assertEqual(C[int, dict], Callable[[int, dict], str]) + self.assertEqual(C[...], Callable[..., str]) + self.assertEqual(C[P], Callable[P, str]) + self.assertEqual(C[Concatenate[int, P]], + Callable[Concatenate[int, P], str]) + + # TODO: C = Callable[Concatenate[str, P], float] + class LiteralTests(BaseTestCase): def test_basics(self): @@ -1890,7 +1950,10 @@ def test_all_repr_eq_any(self): for obj in objs: self.assertNotEqual(repr(obj), '') self.assertEqual(obj, obj) - if getattr(obj, '__parameters__', None) and len(obj.__parameters__) == 1: + if (getattr(obj, '__parameters__', None) + and not isinstance(obj, typing.TypeVar) + and isinstance(obj.__parameters__, tuple) + and len(obj.__parameters__) == 1): self.assertEqual(obj[Any].__args__, (Any,)) if isinstance(obj, type): for base in obj.__mro__: @@ -4496,28 +4559,18 @@ class Z(Generic[P]): self.assertEqual(G5.__parameters__, G6.__parameters__) self.assertEqual(G5, G6) - def test_var_substitution(self): + def test_subscript(self): T = TypeVar("T") P = ParamSpec("P") - C1 = Callable[P, T] - self.assertEqual(C1[int, str], Callable[[int], str]) - self.assertEqual(C1[[int, str, dict], float], Callable[[int, str, dict], float]) - - def test_no_paramspec_in__parameters__(self): - # ParamSpec should not be found in __parameters__ - # of generics. Usages outside Callable, Concatenate - # and Generic are invalid. - T = TypeVar("T") - P = ParamSpec("P") - self.assertNotIn(P, List[P].__parameters__) - self.assertIn(T, Tuple[T, P].__parameters__) - - # Test for consistency with builtin generics. - self.assertNotIn(P, list[P].__parameters__) - self.assertIn(T, tuple[T, P].__parameters__) - - self.assertNotIn(P, (list[P] | int).__parameters__) - self.assertIn(T, (tuple[T, P] | int).__parameters__) + self.assertEqual(P.__parameters__, (P,)) + self.assertIs(P.__parameters__[0], P) + self.assertEqual(P[int], (int,)) + self.assertEqual(P[int, str], (int, str)) + self.assertEqual(P[[int, str]], (int, str)) + self.assertEqual(P[None], (type(None),)) + self.assertIs(P[...], ...) + self.assertIs(P[P], P) + self.assertEqual(P[Concatenate[int, P]], Concatenate[int, P]) def test_paramspec_in_nested_generics(self): # Although ParamSpec should not be found in __parameters__ of most diff --git a/Lib/typing.py b/Lib/typing.py index 702bb647269d0b..d07732e99652b1 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -182,6 +182,10 @@ def _type_check(arg, msg, is_argument=True, module=None): return arg +def _is_param_spec(arg): + return arg is ... or isinstance(arg, (ParamSpec, _ConcatenateGenericAlias)) + + def _type_repr(obj): """Return the repr() of an object, special-casing types (internal helper). @@ -203,21 +207,18 @@ def _type_repr(obj): return repr(obj) -def _collect_type_vars(types_, typevar_types=None): - """Collect all type variable contained - in types in order of first appearance (lexicographic order). For example:: +def _collect_parameters(args): + """Collect all type variables and parameter specifications in args + in order of first appearance (lexicographic order). For example:: - _collect_type_vars((T, List[S, T])) == (T, S) + _collect_parameters((T, Callable[P, T])) == (T, P) """ - if typevar_types is None: - typevar_types = TypeVar - tvars = [] - for t in types_: - if isinstance(t, typevar_types) and t not in tvars: - tvars.append(t) - if isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)): - tvars.extend([t for t in t.__parameters__ if t not in tvars]) - return tuple(tvars) + parameters = [] + for t in args: + for x in getattr(t, '__parameters__', ()): + if x not in parameters: + parameters.append(x) + return tuple(parameters) def _check_generic(cls, parameters, elen): @@ -740,6 +741,10 @@ def __repr__(self): def __reduce__(self): return self.__name__ + @property + def __parameters__(self): + return (self,) + class TypeVar( _Final, _Immutable, _TypeVarLike, _root=True): """Type variable. @@ -802,6 +807,13 @@ def __init__(self, name, *constraints, bound=None, if def_mod != 'typing': self.__module__ = def_mod + def __getitem__(self, arg): + if isinstance(arg, tuple) and len(arg) == 1: + arg, = arg + msg = "Parameters to generic types must be types." + arg = _type_check(arg, msg, is_argument=True) + return arg + class ParamSpecArgs(_Final, _Immutable, _root=True): """The args for a ParamSpec object. @@ -905,6 +917,19 @@ def __init__(self, name, *, bound=None, covariant=False, contravariant=False): if def_mod != 'typing': self.__module__ = def_mod + def __getitem__(self, arg): + if isinstance(arg, tuple) and len(arg) == 1: + arg, = arg + if _is_param_spec(arg): + pass + elif isinstance(arg, (list, tuple)): + arg = tuple(_type_check(a, "Expected a type.") for a in arg) + else: + msg = "Expected a list of types, ellipsis, ParamSpec, or Concatenate." + arg = _type_check(arg, msg) + arg = (arg,) + return arg + def _is_dunder(attr): return attr.startswith('__') and attr.endswith('__') @@ -959,7 +984,7 @@ def __getattr__(self, attr): def __setattr__(self, attr, val): if _is_dunder(attr) or attr in {'_name', '_inst', '_nparams', - '_typevar_types', '_paramspec_tvars'}: + '_paramspec_tvars'}: super().__setattr__(attr, val) else: setattr(self.__origin__, attr, val) @@ -985,7 +1010,6 @@ def __subclasscheck__(self, cls): class _GenericAlias(_BaseGenericAlias, _root=True): def __init__(self, origin, params, *, inst=True, name=None, - _typevar_types=TypeVar, _paramspec_tvars=False): super().__init__(origin, inst=inst, name=name) if not isinstance(params, tuple): @@ -993,9 +1017,8 @@ def __init__(self, origin, params, *, inst=True, name=None, self.__args__ = tuple(... if a is _TypingEllipsis else () if a is _TypingEmpty else a for a in params) - self.__parameters__ = _collect_type_vars(params, typevar_types=_typevar_types) - self._typevar_types = _typevar_types self._paramspec_tvars = _paramspec_tvars + self.__parameters__ = _collect_parameters(params) if not name: self.__module__ = origin.__module__ @@ -1030,13 +1053,10 @@ def __getitem__(self, params): subst = dict(zip(self.__parameters__, params)) new_args = [] for arg in self.__args__: - if isinstance(arg, self._typevar_types): - arg = subst[arg] - elif isinstance(arg, (_GenericAlias, GenericAlias, types.UnionType)): - subparams = arg.__parameters__ - if subparams: - subargs = tuple(subst[x] for x in subparams) - arg = arg[subargs] + subparams = getattr(arg, '__parameters__', ()) + if subparams: + subargs = tuple(subst[x] for x in subparams) + arg = arg[subargs] # Required to flatten out the args for CallableGenericAlias if self.__origin__ == collections.abc.Callable and isinstance(arg, tuple): new_args.extend(arg) @@ -1129,8 +1149,7 @@ class _CallableGenericAlias(_GenericAlias, _root=True): def __repr__(self): assert self._name == 'Callable' args = self.__args__ - if len(args) == 2 and (args[0] is Ellipsis - or isinstance(args[0], (ParamSpec, _ConcatenateGenericAlias))): + if len(args) == 2 and _is_param_spec(args[0]): return super().__repr__() return (f'typing.Callable' f'[[{", ".join([_type_repr(a) for a in args[:-1]])}], ' @@ -1138,8 +1157,7 @@ def __repr__(self): def __reduce__(self): args = self.__args__ - if not (len(args) == 2 and (args[0] is Ellipsis - or isinstance(args[0], (ParamSpec, _ConcatenateGenericAlias)))): + if not (len(args) == 2 and _is_param_spec(args[0])): args = list(args[:-1]), args[-1] return operator.getitem, (Callable, args) @@ -1148,7 +1166,6 @@ class _CallableType(_SpecialGenericAlias, _root=True): def copy_with(self, params): return _CallableGenericAlias(self.__origin__, params, name=self._name, inst=self._inst, - _typevar_types=(TypeVar, ParamSpec), _paramspec_tvars=True) def __getitem__(self, params): @@ -1244,7 +1261,6 @@ def __hash__(self): class _ConcatenateGenericAlias(_GenericAlias, _root=True): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs, - _typevar_types=(TypeVar, ParamSpec), _paramspec_tvars=True) @@ -1294,7 +1310,6 @@ def __class_getitem__(cls, params): params = _prepare_paramspec_params(cls, params) _check_generic(cls, params, len(cls.__parameters__)) return _GenericAlias(cls, params, - _typevar_types=(TypeVar, ParamSpec), _paramspec_tvars=True) def __init_subclass__(cls, *args, **kwargs): @@ -1307,7 +1322,7 @@ def __init_subclass__(cls, *args, **kwargs): if error: raise TypeError("Cannot inherit from plain Generic") if '__orig_bases__' in cls.__dict__: - tvars = _collect_type_vars(cls.__orig_bases__, (TypeVar, ParamSpec)) + tvars = _collect_parameters(cls.__orig_bases__) # Look for Generic[T1, ..., Tn]. # If found, tvars must be a subset of it. # If not found, tvars is it. @@ -1853,8 +1868,7 @@ def get_args(tp): if isinstance(tp, (_GenericAlias, GenericAlias)): res = tp.__args__ if (tp.__origin__ is collections.abc.Callable - and not (res[0] is Ellipsis - or isinstance(res[0], (ParamSpec, _ConcatenateGenericAlias)))): + and not _is_param_spec(res[0])): res = (list(res[:-1]), res[-1]) return res if isinstance(tp, types.UnionType): diff --git a/Misc/NEWS.d/next/Library/2021-07-31-16-33-33.bpo-44796.-pyVhM.rst b/Misc/NEWS.d/next/Library/2021-07-31-16-33-33.bpo-44796.-pyVhM.rst new file mode 100644 index 00000000000000..bdd4569008f261 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-07-31-16-33-33.bpo-44796.-pyVhM.rst @@ -0,0 +1,2 @@ +Add ``__parameters__`` and ``__getitem__`` in :class:`~typing.TypeVar` and +:class:`~ParamSpec`. diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index dda53cb5338645..0c67062f7f7ed3 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -156,25 +156,6 @@ ga_repr(PyObject *self) return NULL; } -// isinstance(obj, TypeVar) without importing typing.py. -// Returns -1 for errors. -static int -is_typevar(PyObject *obj) -{ - PyTypeObject *type = Py_TYPE(obj); - if (strcmp(type->tp_name, "TypeVar") != 0) { - return 0; - } - PyObject *module = PyObject_GetAttrString((PyObject *)type, "__module__"); - if (module == NULL) { - return -1; - } - int res = PyUnicode_Check(module) - && _PyUnicode_EqualToASCIIString(module, "typing"); - Py_DECREF(module); - return res; -} - // 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) @@ -209,39 +190,29 @@ _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); - int typevar = is_typevar(t); - if (typevar < 0) { + _Py_IDENTIFIER(__parameters__); + PyObject *subparams; + if (_PyObject_LookupAttrId(t, &PyId___parameters__, &subparams) < 0) { Py_DECREF(parameters); return NULL; } - if (typevar) { - iparam += tuple_add(parameters, iparam, t); - } - else { - _Py_IDENTIFIER(__parameters__); - PyObject *subparams; - if (_PyObject_LookupAttrId(t, &PyId___parameters__, &subparams) < 0) { - Py_DECREF(parameters); - return NULL; - } - if (subparams && PyTuple_Check(subparams)) { - Py_ssize_t len2 = PyTuple_GET_SIZE(subparams); - Py_ssize_t needed = len2 - 1 - (iarg - iparam); - if (needed > 0) { - len += needed; - if (_PyTuple_Resize(¶meters, len) < 0) { - Py_DECREF(subparams); - Py_DECREF(parameters); - return NULL; - } - } - for (Py_ssize_t j = 0; j < len2; j++) { - PyObject *t2 = PyTuple_GET_ITEM(subparams, j); - iparam += tuple_add(parameters, iparam, t2); + if (subparams && PyTuple_Check(subparams)) { + Py_ssize_t len2 = PyTuple_GET_SIZE(subparams); + Py_ssize_t needed = len2 - 1 - (iarg - iparam); + if (needed > 0) { + len += needed; + if (_PyTuple_Resize(¶meters, len) < 0) { + Py_DECREF(subparams); + Py_DECREF(parameters); + return NULL; } } - Py_XDECREF(subparams); + for (Py_ssize_t j = 0; j < len2; j++) { + PyObject *t2 = PyTuple_GET_ITEM(subparams, j); + iparam += tuple_add(parameters, iparam, t2); + } } + Py_XDECREF(subparams); } if (iparam < len) { if (_PyTuple_Resize(¶meters, iparam) < 0) { @@ -325,24 +296,11 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje } for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { PyObject *arg = PyTuple_GET_ITEM(args, iarg); - int typevar = is_typevar(arg); - if (typevar < 0) { + arg = subs_tvars(arg, parameters, argitems); + if (arg == NULL) { Py_DECREF(newargs); return NULL; } - if (typevar) { - Py_ssize_t iparam = tuple_index(parameters, nparams, arg); - assert(iparam >= 0); - arg = argitems[iparam]; - Py_INCREF(arg); - } - else { - arg = subs_tvars(arg, parameters, argitems); - if (arg == NULL) { - Py_DECREF(newargs); - return NULL; - } - } PyTuple_SET_ITEM(newargs, iarg, arg); } From 98a7c83ed2c151fab8faf8d9ed7e64cde2a4e2b7 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 5 Aug 2021 08:24:34 +0300 Subject: [PATCH 2/6] More tests. --- Lib/test/test_typing.py | 42 ++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index f3016b102077dc..b867900ddc43d4 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -257,13 +257,13 @@ def test_subscript(self): T = TypeVar('T') self.assertEqual(T.__parameters__, (T,)) self.assertIs(T.__parameters__[0], T) - self.assertIs(T[int], int) - self.assertEqual(T[list[int]], list[int]) - self.assertEqual(T[List[int]], List[int]) - self.assertEqual(T[List], List) - self.assertIs(T[Any], Any) - self.assertIs(T[None], type(None)) - self.assertIs(T[T], T) + self.assertIs(T[int,], int) + self.assertEqual(T[list[int],], list[int]) + self.assertEqual(T[List[int],], List[int]) + self.assertEqual(T[List,], List) + self.assertIs(T[Any,], Any) + self.assertIs(T[None,], type(None)) + self.assertIs(T[T,], T) self.assertIs(T[(int,)], int) def test_bad_subscript(self): @@ -276,7 +276,7 @@ def test_bad_subscript(self): for arg in bad_args: with self.subTest(arg=arg): with self.assertRaises(TypeError): - T[arg] + T[arg,] with self.assertRaises(TypeError): List[T][arg] with self.assertRaises(TypeError): @@ -4733,13 +4733,25 @@ def test_subscript(self): P = ParamSpec("P") self.assertEqual(P.__parameters__, (P,)) self.assertIs(P.__parameters__[0], P) - #self.assertEqual(P[int], (int,)) - self.assertEqual(P[int, str], (int, str)) - self.assertEqual(P[[int, str]], (int, str)) - #self.assertEqual(P[None], (type(None),)) - self.assertIs(P[...], ...) - self.assertIs(P[P], P) - self.assertEqual(P[Concatenate[int, P]], Concatenate[int, P]) + self.assertEqual(P[(int, str),], (int, str)) + self.assertEqual(P[[int, str],], (int, str)) + self.assertEqual(P[[None],], (type(None),)) + self.assertIs(P[...,], ...) + self.assertIs(P[P,], P) + self.assertEqual(P[Concatenate[int, P],], Concatenate[int, P]) + + def test_bad_subscript(self): + T = TypeVar('T') + P = ParamSpec('P') + bad_args = (42, int, None, T) + for arg in bad_args: + with self.subTest(arg=arg): + with self.assertRaises(TypeError): + P[arg,] + with self.assertRaises(TypeError): + typing.Callable[P, T][arg, str] + with self.assertRaises(TypeError): + collections.abc.Callable[P, T][arg, str] def test_paramspec_in_nested_generics(self): # Although ParamSpec should not be found in __parameters__ of most From d330520172044875430a20fc9dc95c959e6ce92e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 30 Jan 2022 20:47:02 +0200 Subject: [PATCH 3/6] Fix substitution of bare ClassVar and Final. --- Lib/typing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index 8cfb9452f143fe..cb0a9f90aa79e2 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -173,7 +173,9 @@ def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms= if (isinstance(arg, _GenericAlias) and arg.__origin__ in invalid_generic_forms): raise TypeError(f"{arg} is not valid as type argument") - if arg in (Any, NoReturn, ClassVar, Final): + if arg in (Any, NoReturn): + return arg + if allow_special_forms and arg in (ClassVar, Final): return arg if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol): raise TypeError(f"Plain {arg} is not valid as type argument") From af0d9ea35035ae569d465fc182b308048c40c454 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 3 Feb 2022 21:12:40 +0200 Subject: [PATCH 4/6] Add more tests and fix substitution of a TypeVar with a ParamSpec --- Lib/test/test_typing.py | 7 +++++-- Lib/typing.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b3a2edda7411f4..6e75cd0f84968c 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -267,11 +267,14 @@ def test_subscript(self): self.assertIs(T[None,], type(None)) self.assertIs(T[T,], T) self.assertIs(T[(int,)], int) + self.assertEqual(T[int|str,], int|str) + self.assertEqual(T[Union[int, str],], Union[int, str]) def test_bad_subscript(self): T = TypeVar('T') + P = ParamSpec("P") bad_args = ( - 42, ..., [int], (), (int, str), Union, + 42, ..., [int], (), (int, str), P, Union, Generic, Generic[T], Protocol, Protocol[T], Final, Final[int], ClassVar, ClassVar[int], ) @@ -5030,7 +5033,7 @@ def test_subscript(self): def test_bad_subscript(self): T = TypeVar('T') P = ParamSpec('P') - bad_args = (42, int, None, T) + bad_args = (42, int, None, T, int|str, Union[int, str]) for arg in bad_args: with self.subTest(arg=arg): with self.assertRaises(TypeError): diff --git a/Lib/typing.py b/Lib/typing.py index a17d8099baf6e3..30d859ce5d53e2 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -180,7 +180,7 @@ def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms= return arg if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol): raise TypeError(f"Plain {arg} is not valid as type argument") - if isinstance(arg, (type, TypeVar, ForwardRef, types.UnionType, ParamSpec)): + if isinstance(arg, (type, TypeVar, ForwardRef, types.UnionType)): return arg if not callable(arg): raise TypeError(f"{msg} Got {arg!r:.100}.") From 285ec0085dcee95a80c56b884e7dc01305603c65 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 5 Feb 2022 15:53:47 +0200 Subject: [PATCH 5/6] Add more tests. --- Lib/test/test_typing.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 6e75cd0f84968c..129c13cc85ad19 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -5056,6 +5056,10 @@ def test_paramspec_in_nested_generics(self): self.assertEqual(G1.__parameters__, (P, T)) self.assertEqual(G2.__parameters__, (P, T)) self.assertEqual(G3.__parameters__, (P, T)) + C = Callable[[int, str], float] + self.assertEqual(G1[[int, str], float], List[C]) + self.assertEqual(G2[[int, str], float], list[C]) + self.assertEqual(G3[[int, str], float], list[C] | int) class ConcatenateTests(BaseTestCase): From 16cbd84714b3ae2f918020bff9afcf0e1ce114ca Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 12 Mar 2022 15:39:01 +0200 Subject: [PATCH 6/6] Update generated files. --- Include/internal/pycore_global_strings.h | 1 - Include/internal/pycore_runtime_init.h | 1 - 2 files changed, 2 deletions(-) diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 35bffa7aff9493..755d69a873cdcd 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -199,7 +199,6 @@ struct _Py_global_strings { STRUCT_FOR_ID(__subclasshook__) STRUCT_FOR_ID(__truediv__) STRUCT_FOR_ID(__trunc__) - STRUCT_FOR_ID(__typing_subst__) STRUCT_FOR_ID(__warningregistry__) STRUCT_FOR_ID(__weakref__) STRUCT_FOR_ID(__xor__) diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 20d543a8cbc565..5ba18267aeb349 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -822,7 +822,6 @@ extern "C" { INIT_ID(__subclasshook__), \ INIT_ID(__truediv__), \ INIT_ID(__trunc__), \ - INIT_ID(__typing_subst__), \ INIT_ID(__warningregistry__), \ INIT_ID(__weakref__), \ INIT_ID(__xor__), \