aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2022-11-29 06:46:53 -0800
committerGitHub <noreply@github.com>2022-11-29 06:46:53 -0800
commit74920aa27d0c57443dd7f704d6272cca9c507ab3 (patch)
tree81537f85c9a9edce6420d2ffa5a25b5722e52140
parent[3.11] gh-99811: Use correct variable to search for time in format string (GH... (diff)
downloadcpython-74920aa27d0c57443dd7f704d6272cca9c507ab3.tar.gz
cpython-74920aa27d0c57443dd7f704d6272cca9c507ab3.tar.bz2
cpython-74920aa27d0c57443dd7f704d6272cca9c507ab3.zip
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 8f2fb7dfe72c882e97e524ef7ce40ceb663cc27e) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
-rw-r--r--Lib/test/test_typing.py81
-rw-r--r--Lib/typing.py71
-rw-r--r--Misc/NEWS.d/next/Library/2022-11-12-12-08-34.gh-issue-99344.7M_u8G.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2022-11-12-12-10-23.gh-issue-99379.bcGhxF.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2022-11-12-12-15-30.gh-issue-99382.dKg_rW.rst2
5 files changed, 117 insertions, 41 deletions
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 1206ab7ecc..efa4cb3d4e 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -769,20 +769,42 @@ class GenericAliasSubstitutionTests(BaseTestCase):
('generic[*Ts]', '[*Ts]', 'generic[*Ts]'),
('generic[*Ts]', '[T, *Ts]', 'generic[T, *Ts]'),
('generic[*Ts]', '[*Ts, T]', 'generic[*Ts, T]'),
+ ('generic[T, *Ts]', '[()]', 'TypeError'),
('generic[T, *Ts]', '[int]', 'generic[int]'),
('generic[T, *Ts]', '[int, str]', 'generic[int, str]'),
('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, str]', 'generic[list[int], str]'),
('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, str]', 'generic[int, str]'),
('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, str]', 'generic[int, list[str]]'),
('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]', '[str, *tuple_type[int, ...]]', 'generic[str, *tuple_type[int, ...]]'),
('generic[T, *Ts]', '[*tuple_type[int, ...], str]', 'generic[int, *tuple_type[int, ...], str]'),
@@ -7190,6 +7212,65 @@ class ParamSpecTests(BaseTestCase):
self.assertEqual(G1.__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):
T = TypeVar("T")
P = ParamSpec("P")
diff --git a/Lib/typing.py b/Lib/typing.py
index 9c4d653372..9818237de4 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -284,25 +284,6 @@ def _unpack_args(args):
newargs.append(arg)
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):
# Weed out strict duplicates, preserving the first of each occurrence.
all_params = set(params)
@@ -1226,7 +1207,18 @@ class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin,
return arg
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):
return attr.startswith('__') and attr.endswith('__')
@@ -1789,23 +1781,13 @@ class Generic:
if not isinstance(params, tuple):
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)
if cls in (Generic, Protocol):
# 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):
raise TypeError(
f"Parameters to {cls.__name__}[...] must all be type variables "
@@ -1815,13 +1797,20 @@ class Generic:
f"Parameters to {cls.__name__}[...] must all be unique")
else:
# Subscripting a regular Generic subclass.
- if any(isinstance(t, ParamSpec) for t in cls.__parameters__):
- params = _prepare_paramspec_params(cls, params)
- elif not any(isinstance(p, TypeVarTuple) for p in cls.__parameters__):
- # We only run this if there are no TypeVarTuples, because we
- # don't check variadic generic arity at runtime (to reduce
- # complexity of typing.py).
- _check_generic(cls, params, len(cls.__parameters__))
+ for param in cls.__parameters__:
+ prepare = getattr(param, '__typing_prepare_subst__', None)
+ if prepare is not None:
+ params = prepare(cls, params)
+ _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,
_paramspec_tvars=True)
diff --git a/Misc/NEWS.d/next/Library/2022-11-12-12-08-34.gh-issue-99344.7M_u8G.rst b/Misc/NEWS.d/next/Library/2022-11-12-12-08-34.gh-issue-99344.7M_u8G.rst
new file mode 100644
index 0000000000..412c8c7934
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-11-12-12-08-34.gh-issue-99344.7M_u8G.rst
@@ -0,0 +1,2 @@
+Fix substitution of :class:`~typing.TypeVarTuple` and
+:class:`~typing.ParamSpec` together in user generics.
diff --git a/Misc/NEWS.d/next/Library/2022-11-12-12-10-23.gh-issue-99379.bcGhxF.rst b/Misc/NEWS.d/next/Library/2022-11-12-12-10-23.gh-issue-99379.bcGhxF.rst
new file mode 100644
index 0000000000..1950680b1d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-11-12-12-10-23.gh-issue-99379.bcGhxF.rst
@@ -0,0 +1,2 @@
+Fix substitution of :class:`~typing.ParamSpec` followed by
+:class:`~typing.TypeVarTuple` in generic aliases.
diff --git a/Misc/NEWS.d/next/Library/2022-11-12-12-15-30.gh-issue-99382.dKg_rW.rst b/Misc/NEWS.d/next/Library/2022-11-12-12-15-30.gh-issue-99382.dKg_rW.rst
new file mode 100644
index 0000000000..f153f2fcea
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-11-12-12-15-30.gh-issue-99382.dKg_rW.rst
@@ -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`.