Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds docs about exhaustive literal and enum checks #10860

Merged
merged 3 commits into from
Jul 23, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/source/literal_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,74 @@ using ``isinstance()``:
This feature is sometimes called "sum types" or "discriminated union types"
in other programming languages.

Exhaustive checks
*****************

One may want to check that some code covers all possible ``Literal`` cases, example:

.. code-block:: python

from typing import Literal

PossibleValues = Literal['one', 'two']

def validate(x: PossibleValues) -> bool:
if x == 'one':
return True
elif x == 'two':
return False
raise ValueError('Wrong values passed: {0}'.format(x))

assert validate('one') is True
assert validate('two') is False

In the code above it is really easy to make a mistake in the future:
by adding a new literal value to ``PossibleValues``,
but not adding its handler to ``validate`` function:

.. code-block:: python

PossibleValues = Literal['one', 'two', 'three']

Mypy won't catch that ``'three'`` is not covered.
However, if you want to have exhaustive check, you need to guard it properly:

.. code-block:: python

from typing import Literal, NoReturn

PossibleValues = Literal['one', 'two']

def assert_exhaustive(value: NoReturn) -> NoReturn:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

People actually often call this function assert_never() it can be used is some other cases where people want to check statically some will not be executed.

# This also works in runtime as well:
assert False, 'This code should never be reached, got: {0}'.format(value)

def validate(x: PossibleValues) -> bool:
if x == 'one':
return True
elif x == 'two':
return False
assert_exhaustive(x)

In this case, when adding new values to ``PossibleValues``:

.. code-block:: python

PossibleValues = Literal['one', 'two', 'three']

Mypy will cover you:

.. code-block:: python

def validate(x: PossibleValues) -> bool:
if x == 'one':
return True
elif x == 'two':
return False
assert_exhaustive(x) # E: Argument 1 to "assert_exhaustive" has incompatible type "Literal['three']"; expected "NoReturn"

This technique works with ``Enum`` values as well.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move the Enum reference to the start, and maybe even make this a default example (while mentioning it also works for literal types with a short example). IMO literal types are more for legacy code, while enums are the future.


Limitations
***********

Expand Down