Skip to content

Commit b2f3f8e

Browse files
bpo-44098: Drop ParamSpec from most __parameters__ in typing generics (GH-26013)
Added two new attributes to ``_GenericAlias``: * ``_typevar_types``, a single type or tuple of types indicating what types are treated as a ``TypeVar``. Used for ``isinstance`` checks. * ``_paramspec_tvars ``, a boolean flag which guards special behavior for dealing with ``ParamSpec``. Setting it to ``True`` means this class deals with ``ParamSpec``. Automerge-Triggered-By: GH:gvanrossum
1 parent 7565586 commit b2f3f8e

File tree

3 files changed

+59
-14
lines changed

3 files changed

+59
-14
lines changed

Lib/test/test_typing.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4359,6 +4359,31 @@ def test_var_substitution(self):
43594359
self.assertEqual(C1[int, str], Callable[[int], str])
43604360
self.assertEqual(C1[[int, str, dict], float], Callable[[int, str, dict], float])
43614361

4362+
def test_no_paramspec_in__parameters__(self):
4363+
# ParamSpec should not be found in __parameters__
4364+
# of generics. Usages outside Callable, Concatenate
4365+
# and Generic are invalid.
4366+
T = TypeVar("T")
4367+
P = ParamSpec("P")
4368+
self.assertNotIn(P, List[P].__parameters__)
4369+
self.assertIn(T, Tuple[T, P].__parameters__)
4370+
4371+
# Test for consistency with builtin generics.
4372+
self.assertNotIn(P, list[P].__parameters__)
4373+
self.assertIn(T, tuple[T, P].__parameters__)
4374+
4375+
def test_paramspec_in_nested_generics(self):
4376+
# Although ParamSpec should not be found in __parameters__ of most
4377+
# generics, they probably should be found when nested in
4378+
# a valid location.
4379+
T = TypeVar("T")
4380+
P = ParamSpec("P")
4381+
C1 = Callable[P, T]
4382+
G1 = List[C1]
4383+
G2 = list[C1]
4384+
self.assertEqual(G1.__parameters__, (P, T))
4385+
self.assertEqual(G2.__parameters__, (P, T))
4386+
43624387

43634388
class ConcatenateTests(BaseTestCase):
43644389
def test_basics(self):

Lib/typing.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,17 @@ def _type_repr(obj):
195195
return repr(obj)
196196

197197

