bpo-41559: Implement PEP 612 - Add ParamSpec and Concatenate to typing (#23702)
diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py
index ccf40b1..fd024dc 100644
--- a/Lib/test/test_genericalias.py
+++ b/Lib/test/test_genericalias.py
@@ -369,6 +369,27 @@ def __call__(self):
self.assertEqual(c1.__args__, c2.__args__)
self.assertEqual(hash(c1.__args__), hash(c2.__args__))
+ with self.subTest("Testing ParamSpec uses"):
+ P = typing.ParamSpec('P')
+ C1 = Callable[P, T]
+ # substitution
+ self.assertEqual(C1[int, str], Callable[[int], str])
+ self.assertEqual(C1[[int, str], str], Callable[[int, str], str])
+ self.assertEqual(repr(C1).split(".")[-1], "Callable[~P, ~T]")
+ self.assertEqual(repr(C1[int, str]).split(".")[-1], "Callable[[int], str]")
+
+ C2 = Callable[P, int]
+ # special case in PEP 612 where
+ # X[int, str, float] == X[[int, str, float]]
+ self.assertEqual(C2[int, str, float], C2[[int, str, float]])
+ self.assertEqual(repr(C2).split(".")[-1], "Callable[~P, int]")
+ self.assertEqual(repr(C2[int, str]).split(".")[-1], "Callable[[int, str], int]")
+
+ with self.subTest("Testing Concatenate uses"):
+ P = typing.ParamSpec('P')
+ C1 = Callable[typing.Concatenate[int, P], int]
+ self.assertEqual(repr(C1), "collections.abc.Callable"
+ "[typing.Concatenate[int, ~P], int]")
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 8e86e76..c340c8a 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -25,6 +25,7 @@
from typing import Pattern, Match
from typing import Annotated, ForwardRef
from typing import TypeAlias
+from typing import ParamSpec, Concatenate
import abc
import typing
import weakref
@@ -1130,10 +1131,6 @@ class P(PR[int, T], Protocol[T]):
PR[int]
with self.assertRaises(TypeError):
P[int, str]
- with self.assertRaises(TypeError):
- PR[int, 1]
- with self.assertRaises(TypeError):
- PR[int, ClassVar]
class C(PR[int, T]): pass
@@ -1155,8 +1152,6 @@ class P(PR[int, str], Protocol):
self.assertIsSubclass(P, PR)
with self.assertRaises(TypeError):
PR[int]
- with self.assertRaises(TypeError):
- PR[int, 1]
class P1(Protocol, Generic[T]):
def bar(self, x: T) -> str: ...
@@ -1175,8 +1170,6 @@ def bar(self, x: str) -> str:
return x
self.assertIsInstance(Test(), PSub)
- with self.assertRaises(TypeError):
- PR[int, ClassVar]
def test_init_called(self):
T = TypeVar('T')
@@ -1746,8 +1739,6 @@ def test_extended_generic_rules_eq(self):
self.assertEqual(typing.Iterable[Tuple[T, T]][T], typing.Iterable[Tuple[T, T]])
with self.assertRaises(TypeError):
Tuple[T, int][()]
- with self.assertRaises(TypeError):
- Tuple[T, U][T, ...]
self.assertEqual(Union[T, int][int], int)
self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str])
@@ -1759,10 +1750,6 @@ class Derived(Base): ...
self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT])
self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]])
- with self.assertRaises(TypeError):
- Callable[[T], U][..., int]
- with self.assertRaises(TypeError):
- Callable[[T], U][[], int]
def test_extended_generic_rules_repr(self):
T = TypeVar('T')
@@ -4243,6 +4230,111 @@ def test_cannot_subscript(self):
TypeAlias[int]
+class ParamSpecTests(BaseTestCase):
+
+ def test_basic_plain(self):
+ P = ParamSpec('P')
+ self.assertEqual(P, P)
+ self.assertIsInstance(P, ParamSpec)
+
+ def test_valid_uses(self):
+ P = ParamSpec('P')
+ T = TypeVar('T')
+ C1 = Callable[P, int]
+ self.assertEqual(C1.__args__, (P, int))
+ self.assertEqual(C1.__parameters__, (P,))
+ C2 = Callable[P, T]
+ self.assertEqual(C2.__args__, (P, T))
+ self.assertEqual(C2.__parameters__, (P, T))
+ # Test collections.abc.Callable too.
+ C3 = collections.abc.Callable[P, int]
+ self.assertEqual(C3.__args__, (P, int))
+ self.assertEqual(C3.__parameters__, (P,))
+ C4 = collections.abc.Callable[P, T]
+ self.assertEqual(C4.__args__, (P, T))
+ self.assertEqual(C4.__parameters__, (P, T))
+
+ # ParamSpec instances should also have args and kwargs attributes.
+ self.assertIn('args', dir(P))
+ self.assertIn('kwargs', dir(P))
+ P.args
+ P.kwargs
+
+ def test_user_generics(self):
+ T = TypeVar("T")
+ P = ParamSpec("P")
+ P_2 = ParamSpec("P_2")
+
+ class X(Generic[T, P]):
+ f: Callable[P, int]
+ x: T
+ G1 = X[int, P_2]
+ self.assertEqual(G1.__args__, (int, P_2))
+ self.assertEqual(G1.__parameters__, (P_2,))
+
+ G2 = X[int, Concatenate[int, P_2]]
+ self.assertEqual(G2.__args__, (int, Concatenate[int, P_2]))
+ self.assertEqual(G2.__parameters__, (P_2,))
+
+ G3 = X[int, [int, bool]]
+ self.assertEqual(G3.__args__, (int, (int, bool)))
+ self.assertEqual(G3.__parameters__, ())
+
+ G4 = X[int, ...]
+ self.assertEqual(G4.__args__, (int, Ellipsis))
+ self.assertEqual(G4.__parameters__, ())
+
+ class Z(Generic[P]):
+ f: Callable[P, int]
+
+ G5 = Z[[int, str, bool]]
+ self.assertEqual(G5.__args__, ((int, str, bool),))
+ self.assertEqual(G5.__parameters__, ())
+
+ G6 = Z[int, str, bool]
+ self.assertEqual(G6.__args__, ((int, str, bool),))
+ self.assertEqual(G6.__parameters__, ())
+
+ # G5 and G6 should be equivalent according to the PEP
+ self.assertEqual(G5.__args__, G6.__args__)
+ self.assertEqual(G5.__origin__, G6.__origin__)
+ self.assertEqual(G5.__parameters__, G6.__parameters__)
+ self.assertEqual(G5, G6)
+
+ def test_var_substitution(self):
+ T = TypeVar("T")
+ P = ParamSpec("P")
+ C1 = Callable[P, T]
+ self.assertEqual(C1[int, str], Callable[[int], str])
+ self.assertEqual(C1[[int, str, dict], float], Callable[[int, str, dict], float])
+
+
+class ConcatenateTests(BaseTestCase):
+ def test_basics(self):
+ P = ParamSpec('P')
+ class MyClass: ...
+ c = Concatenate[MyClass, P]
+ self.assertNotEqual(c, Concatenate)
+
+ def test_valid_uses(self):
+ P = ParamSpec('P')
+ T = TypeVar('T')
+ C1 = Callable[Concatenate[int, P], int]
+ self.assertEqual(C1.__args__, (Concatenate[int, P], int))
+ self.assertEqual(C1.__parameters__, (P,))
+ C2 = Callable[Concatenate[int, T, P], T]
+ self.assertEqual(C2.__args__, (Concatenate[int, T, P], T))
+ self.assertEqual(C2.__parameters__, (T, P))
+
+ # Test collections.abc.Callable too.
+ C3 = collections.abc.Callable[Concatenate[int, P], int]
+ self.assertEqual(C3.__args__, (Concatenate[int, P], int))
+ self.assertEqual(C3.__parameters__, (P,))
+ C4 = collections.abc.Callable[Concatenate[int, T, P], T]
+ self.assertEqual(C4.__args__, (Concatenate[int, T, P], T))
+ self.assertEqual(C4.__parameters__, (T, P))
+
+
class AllTests(BaseTestCase):
"""Tests for __all__."""