[Python-checkins] bpo-46475: Add typing.Never and typing.assert_never (GH-30842)
gvanrossum
webhook-mailer at python.org
Tue Feb 8 13:50:33 EST 2022
https://github.com/python/cpython/commit/243436f3779c1e7bed1fd4b23d5a8ec5eff40699
commit: 243436f3779c1e7bed1fd4b23d5a8ec5eff40699
branch: main
author: Jelle Zijlstra <jelle.zijlstra at gmail.com>
committer: gvanrossum <gvanrossum at gmail.com>
date: 2022-02-08T10:50:26-08:00
summary:
bpo-46475: Add typing.Never and typing.assert_never (GH-30842)
files:
A Misc/NEWS.d/next/Library/2022-01-23-15-35-07.bpo-46475.UCe18S.rst
M Doc/library/typing.rst
M Lib/test/test_typing.py
M Lib/typing.py
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 8c1c34e90507f..94a46b01a1a8c 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -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.
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 4d1c50ce73549..78e58928332f0 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -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):
diff --git a/Lib/typing.py b/Lib/typing.py
index 0ee5c8596a021..1de48cca00d84 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -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.
+ at _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.
diff --git a/Misc/NEWS.d/next/Library/2022-01-23-15-35-07.bpo-46475.UCe18S.rst b/Misc/NEWS.d/next/Library/2022-01-23-15-35-07.bpo-46475.UCe18S.rst
new file mode 100644
index 0000000000000..99d5e2b42c4f6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-01-23-15-35-07.bpo-46475.UCe18S.rst
@@ -0,0 +1,2 @@
+Add :data:`typing.Never` and :func:`typing.assert_never`. Patch by Jelle
+Zijlstra.
More information about the Python-checkins
mailing list