198-
def _collect_type_vars(types):
199-
"""Collect all type variable-like variables contained
198+
def _collect_type_vars(types, typevar_types=None):
199+
"""Collect all type variable contained
200200
in types in order of first appearance (lexicographic order). For example::
201201
202202
_collect_type_vars((T, List[S, T])) == (T, S)
203203
"""
204+
if typevar_types is None:
205+
typevar_types = TypeVar
204206
tvars = []
205207
for t in types:
206-
if isinstance(t, _TypeVarLike) and t not in tvars:
208+
if isinstance(t, typevar_types) and t not in tvars:
207209
tvars.append(t)
208210
if isinstance(t, (_GenericAlias, GenericAlias)):
209211
tvars.extend([t for t in t.__parameters__ if t not in tvars])
@@ -932,7 +934,8 @@ def __getattr__(self, attr):
932934
raise AttributeError(attr)
933935

934936
def __setattr__(self, attr, val):
935-
if _is_dunder(attr) or attr in ('_name', '_inst', '_nparams'):
937+
if _is_dunder(attr) or attr in {'_name', '_inst', '_nparams',
938+
'_typevar_types', '_paramspec_tvars'}:
936939
super().__setattr__(attr, val)
937940
else:
938941
setattr(self.__origin__, attr, val)
@@ -957,14 +960,18 @@ def __subclasscheck__(self, cls):
957960

958961

959962
class _GenericAlias(_BaseGenericAlias, _root=True):
960-
def __init__(self, origin, params, *, inst=True, name=None):
963+
def __init__(self, origin, params, *, inst=True, name=None,
964+
_typevar_types=TypeVar,
965+
_paramspec_tvars=False):
961966
super().__init__(origin, inst=inst, name=name)
962967
if not isinstance(params, tuple):
963968
params = (params,)
964969
self.__args__ = tuple(... if a is _TypingEllipsis else
965970
() if a is _TypingEmpty else
966971
a for a in params)
967-
self.__parameters__ = _collect_type_vars(params)
972+
self.__parameters__ = _collect_type_vars(params, typevar_types=_typevar_types)
973+
self._typevar_types = _typevar_types
974+
self._paramspec_tvars = _paramspec_tvars
968975
if not name:
969976
self.__module__ = origin.__module__
970977

@@ -991,14 +998,15 @@ def __getitem__(self, params):
991998
if not isinstance(params, tuple):
992999
params = (params,)
9931000
params = tuple(_type_convert(p) for p in params)
994-
if any(isinstance(t, ParamSpec) for t in self.__parameters__):
995-
params = _prepare_paramspec_params(self, params)
1001+
if self._paramspec_tvars:
1002+
if any(isinstance(t, ParamSpec) for t in self.__parameters__):
1003+
params = _prepare_paramspec_params(self, params)
9961004
_check_generic(self, params, len(self.__parameters__))
9971005

9981006
subst = dict(zip(self.__parameters__, params))
9991007
new_args = []
10001008
for arg in self.__args__:
1001-
if isinstance(arg, _TypeVarLike):
1009+
if isinstance(arg, self._typevar_types):
10021010
arg = subst[arg]
10031011
elif isinstance(arg, (_GenericAlias, GenericAlias)):
10041012
subparams = arg.__parameters__
@@ -1115,7 +1123,9 @@ def __reduce__(self):
11151123
class _CallableType(_SpecialGenericAlias, _root=True):
11161124
def copy_with(self, params):
11171125
return _CallableGenericAlias(self.__origin__, params,
1118-
name=self._name, inst=self._inst)
1126+
name=self._name, inst=self._inst,
1127+
_typevar_types=(TypeVar, ParamSpec),
1128+
_paramspec_tvars=True)
11191129

11201130
def __getitem__(self, params):
11211131
if not isinstance(params, tuple) or len(params) != 2:
@@ -1208,7 +1218,10 @@ def __hash__(self):
12081218

12091219

12101220
class _ConcatenateGenericAlias(_GenericAlias, _root=True):
1211-
pass
1221+
def __init__(self, *args, **kwargs):
1222+
super().__init__(*args, **kwargs,
1223+
_typevar_types=(TypeVar, ParamSpec),
1224+
_paramspec_tvars=True)
12121225

12131226

12141227
class Generic:
@@ -1244,7 +1257,7 @@ def __class_getitem__(cls, params):
12441257
params = tuple(_type_convert(p) for p in params)
12451258
if cls in (Generic, Protocol):
12461259
# Generic and Protocol can only be subscripted with unique type variables.
1247-
if not all(isinstance(p, _TypeVarLike) for p in params):
1260+
if not all(isinstance(p, (TypeVar, ParamSpec)) for p in params):
12481261
raise TypeError(
12491262
f"Parameters to {cls.__name__}[...] must all be type variables "
12501263
f"or parameter specification variables.")
@@ -1256,7 +1269,9 @@ def __class_getitem__(cls, params):
12561269
if any(isinstance(t, ParamSpec) for t in cls.__parameters__):
12571270
params = _prepare_paramspec_params(cls, params)
12581271
_check_generic(cls, params, len(cls.__parameters__))
1259-
return _GenericAlias(cls, params)
1272+
return _GenericAlias(cls, params,
1273+
_typevar_types=(TypeVar, ParamSpec),
1274+
_paramspec_tvars=True)
12601275

12611276
def __init_subclass__(cls, *args, **kwargs):
12621277
super().__init_subclass__(*args, **kwargs)
@@ -1268,7 +1283,7 @@ def __init_subclass__(cls, *args, **kwargs):
12681283
if error:
12691284
raise TypeError("Cannot inherit from plain Generic")
12701285
if '__orig_bases__' in cls.__dict__:
1271-
tvars = _collect_type_vars(cls.__orig_bases__)
1286+
tvars = _collect_type_vars(cls.__orig_bases__, (TypeVar, ParamSpec))
12721287
# Look for Generic[T1, ..., Tn].
12731288
# If found, tvars must be a subset of it.
12741289
# If not found, tvars is it.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``typing.ParamSpec`` will no longer be found in the ``__parameters__`` of
2+
most :mod:`typing` generics except in valid use locations specified by
3+
:pep:`612`. This prevents incorrect usage like ``typing.List[P][int]``. This
4+
change means incorrect usage which may have passed silently in 3.10 beta 1
5+
and earlier will now error.

0 commit comments

Comments
 (0)