summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2022-01-27 12:34:55 (GMT)
committerGitHub <noreply@github.com>2022-01-27 12:34:55 (GMT)
commitecfacc362dd7fef7715dcd94f2e2ca6c622ef115 (patch)
tree3a907fea5ce2b03d03e7c6619e0cf12c4785d2cc
parent82bce54614f8116a40454fbbbf96a3fd460ca7df (diff)
downloadcpython-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.py5
-rw-r--r--Lib/test/test_typing.py48
-rw-r--r--Lib/typing.py12
-rw-r--r--Misc/NEWS.d/next/Library/2021-07-31-23-18-50.bpo-44791.4jFdpO.rst5
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.