1.. _unreachable: 2 3******************************************** 4Unreachable Code and Exhaustiveness Checking 5******************************************** 6 7Sometimes it is necessary to write code that should never execute, and 8sometimes we write code that we expect to execute, but that is actually 9unreachable. The type checker can help in both cases. 10 11In this guide, we'll cover: 12 13- ``Never``, the primitive type used for unreachable code 14- ``assert_never()``, a helper for exhaustiveness checking 15- Directly marking code as unreachable 16- Detecting unexpectedly unreachable code 17 18``Never`` and ``NoReturn`` 19========================== 20 21Type theory has a concept of a 22`bottom type <https://en.wikipedia.org/wiki/Bottom_type>`__, 23a type that has no values. Concretely, this can be used to represent 24the return type of a function that never returns, or the argument type 25of a function that may never be called. You can also think of the 26bottom type as a union with no members. 27 28The Python type system has long provided a type called ``NoReturn``. 29While it was originally meant only for functions that never return, 30this concept is naturally extended to the bottom type in general, and all 31type checkers treat ``NoReturn`` as a general bottom type. 32 33To make the meaning of this type more explicit, Python 3.11 and 34typing-extensions 4.1 add a new primitive, ``Never``. To type checkers, 35it has the same meaning as ``NoReturn``. 36 37In this guide, we'll use ``Never`` for the bottom type, but if you cannot 38use it yet, you can always use ``typing.NoReturn`` instead. 39 40``assert_never()`` and Exhaustiveness Checking 41============================================== 42 43The ``Never`` type can be leveraged to perform static exhaustiveness checking, 44where we use the type checker to make sure that we covered all possible 45cases. For example, this can come up when code performs a separate action 46for each member of an enum, or for each type in a union. 47 48To have the type checker do exhaustiveness checking for us, we call a 49function with a parameter typed as ``Never``. The type checker will allow 50this call only if it can prove that the code is not reachable. 51 52As an example, consider this simple calculator: 53 54.. code:: python 55 56 import enum 57 from typing_extensions import Never 58 59 def assert_never(arg: Never) -> Never: 60 raise AssertionError("Expected code to be unreachable") 61 62 class Op(enum.Enum): 63 ADD = 1 64 SUBTRACT = 2 65 66 def calculate(left: int, op: Op, right: int) -> int: 67 match op: 68 case Op.ADD: 69 return left + right 70 case Op.SUBTRACT: 71 return left - right 72 case _: 73 assert_never(op) 74 75The ``match`` statement covers all members of the ``Op`` enum, 76so the ``assert_never()`` call is unreachable and the type checker 77will accept this code. However, if you add another member to the 78enum (say, ``MULTIPLY``) but don't update the ``match`` statement, 79the type checker will give an error saying that you are not handling 80the ``MULTIPLY`` case. 81 82Because the ``assert_never()`` helper function is frequently useful, 83it is provided by the standard library as ``typing.assert_never`` 84starting in Python 3.11, 85and is also present in ``typing_extensions`` starting at version 4.1. 86However, it is also possible to define a similar function in your own 87code, for example if you want to customize the runtime error message. 88 89You can also use ``assert_never()`` with a sequence of ``if`` statements: 90 91.. code:: python 92 93 def calculate(left: int, op: Op, right: int) -> int: 94 if op is Op.ADD: 95 return left + right 96 elif op is Op.SUBTRACT: 97 return left - right 98 else: 99 assert_never(op) 100 101Marking Code as Unreachable 102======================= 103 104Sometimes a piece of code is unreachable, but the type system is not 105powerful enough to recognize that. For example, consider a function that 106finds the lowest unused street number in a street: 107 108.. code:: python 109 110 import itertools 111 112 def is_used(street: str, number: int) -> bool: 113 ... 114 115 def lowest_unused(street: str) -> int: 116 for i in itertools.count(1): 117 if not is_used(street, i): 118 return i 119 assert False, "unreachable" 120 121Because ``itertools.count()`` is an infinite iterator, this function 122will never reach the ``assert False`` statement. However, there is 123no way for the type checker to know that, so without the ``assert False``, 124the type checker will complain that the function is missing a return 125statement. 126 127Note how this is different from ``assert_never()``: 128 129- If we used ``assert_never()`` in the ``lowest_unused()`` function, 130 the type checker would produce an error, because the type checker 131 cannot prove that the line is unreachable. 132- If we used ``assert False`` instead of ``assert_never()`` in the 133 ``calculate()`` example above, we would not get the benefits of 134 exhaustiveness checking. If the code is actually reachable, 135 the type checker will not warn us and we could hit the assertion 136 at runtime. 137 138While ``assert False`` is the most idiomatic way to express this pattern, 139any statement that ends execution will do. For example, you could raise 140an exception or call a function that returns ``Never``. 141 142Detecting Unexpectedly Unreachable Code 143======================================= 144 145Another possible problem is code that is supposed to execute, but that 146can actually be statically determined to be unreachable. 147Some type checkers have an option that enables warnings for code 148detected as unreachable (e.g., ``--warn-unreachable`` in mypy). 149