Skip to content

Commit fafb5c5

Browse files
cdce8pGobot1234AlexWaygood
authored
Initial support for TypeVarLike default parameter (PEP 696) (#77)
Co-authored-by: James Hilton-Balfe <[email protected]> Co-authored-by: Alex Waygood <[email protected]>
1 parent 2979419 commit fafb5c5

File tree

3 files changed

+141
-8
lines changed

3 files changed

+141
-8
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
- Add `typing_extensions.Any` a backport of python 3.11's Any class which is
33
subclassable at runtime. (backport from python/cpython#31841, by Shantanu
44
and Jelle Zijlstra). Patch by James Hilton-Balfe (@Gobot1234).
5+
- Add initial support for TypeVarLike `default` parameter, PEP 696.
6+
Patch by Marc Mueller (@cdce8p).
57

68
# Release 4.3.0 (July 1, 2022)
79

Diff for: src/test_typing_extensions.py

+56-2
Original file line numberDiff line numberDiff line change
@@ -2463,18 +2463,20 @@ class Z(Generic[P]):
24632463
pass
24642464

24652465
def test_pickle(self):
2466-
global P, P_co, P_contra
2466+
global P, P_co, P_contra, P_default
24672467
P = ParamSpec('P')
24682468
P_co = ParamSpec('P_co', covariant=True)
24692469
P_contra = ParamSpec('P_contra', contravariant=True)
2470+
P_default = ParamSpec('P_default', default=int)
24702471
for proto in range(pickle.HIGHEST_PROTOCOL):
24712472
with self.subTest(f'Pickle protocol {proto}'):
2472-
for paramspec in (P, P_co, P_contra):
2473+
for paramspec in (P, P_co, P_contra, P_default):
24732474
z = pickle.loads(pickle.dumps(paramspec, proto))
24742475
self.assertEqual(z.__name__, paramspec.__name__)
24752476
self.assertEqual(z.__covariant__, paramspec.__covariant__)
24762477
self.assertEqual(z.__contravariant__, paramspec.__contravariant__)
24772478
self.assertEqual(z.__bound__, paramspec.__bound__)
2479+
self.assertEqual(z.__default__, paramspec.__default__)
24782480

24792481
def test_eq(self):
24802482
P = ParamSpec('P')
@@ -2840,6 +2842,17 @@ def test_args_and_parameters(self):
28402842
self.assertEqual(t.__args__, (Unpack[Ts],))
28412843
self.assertEqual(t.__parameters__, (Ts,))
28422844

2845+
def test_pickle(self):
2846+
global Ts, Ts_default # pickle wants to reference the class by name
2847+
Ts = TypeVarTuple('Ts')
2848+
Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[int, str]])
2849+
2850+
for proto in range(pickle.HIGHEST_PROTOCOL):
2851+
for typevartuple in (Ts, Ts_default):
2852+
z = pickle.loads(pickle.dumps(typevartuple, proto))
2853+
self.assertEqual(z.__name__, typevartuple.__name__)
2854+
self.assertEqual(z.__default__, typevartuple.__default__)
2855+
28432856

28442857
class FinalDecoratorTests(BaseTestCase):
28452858
def test_final_unmodified(self):
@@ -3067,8 +3080,11 @@ def test_all_names_in___all__(self):
30673080
def test_typing_extensions_defers_when_possible(self):
30683081
exclude = {
30693082
'overload',
3083+
'ParamSpec',
30703084
'Text',
30713085
'TypedDict',
3086+
'TypeVar',
3087+
'TypeVarTuple',
30723088
'TYPE_CHECKING',
30733089
'Final',
30743090
'get_type_hints',
@@ -3395,5 +3411,43 @@ def test_same_as_typing_NamedTuple_38_minus(self):
33953411
)
33963412

33973413

3414+
class TypeVarLikeDefaultsTests(BaseTestCase):
3415+
def test_typevar(self):
3416+
T = typing_extensions.TypeVar('T', default=int)
3417+
self.assertEqual(T.__default__, int)
3418+
3419+
class A(Generic[T]): ...
3420+
Alias = Optional[T]
3421+
3422+
def test_paramspec(self):
3423+
P = ParamSpec('P', default=(str, int))
3424+
self.assertEqual(P.__default__, (str, int))
3425+
3426+
class A(Generic[P]): ...
3427+
Alias = typing.Callable[P, None]
3428+
3429+
def test_typevartuple(self):
3430+
Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]])
3431+
self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]])
3432+
3433+
class A(Generic[Unpack[Ts]]): ...
3434+
Alias = Optional[Unpack[Ts]]
3435+
3436+
def test_pickle(self):
3437+
global U, U_co, U_contra, U_default # pickle wants to reference the class by name
3438+
U = typing_extensions.TypeVar('U')
3439+
U_co = typing_extensions.TypeVar('U_co', covariant=True)
3440+
U_contra = typing_extensions.TypeVar('U_contra', contravariant=True)
3441+
U_default = typing_extensions.TypeVar('U_default', default=int)
3442+
for proto in range(pickle.HIGHEST_PROTOCOL):
3443+
for typevar in (U, U_co, U_contra, U_default):
3444+
z = pickle.loads(pickle.dumps(typevar, proto))
3445+
self.assertEqual(z.__name__, typevar.__name__)
3446+
self.assertEqual(z.__covariant__, typevar.__covariant__)
3447+
self.assertEqual(z.__contravariant__, typevar.__contravariant__)
3448+
self.assertEqual(z.__bound__, typevar.__bound__)
3449+
self.assertEqual(z.__default__, typevar.__default__)
3450+
3451+
33983452
if __name__ == '__main__':
33993453
main()

Diff for: src/typing_extensions.py

+83-6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
'ParamSpecKwargs',
2222
'Self',
2323
'Type',
24+
'TypeVar',
2425
'TypeVarTuple',
2526
'Unpack',
2627

@@ -1147,6 +1148,43 @@ def __repr__(self):
11471148
above.""")
11481149

11491150

1151+
class _DefaultMixin:
1152+
"""Mixin for TypeVarLike defaults."""
1153+
1154+
__slots__ = ()
1155+
1156+
def __init__(self, default):
1157+
if isinstance(default, (tuple, list)):
1158+
self.__default__ = tuple((typing._type_check(d, "Default must be a type")
1159+
for d in default))
1160+
elif default:
1161+
self.__default__ = typing._type_check(default, "Default must be a type")
1162+
else:
1163+
self.__default__ = None
1164+
1165+
1166+
# Add default Parameter - PEP 696
1167+
class TypeVar(typing.TypeVar, _DefaultMixin, _root=True):
1168+
"""Type variable."""
1169+
1170+
__module__ = 'typing'
1171+
1172+
def __init__(self, name, *constraints, bound=None,
1173+
covariant=False, contravariant=False,
1174+
default=None):
1175+
super().__init__(name, *constraints, bound=bound, covariant=covariant,
1176+
contravariant=contravariant)
1177+
_DefaultMixin.__init__(self, default)
1178+
1179+
# for pickling:
1180+
try:
1181+
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
1182+
except (AttributeError, ValueError):
1183+
def_mod = None
1184+
if def_mod != 'typing_extensions':
1185+
self.__module__ = def_mod
1186+
1187+
11501188
# Python 3.10+ has PEP 612
11511189
if hasattr(typing, 'ParamSpecArgs'):
11521190
ParamSpecArgs = typing.ParamSpecArgs
@@ -1211,12 +1249,32 @@ def __eq__(self, other):
12111249

12121250
# 3.10+
12131251
if hasattr(typing, 'ParamSpec'):
1214-
ParamSpec = typing.ParamSpec
1252+
1253+
# Add default Parameter - PEP 696
1254+
class ParamSpec(typing.ParamSpec, _DefaultMixin, _root=True):
1255+
"""Parameter specification variable."""
1256+
1257+
__module__ = 'typing'
1258+
1259+
def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
1260+
default=None):
1261+
super().__init__(name, bound=bound, covariant=covariant,
1262+
contravariant=contravariant)
1263+
_DefaultMixin.__init__(self, default)
1264+
1265+
# for pickling:
1266+
try:
1267+
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
1268+
except (AttributeError, ValueError):
1269+
def_mod = None
1270+
if def_mod != 'typing_extensions':
1271+
self.__module__ = def_mod
1272+
12151273
# 3.7-3.9
12161274
else:
12171275

12181276
# Inherits from list as a workaround for Callable checks in Python < 3.9.2.
1219-
class ParamSpec(list):
1277+
class ParamSpec(list, _DefaultMixin):
12201278
"""Parameter specification variable.
12211279
12221280
Usage::
@@ -1274,7 +1332,8 @@ def args(self):
12741332
def kwargs(self):
12751333
return ParamSpecKwargs(self)
12761334

1277-
def __init__(self, name, *, bound=None, covariant=False, contravariant=False):
1335+
def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
1336+
default=None):
12781337
super().__init__([self])
12791338
self.__name__ = name
12801339
self.__covariant__ = bool(covariant)
@@ -1283,6 +1342,7 @@ def __init__(self, name, *, bound=None, covariant=False, contravariant=False):
12831342
self.__bound__ = typing._type_check(bound, 'Bound must be a type.')
12841343
else:
12851344
self.__bound__ = None
1345+
_DefaultMixin.__init__(self, default)
12861346

12871347
# for pickling:
12881348
try:
@@ -1784,9 +1844,25 @@ def _is_unpack(obj):
17841844

17851845

17861846
if hasattr(typing, "TypeVarTuple"): # 3.11+
1787-
TypeVarTuple = typing.TypeVarTuple
1847+
1848+
# Add default Parameter - PEP 696
1849+
class TypeVarTuple(typing.TypeVarTuple, _DefaultMixin, _root=True):
1850+
"""Type variable tuple."""
1851+
1852+
def __init__(self, name, *, default=None):
1853+
super().__init__(name)
1854+
_DefaultMixin.__init__(self, default)
1855+
1856+
# for pickling:
1857+
try:
1858+
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
1859+
except (AttributeError, ValueError):
1860+
def_mod = None
1861+
if def_mod != 'typing_extensions':
1862+
self.__module__ = def_mod
1863+
17881864
else:
1789-
class TypeVarTuple:
1865+
class TypeVarTuple(_DefaultMixin):
17901866
"""Type variable tuple.
17911867
17921868
Usage::
@@ -1836,8 +1912,9 @@ def get_shape(self) -> Tuple[*Ts]:
18361912
def __iter__(self):
18371913
yield self.__unpacked__
18381914

1839-
def __init__(self, name):
1915+
def __init__(self, name, *, default=None):
18401916
self.__name__ = name
1917+
_DefaultMixin.__init__(self, default)
18411918

18421919
# for pickling:
18431920
try:

0 commit comments

Comments
 (0)