[Python-checkins] bpo-44791: Fix substitution of ParamSpec in Concatenate with different parameter expressions (GH-27518)

miss-islington webhook-mailer at python.org
Thu Jan 27 08:01:35 EST 2022


https://github.com/python/cpython/commit/89db09029566cf3af04b540e33fe1ff9b32f8c8b
commit: 89db09029566cf3af04b540e33fe1ff9b32f8c8b
branch: 3.10
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: miss-islington <31488909+miss-islington at users.noreply.github.com>
date: 2022-01-27T05:01:24-08:00
summary:

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.
(cherry picked from commit ecfacc362dd7fef7715dcd94f2e2ca6c622ef115)

Co-authored-by: Serhiy Storchaka <storchaka at gmail.com>

files:
A Misc/NEWS.d/next/Library/2021-07-31-23-18-50.bpo-44791.4jFdpO.rst
M Lib/_collections_abc.py
M Lib/test/test_typing.py
M Lib/typing.py

diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py
index 87a9cd2d46de9..97913c77721da 100644
--- a/Lib/_collections_abc.py
+++ b/Lib/_collections_abc.py
@@ -500,7 +500,10 @@ def __getitem__(self, item):
                 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 d4068242da6da..eb72b098bbf5c 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -621,10 +621,31 @@ def test_paramspec(self):
     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
@@ -4894,6 +4915,27 @@ def test_valid_uses(self):
         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 705331a9a89a0..aca3f7a7e8255 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -598,7 +598,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 @@ def __init__(self, *args, **kwargs):
                          _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 0000000000000..8182aa4e5358a
--- /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.



More information about the Python-checkins mailing list