bpo-46475: Add typing.Never and typing.assert_never (GH-30842)

This commit is contained in:
Jelle Zijlstra 2022-02-08 10:50:26 -08:00 committed by GitHub
parent 1e6214dbd6
commit 243436f377
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 151 additions and 19 deletions

View file

@ -574,6 +574,34 @@ These can be used as types in annotations and do not support ``[]``.
* Every type is compatible with :data:`Any`.
* :data:`Any` is compatible with every type.
.. data:: Never
The `bottom type <https://en.wikipedia.org/wiki/Bottom_type>`_,
a type that has no members.
This can be used to define a function that should never be
called, or a function that never returns::
from typing import Never
def never_call_me(arg: Never) -> None:
pass
def int_or_str(arg: int | str) -> None:
never_call_me(arg) # type checker error
match arg:
case int():
print("It's an int")
case str():
print("It's a str")
case _:
never_call_me(arg) # ok, arg is of type Never
.. versionadded:: 3.11
On older Python versions, :data:`NoReturn` may be used to express the
same concept. ``Never`` was added to make the intended meaning more explicit.
.. data:: NoReturn
Special type indicating that a function never returns.
@ -584,6 +612,12 @@ These can be used as types in annotations and do not support ``[]``.
def stop() -> NoReturn:
raise RuntimeError('no way')
``NoReturn`` can also be used as a
`bottom type <https://en.wikipedia.org/wiki/Bottom_type>`_, a type that
has no values. Starting in Python 3.11, the :data:`Never` type should
be used for this concept instead. Type checkers should treat the two
equivalently.
.. versionadded:: 3.5.4
.. versionadded:: 3.6.2
@ -1979,6 +2013,28 @@ Functions and decorators
runtime we intentionally don't check anything (we want this
to be as fast as possible).
.. function:: assert_never(arg, /)
Assert to the type checker that a line of code is unreachable.
Example::
def int_or_str(arg: int | str) -> None:
match arg:
case int():
print("It's an int")
case str():
print("It's a str")
case _ as unreachable:
assert_never(unreachable)
If a type checker finds that a call to ``assert_never()`` is
reachable, it will emit an error.
At runtime, this throws an exception when called.
.. versionadded:: 3.11
.. function:: reveal_type(obj)
Reveal the inferred static type of an expression.

View file

@ -9,7 +9,7 @@
from unittest import TestCase, main, skipUnless, skip
from copy import copy, deepcopy
from typing import Any, NoReturn
from typing import Any, NoReturn, Never, assert_never
from typing import TypeVar, AnyStr
from typing import T, KT, VT # Not in __all__.
from typing import Union, Optional, Literal
@ -124,38 +124,56 @@ def test_any_works_with_alias(self):
typing.IO[Any]
class NoReturnTests(BaseTestCase):
class BottomTypeTestsMixin:
bottom_type: ClassVar[Any]
def test_noreturn_instance_type_error(self):
def test_instance_type_error(self):
with self.assertRaises(TypeError):
isinstance(42, NoReturn)
isinstance(42, self.bottom_type)
def test_noreturn_subclass_type_error(self):
def test_subclass_type_error(self):
with self.assertRaises(TypeError):
issubclass(Employee, NoReturn)
issubclass(Employee, self.bottom_type)
with self.assertRaises(TypeError):
issubclass(NoReturn, Employee)
def test_repr(self):
self.assertEqual(repr(NoReturn), 'typing.NoReturn')
issubclass(NoReturn, self.bottom_type)
def test_not_generic(self):
with self.assertRaises(TypeError):
NoReturn[int]
self.bottom_type[int]
def test_cannot_subclass(self):
with self.assertRaises(TypeError):
class A(NoReturn):
class A(self.bottom_type):
pass
with self.assertRaises(TypeError):
class A(type(NoReturn)):
class A(type(self.bottom_type)):
pass
def test_cannot_instantiate(self):
with self.assertRaises(TypeError):
NoReturn()
self.bottom_type()
with self.assertRaises(TypeError):
type(NoReturn)()
type(self.bottom_type)()
class NoReturnTests(BottomTypeTestsMixin, BaseTestCase):
bottom_type = NoReturn
def test_repr(self):
self.assertEqual(repr(NoReturn), 'typing.NoReturn')
class NeverTests(BottomTypeTestsMixin, BaseTestCase):
bottom_type = Never
def test_repr(self):
self.assertEqual(repr(Never), 'typing.Never')
class AssertNeverTests(BaseTestCase):
def test_exception(self):
with self.assertRaises(AssertionError):
assert_never(None)
class SelfTests(BaseTestCase):

