gh-116126: Implement PEP 696 (#116129)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
This commit is contained in:
Jelle Zijlstra 2024-05-03 06:17:32 -07:00 committed by GitHub
parent 852263e108
commit ca269e58c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 1924 additions and 623 deletions

View file

@ -38,7 +38,7 @@ from typing import Annotated, ForwardRef
from typing import Self, LiteralString
from typing import TypeAlias
from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs
from typing import TypeGuard, TypeIs
from typing import TypeGuard, TypeIs, NoDefault
import abc
import textwrap
import typing
@ -580,6 +580,174 @@ class TypeVarTests(BaseTestCase):
self.assertIs(T.__bound__, None)
class TypeParameterDefaultsTests(BaseTestCase):
def test_typevar(self):
T = TypeVar('T', default=int)
self.assertEqual(T.__default__, int)
self.assertTrue(T.has_default())
self.assertIsInstance(T, TypeVar)
class A(Generic[T]): ...
Alias = Optional[T]
def test_typevar_none(self):
U = TypeVar('U')
U_None = TypeVar('U_None', default=None)
self.assertIs(U.__default__, NoDefault)
self.assertFalse(U.has_default())
self.assertIs(U_None.__default__, None)
self.assertTrue(U_None.has_default())
class X[T]: ...
T, = X.__type_params__
self.assertIs(T.__default__, NoDefault)
self.assertFalse(T.has_default())
def test_paramspec(self):
P = ParamSpec('P', default=(str, int))
self.assertEqual(P.__default__, (str, int))
self.assertTrue(P.has_default())
self.assertIsInstance(P, ParamSpec)
class A(Generic[P]): ...
Alias = typing.Callable[P, None]
P_default = ParamSpec('P_default', default=...)
self.assertIs(P_default.__default__, ...)
def test_paramspec_none(self):
U = ParamSpec('U')
U_None = ParamSpec('U_None', default=None)
self.assertIs(U.__default__, NoDefault)
self.assertFalse(U.has_default())
self.assertIs(U_None.__default__, None)
self.assertTrue(U_None.has_default())
class X[**P]: ...
P, = X.__type_params__
self.assertIs(P.__default__, NoDefault)
self.assertFalse(P.has_default())
def test_typevartuple(self):
Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]])
self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]])
self.assertTrue(Ts.has_default())
self.assertIsInstance(Ts, TypeVarTuple)
class A(Generic[Unpack[Ts]]): ...
Alias = Optional[Unpack[Ts]]
def test_typevartuple_specialization(self):
T = TypeVar("T")
Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]])
self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]])
class A(Generic[T, Unpack[Ts]]): ...
self.assertEqual(A[float].__args__, (float, str, int))
self.assertEqual(A[float, range].__args__, (float, range))
self.assertEqual(A[float, *tuple[int, ...]].__args__, (float, *tuple[int, ...]))
def test_typevar_and_typevartuple_specialization(self):
T = TypeVar("T")
U = TypeVar("U", default=float)
Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]])
self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]])
class A(Generic[T, U, Unpack[Ts]]): ...
self.assertEqual(A[int].__args__, (int, float, str, int))
self.assertEqual(A[int, str].__args__, (int, str, str, int))
self.assertEqual(A[int, str, range].__args__, (int, str, range))
self.assertEqual(A[int, str, *tuple[int, ...]].__args__, (int, str, *tuple[int, ...]))
def test_no_default_after_typevar_tuple(self):
T = TypeVar("T", default=int)
Ts = TypeVarTuple("Ts")
Ts_default = TypeVarTuple("Ts_default", default=Unpack[Tuple[str, int]])
with self.assertRaises(TypeError):
class X(Generic[*Ts, T]): ...
with self.assertRaises(TypeError):
class Y(Generic[*Ts_default, T]): ...
def test_paramspec_specialization(self):
T = TypeVar("T")
P = ParamSpec('P', default=[str, int])
self.assertEqual(P.__default__, [str, int])
class A(Generic[T, P]): ...
self.assertEqual(A[float].__args__, (float, (str, int)))
self.assertEqual(A[float, [range]].__args__, (float, (range,)))
def test_typevar_and_paramspec_specialization(self):
T = TypeVar("T")
U = TypeVar("U", default=float)
P = ParamSpec('P', default=[str, int])
self.assertEqual(P.__default__, [str, int])
class A(Generic[T, U, P]): ...
self.assertEqual(A[float].__args__, (float, float, (str, int)))
self.assertEqual(A[float, int].__args__, (float, int, (str, int)))
self.assertEqual(A[float, int, [range]].__args__, (float, int, (range,)))
def test_paramspec_and_typevar_specialization(self):
T = TypeVar("T")
P = ParamSpec('P', default=[str, int])
U = TypeVar("U", default=float)
self.assertEqual(P.__default__, [str, int])
class A(Generic[T, P, U]): ...
self.assertEqual(A[float].__args__, (float, (str, int), float))
self.assertEqual(A[float, [range]].__args__, (float, (range,), float))
self.assertEqual(A[float, [range], int].__args__, (float, (range,), int))
def test_typevartuple_none(self):
U = TypeVarTuple('U')
U_None = TypeVarTuple('U_None', default=None)
self.assertIs(U.__default__, NoDefault)
self.assertFalse(U.has_default())
self.assertIs(U_None.__default__, None)
self.assertTrue(U_None.has_default())
class X[**Ts]: ...
Ts, = X.__type_params__
self.assertIs(Ts.__default__, NoDefault)
self.assertFalse(Ts.has_default())
def test_no_default_after_non_default(self):
DefaultStrT = TypeVar('DefaultStrT', default=str)
T = TypeVar('T')
with self.assertRaisesRegex(
TypeError, r"Type parameter ~T without a default follows type parameter with a default"
):
Test = Generic[DefaultStrT, T]
def test_need_more_params(self):
DefaultStrT = TypeVar('DefaultStrT', default=str)
T = TypeVar('T')
U = TypeVar('U')
class A(Generic[T, U, DefaultStrT]): ...
A[int, bool]
A[int, bool, str]
with self.assertRaisesRegex(
TypeError, r"Too few arguments for .+; actual 1, expected at least 2"
):
Test = A[int]
def test_pickle(self):
global U, U_co, U_contra, U_default # pickle wants to reference the class by name
U = TypeVar('U')
U_co = TypeVar('U_co', covariant=True)
U_contra = TypeVar('U_contra', contravariant=True)
U_default = TypeVar('U_default', default=int)
for proto in range(pickle.HIGHEST_PROTOCOL):
for typevar in (U, U_co, U_contra, U_default):
z = pickle.loads(pickle.dumps(typevar, proto))
self.assertEqual(z.__name__, typevar.__name__)
self.assertEqual(z.__covariant__, typevar.__covariant__)
self.assertEqual(z.__contravariant__, typevar.__contravariant__)
self.assertEqual(z.__bound__, typevar.__bound__)
self.assertEqual(z.__default__, typevar.__default__)
def template_replace(templates: list[str], replacements: dict[str, list[str]]) -> list[tuple[str]]:
"""Renders templates with possible combinations of replacements.
@ -10001,6 +10169,26 @@ class DataclassTransformTests(BaseTestCase):
self.assertIsInstance(CustomerModel, Decorated)
class NoDefaultTests(BaseTestCase):
def test_pickling(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
s = pickle.dumps(NoDefault, proto)
loaded = pickle.loads(s)
self.assertIs(NoDefault, loaded)
def test_constructor(self):
self.assertIs(NoDefault, type(NoDefault)())
with self.assertRaises(TypeError):
NoDefault(1)
def test_repr(self):
self.assertEqual(repr(NoDefault), 'typing.NoDefault')
def test_no_call(self):
with self.assertRaises(TypeError):
NoDefault()
class AllTests(BaseTestCase):
"""Tests for __all__."""