Skip to content

Commit

Permalink
Initial support for TypeVarLike default parameter (PEP 696) (#77)
Browse files Browse the repository at this point in the history
Co-authored-by: James Hilton-Balfe <gobot1234yt@gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
  • Loading branch information
3 people committed Oct 3, 2022
1 parent 2979419 commit fafb5c5
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
- Add `typing_extensions.Any` a backport of python 3.11's Any class which is
subclassable at runtime. (backport from python/cpython#31841, by Shantanu
and Jelle Zijlstra). Patch by James Hilton-Balfe (@Gobot1234).
- Add initial support for TypeVarLike `default` parameter, PEP 696.
Patch by Marc Mueller (@cdce8p).

# Release 4.3.0 (July 1, 2022)

Expand Down
58 changes: 56 additions & 2 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2463,18 +2463,20 @@ class Z(Generic[P]):
pass

def test_pickle(self):
global P, P_co, P_contra
global P, P_co, P_contra, P_default
P = ParamSpec('P')
P_co = ParamSpec('P_co', covariant=True)
P_contra = ParamSpec('P_contra', contravariant=True)
P_default = ParamSpec('P_default', default=int)
for proto in range(pickle.HIGHEST_PROTOCOL):
with self.subTest(f'Pickle protocol {proto}'):
for paramspec in (P, P_co, P_contra):
for paramspec in (P, P_co, P_contra, P_default):
z = pickle.loads(pickle.dumps(paramspec, proto))
self.assertEqual(z.__name__, paramspec.__name__)
self.assertEqual(z.__covariant__, paramspec.__covariant__)
self.assertEqual(z.__contravariant__, paramspec.__contravariant__)
self.assertEqual(z.__bound__, paramspec.__bound__)
self.assertEqual(z.__default__, paramspec.__default__)

def test_eq(self):
P = ParamSpec('P')
Expand Down Expand Up @@ -2840,6 +2842,17 @@ def test_args_and_parameters(self):
self.assertEqual(t.__args__, (Unpack[Ts],))
self.assertEqual(t.__parameters__, (Ts,))

def test_pickle(self):
global Ts, Ts_default # pickle wants to reference the class by name
Ts = TypeVarTuple('Ts')
Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[int, str]])

for proto in range(pickle.HIGHEST_PROTOCOL):
for typevartuple in (Ts, Ts_default):
z = pickle.loads(pickle.dumps(typevartuple, proto))
self.assertEqual(z.__name__, typevartuple.__name__)
self.assertEqual(z.__default__, typevartuple.__default__)


class FinalDecoratorTests(BaseTestCase):
def test_final_unmodified(self):
Expand Down Expand Up @@ -3067,8 +3080,11 @@ def test_all_names_in___all__(self):
def test_typing_extensions_defers_when_possible(self):
exclude = {
'overload',
'ParamSpec',
'Text',
'TypedDict',
'TypeVar',
'TypeVarTuple',
'TYPE_CHECKING',
'Final',
'get_type_hints',
Expand Down Expand Up @@ -3395,5 +3411,43 @@ def test_same_as_typing_NamedTuple_38_minus(self):
)


class TypeVarLikeDefaultsTests(BaseTestCase):
def test_typevar(self):
T = typing_extensions.TypeVar('T', default=int)
self.assertEqual(T.__default__, int)

class A(Generic[T]): ...
Alias = Optional[T]

def test_paramspec(self):
P = ParamSpec('P', default=(str, int))
self.assertEqual(P.__default__, (str, int))

class A(Generic[P]): ...
Alias = typing.Callable[P, None]

def test_typevartuple(self):
Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]])
self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]])

class A(Generic[Unpack[Ts]]): ...
Alias = Optional[Unpack[Ts]]

def test_pickle(self):
global U, U_co, U_contra, U_default # pickle wants to reference the class by name
U = typing_extensions.TypeVar('U')
U_co = typing_extensions.TypeVar('U_co', covariant=True)
U_contra = typing_extensions.TypeVar('U_contra', contravariant=True)
U_default = typing_extensions.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__)


