Skip to content

Commit

Permalink
PEP 646 implementation (#963)
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra authored Feb 12, 2022
1 parent 46094aa commit 9403ccf
Show file tree
Hide file tree
Showing 4 changed files with 448 additions and 29 deletions.
2 changes: 2 additions & 0 deletions typing_extensions/CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Release 4.x.x

- Runtime support for PEP 646, adding `typing_extensions.TypeVarTuple`
and `typing_extensions.Unpack`.
- Add interaction of `Required` and `NotRequired` with `__required_keys__`,
`__optional_keys__` and `get_type_hints()`. Patch by David Cabot (@d-k-bo).
- Runtime support for PEP 675 and `typing_extensions.LiteralString`.
Expand Down
14 changes: 11 additions & 3 deletions typing_extensions/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ This module currently contains the following:
- ``Never``
- ``reveal_type``
- ``Self`` (see PEP 673)
- ``TypeVarTuple`` (see PEP 646)
- ``Unpack`` (see PEP 646)

- In ``typing`` since Python 3.10

Expand Down Expand Up @@ -124,9 +126,15 @@ These changes are _not_ backported to prevent subtle compatibility
issues when mixing the differing implementations of modified classes.

Certain types have incorrect runtime behavior due to limitations of older
versions of the typing module. For example, ``ParamSpec`` and ``Concatenate``
will not work with ``get_args``, ``get_origin``. Certain PEP 612 special cases
in user-defined ``Generic``\ s are also not available.
versions of the typing module:

- ``ParamSpec`` and ``Concatenate`` will not work with ``get_args`` and
``get_origin``. Certain PEP 612 special cases in user-defined
``Generic``\ s are also not available.
- ``Unpack`` from PEP 646 does not work properly with user-defined
``Generic``\ s in Python 3.6: ``class X(Generic[Unpack[Ts]]):`` does
not work.

These types are only guaranteed to work for static type checking.

Running tests
Expand Down
147 changes: 146 additions & 1 deletion typing_extensions/src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard
from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired
from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict
from typing_extensions import dataclass_transform, reveal_type, Never, assert_never, LiteralString
from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString
try:
from typing_extensions import get_type_hints
except ImportError:
Expand Down Expand Up @@ -622,6 +622,7 @@ def test_get_origin(self):

T = TypeVar('T')
P = ParamSpec('P')
Ts = TypeVarTuple('Ts')
class C(Generic[T]): pass
self.assertIs(get_origin(C[int]), C)
self.assertIs(get_origin(C[T]), C)
Expand All @@ -642,11 +643,16 @@ class C(Generic[T]): pass
self.assertIs(get_origin(list), None)
self.assertIs(get_origin(P.args), P)
self.assertIs(get_origin(P.kwargs), P)
self.assertIs(get_origin(Required[int]), Required)
self.assertIs(get_origin(NotRequired[int]), NotRequired)
self.assertIs(get_origin(Unpack[Ts]), Unpack)
self.assertIs(get_origin(Unpack), None)

def test_get_args(self):
from typing_extensions import get_args

T = TypeVar('T')
Ts = TypeVarTuple('Ts')
class C(Generic[T]): pass
self.assertEqual(get_args(C[int]), (int,))
self.assertEqual(get_args(C[T]), (T,))
Expand Down Expand Up @@ -687,6 +693,10 @@ class C(Generic[T]): pass
self.assertIn(get_args(Callable[P, int]), [(P, int), ([P], int)])
self.assertEqual(get_args(Callable[Concatenate[int, P], int]),
(Concatenate[int, P], int))
self.assertEqual(get_args(Required[int]), (int,))
self.assertEqual(get_args(NotRequired[int]), (int,))
self.assertEqual(get_args(Unpack[Ts]), (Ts,))
self.assertEqual(get_args(Unpack), ())


class CollectionsAbcTests(BaseTestCase):
Expand Down Expand Up @@ -2438,6 +2448,141 @@ def test_pickle(self):
self.assertIs(Self, pickle.loads(pickled))


class UnpackTests(BaseTestCase):
def test_basic_plain(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(Unpack[Ts], Unpack[Ts])
with self.assertRaises(TypeError):
Unpack()

def test_repr(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(repr(Unpack[Ts]), 'typing_extensions.Unpack[Ts]')

def test_cannot_subclass_vars(self):
with self.assertRaises(TypeError):
class V(Unpack[TypeVarTuple('Ts')]):
pass

def test_tuple(self):
Ts = TypeVarTuple('Ts')
Tuple[Unpack[Ts]]

def test_union(self):
Xs = TypeVarTuple('Xs')
Ys = TypeVarTuple('Ys')
self.assertEqual(
Union[Unpack[Xs]],
Unpack[Xs]
)
self.assertNotEqual(
Union[Unpack[Xs]],
Union[Unpack[Xs], Unpack[Ys]]
)
self.assertEqual(
Union[Unpack[Xs], Unpack[Xs]],
Unpack[Xs]
)
self.assertNotEqual(
Union[Unpack[Xs], int],
Union[Unpack[Xs]]
)
self.assertNotEqual(
Union[Unpack[Xs], int],
Union[int]
)
self.assertEqual(
Union[Unpack[Xs], int].__args__,
(Unpack[Xs], int)
)
self.assertEqual(
Union[Unpack[Xs], int].__parameters__,
(Xs,)
)
self.assertIs(
Union[Unpack[Xs], int].__origin__,
Union
)

@skipUnless(PEP_560, "Unimplemented for 3.6")
def test_concatenation(self):
Xs = TypeVarTuple('Xs')
self.assertEqual(Tuple[int, Unpack[Xs]].__args__, (int, Unpack[Xs]))
self.assertEqual(Tuple[Unpack[Xs], int].__args__, (Unpack[Xs], int))
self.assertEqual(Tuple[int, Unpack[Xs], str].__args__,
(int, Unpack[Xs], str))
class C(Generic[Unpack[Xs]]): pass
self.assertEqual(C[int, Unpack[Xs]].__args__, (int, Unpack[Xs]))
self.assertEqual(C[Unpack[Xs], int].__args__, (Unpack[Xs], int))
self.assertEqual(C[int, Unpack[Xs], str].__args__,
(int, Unpack[Xs], str))

@skipUnless(PEP_560, "Unimplemented for 3.6")
def test_class(self):
Ts = TypeVarTuple('Ts')

class C(Generic[Unpack[Ts]]): pass
self.assertEqual(C[int].__args__, (int,))
self.assertEqual(C[int, str].__args__, (int, str))

with self.assertRaises(TypeError):
class C(Generic[Unpack[Ts], int]): pass

T1 = TypeVar('T')
T2 = TypeVar('T')
class C(Generic[T1, T2, Unpack[Ts]]): pass
self.assertEqual(C[int, str].__args__, (int, str))
self.assertEqual(C[int, str, float].__args__, (int, str, float))
self.assertEqual(C[int, str, float, bool].__args__, (int, str, float, bool))
with self.assertRaises(TypeError):
C[int]


class TypeVarTupleTests(BaseTestCase):

def test_basic_plain(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(Ts, Ts)
self.assertIsInstance(Ts, TypeVarTuple)
Xs = TypeVarTuple('Xs')
Ys = TypeVarTuple('Ys')
self.assertNotEqual(Xs, Ys)

def test_repr(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(repr(Ts), 'Ts')

def test_no_redefinition(self):
self.assertNotEqual(TypeVarTuple('Ts'), TypeVarTuple('Ts'))

def test_cannot_subclass_vars(self):
with self.assertRaises(TypeError):
class V(TypeVarTuple('Ts')):
pass

def test_cannot_subclass_var_itself(self):
with self.assertRaises(TypeError):
class V(TypeVarTuple):
pass

def test_cannot_instantiate_vars(self):
Ts = TypeVarTuple('Ts')
with self.assertRaises(TypeError):
Ts()

def test_tuple(self):
Ts = TypeVarTuple('Ts')
# Not legal at type checking time but we can't really check against it.
Tuple[Ts]

def test_args_and_parameters(self):
Ts = TypeVarTuple('Ts')

t = Tuple[tuple(Ts)]
self.assertEqual(t.__args__, (Ts.__unpacked__,))
self.assertEqual(t.__parameters__, (Ts,))


class FinalDecoratorTests(BaseTestCase):
def test_final_unmodified(self):
def func(x): ...
Expand Down
Loading

0 comments on commit 9403ccf

Please sign in to comment.