Expected both of the match statements to raise errors due to not exhaustively matching on the enum/int values
Actual Behavior
File is passed as ok when it should raise errors
Pyright is able to catch both cases in strict mode as expected in case this helps: pyright playground link
Your Environment
Mypy version used: tested on 1.15.0, 1.14.1, 1.10.1
Mypy command-line flags: --strict
Python version used: 3.12 & 3.10
Possibly Related Issues?
Below are the most relevant issues I could spot, but don't seem to quite align with the case here to the best of my understanding
Ah apologies then, I could have sworn I had seen this working in the past, but must have just been from my IDE's checking rather than running mypy (VsCode with pylance for IDE)
Would be a great feature though! Understand if this gets closed though
Yeah maybe opt-in (what mypy currently has) is worse than opt-out (what pyright currently has) for strict mode at least. But I'm not too good with knowing what features mypy should add!
If the maintainers are open to it, I can try to take a stab at implementing this, but admittedly I am not very familiar with mypy's code base to know how hard/easy this may be to tackle.
check whether mypy is in strict mode (maybe add a new mode?) and whether the narrowed type (copy how current_subject_type is done, I think?) is UninhabitedType.
It should be simple to add, but I don't think it's really a sensible check to enable globally. Python's match statement is not as good as Rust's (mostly because it's a statement and not a statement, so has no "return value"). Enforcing that match is always exhaustive is equivalent to requiring that any if has a corresponding else, which isn't something commonly enforced in python code. There's a simple case other: assert_never(other) option to enforce exhaustiveness of every match stmt separately.
I agree partly here, while not quite as powerful as some other languages like Rust's or Scala's match statements, there is still a lot of power in python's version. For me one great benefit of being able to turn this on would be to flag any areas that were previously thought to be exhaustive or the dev thought they were exhaustive and didn't realise otherwise. If a new pattern becomes possible and there isn't a defined path, it feels like a code smell to me. Having an option at least in mypy to catch this would be great IMO.
You can still "opt-out" if no match causing no effect is the intended behaviour with something like case _: pass in the final clause. I can totally see how this comes down to which scenario you want to be explicit in though.
Hm, case_: pass is a good explicit opt-out, quite in line with _ => {} in Rust to express "yes, it's non-exhaustive, I know, stfu". That's a good argument in favour of this proposal. It's especially nice because exhaustive matches should still occur more often than non-exhaustive, and case _: pass is shorter, needs no extra imports and is more immediately obvious compared to case other: assert_never(other). And match predates assert_never by one version, so that import may not even come from stdlib typing.
I'm +1 on adding this check, except for one problem: it shouldn't be on by default (and even in strict mode IMO), and everything else is seldom ever enabled by users, be it a config flag or --enable-error-code.
Activity
A5rocks commented on May 23, 2025
mypy doesn't have this feature, you need to add case _: typing.assert_never(val) to get exhaustiveness checking.
Don-Burns commented on May 23, 2025
Ah apologies then, I could have sworn I had seen this working in the past, but must have just been from my IDE's checking rather than running mypy (VsCode with pylance for IDE)
Would be a great feature though! Understand if this gets closed though
A5rocks commented on May 23, 2025
Yeah maybe opt-in (what mypy currently has) is worse than opt-out (what pyright currently has) for strict mode at least. But I'm not too good with knowing what features mypy should add!
Don-Burns commented on May 23, 2025
If the maintainers are open to it, I can try to take a stab at implementing this, but admittedly I am not very familiar with mypy's code base to know how hard/easy this may be to tackle.
A5rocks commented on May 23, 2025
Implementation wise, I think this is simple: at the end of this loop:
mypy/mypy/checker.py
Line 5455 in a8ec893
check whether mypy is in strict mode (maybe add a new mode?) and whether the narrowed type (copy how current_subject_type is done, I think?) is UninhabitedType.
sterliakov commented on May 23, 2025
It should be simple to add, but I don't think it's really a sensible check to enable globally. Python's match statement is not as good as Rust's (mostly because it's a statement and not a statement, so has no "return value"). Enforcing that match is always exhaustive is equivalent to requiring that any if has a corresponding else, which isn't something commonly enforced in python code. There's a simple case other: assert_never(other) option to enforce exhaustiveness of every match stmt separately.
Don-Burns commented on May 23, 2025
I agree partly here, while not quite as powerful as some other languages like Rust's or Scala's match statements, there is still a lot of power in python's version. For me one great benefit of being able to turn this on would be to flag any areas that were previously thought to be exhaustive or the dev thought they were exhaustive and didn't realise otherwise. If a new pattern becomes possible and there isn't a defined path, it feels like a code smell to me. Having an option at least in mypy to catch this would be great IMO.
You can still "opt-out" if no match causing no effect is the intended behaviour with something like case _: pass in the final clause. I can totally see how this comes down to which scenario you want to be explicit in though.
sterliakov commented on May 23, 2025
Hm, case_: pass is a good explicit opt-out, quite in line with _ => {} in Rust to express "yes, it's non-exhaustive, I know, stfu". That's a good argument in favour of this proposal. It's especially nice because exhaustive matches should still occur more often than non-exhaustive, and case _: pass is shorter, needs no extra imports and is more immediately obvious compared to case other: assert_never(other). And match predates assert_never by one version, so that import may not even come from stdlib typing.
I'm +1 on adding this check, except for one problem: it shouldn't be on by default (and even in strict mode IMO), and everything else is seldom ever enabled by users, be it a config flag or --enable-error-code.
Add flag to raise error if match statement does not match exaustively (…