python / python/mypy

Exhaustiveness checking fails on non-trivial use of pattern matching

Open
#14,833 7 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug topic-match-statement topic-reachability
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

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] on Sequence will not narrow the type

from collections.abc import Sequence
from typing import assert_never

def my_sum(s: Sequence[float]) -> float:
    match s:
        case []:
            return 0
        case [num, *rest]:
            return num + my_sum(rest)
        case _ as u:
            # error: Argument 1 to "assert_never" has incompatible type "Sequence[float]"; expected "NoReturn"
            return assert_never(u)

Matching against all Enum values that a single-member wrapper class may contain

import dataclasses, enum
from typing import assert_never, final

class E(enum.Enum):
    A = enum.auto()
    B = enum.auto()

@final
@dataclasses.dataclass(frozen=True)
class Foo:
    x: E

def match_enum_attribute(f: Foo) -> None:
    match f:
        case Foo(E.A):
            pass
        case Foo(E.B):
            pass
        case _ as r:
            # error: Argument 1 to "assert_never" has incompatible type "Foo"; expected "NoReturn"  [arg-type]
            assert_never(r)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the two examples using assert_never(), match-case, Sequence patterns, and the wrapped Enum attribute shown in the issue. Trace mypy's exhaustiveness and narrowing behavior for these cases; done means supported non-trivial matches are recognized as exhaustive without the reported type errors, with coverage for the examples.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.