summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_typing.py25
-rw-r--r--Lib/typing.py43
-rw-r--r--Misc/NEWS.d/next/Library/2021-05-10-17-45-00.bpo-44098._MoxuZ.rst5
3 files changed, 59 insertions, 14 deletions
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index a2a5d8f..79c5c3a 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -4359,6 +4359,31 @@ class ParamSpecTests(BaseTestCase):
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__)
+
+ def test_paramspec_in_nested_generics(self):
+ # Although ParamSpec should not be found in __parameters__ of most
+ # generics, they probably should be found when nested in
+ # a valid location.
+ T = TypeVar("T")
+ P = ParamSpec("P")
+ C1 = Callable[P, T]
+ G1 = List[C1]
+ G2 = list[C1]
+ self.assertEqual(G1.__parameters__, (P, T))
+ self.assertEqual(G2.__parameters__, (P, T))
+
class ConcatenateTests(BaseTestCase):
def test_basics(self):
diff --git a/Lib/typing.py b/Lib/typing.py
index 26efe4a..639feee 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -195,15 +195,17 @@ def _type_repr(obj):
return repr(obj)
-def _collect_type_vars(types):
- """Collect all type variable-like variables contained
+def _collect_type_vars(types, typevar_types=None):
+ """Collect all type variable contained
in types in order of first appearance (lexicographic order). For example::
_collect_type_vars((T, List[S, T])) == (T, S)
"""
+ if typevar_types is None:
+ typevar_types = TypeVar
tvars = []
for t in types:
- if isinstance(t, _TypeVarLike) and t not in tvars:
+ if isinstance(t, typevar_types) and t not in tvars:
tvars.append(t)
if isinstance(t, (_GenericAlias, GenericAlias)):
tvars.extend([t for t in t.__parameters__ if t not in tvars])
@@ -932,7 +934,8 @@ class _BaseGenericAlias(_Final, _root=True):
raise AttributeError(attr)
def __setattr__(self, attr, val):
- if _is_dunder(attr) or attr in ('_name', '_inst', '_nparams'):
+ if _is_dunder(attr) or attr in {'_name', '_inst', '_nparams',
+ '_typevar_types', '_paramspec_tvars'}:
super().__setattr__(attr, val)
else:
setattr(self.__origin__, attr, val)
@@ -957,14 +960,18 @@ class _BaseGenericAlias(_Final, _root=True):
class _GenericAlias(_BaseGenericAlias, _root=True):
- def __init__(self, origin, params, *, inst=True, name=None):
+ 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):
params = (params,)
self.__args__ = tuple(... if a is _TypingEllipsis else
() if a is _TypingEmpty else
a for a in params)
- self.__parameters__ = _collect_type_vars(params)
+ self.__parameters__ = _collect_type_vars(params, typevar_types=_typevar_types)
+ self._typevar_types = _typevar_types
+ self._paramspec_tvars = _paramspec_tvars
if not name:
self.__module__ = origin.__module__
@@ -991,14 +998,15 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
if not isinstance(params, tuple):
params = (params,)
params = tuple(_type_convert(p) for p in params)
- if any(isinstance(t, ParamSpec) for t in self.__parameters__):
- params = _prepare_paramspec_params(self, params)
+ if self._paramspec_tvars:
+ if any(isinstance(t, ParamSpec) for t in self.__parameters__):
+ params = _prepare_paramspec_params(self, params)
_check_generic(self, params, len(self.__parameters__))
subst = dict(zip(self.__parameters__, params))
new_args = []
for arg in self.__args__:
- if isinstance(arg, _TypeVarLike):
+ if isinstance(arg, self._typevar_types):
arg = subst[arg]
elif isinstance(arg, (_GenericAlias, GenericAlias)):
subparams = arg.__parameters__
@@ -1115,7 +1123,9 @@ class _CallableGenericAlias(_GenericAlias, _root=True):
class _CallableType(_SpecialGenericAlias, _root=True):
def copy_with(self, params):
return _CallableGenericAlias(self.__origin__, params,
- name=self._name, inst=self._inst)
+ name=self._name, inst=self._inst,
+ _typevar_types=(TypeVar, ParamSpec),
+ _paramspec_tvars=True)
def __getitem__(self, params):
if not isinstance(params, tuple) or len(params) != 2:
@@ -1208,7 +1218,10 @@ class _LiteralGenericAlias(_GenericAlias, _root=True):
class _ConcatenateGenericAlias(_GenericAlias, _root=True):
- pass
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs,
+ _typevar_types=(TypeVar, ParamSpec),
+ _paramspec_tvars=True)
class Generic:
@@ -1244,7 +1257,7 @@ class Generic:
params = tuple(_type_convert(p) for p in params)
if cls in (Generic, Protocol):
# Generic and Protocol can only be subscripted with unique type variables.
- if not all(isinstance(p, _TypeVarLike) for p in params):
+ if not all(isinstance(p, (TypeVar, ParamSpec)) for p in params):
raise TypeError(
f"Parameters to {cls.__name__}[...] must all be type variables "
f"or parameter specification variables.")
@@ -1256,7 +1269,9 @@ class Generic:
if any(isinstance(t, ParamSpec) for t in cls.__parameters__):
params = _prepare_paramspec_params(cls, params)
_check_generic(cls, params, len(cls.__parameters__))
- return _GenericAlias(cls, params)
+ return _GenericAlias(cls, params,
+ _typevar_types=(TypeVar, ParamSpec),
+ _paramspec_tvars=True)
def __init_subclass__(cls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
@@ -1268,7 +1283,7 @@ class Generic:
if error:
raise TypeError("Cannot inherit from plain Generic")
if '__orig_bases__' in cls.__dict__:
- tvars = _collect_type_vars(cls.__orig_bases__)
+ tvars = _collect_type_vars(cls.__orig_bases__, (TypeVar, ParamSpec))
# Look for Generic[T1, ..., Tn].
# If found, tvars must be a subset of it.
# If not found, tvars is it.
diff --git a/Misc/NEWS.d/next/Library/2021-05-10-17-45-00.bpo-44098._MoxuZ.rst b/Misc/NEWS.d/next/Library/2021-05-10-17-45-00.bpo-44098._MoxuZ.rst
new file mode 100644
index 0000000..2aaa8b6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-05-10-17-45-00.bpo-44098._MoxuZ.rst
@@ -0,0 +1,5 @@
+``typing.ParamSpec`` will no longer be found in the ``__parameters__`` of
+most :mod:`typing` generics except in valid use locations specified by
+:pep:`612`. This prevents incorrect usage like ``typing.List[P][int]``. This
+change means incorrect usage which may have passed silently in 3.10 beta 1
+and earlier will now error.