From f8242bf860f578c10737a4dff4ec8affbb424f0a Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Thu, 22 Jul 2021 13:30:07 +0300 Subject: [PATCH 1/3] Adds docs about exhaustive literal and enum checks Refs https://github.com/python/mypy/issues/6366 --- docs/source/literal_types.rst | 68 +++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/docs/source/literal_types.rst b/docs/source/literal_types.rst index a223011effe9..4818ac38f571 100644 --- a/docs/source/literal_types.rst +++ b/docs/source/literal_types.rst @@ -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: + # 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. + Limitations *********** From 35f7eab9037238c33153d36dfd10a65cc17a7fa5 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Thu, 22 Jul 2021 16:27:07 +0300 Subject: [PATCH 2/3] Addresses review --- docs/source/literal_types.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/source/literal_types.rst b/docs/source/literal_types.rst index 4818ac38f571..50c885f1cde0 100644 --- a/docs/source/literal_types.rst +++ b/docs/source/literal_types.rst @@ -292,7 +292,8 @@ in other programming languages. Exhaustive checks ***************** -One may want to check that some code covers all possible ``Literal`` cases, example: +One may want to check that some code covers all possible ``Literal`` or ``Enum`` cases, +example: .. code-block:: python @@ -327,7 +328,7 @@ However, if you want to have exhaustive check, you need to guard it properly: PossibleValues = Literal['one', 'two'] - def assert_exhaustive(value: NoReturn) -> NoReturn: + def assert_never(value: NoReturn) -> NoReturn: # This also works in runtime as well: assert False, 'This code should never be reached, got: {0}'.format(value) @@ -336,7 +337,7 @@ However, if you want to have exhaustive check, you need to guard it properly: return True elif x == 'two': return False - assert_exhaustive(x) + assert_never(x) In this case, when adding new values to ``PossibleValues``: @@ -353,9 +354,7 @@ Mypy will cover you: 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. + assert_never(x) # E: Argument 1 to "assert_exhaustive" has incompatible type "Literal['three']"; expected "NoReturn" Limitations *********** From 7e3f0861706fd4fe3e0976000ff5dd6815411e40 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Thu, 22 Jul 2021 22:31:47 +0300 Subject: [PATCH 3/3] Update literal_types.rst --- docs/source/literal_types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/literal_types.rst b/docs/source/literal_types.rst index 50c885f1cde0..216313b6c20e 100644 --- a/docs/source/literal_types.rst +++ b/docs/source/literal_types.rst @@ -354,7 +354,7 @@ Mypy will cover you: return True elif x == 'two': return False - assert_never(x) # E: Argument 1 to "assert_exhaustive" has incompatible type "Literal['three']"; expected "NoReturn" + assert_never(x) # E: Argument 1 to "assert_never" has incompatible type "Literal['three']"; expected "NoReturn" Limitations ***********