mirror of
https://github.com/python/cpython
synced 2024-09-15 22:38:37 +00:00
bpo-46475: Add typing.Never and typing.assert_never (GH-30842)
This commit is contained in:
parent
1e6214dbd6
commit
243436f377
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Add :data:`typing.Never` and :func:`typing.assert_never`. Patch by Jelle
|
||||
Zijlstra.
|
Loading…
Reference in a new issue