Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for TypeVarLike default parameter (PEP 696) #77

Merged
merged 6 commits into from
Oct 3, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 33 additions & 5 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 @@ -3400,26 +3413,41 @@ def test_same_as_typing_NamedTuple_38_minus(self):

class TypeVarLikeDefaultsTests(BaseTestCase):
def test_typevar(self):
T = typing_extensions.TypeVar("T", default=int)
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))
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]])
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()
52 changes: 32 additions & 20 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,8 @@ def __repr__(self):
class _DefaultMixin:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class should define empty slots to make TypeVar not have dict

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Although I'm not sure it will work. For 3.10 _TypeVarLike and for 3.11 _BoundVarianceMixin don't add __slots__, so the __dict__ for TypeVar is created anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're interested, we can fix that for 3.12. (I'd be hesitant for older versions since it may break some users.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like __slots__ was removed on purpose since it didn't have any use. I've updated the PR to remove it from TypeVar as well. Ref: python/cpython#30444

"""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")
Expand All @@ -1161,27 +1163,31 @@ def __init__(self, default):
self.__default__ = None


# Add default Parameter - PEP 696
class TypeVar(typing.TypeVar, _DefaultMixin, _root=True):
"""Type variable."""
if sys.version_info >= (3, 12):
cdce8p marked this conversation as resolved.
Show resolved Hide resolved
TypeVar = typing.TypeVar

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

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)
__slots__ = ('__default__',)
__module__ = 'typing'

# 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
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
Expand Down Expand Up @@ -1246,14 +1252,18 @@ def __eq__(self, other):
return NotImplemented
return self.__origin__ == other.__origin__


if sys.version_info >= (3, 12):
ParamSpec = typing.ParamSpec

# 3.10+
if hasattr(typing, 'ParamSpec'):
cdce8p marked this conversation as resolved.
Show resolved Hide resolved

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

__module__ = "typing"
__module__ = 'typing'

def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
default=None):
Expand Down Expand Up @@ -1841,8 +1851,10 @@ def add_batch_axis(
def _is_unpack(obj):
return isinstance(obj, _UnpackAlias)

if sys.version_info >= (3, 12):
TypeVarTuple = typing.TypeVarTuple

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

# Add default Parameter - PEP 696
class TypeVarTuple(typing.TypeVarTuple, _DefaultMixin, _root=True):
Expand Down