View file

@ -5,7 +5,7 @@
* Imports and exports, all public names should be explicitly added to __all__.
* Internal helper functions: these should never be used in code outside this module.
* _SpecialForm and its instances (special forms):
Any, NoReturn, ClassVar, Union, Optional, Concatenate
Any, NoReturn, Never, ClassVar, Union, Optional, Concatenate
* Classes whose instances can be type arguments in addition to types:
ForwardRef, TypeVar and ParamSpec
* The core of internal generics API: _GenericAlias and _VariadicGenericAlias, the latter is
@ -117,12 +117,14 @@ def _idfunc(_, x):
# One-off things.
'AnyStr',
'assert_never',
'cast',
'final',
'get_args',
'get_origin',
'get_type_hints',
'is_typeddict',
'Never',
'NewType',
'no_type_check',
'no_type_check_decorator',
@ -175,7 +177,7 @@ def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms=
if (isinstance(arg, _GenericAlias) and
arg.__origin__ in invalid_generic_forms):
raise TypeError(f"{arg} is not valid as type argument")
if arg in (Any, NoReturn, Self, ClassVar, Final, TypeAlias):
if arg in (Any, NoReturn, Never, Self, ClassVar, Final, TypeAlias):
return arg
if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol):
raise TypeError(f"Plain {arg} is not valid as type argument")
@ -441,8 +443,39 @@ def NoReturn(self, parameters):
def stop() -> NoReturn:
raise Exception('no way')
This type is invalid in other positions, e.g., ``List[NoReturn]``
will fail in static type checkers.
NoReturn can also be used as a bottom type, a type that
has no values. Starting in Python 3.11, the Never type should
be used for this concept instead. Type checkers should treat the two
equivalently.
"""
raise TypeError(f"{self} is not subscriptable")
# This is semantically identical to NoReturn, but it is implemented
# separately so that type checkers can distinguish between the two
# if they want.
@_SpecialForm
def Never(self, parameters):
"""The bottom type, a type that has no members.
This can be used to define a function that should never be
called, or a function that never returns::
from typing import Never
def never_call_me(arg: Never) -> None:
pass
def int_or_str(arg: int | str) -> None:
never_call_me(arg) # type checker error
match arg:
case int():
print("It's an int")
case str():
print("It's a str")
case _:
never_call_me(arg) # ok, arg is of type Never
"""
raise TypeError(f"{self} is not subscriptable")
@ -2060,6 +2093,29 @@ class Film(TypedDict):
return isinstance(tp, _TypedDictMeta)
def assert_never(arg: Never, /) -> Never:
"""Statically assert that a line of code is unreachable.
Example::
def int_or_str(arg: int | str) -> None:
match arg:
case int():
print("It's an int")
case str():
print("It's a str")
case _:
assert_never(arg)
If a type checker finds that a call to assert_never() is
reachable, it will emit an error.
At runtime, this throws an exception when called.
"""
raise AssertionError("Expected code to be unreachable")
def no_type_check(arg):
"""Decorator to indicate that annotations are not type hints.

View file

@ -0,0 +1,2 @@
Add :data:`typing.Never` and :func:`typing.assert_never`. Patch by Jelle
Zijlstra.