Skip to content

Commit 74920aa

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. (cherry picked from commit 8f2fb7d) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 5bbf8ed commit 74920aa

File tree

5 files changed

+117
-41
lines changed

5 files changed

+117
-41
lines changed

Lib/test/test_typing.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,20 +769,42 @@ class C(Generic[*Ts]): pass
769769
('generic[*Ts]', '[*Ts]', 'generic[*Ts]'),
770770
('generic[*Ts]', '[T, *Ts]', 'generic[T, *Ts]'),
771771
('generic[*Ts]', '[*Ts, T]', 'generic[*Ts, T]'),
772+
('generic[T, *Ts]', '[()]', 'TypeError'),
772773
('generic[T, *Ts]', '[int]', 'generic[int]'),
773774
('generic[T, *Ts]', '[int, str]', 'generic[int, str]'),
774775
('generic[T, *Ts]', '[int, str, bool]', 'generic[int, str, bool]'),
776+
('generic[list[T], *Ts]', '[()]', 'TypeError'),
775777
('generic[list[T], *Ts]', '[int]', 'generic[list[int]]'),
776778
('generic[list[T], *Ts]', '[int, str]', 'generic[list[int], str]'),
777779
('generic[list[T], *Ts]', '[int, str, bool]', 'generic[list[int], str, bool]'),
778780

781+
('generic[*Ts, T]', '[()]', 'TypeError'),
779782
('generic[*Ts, T]', '[int]', 'generic[int]'),
780783
('generic[*Ts, T]', '[int, str]', 'generic[int, str]'),
781784
('generic[*Ts, T]', '[int, str, bool]', 'generic[int, str, bool]'),
785+
('generic[*Ts, list[T]]', '[()]', 'TypeError'),
782786
('generic[*Ts, list[T]]', '[int]', 'generic[list[int]]'),
783787
('generic[*Ts, list[T]]', '[int, str]', 'generic[int, list[str]]'),
784788
('generic[*Ts, list[T]]', '[int, str, bool]', 'generic[int, str, list[bool]]'),
785789

790+
('generic[T1, T2, *Ts]', '[()]', 'TypeError'),
791+
('generic[T1, T2, *Ts]', '[int]', 'TypeError'),
792+
('generic[T1, T2, *Ts]', '[int, str]', 'generic[int, str]'),
793+
('generic[T1, T2, *Ts]', '[int, str, bool]', 'generic[int, str, bool]'),
794+
('generic[T1, T2, *Ts]', '[int, str, bool, bytes]', 'generic[int, str, bool, bytes]'),
795+
796+
('generic[*Ts, T1, T2]', '[()]', 'TypeError'),
797+
('generic[*Ts, T1, T2]', '[int]', 'TypeError'),
798+
('generic[*Ts, T1, T2]', '[int, str]', 'generic[int, str]'),
799+
('generic[*Ts, T1, T2]', '[int, str, bool]', 'generic[int, str, bool]'),
800+
('generic[*Ts, T1, T2]', '[int, str, bool, bytes]', 'generic[int, str, bool, bytes]'),
801+
802+
('generic[T1, *Ts, T2]', '[()]', 'TypeError'),
803+
('generic[T1, *Ts, T2]', '[int]', 'TypeError'),
804+
('generic[T1, *Ts, T2]', '[int, str]', 'generic[int, str]'),
805+
('generic[T1, *Ts, T2]', '[int, str, bool]', 'generic[int, str, bool]'),
806+
('generic[T1, *Ts, T2]', '[int, str, bool, bytes]', 'generic[int, str, bool, bytes]'),
807+
786808
('generic[T, *Ts]', '[*tuple_type[int, ...]]', 'generic[int, *tuple_type[int, ...]]'),
787809
('generic[T, *Ts]', '[str, *tuple_type[int, ...]]', 'generic[str, *tuple_type[int, ...]]'),
788810
('generic[T, *Ts]', '[*tuple_type[int, ...], str]', 'generic[int, *tuple_type[int, ...], str]'),
@@ -7190,6 +7212,65 @@ class X(Generic[P, P2]):
71907212
self.assertEqual(G1.__args__, ((int, str), (bytes,)))
71917213
self.assertEqual(G2.__args__, ((int,), (str, bytes)))
71927214

7215+
def test_typevartuple_and_paramspecs_in_user_generics(self):
7216+
Ts = TypeVarTuple("Ts")
7217+
P = ParamSpec("P")
7218+
7219+
class X(Generic[*Ts, P]):
7220+
f: Callable[P, int]
7221+
g: Tuple[*Ts]
7222+
7223+
G1 = X[int, [bytes]]
7224+
self.assertEqual(G1.__args__, (int, (bytes,)))
7225+
G2 = X[int, str, [bytes]]
7226+
self.assertEqual(G2.__args__, (int, str, (bytes,)))
7227+
G3 = X[[bytes]]
7228+
self.assertEqual(G3.__args__, ((bytes,),))
7229+
G4 = X[[]]
7230+
self.assertEqual(G4.__args__, ((),))
7231+
with self.assertRaises(TypeError):
7232+
X[()]
7233+
7234+
class Y(Generic[P, *Ts]):
7235+
f: Callable[P, int]
7236+
g: Tuple[*Ts]
7237+
7238+
G1 = Y[[bytes], int]
7239+
self.assertEqual(G1.__args__, ((bytes,), int))
7240+
G2 = Y[[bytes], int, str]
7241+
self.assertEqual(G2.__args__, ((bytes,), int, str))
7242+
G3 = Y[[bytes]]
7243+
self.assertEqual(G3.__args__, ((bytes,),))
7244+
G4 = Y[[]]
7245+
self.assertEqual(G4.__args__, ((),))
7246+
with self.assertRaises(TypeError):
7247+
Y[()]
7248+
7249+
def test_typevartuple_and_paramspecs_in_generic_aliases(self):
7250+
P = ParamSpec('P')
7251+
T = TypeVar('T')
7252+
Ts = TypeVarTuple('Ts')
7253+
7254+
for C in Callable, collections.abc.Callable:
7255+
with self.subTest(generic=C):
7256+
A = C[P, Tuple[*Ts]]
7257+
B = A[[int, str], bytes, float]
7258+
self.assertEqual(B.__args__, (int, str, Tuple[bytes, float]))
7259+
7260+
class X(Generic[T, P]):
7261+
pass
7262+
7263+
A = X[Tuple[*Ts], P]
7264+
B = A[bytes, float, [int, str]]
7265+
self.assertEqual(B.__args__, (Tuple[bytes, float], (int, str,)))
7266+
7267+
class Y(Generic[P, T]):
7268+
pass
7269+
7270+
A = Y[P, Tuple[*Ts]]
7271+
B = A[[int, str], bytes, float]
7272+
self.assertEqual(B.__args__, ((int, str,), Tuple[bytes, float]))
7273+
71937274
def test_var_substitution(self):
71947275
T = TypeVar("T")
71957276
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)
@@ -1226,7 +1207,18 @@ def __typing_subst__(self, arg):
12261207
return arg
12271208

12281209
def __typing_prepare_subst__(self, alias, args):
1229-
return _prepare_paramspec_params(alias, args)
1210+
params = alias.__parameters__
1211+
i = params.index(self)
1212+
if i >= len(args):
1213+
raise TypeError(f"Too few arguments for {alias}")
1214+
# Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612.
1215+
if len(params) == 1 and not _is_param_expr(args[0]):
1216+
assert i == 0
1217+
args = (args,)
1218+
# Convert lists to tuples to help other libraries cache the results.
1219+
elif isinstance(args[i], list):
1220+
args = (*args[:i], tuple(args[i]), *args[i+1:])
1221+
return args
12301222

12311223
def _is_dunder(attr):
12321224
return attr.startswith('__') and attr.endswith('__')
@@ -1789,23 +1781,13 @@ def __class_getitem__(cls, params):
17891781
if not isinstance(params, tuple):
17901782
params = (params,)
17911783

1792-
if not params:
1793-
# We're only ok with `params` being empty if the class's only type
1794-
# parameter is a `TypeVarTuple` (which can contain zero types).
1795-
class_params = getattr(cls, "__parameters__", None)
1796-
only_class_parameter_is_typevartuple = (
1797-
class_params is not None
1798-
and len(class_params) == 1
1799-
and isinstance(class_params[0], TypeVarTuple)
1800-
)
1801-
if not only_class_parameter_is_typevartuple:
1802-
raise TypeError(
1803-
f"Parameter list to {cls.__qualname__}[...] cannot be empty"
1804-
)
1805-
18061784
params = tuple(_type_convert(p) for p in params)
18071785
if cls in (Generic, Protocol):
18081786
# Generic and Protocol can only be subscripted with unique type variables.
1787+
if not params:
1788+
raise TypeError(
1789+
f"Parameter list to {cls.__qualname__}[...] cannot be empty"
1790+
)
18091791
if not all(_is_typevar_like(p) for p in params):
18101792
raise TypeError(
18111793
f"Parameters to {cls.__name__}[...] must all be type variables "
@@ -1815,13 +1797,20 @@ def __class_getitem__(cls, params):
18151797
f"Parameters to {cls.__name__}[...] must all be unique")
18161798
else:
18171799
# Subscripting a regular Generic subclass.
1818-
if any(isinstance(t, ParamSpec) for t in cls.__parameters__):
1819-
params = _prepare_paramspec_params(cls, params)
1820-
elif not any(isinstance(p, TypeVarTuple) for p in cls.__parameters__):
1821-
# We only run this if there are no TypeVarTuples, because we
1822-
# don't check variadic generic arity at runtime (to reduce
1823-
# complexity of typing.py).
1824-
_check_generic(cls, params, len(cls.__parameters__))
1800+
for param in cls.__parameters__:
1801+
prepare = getattr(param, '__typing_prepare_subst__', None)
1802+
if prepare is not None:
1803+
params = prepare(cls, params)
1804+
_check_generic(cls, params, len(cls.__parameters__))
1805+
1806+
new_args = []
1807+
for param, new_arg in zip(cls.__parameters__, params):
1808+
if isinstance(param, TypeVarTuple):
1809+
new_args.extend(new_arg)
1810+
else:
1811+
new_args.append(new_arg)
1812+
params = tuple(new_args)
1813+
18251814
return _GenericAlias(cls, params,
18261815
_paramspec_tvars=True)
18271816

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy