Skip to content

Commit

Permalink
Fix tests on Python 3.11 (#1139)
Browse files Browse the repository at this point in the history
- Defer to the PEP 646 implementation in typing.py on 3.11
- Adjust some tests accordingly. Noted a bug in
  python/cpython#32341 (comment)
- typing._type_check() is more lenient in 3.11 and no longer rejects ints
- The representation of the empty tuple type changed

Tests pass for me on a 3.11 build from today now.
  • Loading branch information
JelleZijlstra committed Apr 16, 2022
1 parent 2312c74 commit 783c8ca
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 79 deletions.
1 change: 1 addition & 0 deletions typing_extensions/CHANGELOG
@@ -1,5 +1,6 @@
# Unreleased

- Re-export `typing.Unpack` and `typing.TypeVarTuple` on Python 3.11.
- Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`.
- Improve "accepts only single type" error messages.
- Improve the distributed package. Patch by Marc Mueller (@cdce8p).
Expand Down
61 changes: 39 additions & 22 deletions typing_extensions/src/test_typing_extensions.py
Expand Up @@ -32,6 +32,8 @@
# version of the typing module.
TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0)
TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0)

# 3.11 makes runtime type checks (_type_check) more lenient.
TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0)


Expand Down Expand Up @@ -157,8 +159,9 @@ def test_exception(self):
class ClassVarTests(BaseTestCase):

def test_basics(self):
with self.assertRaises(TypeError):
ClassVar[1]
if not TYPING_3_11_0:
with self.assertRaises(TypeError):
ClassVar[1]
with self.assertRaises(TypeError):
ClassVar[int, str]
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -201,8 +204,9 @@ def test_no_isinstance(self):
class FinalTests(BaseTestCase):

def test_basics(self):
with self.assertRaises(TypeError):
Final[1]
if not TYPING_3_11_0:
with self.assertRaises(TypeError):
Final[1]
with self.assertRaises(TypeError):
Final[int, str]
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -245,8 +249,9 @@ def test_no_isinstance(self):
class RequiredTests(BaseTestCase):

def test_basics(self):
with self.assertRaises(TypeError):
Required[1]
if not TYPING_3_11_0:
with self.assertRaises(TypeError):
Required[1]
with self.assertRaises(TypeError):
Required[int, str]
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -289,8 +294,9 @@ def test_no_isinstance(self):
class NotRequiredTests(BaseTestCase):

def test_basics(self):
with self.assertRaises(TypeError):
NotRequired[1]
if not TYPING_3_11_0:
with self.assertRaises(TypeError):
NotRequired[1]
with self.assertRaises(TypeError):
NotRequired[int, str]
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -738,7 +744,10 @@ class C(Generic[T]): pass
self.assertEqual(get_args(Union[int, Callable[[Tuple[T, ...]], str]]),
(int, Callable[[Tuple[T, ...]], str]))
self.assertEqual(get_args(Tuple[int, ...]), (int, ...))
self.assertEqual(get_args(Tuple[()]), ((),))
if TYPING_3_11_0:
self.assertEqual(get_args(Tuple[()]), ())
else:
self.assertEqual(get_args(Tuple[()]), ((),))
self.assertEqual(get_args(Annotated[T, 'one', 2, ['three']]), (T, 'one', 2, ['three']))
self.assertEqual(get_args(List), ())
self.assertEqual(get_args(Tuple), ())
Expand Down Expand Up @@ -1731,10 +1740,12 @@ def test_typeddict_errors(self):
isinstance(jim, Emp)
with self.assertRaises(TypeError):
issubclass(dict, Emp)
with self.assertRaises(TypeError):
TypedDict('Hi', x=1)
with self.assertRaises(TypeError):
TypedDict('Hi', [('x', int), ('y', 1)])

if not TYPING_3_11_0:
with self.assertRaises(TypeError):
TypedDict('Hi', x=1)
with self.assertRaises(TypeError):
TypedDict('Hi', [('x', int), ('y', 1)])
with self.assertRaises(TypeError):
TypedDict('Hi', [('x', int)], y=int)

Expand Down Expand Up @@ -2313,11 +2324,12 @@ def test_invalid_uses(self):
):
Concatenate[P, T]

with self.assertRaisesRegex(
TypeError,
'each arg must be a type',
):
Concatenate[1, P]
if not TYPING_3_11_0:
with self.assertRaisesRegex(
TypeError,
'each arg must be a type',
):
Concatenate[1, P]

def test_basic_introspection(self):
P = ParamSpec('P')
Expand Down Expand Up @@ -2497,7 +2509,10 @@ def test_basic_plain(self):

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

def test_cannot_subclass_vars(self):
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -2572,8 +2587,10 @@ 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]
# TODO This should probably also fail on 3.11, pending changes to CPython.
if not TYPING_3_11_0:
with self.assertRaises(TypeError):
C[int]


class TypeVarTupleTests(BaseTestCase):
Expand Down Expand Up @@ -2617,7 +2634,7 @@ def test_args_and_parameters(self):
Ts = TypeVarTuple('Ts')

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


Expand Down
119 changes: 62 additions & 57 deletions typing_extensions/src/typing_extensions.py
Expand Up @@ -1673,7 +1673,9 @@ class Movie(TypedDict):
""")


if sys.version_info[:2] >= (3, 9):
if hasattr(typing, "Unpack"): # 3.11+
Unpack = typing.Unpack
elif sys.version_info[:2] >= (3, 9):
class _UnpackSpecialForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
Expand Down Expand Up @@ -1729,84 +1731,87 @@ def _is_unpack(obj):
return isinstance(obj, _UnpackAlias)


class TypeVarTuple:
"""Type variable tuple.
if hasattr(typing, "TypeVarTuple"): # 3.11+
TypeVarTuple = typing.TypeVarTuple
else:
class TypeVarTuple:
"""Type variable tuple.
Usage::
Usage::
Ts = TypeVarTuple('Ts')
Ts = TypeVarTuple('Ts')
In the same way that a normal type variable is a stand-in for a single
type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* type such as
``Tuple[int, str]``.
In the same way that a normal type variable is a stand-in for a single
type such as ``int``, a type variable *tuple* is a stand-in for a *tuple*
type such as ``Tuple[int, str]``.
Type variable tuples can be used in ``Generic`` declarations.
Consider the following example::
Type variable tuples can be used in ``Generic`` declarations.
Consider the following example::
class Array(Generic[*Ts]): ...
class Array(Generic[*Ts]): ...
The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
where ``T1`` and ``T2`` are type variables. To use these type variables
as type parameters of ``Array``, we must *unpack* the type variable tuple using
the star operator: ``*Ts``. The signature of ``Array`` then behaves
as if we had simply written ``class Array(Generic[T1, T2]): ...``.
In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows
us to parameterise the class with an *arbitrary* number of type parameters.
The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
where ``T1`` and ``T2`` are type variables. To use these type variables
as type parameters of ``Array``, we must *unpack* the type variable tuple using
the star operator: ``*Ts``. The signature of ``Array`` then behaves
as if we had simply written ``class Array(Generic[T1, T2]): ...``.
In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows
us to parameterise the class with an *arbitrary* number of type parameters.
Type variable tuples can be used anywhere a normal ``TypeVar`` can.
This includes class definitions, as shown above, as well as function
signatures and variable annotations::
Type variable tuples can be used anywhere a normal ``TypeVar`` can.
This includes class definitions, as shown above, as well as function
signatures and variable annotations::
class Array(Generic[*Ts]):
class Array(Generic[*Ts]):
def __init__(self, shape: Tuple[*Ts]):
self._shape: Tuple[*Ts] = shape
def __init__(self, shape: Tuple[*Ts]):
self._shape: Tuple[*Ts] = shape
def get_shape(self) -> Tuple[*Ts]:
return self._shape
def get_shape(self) -> Tuple[*Ts]:
return self._shape
shape = (Height(480), Width(640))
x: Array[Height, Width] = Array(shape)
y = abs(x) # Inferred type is Array[Height, Width]
z = x + x # ... is Array[Height, Width]
x.get_shape() # ... is tuple[Height, Width]
shape = (Height(480), Width(640))
x: Array[Height, Width] = Array(shape)
y = abs(x) # Inferred type is Array[Height, Width]
z = x + x # ... is Array[Height, Width]
x.get_shape() # ... is tuple[Height, Width]
"""
"""

# Trick Generic __parameters__.
__class__ = typing.TypeVar
# Trick Generic __parameters__.
__class__ = typing.TypeVar

def __iter__(self):
yield self.__unpacked__
def __iter__(self):
yield self.__unpacked__

def __init__(self, name):
self.__name__ = name
def __init__(self, name):
self.__name__ = name

# 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
# 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

self.__unpacked__ = Unpack[self]
self.__unpacked__ = Unpack[self]

def __repr__(self):
return self.__name__
def __repr__(self):
return self.__name__

def __hash__(self):
return object.__hash__(self)
def __hash__(self):
return object.__hash__(self)

def __eq__(self, other):
return self is other
def __eq__(self, other):
return self is other

def __reduce__(self):
return self.__name__
def __reduce__(self):
return self.__name__

def __init_subclass__(self, *args, **kwds):
if '_root' not in kwds:
raise TypeError("Cannot subclass special typing classes")
def __init_subclass__(self, *args, **kwds):
if '_root' not in kwds:
raise TypeError("Cannot subclass special typing classes")


if hasattr(typing, "reveal_type"):
Expand Down

0 comments on commit 783c8ca

Please sign in to comment.