-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Add [disallow-non-exhaustive-match]
check
#13597
Comments
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. |
Note that mypy will already complain about code like the following, so this shouldn't be a hard check to add:
Take 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. |
(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. |
This function will replace the current `Select` implementation with the following improvements: * Proper type hinting by using the new helper type guard `selected_from()`. * Fixes potential starvation issues. * Simplifies the interface by providing values one-by-one. * Simplifies the implementation, so it is easier to maintain. There are some other improvements we would have liked to be able to make but was difficult. For example, typing for `select()` is tricky. We had the idea of using a declarative design, something like: ```python class MySelector(Selector): receiver1: x.new_receiver() receiver2: y.new_receiver() async for selected in MySelector: if selected.receiver is receiver1: # Do something with selected.value elif selected.receiver is receiver1: # Do something with selected.value ``` This is similar to `Enum`, but `Enum` has special support in `mypy` that we can't have. With the current implementation, the typing could be slightly improved by using `TypeVarTuple`, but we are not because "transformations" are not supported yet, see: python/typing#1216 Also support for `TypeVarTuple` in general is still experimental (and very incomplete in `mypy`). With this we would also probably be able to properly type `select` and *maybe* even be able to leverage the exhaustiveness checking of `mypy` to make sure the selected value is narrowed down to the correct type to make sure all receivers are handled, with the help of `assert_never` as described in: https://docs.python.org/3.11/library/typing.html#typing.assert_never We also explored the possibility of using `match` to perform exhaustiveness checking, but we couldn't find a way to make it work with `match`, and `match` is not yet checked for exhaustiveness by `mypy` anyway, see: python/mypy#13597 Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
This function will replace the current `Select` implementation with the following improvements: * Proper type hinting by using the new helper type guard `selected_from()`. * Fixes potential starvation issues. * Simplifies the interface by providing values one-by-one. * Simplifies the implementation, so it is easier to maintain. There are some other improvements we would have liked to be able to make but was difficult. For example, typing for `select()` is tricky. We had the idea of using a declarative design, something like: ```python class MySelector(Selector): receiver1: x.new_receiver() receiver2: y.new_receiver() async for selected in MySelector: if selected.receiver is receiver1: # Do something with selected.value elif selected.receiver is receiver1: # Do something with selected.value ``` This is similar to `Enum`, but `Enum` has special support in `mypy` that we can't have. With the current implementation, the typing could be slightly improved by using `TypeVarTuple`, but we are not because "transformations" are not supported yet, see: python/typing#1216 Also support for `TypeVarTuple` in general is still experimental (and very incomplete in `mypy`). With this we would also probably be able to properly type `select` and *maybe* even be able to leverage the exhaustiveness checking of `mypy` to make sure the selected value is narrowed down to the correct type to make sure all receivers are handled, with the help of `assert_never` as described in: https://docs.python.org/3.11/library/typing.html#typing.assert_never We also explored the possibility of using `match` to perform exhaustiveness checking, but we couldn't find a way to make it work with `match`, and `match` is not yet checked for exhaustiveness by `mypy` anyway, see: python/mypy#13597 Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
I was also surprised that even in class A:
pass
class B:
pass
Things = Union[A, B]
def foo(v: Things) -> None:
match v:
case A():
# do thing and the entire point of the union was the assumption that
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 The following shows a
|
I was pretty shocked to discover that this behavior isn't implemented. This error message is also not great: class Animal(enum.Enum):
DOG = "Dog"
CAT = "Cat"
def __bool__(self) -> bool:
match self:
case Animal.DOG:
return True
|
Swift doesn't seem to be on this list. In Swift you'll always need a |
... did you misread my comment? That's what exhaustive matching is, you must cover all cases (whether individually or via a fallback clause -- |
😮 OCaml, check by default the exhaustiveness and many other properties in patterns, and Haskell check exhaustiveness with 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. |
Feature
Add a new check,
[disallow-non-exhaustive-match]
, that enforces at type check time that nomatch
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.
Consider we have the following code:
Now, consider in the future another
Color
variant is added:Oops! We forgot to add another case to
print_color
'smatch
,Color.Blue
is not handled, and this bug slips though into runtime.Even if
mypy
is configured instrict
mode, this bug isn't caught (because Python's default behavior is to allowmatch
fall through).So how does a programmer defend against this bug? Currently, the best solution is to sprinkle
assert_never
clauses into everymatch
.This will catch the error:
This boilerplate requires the programmer to:
typing_extensions
to their dependenciesmatch
, importassert_never
match
statement, add thecase _
clauseThis 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 unreachablecase
s. An extra# pragma: no cover
comment must be added to every unreachablecase
: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.
The text was updated successfully, but these errors were encountered: