mirror of
https://github.com/python/cpython.git
synced 2025-12-10 02:50:09 +00:00
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.
This commit is contained in:
parent
1d1bb95abd
commit
8f2fb7dfe7
5 changed files with 117 additions and 41 deletions
|
|
@ -772,20 +772,42 @@ class GenericAliasSubstitutionTests(BaseTestCase):
|
||||||
('generic[*Ts]', '[*Ts]', 'generic[*Ts]'),
|
('generic[*Ts]', '[*Ts]', 'generic[*Ts]'),
|
||||||
('generic[*Ts]', '[T, *Ts]', 'generic[T, *Ts]'),
|
('generic[*Ts]', '[T, *Ts]', 'generic[T, *Ts]'),
|
||||||
('generic[*Ts]', '[*Ts, T]', 'generic[*Ts, T]'),
|
('generic[*Ts]', '[*Ts, T]', 'generic[*Ts, T]'),
|
||||||
|
('generic[T, *Ts]', '[()]', 'TypeError'),
|
||||||
('generic[T, *Ts]', '[int]', 'generic[int]'),
|
('generic[T, *Ts]', '[int]', 'generic[int]'),
|
||||||
('generic[T, *Ts]', '[int, str]', 'generic[int, str]'),
|
('generic[T, *Ts]', '[int, str]', 'generic[int, str]'),
|
||||||
('generic[T, *Ts]', '[int, str, bool]', 'generic[int, str, bool]'),
|
('generic[T, *Ts]', '[int, str, bool]', 'generic[int, str, bool]'),
|
||||||
|
('generic[list[T], *Ts]', '[()]', 'TypeError'),
|
||||||
('generic[list[T], *Ts]', '[int]', 'generic[list[int]]'),
|
('generic[list[T], *Ts]', '[int]', 'generic[list[int]]'),
|
||||||
('generic[list[T], *Ts]', '[int, str]', 'generic[list[int], str]'),
|
('generic[list[T], *Ts]', '[int, str]', 'generic[list[int], str]'),
|
||||||
('generic[list[T], *Ts]', '[int, str, bool]', 'generic[list[int], str, bool]'),
|
('generic[list[T], *Ts]', '[int, str, bool]', 'generic[list[int], str, bool]'),
|
||||||
|
|
||||||
|
('generic[*Ts, T]', '[()]', 'TypeError'),
|
||||||
('generic[*Ts, T]', '[int]', 'generic[int]'),
|
('generic[*Ts, T]', '[int]', 'generic[int]'),
|
||||||
('generic[*Ts, T]', '[int, str]', 'generic[int, str]'),
|
('generic[*Ts, T]', '[int, str]', 'generic[int, str]'),
|
||||||
('generic[*Ts, T]', '[int, str, bool]', 'generic[int, str, bool]'),
|
('generic[*Ts, T]', '[int, str, bool]', 'generic[int, str, bool]'),
|
||||||
|
('generic[*Ts, list[T]]', '[()]', 'TypeError'),
|
||||||
('generic[*Ts, list[T]]', '[int]', 'generic[list[int]]'),
|
('generic[*Ts, list[T]]', '[int]', 'generic[list[int]]'),
|
||||||
('generic[*Ts, list[T]]', '[int, str]', 'generic[int, list[str]]'),
|
('generic[*Ts, list[T]]', '[int, str]', 'generic[int, list[str]]'),
|
||||||
('generic[*Ts, list[T]]', '[int, str, bool]', 'generic[int, str, list[bool]]'),
|
('generic[*Ts, list[T]]', '[int, str, bool]', 'generic[int, str, list[bool]]'),
|
||||||
|
|
||||||
|
('generic[T1, T2, *Ts]', '[()]', 'TypeError'),
|
||||||
|
('generic[T1, T2, *Ts]', '[int]', 'TypeError'),
|
||||||
|
('generic[T1, T2, *Ts]', '[int, str]', 'generic[int, str]'),
|
||||||
|
('generic[T1, T2, *Ts]', '[int, str, bool]', 'generic[int, str, bool]'),
|
||||||
|
('generic[T1, T2, *Ts]', '[int, str, bool, bytes]', 'generic[int, str, bool, bytes]'),
|
||||||
|
|
||||||
|
('generic[*Ts, T1, T2]', '[()]', 'TypeError'),
|
||||||
|
('generic[*Ts, T1, T2]', '[int]', 'TypeError'),
|
||||||
|
('generic[*Ts, T1, T2]', '[int, str]', 'generic[int, str]'),
|
||||||
|
('generic[*Ts, T1, T2]', '[int, str, bool]', 'generic[int, str, bool]'),
|
||||||
|
('generic[*Ts, T1, T2]', '[int, str, bool, bytes]', 'generic[int, str, bool, bytes]'),
|
||||||
|
|
||||||
|
('generic[T1, *Ts, T2]', '[()]', 'TypeError'),
|
||||||
|
('generic[T1, *Ts, T2]', '[int]', 'TypeError'),
|
||||||
|
('generic[T1, *Ts, T2]', '[int, str]', 'generic[int, str]'),
|
||||||
|
('generic[T1, *Ts, T2]', '[int, str, bool]', 'generic[int, str, bool]'),
|
||||||
|
('generic[T1, *Ts, T2]', '[int, str, bool, bytes]', 'generic[int, str, bool, bytes]'),
|
||||||
|
|
||||||
('generic[T, *Ts]', '[*tuple_type[int, ...]]', 'generic[int, *tuple_type[int, ...]]'),
|
('generic[T, *Ts]', '[*tuple_type[int, ...]]', 'generic[int, *tuple_type[int, ...]]'),
|
||||||
('generic[T, *Ts]', '[str, *tuple_type[int, ...]]', 'generic[str, *tuple_type[int, ...]]'),
|
('generic[T, *Ts]', '[str, *tuple_type[int, ...]]', 'generic[str, *tuple_type[int, ...]]'),
|
||||||
('generic[T, *Ts]', '[*tuple_type[int, ...], str]', 'generic[int, *tuple_type[int, ...], str]'),
|
('generic[T, *Ts]', '[*tuple_type[int, ...], str]', 'generic[int, *tuple_type[int, ...], str]'),
|
||||||
|
|
@ -7241,6 +7263,65 @@ class ParamSpecTests(BaseTestCase):
|
||||||
self.assertEqual(G1.__args__, ((int, str), (bytes,)))
|
self.assertEqual(G1.__args__, ((int, str), (bytes,)))
|
||||||
self.assertEqual(G2.__args__, ((int,), (str, bytes)))
|
self.assertEqual(G2.__args__, ((int,), (str, bytes)))
|
||||||
|
|
||||||
|
def test_typevartuple_and_paramspecs_in_user_generics(self):
|
||||||
|
Ts = TypeVarTuple("Ts")
|
||||||
|
P = ParamSpec("P")
|
||||||
|
|
||||||
|
class X(Generic[*Ts, P]):
|
||||||
|
f: Callable[P, int]
|
||||||
|
g: Tuple[*Ts]
|
||||||
|
|
||||||
|
G1 = X[int, [bytes]]
|
||||||
|
self.assertEqual(G1.__args__, (int, (bytes,)))
|
||||||
|
G2 = X[int, str, [bytes]]
|
||||||
|
self.assertEqual(G2.__args__, (int, str, (bytes,)))
|
||||||
|
G3 = X[[bytes]]
|
||||||
|
self.assertEqual(G3.__args__, ((bytes,),))
|
||||||
|
G4 = X[[]]
|
||||||
|
self.assertEqual(G4.__args__, ((),))
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
X[()]
|
||||||
|
|
||||||
|
class Y(Generic[P, *Ts]):
|
||||||
|
f: Callable[P, int]
|
||||||
|
g: Tuple[*Ts]
|
||||||
|
|
||||||
|
G1 = Y[[bytes], int]
|
||||||
|
self.assertEqual(G1.__args__, ((bytes,), int))
|
||||||
|
G2 = Y[[bytes], int, str]
|
||||||
|
self.assertEqual(G2.__args__, ((bytes,), int, str))
|
||||||
|
G3 = Y[[bytes]]
|
||||||
|
self.assertEqual(G3.__args__, ((bytes,),))
|
||||||
|
G4 = Y[[]]
|
||||||
|
self.assertEqual(G4.__args__, ((),))
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Y[()]
|
||||||
|
|
||||||
|
def test_typevartuple_and_paramspecs_in_generic_aliases(self):
|
||||||
|
P = ParamSpec('P')
|
||||||
|
T = TypeVar('T')
|
||||||
|
Ts = TypeVarTuple('Ts')
|
||||||
|
|
||||||
|
for C in Callable, collections.abc.Callable:
|
||||||
|
with self.subTest(generic=C):
|
||||||
|
A = C[P, Tuple[*Ts]]
|
||||||
|
B = A[[int, str], bytes, float]
|
||||||
|
self.assertEqual(B.__args__, (int, str, Tuple[bytes, float]))
|
||||||
|
|
||||||
|
class X(Generic[T, P]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
A = X[Tuple[*Ts], P]
|
||||||
|
B = A[bytes, float, [int, str]]
|
||||||
|
self.assertEqual(B.__args__, (Tuple[bytes, float], (int, str,)))
|
||||||
|
|
||||||
|
class Y(Generic[P, T]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
A = Y[P, Tuple[*Ts]]
|
||||||
|
B = A[[int, str], bytes, float]
|
||||||
|
self.assertEqual(B.__args__, ((int, str,), Tuple[bytes, float]))
|
||||||
|
|
||||||
def test_var_substitution(self):
|
def test_var_substitution(self):
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
|
|
|
||||||
|
|
@ -284,25 +284,6 @@ def _unpack_args(args):
|
||||||
newargs.append(arg)
|
newargs.append(arg)
|
||||||
return newargs
|
return newargs
|
||||||
|
|
||||||
def _prepare_paramspec_params(cls, params):
|
|
||||||
"""Prepares the parameters for a Generic containing ParamSpec
|
|
||||||
variables (internal helper).
|
|
||||||
"""
|
|
||||||
# Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612.
|
|
||||||
if (len(cls.__parameters__) == 1
|
|
||||||
and params and not _is_param_expr(params[0])):
|
|
||||||
assert isinstance(cls.__parameters__[0], ParamSpec)
|
|
||||||
return (params,)
|
|
||||||
else:
|
|
||||||
_check_generic(cls, params, len(cls.__parameters__))
|
|
||||||
_params = []
|
|
||||||
# Convert lists to tuples to help other libraries cache the results.
|
|
||||||
for p, tvar in zip(params, cls.__parameters__):
|
|
||||||
if isinstance(tvar, ParamSpec) and isinstance(p, list):
|
|
||||||
p = tuple(p)
|
|
||||||
_params.append(p)
|
|
||||||
return tuple(_params)
|
|
||||||
|
|
||||||
def _deduplicate(params):
|
def _deduplicate(params):
|
||||||
# Weed out strict duplicates, preserving the first of each occurrence.
|
# Weed out strict duplicates, preserving the first of each occurrence.
|
||||||
all_params = set(params)
|
all_params = set(params)
|
||||||
|
|
@ -1238,7 +1219,18 @@ class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin,
|
||||||
return arg
|
return arg
|
||||||
|
|
||||||
def __typing_prepare_subst__(self, alias, args):
|
def __typing_prepare_subst__(self, alias, args):
|
||||||
return _prepare_paramspec_params(alias, args)
|
params = alias.__parameters__
|
||||||
|
i = params.index(self)
|
||||||
|
if i >= len(args):
|
||||||
|
raise TypeError(f"Too few arguments for {alias}")
|
||||||
|
# Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612.
|
||||||
|
if len(params) == 1 and not _is_param_expr(args[0]):
|
||||||
|
assert i == 0
|
||||||
|
args = (args,)
|
||||||
|
# Convert lists to tuples to help other libraries cache the results.
|
||||||
|
elif isinstance(args[i], list):
|
||||||
|
args = (*args[:i], tuple(args[i]), *args[i+1:])
|
||||||
|
return args
|
||||||
|
|
||||||
def _is_dunder(attr):
|
def _is_dunder(attr):
|
||||||
return attr.startswith('__') and attr.endswith('__')
|
return attr.startswith('__') and attr.endswith('__')
|
||||||
|
|
@ -1801,23 +1793,13 @@ class Generic:
|
||||||
if not isinstance(params, tuple):
|
if not isinstance(params, tuple):
|
||||||
params = (params,)
|
params = (params,)
|
||||||
|
|
||||||
if not params:
|
|
||||||
# We're only ok with `params` being empty if the class's only type
|
|
||||||
# parameter is a `TypeVarTuple` (which can contain zero types).
|
|
||||||
class_params = getattr(cls, "__parameters__", None)
|
|
||||||
only_class_parameter_is_typevartuple = (
|
|
||||||
class_params is not None
|
|
||||||
and len(class_params) == 1
|
|
||||||
and isinstance(class_params[0], TypeVarTuple)
|
|
||||||
)
|
|
||||||
if not only_class_parameter_is_typevartuple:
|
|
||||||
raise TypeError(
|
|
||||||
f"Parameter list to {cls.__qualname__}[...] cannot be empty"
|
|
||||||
)
|
|
||||||
|
|
||||||
params = tuple(_type_convert(p) for p in params)
|
params = tuple(_type_convert(p) for p in params)
|
||||||
if cls in (Generic, Protocol):
|
if cls in (Generic, Protocol):
|
||||||
# Generic and Protocol can only be subscripted with unique type variables.
|
# Generic and Protocol can only be subscripted with unique type variables.
|
||||||
|
if not params:
|
||||||
|
raise TypeError(
|
||||||
|
f"Parameter list to {cls.__qualname__}[...] cannot be empty"
|
||||||
|
)
|
||||||
if not all(_is_typevar_like(p) for p in params):
|
if not all(_is_typevar_like(p) for p in params):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"Parameters to {cls.__name__}[...] must all be type variables "
|
f"Parameters to {cls.__name__}[...] must all be type variables "
|
||||||
|
|
@ -1827,13 +1809,20 @@ class Generic:
|
||||||
f"Parameters to {cls.__name__}[...] must all be unique")
|
f"Parameters to {cls.__name__}[...] must all be unique")
|
||||||
else:
|
else:
|
||||||
# Subscripting a regular Generic subclass.
|
# Subscripting a regular Generic subclass.
|
||||||
if any(isinstance(t, ParamSpec) for t in cls.__parameters__):
|
for param in cls.__parameters__:
|
||||||
params = _prepare_paramspec_params(cls, params)
|
prepare = getattr(param, '__typing_prepare_subst__', None)
|
||||||
elif not any(isinstance(p, TypeVarTuple) for p in cls.__parameters__):
|
if prepare is not None:
|
||||||
# We only run this if there are no TypeVarTuples, because we
|
params = prepare(cls, params)
|
||||||
# don't check variadic generic arity at runtime (to reduce
|
_check_generic(cls, params, len(cls.__parameters__))
|
||||||
# complexity of typing.py).
|
|
||||||
_check_generic(cls, params, len(cls.__parameters__))
|
new_args = []
|
||||||
|
for param, new_arg in zip(cls.__parameters__, params):
|
||||||
|
if isinstance(param, TypeVarTuple):
|
||||||
|
new_args.extend(new_arg)
|
||||||
|
else:
|
||||||
|
new_args.append(new_arg)
|
||||||
|
params = tuple(new_args)
|
||||||
|
|
||||||
return _GenericAlias(cls, params,
|
return _GenericAlias(cls, params,
|
||||||
_paramspec_tvars=True)
|
_paramspec_tvars=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix substitution of :class:`~typing.TypeVarTuple` and
|
||||||
|
:class:`~typing.ParamSpec` together in user generics.
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix substitution of :class:`~typing.ParamSpec` followed by
|
||||||
|
:class:`~typing.TypeVarTuple` in generic aliases.
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Check the number of arguments in substitution in user generics containing a
|
||||||
|
:class:`~typing.TypeVarTuple` and one or more :class:`~typing.TypeVar`.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue