-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Exhaustiveness checking fails on non-trivial use of pattern matching #14833
Comments
I think mypy is working as intended here. Exhaustiveness checking is based on type narrowing. In your top example, the initial type of In your second example, the initial type of def match_enum_attribute(f: Foo) -> None:
match f:
case Foo():
match f.x:
case E.A:
pass
case E.B:
pass
case _ as r:
assert_never(r)
case _ as r:
assert_never(r) |
When rewriting the second example to match on import enum
from typing_extensions import assert_never
class E(enum.Enum):
A = enum.auto()
B = enum.auto()
def match_enum_attribute(f: tuple[E]) -> None:
match f:
case (E.A,):
pass
case (E.B,):
pass
case _ as r:
assert_never(r)
|
There's a bug in your code here. You want to call import enum
from typing_extensions import assert_never
class E(enum.Enum):
A = enum.auto()
B = enum.auto()
def match_enum_attribute(f: tuple[E]) -> None:
match f:
case (E.A,):
pass
case (E.B,):
pass
case (_,) as r:
assert_never(r) https://mypy-play.net/?mypy=latest&python=3.11&gist=502599bec6b2cd6a24be96939a45c34b |
When matching on tuples of two enums, it's also possible for enum values to be narrowed too aggressively. import enum
from typing_extensions import assert_never
class E(enum.Enum):
A = enum.auto()
B = enum.auto()
def do_match(t: tuple[E,E]) -> None:
match t:
case (E.A,E.A):
pass
case (E.B,_):
pass
case (x,y):
# no type error there
assert_never(x)
assert_never(y)
# Fails at runtime
do_match((E.A,E.B)) From my understanding, we'd need a broad match like With |
I stumbled upon this issue with a similar code def f(xs: Optional[tuple[str, bool]]) -> None:
match xs:
case None:
print("None")
case [msg, flag]:
print(f"{msg} and {flag}")
case rest:
# fails with `rest` is of type tuple[Never, Never]
assert_never(rest) Technically you could workaround with something like this (or import assert_type from typing extensions) from typing import TypeVar, Generic, Optional, Never, assert_never
T = TypeVar("T")
class assert_type(Generic[T]):
def __init__(self, x: T) -> None:
pass
def f(xs: Optional[tuple[str, bool]]) -> None:
match xs:
case None:
print("None")
case [msg, flag]:
print(f"{msg} and {flag}")
case other:
# ideally wouldn't be needed
assert_type[tuple[Never, Never]](other) But this third step looks quite strange, especially considering that in most languages with pattern matching (at least with static type systems, not gradual ones) this whole step is redundant, but that's another topic. from collections.abc import Sequence
from typing import Never, assert_never, overload
@overload
def assert_never_seq(rest: Never) -> Never: pass
@overload
def assert_never_seq(rest: Sequence[Never]) -> Never: pass
def assert_never_seq(rest):
raise RuntimeError(rest) It doesn't help with the issue above though, but that looks more like a bug in mypy. |
Code using
assert_never()
to ensure exhaustive use of match-case yields a type error on many non-trivial cases.mypy can only verify exhaustiveness when matching directly against a Union type or Enum value.
Though, proving the exhaustiveness of non-trivial matches may be beyond mypy domain. Here are a couple of examples for the sake of argument:
Matching on both
[]
and[x, *xs]
onSequence
will not narrow the typeMatching against all Enum values that a single-member wrapper class may contain
The text was updated successfully, but these errors were encountered: