Add a new check, [disallow-non-exhaustive-match], that enforces at type check time that no match statements implicitly fall through their bottom.
I would prefer if this check were on-by-default, or at least enabled when strict = true, but if this is controversial to the mypy team, off-by-default would be acceptable.
Pitch
The (superseded) PEP 622 has a great discussion on the motivation for this feature with several examples.
For brevity, below we list a very simple example demonstrating the kinds of correctness checks that motivate this feature request.
$ mypy main.py
main.py:19: error: Argument 1 to "assert_never" has incompatible type"Literal[Color.Blue]"; expected "NoReturn"
This boilerplate requires the programmer to:
If running on Python <3.11, add typing_extensions to their dependencies
For each module they are using match, import assert_never
For each match statement, add the case _ clause
This hurts the ergonomics of trying to pervasively enforce this kind of correctness.
This also requires the programmer not to forget to add this boilerplate to every match clause, which can be error prone, especially for large projects wanting to guarantee correctness.
If using coverage, the boilerplate increases further as there will be no reasonable way to add tests to cover the extra unreachable cases. An extra # pragma: no cover comment must be added to every unreachable case:
defprint_color(color: Color) ->None:
matchcolor:
caseColor.Red:
print("red")
caseColor.Green:
print("green")
case _ asunreachable: # pragma: no coverassert_never(unreachable)
Other Languages
Other languages with pattern matching, such as Rust, enforce exhaustive pattern matching, and it has been observed to have a positive effect on program correctness.
I actually would prefer this to be on by default. The superseding PEP 634 says:
For cases such as sealed classes and enums, where the patterns are all known to be members of a discrete set, static checkers can warn about missing patterns.
In addition, there are already so many flags and I think this is a case where it is reasonable to diverge from runtime.
Note that mypy will already complain about code like the following, so this shouldn't be a hard check to add:
from enum import Enum, auto
class Color(Enum):
Red = auto()
Green = auto()
Blue = auto()
def print_color(color: Color) -> int:
match color:
case Color.Red:
return 4
case Color.Green:
return 5
Take a look at #12267 if you're interested in implementing this.
Guido was also hesitant about enforcing exhaustiveness by default, but mentioned he could see value in having this per-module (and mypy master now supports per-module error codes): Support for python 3.10 match statement #10191 (comment)
I would certainly accept a disabled-by-default error code for this to mypy. I don't have enough experience with the match statement or sense of how it's used in the ecosystem to have an opinion on enabled-by-default or not.
(Btw, a little off topic, but if you have open source code that makes interesting use of typing features, pattern matching, or has surfaced mypy usability issues for you previously, let me know, and I can add it to https://github.com/hauntsaninja/mypy_primer )
@ethanhs I would too. I clarified the initial post to express this. I originally posted off-by-default to try to avoid controversy if the mypy team feels we shouldn't diverge from the runtime behavior. But I agree with everything you said above.
and the entire point of the union was the assumption that match would perform exhaustiveness checking, and would tell me if I forgot to process new commands. Sadly it's not to be.
Other languages with pattern matching, such as Rust, enforce exhaustive pattern matching
OCaml and Swift as well, Haskell -- or at least ghc -- stands out for not having exhaustive checking by default, and IIRC it's because the type system is sufficiently complex that it's a hard problem in that specific language, as well as the relevant GHC code being pretty old and crufty and not having a real champion / owner.
TypeScript also is equipped to evaluate its [key in <type>] construction exhaustively, I believe by default.
The following shows a Property '[E_Color.Blue]' is missing in type '{ red: string; green: string; }' but required in type '{ red: string; green: string; blue: string; }' error in one of my projects:
export enum E_Color {
Red = 'red',
Green = 'green',
Blue = 'blue',
}
export const colorsUpper: {[key in E_Color]: string} = { // <-- Error
[E_Color.Red]: 'RED',
[E_Color.Green]: 'GREEN',
}
OCaml and Swift as well, Haskell -- or at least ghc -- stands out for not having exhaustive checking by default, and IIRC it's because the type system is sufficiently complex that it's a hard problem in that specific language, as well as the relevant GHC code being pretty old and crufty and not having a real champion / owner.
Swift doesn't seem to be on this list. In Swift you'll always need a default: block (identical to case _: in Python) if you're not covering all the cases. What's not enabled by default is the enforcement of @unknown default:, i.e. always requiring a fallback for non-frozen enums in case a new case is added afterwards.
Swift doesn't seem to be on this list. In Swift you'll always need a default: block (identical to case _: in Python) if you're not covering all the cases.
... did you misread my comment? That's what exhaustive matching is, you must cover all cases (whether individually or via a fallback clause -- default: in swift). Which is exactly what's requested of mypy.
OCaml and Swift as well, Haskell -- or at least ghc -- stands out for not having exhaustive checking by default,
😮 OCaml, check by default the exhaustiveness and many other properties in patterns, and Haskell check exhaustiveness with -Wall.
EDIT: Ok @xmo-odoo sorry I misread your comment for OCaml, the first comma (instead of a point) made me think that OCaml, Swift and Haskell where in the same boat.
This is now implemented as the exhaustive-match error, disabled by default. Hopefully we can enable it by default in strict modes but that's for another day.
Reacted by Shantanu, xmo-odoo, Derek Kulinski, François Bobot, John Hagen, buster-blue, Brian Skinn and Shahriar Heidrich
Activity
emmatyping commented on Sep 4, 2022
I actually would prefer this to be on by default. The superseding PEP 634 says:
In addition, there are already so many flags and I think this is a case where it is reasonable to diverge from runtime.
hauntsaninja commented on Sep 4, 2022
Note that mypy will already complain about code like the following, so this shouldn't be a hard check to add:
from enum import Enum, auto class Color(Enum): Red = auto() Green = auto() Blue = auto() def print_color(color: Color) -> int: match color: case Color.Red: return 4 case Color.Green: return 5Take a look at #12267 if you're interested in implementing this.
For what it's worth:
I would certainly accept a disabled-by-default error code for this to mypy. I don't have enough experience with the match statement or sense of how it's used in the ecosystem to have an opinion on enabled-by-default or not.
hauntsaninja commented on Sep 4, 2022
(Btw, a little off topic, but if you have open source code that makes interesting use of typing features, pattern matching, or has surfaced mypy usability issues for you previously, let me know, and I can add it to https://github.com/hauntsaninja/mypy_primer )
johnthagen commented on Sep 4, 2022
@ethanhs I would too. I clarified the initial post to express this. I originally posted off-by-default to try to avoid controversy if the mypy team feels we shouldn't diverge from the runtime behavior. But I agree with everything you said above.
Add a new async iterable select() function
Add a new async iterable select() function
xmo-odoo commented on Oct 16, 2023
I was also surprised that even in --strict mode mypy does not warn about this issue, my use case was processing a commands set so along the lines of
and the entire point of the union was the assumption that match would perform exhaustiveness checking, and would tell me if I forgot to process new commands. Sadly it's not to be.
OCaml and Swift as well, Haskell -- or at least ghc -- stands out for not having exhaustive checking by default, and IIRC it's because the type system is sufficiently complex that it's a hard problem in that specific language, as well as the relevant GHC code being pretty old and crufty and not having a real champion / owner.
bskinn commented on Dec 23, 2023
TypeScript also is equipped to evaluate its [key in <type>] construction exhaustively, I believe by default.
The following shows a Property '[E_Color.Blue]' is missing in type '{ red: string; green: string; }' but required in type '{ red: string; green: string; blue: string; }' error in one of my projects:
export enum E_Color { Red = 'red', Green = 'green', Blue = 'blue', } export const colorsUpper: {[key in E_Color]: string} = { // <-- Error [E_Color.Red]: 'RED', [E_Color.Green]: 'GREEN', }9999years commented on Mar 26, 2024
I was pretty shocked to discover that this behavior isn't implemented. This error message is also not great:
error: Missing return statement [return] def __bool__(self) -> bool: ^stevapple commented on Jun 19, 2024
Swift doesn't seem to be on this list. In Swift you'll always need a default: block (identical to case _: in Python) if you're not covering all the cases. What's not enabled by default is the enforcement of @unknown default:, i.e. always requiring a fallback for non-frozen enums in case a new case is added afterwards.
xmo-odoo commented on Jun 19, 2024
... did you misread my comment? That's what exhaustive matching is, you must cover all cases (whether individually or via a fallback clause -- default: in swift). Which is exactly what's requested of mypy.
bobot commented on Aug 6, 2024
😮 OCaml, check by default the exhaustiveness and many other properties in patterns, and Haskell check exhaustiveness with -Wall.
EDIT: Ok @xmo-odoo sorry I misread your comment for OCaml, the first comma (instead of a point) made me think that OCaml, Swift and Haskell where in the same boat.
A5rocks commented on Jun 11, 2025
This is now implemented as the exhaustive-match error, disabled by default. Hopefully we can enable it by default in strict modes but that's for another day.
hauntsaninja commented on Jun 11, 2025
For copy-pasting purposes: --enable-error-code exhaustive-match
https://mypy.readthedocs.io/en/latest/error_code_list2.html#check-that-match-statements-match-exhaustively-match-exhaustive
Thanks to Don-Burns in #19144
johnthagen commented on Jun 11, 2025
For copy-pasting purposes to pyproject.toml:
Also, @hauntsaninja, is there a typo in the docs, or is the error code inconsistent with the CLI flag?
https://mypy.readthedocs.io/en/latest/error_code_list2.html#check-that-match-statements-match-exhaustively-match-exhaustive
The title says match-exhaustive but the examples use exhaustive-match.
brianschubert commented on Jun 11, 2025
Yeah, that looks like a typo. Feel free to send a PR!
johnthagen commented on Jun 11, 2025
@brianschubert
Fix `exhaustive-match` error code in title (#19276)