if __name__ == '__main__':
main()
89 changes: 83 additions & 6 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
'ParamSpecKwargs',
'Self',
'Type',
'TypeVar',
'TypeVarTuple',
'Unpack',

Expand Down Expand Up @@ -1147,6 +1148,43 @@ def __repr__(self):
above.""")


class _DefaultMixin:
"""Mixin for TypeVarLike defaults."""

__slots__ = ()

def __init__(self, default):
if isinstance(default, (tuple, list)):
self.__default__ = tuple((typing._type_check(d, "Default must be a type")
for d in default))
elif default:
self.__default__ = typing._type_check(default, "Default must be a type")
else:
self.__default__ = None


# Add default Parameter - PEP 696
class TypeVar(typing.TypeVar, _DefaultMixin, _root=True):
"""Type variable."""

__module__ = 'typing'

def __init__(self, name, *constraints, bound=None,
covariant=False, contravariant=False,
default=None):
super().__init__(name, *constraints, bound=bound, covariant=covariant,
contravariant=contravariant)
_DefaultMixin.__init__(self, default)

# for pickling:
try:
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
def_mod = None
if def_mod != 'typing_extensions':
self.__module__ = def_mod


# Python 3.10+ has PEP 612
if hasattr(typing, 'ParamSpecArgs'):
ParamSpecArgs = typing.ParamSpecArgs
Expand Down Expand Up @@ -1211,12 +1249,32 @@ def __eq__(self, other):

# 3.10+
if hasattr(typing, 'ParamSpec'):
ParamSpec = typing.ParamSpec

# Add default Parameter - PEP 696
class ParamSpec(typing.ParamSpec, _DefaultMixin, _root=True):
"""Parameter specification variable."""

__module__ = 'typing'

def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
default=None):
super().__init__(name, bound=bound, covariant=covariant,
contravariant=contravariant)
_DefaultMixin.__init__(self, default)

# for pickling:
try:
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
def_mod = None
if def_mod != 'typing_extensions':
self.__module__ = def_mod

# 3.7-3.9
else:

# Inherits from list as a workaround for Callable checks in Python < 3.9.2.
class ParamSpec(list):
class ParamSpec(list, _DefaultMixin):
"""Parameter specification variable.
Usage::
Expand Down Expand Up @@ -1274,7 +1332,8 @@ def args(self):
def kwargs(self):
return ParamSpecKwargs(self)

def __init__(self, name, *, bound=None, covariant=False, contravariant=False):
def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
default=None):
super().__init__([self])
self.__name__ = name
self.__covariant__ = bool(covariant)
Expand All @@ -1283,6 +1342,7 @@ def __init__(self, name, *, bound=None, covariant=False, contravariant=False):
self.__bound__ = typing._type_check(bound, 'Bound must be a type.')
else:
self.__bound__ = None
_DefaultMixin.__init__(self, default)

# for pickling:
try:
Expand Down Expand Up @@ -1784,9 +1844,25 @@ def _is_unpack(obj):


if hasattr(typing, "TypeVarTuple"): # 3.11+
TypeVarTuple = typing.TypeVarTuple

# Add default Parameter - PEP 696
class TypeVarTuple(typing.TypeVarTuple, _DefaultMixin, _root=True):
"""Type variable tuple."""

def __init__(self, name, *, default=None):
super().__init__(name)
_DefaultMixin.__init__(self, default)

# for pickling:
try:
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
def_mod = None
if def_mod != 'typing_extensions':
self.__module__ = def_mod

else:
class TypeVarTuple:
class TypeVarTuple(_DefaultMixin):
"""Type variable tuple.
Usage::
Expand Down Expand Up @@ -1836,8 +1912,9 @@ def get_shape(self) -> Tuple[*Ts]:
def __iter__(self):
yield self.__unpacked__

def __init__(self, name):
def __init__(self, name, *, default=None):
self.__name__ = name
_DefaultMixin.__init__(self, default)

# for pickling:
try:
Expand Down

0 comments on commit fafb5c5

Please sign in to comment.