diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index d4a6f32aa36d2c..0de380551c9fb4 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -852,7 +852,8 @@ These can be used as types in annotations using ``[]``, each having a unique syn callable. Usage is in the form ``Concatenate[Arg1Type, Arg2Type, ..., ParamSpecVariable]``. ``Concatenate`` is currently only valid when used as the first argument to a :data:`Callable`. - The last parameter to ``Concatenate`` must be a :class:`ParamSpec`. + The last parameter to ``Concatenate`` must be a :class:`ParamSpec` or + ellipsis (``...``). For example, to annotate a decorator ``with_lock`` which provides a :class:`threading.Lock` to the decorated function, ``Concatenate`` can be diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index a904b7a790c04d..e2f8c31871aeb6 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1368,8 +1368,7 @@ def test_concatenate(self): self.assertEqual(C[[], int], Callable[[int], int]) self.assertEqual(C[Concatenate[str, P2], int], Callable[Concatenate[int, str, P2], int]) - with self.assertRaises(TypeError): - C[..., int] + self.assertEqual(C[..., int], Callable[Concatenate[int, ...], int]) C = Callable[Concatenate[int, P], int] self.assertEqual(repr(C), @@ -1380,8 +1379,7 @@ def test_concatenate(self): self.assertEqual(C[[]], Callable[[int], int]) self.assertEqual(C[Concatenate[str, P2]], Callable[Concatenate[int, str, P2], int]) - with self.assertRaises(TypeError): - C[...] + self.assertEqual(C[...], Callable[Concatenate[int, ...], int]) def test_errors(self): Callable = self.Callable @@ -6352,8 +6350,7 @@ def test_var_substitution(self): self.assertEqual(C[int, []], (int,)) self.assertEqual(C[int, Concatenate[str, P2]], Concatenate[int, str, P2]) - with self.assertRaises(TypeError): - C[int, ...] + self.assertEqual(C[int, ...], Concatenate[int, ...]) C = Concatenate[int, P] self.assertEqual(C[P2], Concatenate[int, P2]) @@ -6361,8 +6358,7 @@ def test_var_substitution(self): self.assertEqual(C[str, float], (int, str, float)) self.assertEqual(C[[]], (int,)) self.assertEqual(C[Concatenate[str, P2]], Concatenate[int, str, P2]) - with self.assertRaises(TypeError): - C[...] + self.assertEqual(C[...], Concatenate[int, ...]) class TypeGuardTests(BaseTestCase): def test_basics(self): diff --git a/Lib/typing.py b/Lib/typing.py index f4d4fa4d6713c5..b250f2992dfff3 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -714,9 +714,9 @@ def Concatenate(self, parameters): raise TypeError("Cannot take a Concatenate of no types.") if not isinstance(parameters, tuple): parameters = (parameters,) - if not isinstance(parameters[-1], ParamSpec): + if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)): raise TypeError("The last parameter to Concatenate should be a " - "ParamSpec variable.") + "ParamSpec variable or ellipsis.") msg = "Concatenate[arg, ...]: each arg must be a type." parameters = (*(_type_check(p, msg) for p in parameters[:-1]), parameters[-1]) return _ConcatenateGenericAlias(self, parameters, @@ -1641,9 +1641,6 @@ def copy_with(self, params): return (*params[:-1], *params[-1]) if isinstance(params[-1], _ConcatenateGenericAlias): params = (*params[:-1], *params[-1].__args__) - elif not isinstance(params[-1], ParamSpec): - raise TypeError("The last parameter to Concatenate should be a " - "ParamSpec variable.") return super().copy_with(params) diff --git a/Misc/NEWS.d/next/Library/2022-01-27-14-46-15.bpo-44791.tR1JFG.rst b/Misc/NEWS.d/next/Library/2022-01-27-14-46-15.bpo-44791.tR1JFG.rst new file mode 100644 index 00000000000000..31c6dcc058e94f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-27-14-46-15.bpo-44791.tR1JFG.rst @@ -0,0 +1 @@ +Accept ellipsis as the last argument of :data:`typing.Concatenate`.