Skip to content

Fix narrowing of bool literal match subjects - #22313

Open
DipanshuAmbilkar03 wants to merge 1 commit into
python:masterfrom
DipanshuAmbilkar03:fix/bool-literal-match-narrowing
Open

DipanshuAmbilkar03 wants to merge 1 commit into
python:masterfrom
DipanshuAmbilkar03:fix/bool-literal-match-narrowing

Conversation

@DipanshuAmbilkar03

Copy link
Copy Markdown

Fixes #22202

Summary

match False: is not treated as exhaustive under case True / case False, and a following case other is reachable even though the subject can only be False:

def f() -> int:
    match False:
        case True:
            return 1
        case False:
            return 0

def h() -> int:
    match False:
        case True:
            return 1
        case False:
            return 0
        case other:
            reveal_type(other)  # Literal[False]
            return 2

The subject expression False has type bool with a last_known_value of Literal[False]. To narrow against a bool literal pattern, conditional_types() expands the bool instance into Literal[True] | Literal[False], but that expansion discarded the known value. Matching against case False therefore left Literal[True] as the remaining type, so the match was not exhaustive and case other stayed reachable.

Fix

I changed try_expanding_sum_type_to_union() so that an instance carrying a last_known_value expands to that literal instead of the full union. Plain bool and enum instances have no last_known_value, so their behaviour is unchanged.

Test

testMatchBoolLiteralSubjectIsExhaustive in test-data/unit/check-python310.test covers:

  • match False with case True / case False being exhaustive (no missing return);
  • the unreachable case True bodies being reported under --warn-unreachable;
  • a case other after them being unreachable;
  • the same match on a plain bool parameter still behaving as before.

Verification

  • pytest mypy/test/testcheck.py -k Match — 262 passed, 1 skipped.
  • pytest mypy/test/testcheck.py — 8341 passed, 27 skipped, 7 xfailed.
  • pytest mypy/test (excluding testmypyc.py, which needs a mypyc build) — 12557 passed, 362 skipped, 10 xfailed.
  • python -m mypy --config-file mypy_self_check.ini -p mypy — clean.

An expression such as `False` has type `bool` with a known value, stored as
`last_known_value`. When narrowing against a bool literal pattern,
`conditional_types()` expands the instance into `Literal[True] |
Literal[False]` so that one member can be removed, but the expansion dropped
the known value.

As a result, `match False: case False:` left `Literal[True]` as the remaining
type, the match was not considered exhaustive, and a following `case other`
captured `Literal[False]` even though the subject could only be `False`.

Keep the known value when expanding a sum type: if the instance already has a
last known value, expand to that literal instead of the full union. The
existing behaviour for plain `bool` and enum instances is unchanged.
@DipanshuAmbilkar03
DipanshuAmbilkar03 force-pushed the fix/bool-literal-match-narrowing branch from 96f8a99 to 7a490d2 Compare October 28, 2026 17:44
@github-actions

Copy link
Copy Markdown
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Boolean literal scrutinee not narrowed by literal patterns in match

1 participant