8000 gh-99344, gh-99379, gh-99382: Fix issues in substitution of ParamSpec… · python/cpython@8f2fb7d · GitHub
[go: up one dir, main page]

Skip to content

Commit 8f2fb7d

Browse files
gh-99344, gh-99379, gh-99382: Fix issues in substitution of ParamSpec and TypeVarTuple (GH-99412)
* Fix substitution of TypeVarTuple and ParamSpec together in user generics. * Fix substitution of ParamSpec followed by TypeVarTuple in generic aliases. * Check the number of arguments in substitution in user generics containing a TypeVarTuple and one or more TypeVar.
1 parent 1d1bb95 commit 8f2fb7d

5 files changed

+117
-41
lines changed

Lib/test/test_ 10000 typing.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,20 +772,42 @@ class C(Generic[*Ts]): pass
772772
('generic[*Ts]', '[*Ts]', 'generic[*Ts]'),
773773
('generic[*Ts]', '[T, *Ts]', 'generic[T, *Ts]'),
774774
('generic[*Ts]', '[*Ts, T]', 'generic[*Ts, T]'),
775+
('generic[T, *Ts]', '[()]', 'TypeError'),
775776
('generic[T, *Ts]', '[int]', 'generic[int]'),
776777
('generic[T, *Ts]', '[int, str]', 'generic[int, str]'),
777778
('generic[T, *Ts]', '[int, str, bool]', 'generic[int, str, bool]'),
779+
('generic[list[T], *Ts]', '[()]', 'TypeError'),
778780
('generic[list[T], *Ts]', '[int]', 'generic[list[int]]'),
779781
('generic[list[T], *Ts]', '[int, str]', 'generic[list[int], str]'),
780782
('generic[list[T], *Ts]', '[int, str, bool]', 'generic[list[int], str, bool]'),
781783

784+
('generic[*Ts, T]', '[()]', 'TypeError'),
782785
('generic[*Ts, T]', '[int]', 'generic[int]'),
783786
('generic[*Ts, T]', '[int, str]', 'generic[int, str]'),
784787
('generic[*Ts, T]', '[int, str, bool]', 'generic[int, str, bool]'),
788+
('generic[*Ts, list[T]]', '[()]', 'TypeError'),
785789
('generic[*Ts, list[T]]', '[int]', 'generic[list[int]]'),
786790
('generic[*Ts, list[T]]', '[int, str]', 'generic[int, list[str]]'),
787791
('generic[*Ts, list[T]]', '[int, str, bool]', 'generic[int, str, list[bool]]'),
788792

793+
('generic[T1, T2, *Ts]', '[()]', 'TypeError'),
794+
('generic[T1, T2, *Ts]', '[int]', 'TypeError'),
795+
('generic[T1, T2, *Ts]', '[int, str]', 'generic[int, str]'),
796+
('generic[T1, T2, *Ts]', '[int, str, bool]', 'generic[int, str, bool]'),
797+
('generic[T1, T2, *Ts]', '[int, str, bool, bytes]', 'generic[int, str, bool, bytes]'),
798+
799+
('generic[*Ts, T1, T2]', '[()]', 'TypeError'),
800+
('generic[*Ts, T1, T2]', '[int]', 'TypeError'),
801+
('generic[*Ts, T1, T2]', '[int, str]', 'generic[int, str]'),
802+
('generic[*Ts, T1, T2]', '[int, str, bool]', 'generic[int, str, bool]'),
803+
('generic[*Ts, T1, T2]', '[int, str, bool, bytes]', 'generic[int, str, bool, bytes]'),
804+
805+
('generic[T1, *Ts, T2]', '[()]', 'TypeError'),
806+
('generic[T1, *Ts, T2]', '[int]', 'TypeError'),
807+
('generic[T1, *Ts, T2]', '[int, str]', 'generic[int, str]'),
808+
('generic[T1, *Ts, T2]', '[int, str, bool]', 'generic[int, str, bool]'),
809+
('generic[T1, *Ts, T2]', '[int, str, bool, bytes]', 'generic[int, str, bool, bytes]'),
810+
789811
('generic[T, *Ts]', '[*tuple_type[int, ...]]', 'generic[int, *tuple_type[int, ...]]'),
790812
('generic[T, *Ts]', '[str, *tuple_type[int, ...]]', 'generic[str, *tuple_type[int, ...]]'),
791813
('generic[T, *Ts]', '[*tuple_type[int, ...], str]', 'generic[int, *tuple_type[int, ...], str]'),
@@ -7241,6 +7263,65 @@ class X(Generic[P, P2]):
72417263
self.assertEqual(G1.__args__, ((int, str), (bytes,)))
72427264
self.assertEqual(G2.__args__, ((int,), (str, bytes)))
72437265

7266+
def test_typevartuple_and_paramspecs_in_user_generics(self):
7267+
Ts = TypeVarTuple("Ts")
7268+
P = ParamSpec("P")
7269+
7270+
class X(Generic[*Ts, P]):
7271+
f: Callable[P, int]
7272+
g: Tuple[*Ts]
7273+
7274+
G1 = X[int, [bytes]]
7275+
self.assertEqual(G1.__args__, (int, (bytes,)))
7276+
G2 = X[int, str, [bytes]]
7277+
self.assertEqual(G2.__args__, (int, str, (bytes,)))
7278+
G3 = X[[bytes]]
7279+
self.assertEqual(G3.__args__, ((bytes,),))
7280+
G4 = X[[]]
7281+
self.assertEqual(G4.__args__, ((),))
7282+
with self.assertRaises(TypeError):
7283+
X[()]
7284+
7285+
class Y(Generic[P, *Ts]):
7286+
f: Callable[P, int]
7287+
g: Tuple[*Ts]
7288+
7289+
G1 = Y[[bytes], int]
7290+
self.assertEqual(G1.__args__, ((bytes,), int))
7291+
G2 = Y[[bytes], int, str]
7292+
self.assertEqual(G2.__args__, ((bytes,), int, str))
7293+
G3 = Y[[bytes]]
7294+
self.assertEqual(G3.__args__, ((bytes,),))
7295+
G4 = Y[[]]
7296+
self.assertEqual(G4.__args__, ((),))
7297+
with self.assertRaises(TypeError):
7298+
Y[()]
7299+
7300+
def test_typevartuple_and_paramspecs_in_generic_aliases(self):
7301+
P = ParamSpec('P')
7302+
T = TypeVar('T')
7303+
Ts = TypeVarTuple('Ts')
7304+
7305+
for C in Callable, collections.abc.Callable:
7306+
with self.subTest(generic=C):
7307+
A = C[P, Tuple[*Ts]]
7308+
B = A[[int, str], bytes, float]
7309+
self.assertEqual(B.__args__, (int, str, Tuple[bytes, float]))
7310+
7311+
class X(Generic[T, P]):
7312+
pass
7313+
7314+
A = X[Tuple[*Ts], P]
7315+
B = A[bytes, float, [int, str]]
7316+
self.assertEqual(B.__args__, (Tuple[bytes, float], (int, str,)))
7317+
7318+
class Y(Generic[P, T]):
7319+
pass
7320+
7321+
A = Y[P, Tuple[*Ts]]
7322+
B = A[[int, str], bytes, float]
7323+
self.assertEqual(B.__args__, ((int, str,), Tuple[bytes, float]))
7324+
72447325
def test_var_substitution(self):
72457326
T = TypeVar("T")
72467327
P = ParamSpec("P")

Lib/typing.py

Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -284,25 +284,6 @@ def _unpack_args(args):
284284
newargs.append(arg)
285285
return newargs
286286

287-
def _prepare_paramspec_params(cls, params):
288-
"""Prepares the parameters for a Generic containing ParamSpec
289-
variables (internal helper).
290-
"""
291-
# Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612.
292-
if (len(cls.__parameters__) == 1
293-
and params and not _is_param_expr(params[0])):
294-
assert isinstance(cls.__parameters__[0], ParamSpec)
295-
return (params,)
296-
else:
297-
_check_generic(cls, params, len(cls.__parameters__))
298-
_params = []
299-
# Convert lists to tuples to help other libraries cache the results.
300-
for p, tvar in zip(params, cls.__parameters__):
301-
if isinstance(tvar, ParamSpec) and isinstance(p, list):
302-
p = tuple(p)
303-
_params.append(p)
304-
return tuple(_params)
305-
306287
def _deduplicate(params):
307288
# Weed out strict duplicates, preserving the first of each occurrence.
308289
all_params = set(params)
@@ -1238,7 +1219,18 @@ def __typing_subst__(self, arg):
12381219
return arg
12391220

