Skip to content

[1.20 regression] value pattern match statement narrowing broken for typevars with values #21356

Description

@m-richards

Bug Report
I think I've found a regression in 1.20 - was recently trying to migrate a larger codebase from 1.18 and noticed this issue in a few places which didn't make sense. I've isolated it down to a small example which is a bit silly, but hopefully clear. I think this is a consequence of a deliberate change in #20908. Happy to try and put together a PR to resolve if useful, but I'm not exactly across the mypy internals, so might need a bit of guidance.

To Reproduce
https://mypy-play.net/?mypy=1.20.2&python=3.12&gist=ae5055b7e174f0ffd34db390a192aef5

import builtins
from typing import TypeVar

T = TypeVar("T", int, str)

def f(x: T) -> T:
    match type(x):
        case builtins.int:
            return 42        # mypy 1.20: error: Incompatible return value type (got "int", expected "str")
        case builtins.str:
            return "hello"   # mypy 1.20: error: Incompatible return value type (got "str", expected "int")
        case _:
            raise TypeError

Expected Behavior
Expected there to be no issues
Actual Behavior

uv run --with mypy==1.20.2 mypy mre.py 
mre.py:24: error: Incompatible return value type (got "int", expected "str")  [return-value]
mre.py:26: error: Incompatible return value type (got "str", expected "int")  [return-value]

edit: I did a bit more testing, there's nothing special about builtins here, but it needs to be a value pattern and not a class pattern for this example to even work in 1.19

Your Environment

  • Mypy version used: 1.20.2 fails, 1.19 works, a quick bisect suggests afb99eb is where this changes
  • Mypy command-line flags: None in minimal example
  • Mypy configuration options from mypy.ini (and other config files): None in minimal example
  • Python version used: 3.13 / 3.12 in playground link above

Activity

  1. changed the title [-]REGR: match statement builtins narrowing in 1.20[/-] [+]REGR: match statement narrowing with typevars in 1.20[/+] on Apr 28, 2026
  2. changed the title [-]REGR: match statement narrowing with typevars in 1.20[/-] [+]REGR: value pattern match statement narrowing with typevars in 1.20[/+] on Apr 28, 2026
  3. brianschubert commented on Apr 28, 2026

    @brianschubert
    Collaborator

    Thanks, I notice this only happens when --warn-unreachable is not set. Setting --warn-unreachable gives the old expected behavior: https://mypy-play.net/?mypy=1.20.2&python=3.12&gist=c9f38f7eb2f7fb045aad1ecc5cb42cbb&flags=warn-unreachable. I guess something went wrong with how we're treating unreachable blocks when checking constrained typevar cases.

  4. brianschubert commented on Apr 28, 2026

    @brianschubert
    Collaborator

    Bisects to #20146, cc @sterliakov

  5. brianschubert commented on Apr 28, 2026

    @brianschubert
    Collaborator

    Thanks, I notice this only happens when --warn-unreachable is not set.

    (note that this is only true as of #20660 - at #20146 it manifests regardless of whether --warn-unreachable is set)

  6. changed the title [-]REGR: value pattern match statement narrowing with typevars in 1.20[/-] [+][1.20 regression] value pattern match statement narrowing broken for typevars with values[/+] on Apr 28, 2026
  7. hauntsaninja commented on Apr 30, 2026

    @hauntsaninja
    Collaborator

    Oh, interesting case...

    So #20146 is a correct change. It make mypy's match narrowing use the same code paths as its equality narrowing. This makes mypy match Python semantics better.

    Unfortunately, that means it is also a bit of an all-or-nothing change... differences between the equality narrowing logic and the (incorrect) logic mypy used to use for match narrowing, can show up as regressions like this one.

    But fortunately, I spent a lot of time improving equality narrowing in the last release, which is where the --warn-unreachable comes into play. #20660 makes mypy's reachability logic a little stricter when --warn-unreachable is set. This happens to solve your false positive because of how mypy's logic for constrained type variables works.

    Ideally, I would like for this reachability logic to not depend on --warn-unreachable. But as I mention in that PR, and as based on mypy_primer results, we need to make mypy check unreachable code for this to be a good idea.

    So I'm not quite sure how best to help you. If --warn-unreachable is usable for you, that is great. Otherwise, we might just have to wait for changes to make mypy check unreachable code.

  8. m-richards commented on Apr 30, 2026

    @m-richards
    Author

    Thanks for the reply! Appreciate this is all super complicated and sometimes there is a price in pushing things forward - and I'm very glad to see continual improvement in mypy. I think in our case we can migrate to warn unreachable so that should work well enough for us.

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions