diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2022-01-27 12:34:55 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-27 12:34:55 (GMT) |
commit | ecfacc362dd7fef7715dcd94f2e2ca6c622ef115 (patch) | |
tree | 3a907fea5ce2b03d03e7c6619e0cf12c4785d2cc | |
parent | 82bce54614f8116a40454fbbbf96a3fd460ca7df (diff) | |
download | cpython-ecfacc362dd7fef7715dcd94f2e2ca6c622ef115.zip cpython-ecfacc362dd7fef7715dcd94f2e2ca6c622ef115.tar.gz cpython-ecfacc362dd7fef7715dcd94f2e2ca6c622ef115.tar.bz2 |
bpo-44791: Fix substitution of ParamSpec in Concatenate with different parameter expressions (GH-27518)
* Substitution with a list of types returns now a tuple of types.
* Substitution with Concatenate returns now a Concatenate with
concatenated lists of arguments.
* Substitution with Ellipsis is not supported.
-rw-r--r-- | Lib/_collections_abc.py | 5 | ||||
-rw-r--r-- | Lib/test/test_typing.py | 48 | ||||
-rw-r--r-- | Lib/typing.py | 12 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2021-07-31-23-18-50.bpo-44791.4jFdpO.rst | 5 |
4 files changed, 65 insertions, 5 deletions
diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 87a9cd2..97913c7 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -500,7 +500,10 @@ class _CallableGenericAlias(GenericAlias): if subparams: subargs = tuple(subst[x] for x in subparams) arg = arg[subargs] - new_args.append(arg) + if isinstance(arg, tuple): + new_args.extend(arg) + else: + new_args.append(arg) # args[0] occurs due to things like Z[[int, str, bool]] from PEP 612 if not isinstance(new_args[0], list): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 4b260d4..3069331 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -628,10 +628,31 @@ class BaseCallableTests: def test_concatenate(self): Callable = self.Callable fullname = f"{Callable.__module__}.Callable" + T = TypeVar('T') P = ParamSpec('P') - C1 = Callable[typing.Concatenate[int, P], int] - self.assertEqual(repr(C1), - f"{fullname}[typing.Concatenate[int, ~P], int]") + P2 = ParamSpec('P2') + C = Callable[Concatenate[int, P], T] + self.assertEqual(repr(C), + f"{fullname}[typing.Concatenate[int, ~P], ~T]") + self.assertEqual(C[P2, int], Callable[Concatenate[int, P2], int]) + self.assertEqual(C[[str, float], int], Callable[[int, str, float], int]) + 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] + + C = Callable[Concatenate[int, P], int] + self.assertEqual(repr(C), + f"{fullname}[typing.Concatenate[int, ~P], int]") + self.assertEqual(C[P2], Callable[Concatenate[int, P2], int]) + self.assertEqual(C[[str, float]], Callable[[int, str, float], int]) + self.assertEqual(C[str, float], Callable[[int, str, float], int]) + self.assertEqual(C[[]], Callable[[int], int]) + self.assertEqual(C[Concatenate[str, P2]], + Callable[Concatenate[int, str, P2], int]) + with self.assertRaises(TypeError): + C[...] def test_errors(self): Callable = self.Callable @@ -5004,6 +5025,27 @@ class ConcatenateTests(BaseTestCase): self.assertEqual(C4.__args__, (Concatenate[int, T, P], T)) self.assertEqual(C4.__parameters__, (T, P)) + def test_var_substitution(self): + T = TypeVar('T') + P = ParamSpec('P') + P2 = ParamSpec('P2') + C = Concatenate[T, P] + self.assertEqual(C[int, P2], Concatenate[int, P2]) + self.assertEqual(C[int, [str, float]], (int, str, float)) + self.assertEqual(C[int, []], (int,)) + self.assertEqual(C[int, Concatenate[str, P2]], + Concatenate[int, str, P2]) + with self.assertRaises(TypeError): + C[int, ...] + + C = Concatenate[int, P] + self.assertEqual(C[P2], Concatenate[int, P2]) + self.assertEqual(C[[str, float]], (int, str, float)) + 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[...] class TypeGuardTests(BaseTestCase): def test_basics(self): diff --git a/Lib/typing.py b/Lib/typing.py index 450cd7b..36b95d7 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -604,7 +604,7 @@ def Concatenate(self, parameters): raise TypeError("The last parameter to Concatenate should be a " "ParamSpec variable.") msg = "Concatenate[arg, ...]: each arg must be a type." - parameters = tuple(_type_check(p, msg) for p in parameters) + parameters = (*(_type_check(p, msg) for p in parameters[:-1]), parameters[-1]) return _ConcatenateGenericAlias(self, parameters) @@ -1274,6 +1274,16 @@ class _ConcatenateGenericAlias(_GenericAlias, _root=True): _typevar_types=(TypeVar, ParamSpec), _paramspec_tvars=True) + def copy_with(self, params): + if isinstance(params[-1], (list, tuple)): + 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) + class Generic: """Abstract base class for generic types. diff --git a/Misc/NEWS.d/next/Library/2021-07-31-23-18-50.bpo-44791.4jFdpO.rst b/Misc/NEWS.d/next/Library/2021-07-31-23-18-50.bpo-44791.4jFdpO.rst new file mode 100644 index 0000000..8182aa4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-07-31-23-18-50.bpo-44791.4jFdpO.rst @@ -0,0 +1,5 @@ +Fix substitution of :class:`~typing.ParamSpec` in +:data:`~typing.Concatenate` with different parameter expressions. +Substitution with a list of types returns now a tuple of types. Substitution +with ``Concatenate`` returns now a ``Concatenate`` with concatenated lists +of arguments. |