12401221
def __typing_prepare_subst__(self, alias, args):
1241-
return _prepare_paramspec_params(alias, args)
1222+
params = alias.__parameters__
1223+
i = params.index(self)
1224+
if i >= len(args):
1225+
raise TypeError(f"Too few arguments for {alias}")
1226+
# Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612.
1227+
if len(params) == 1 and not _is_param_expr(args[0]):
1228+
assert i == 0
1229+
args = (args,)
1230+
# Convert lists to tuples to help other libraries cache the results.
1231+
elif isinstance(args[i], list):
1232+
args = (*args[:i], tuple(args[i]), *args[i+1:])
1233+
return args
12421234

12431235
def _is_dunder(attr):
12441236
return attr.startswith('__') and attr.endswith('__')
@@ -1801,23 +1793,13 @@ def __class_getitem__(cls, params):
18011793
if not isinstance(params, tuple):
18021794
params = (params,)
18031795

1804-
if not params:
1805-
# We're only ok with `params` being empty if the class's only type
1806-
# parameter is a `TypeVarTuple` (which can contain zero types).
1807-
class_params = getattr(cls, "__parameters__", None)
1808-
only_class_parameter_is_typevartuple = (
1809-
class_params is not None
1810-
and len(class_params) == 1
1811-
and isinstance(class_params[0], TypeVarTuple)
1812-
)
1813-
if not only_class_parameter_is_typevartuple:
1814-
raise TypeError(
1815-
f"Parameter list to {cls.__qualname__}[...] cannot be empty"
1816-
)
1817-
18181796
params = tuple(_type_convert(p) for p in params)
18191797
if cls in (Generic, Protocol):
18201798
# Generic and Protocol can only be subscripted with unique type variables.
1799+
if not params:
1800+
raise TypeError(
1801+
f"Parameter list to {cls.__qualname__}[...] cannot be empty"
1802+
)
18211803
if not all(_is_typevar_like(p) for p in params):
18221804
raise TypeError(
18231805
f"Parameters to {cls.__name__}[...] must all be type variables "
@@ -1827,13 +1809,20 @@ def __class_getitem__(cls, params):
18271809
f"Parameters to {cls.__name__}[...] must all be unique")
18281810
else:
18291811
# Subscripting a regular Generic subclass.
1830-
if any(isinstance(t, ParamSpec) for t in cls.__parameters__):
1831-
params = _prepare_paramspec_params(cls, params)
1832-
elif not any(isinstance(p, TypeVarTuple) for p in cls.__parameters__):
1833-
# We only run this if there are no TypeVarTuples, because we
1834-
# don't check variadic generic arity at runtime (to reduce
1835-
# complexity of typing.py).
1836-
_check_generic(cls, params, len(cls.__parameters__))
1812+
for param in cls.__parameters__:
1813+
prepare = getattr(param, '__typing_prepare_subst__', None)
1814+
if prepare is not None:
1815+
params = prepare(cls, params)
1816+
_check_generic(cls, params, len(cls.__parameters__))
1817+
1818+
new_args = []
1819+
for param, new_arg in zip(cls.__parameters__, params):
1820+
if isinstance(param, TypeVarTuple):
1821+
new_args.extend(new_arg)
1822+
else:
1823+
new_args.append(new_arg)
1824+
params = tuple(new_args)
1825+
18371826
return _GenericAlias(cls, params,
18381827
_paramspec_tvars=True)
18391828

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix substitution of :class:`~typing.TypeVarTuple` and
2+
:class:`~typing.ParamSpec` together in user generics.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix substitution of :class:`~typing.ParamSpec` followed by
2+
:class:`~typing.TypeVarTuple` in generic aliases.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Check the number of arguments in substitution in user generics containing a
2+
:class:`~typing.TypeVarTuple` and one or more :class:`~typing.TypeVar`.

0 commit comments

Comments
 (0)
0