Skip to content

Exhaustive checks with assert_never when using match on a 2-tuple fails #16650

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

Open
MaxG87 opened this issue Dec 12, 2023 · 4 comments · May be fixed by #18446
Open

Exhaustive checks with assert_never when using match on a 2-tuple fails #16650

MaxG87 opened this issue Dec 12, 2023 · 4 comments · May be fixed by #18446
Labels
bug mypy got something wrong topic-match-statement Python 3.10's match statement

Comments

@MaxG87
Copy link

MaxG87 commented Dec 12, 2023

Bug Report

I learned about assert_never, with which checks for exhaustiveness can be performed. I wanted to use it in a match statement where I want to cover all 4 possible scenarios of a tuple[int|None, int|None]. Unfortunately, when adding the catch-all case arm that contains typing.assert_never, I get a typing error indicating a non-exhaustive check above.

The error persists in a couple of variations. In particular, it persists when using 1-tuple. However, when matching on one value directly, using assert_never works as intended.

To Reproduce

https://gist.github.com/mypy-play/1df3d1b4c9e82226df17094a9f9c6af5
https://mypy-play.net/?mypy=latest&python=3.12&gist=1df3d1b4c9e82226df17094a9f9c6af5

Expected Behavior

All examples in the provided Gist should pass the type checks.

Actual Behavior

Mypy rejects the first three functions of the Gist. In particular, it's output reads:

Failed (exit code: 1) (2476 ms)

main.py:22: error: Argument 1 to "assert_never" has incompatible type "int | None"; expected "NoReturn" [arg-type]
main.py:43: error: Argument 1 to "assert_never" has incompatible type "int | None"; expected "NoReturn" [arg-type]
main.py:58: error: Argument 1 to "assert_never" has incompatible type "int | None"; expected "NoReturn" [arg-type]
Found 3 errors in 1 file (checked 1 source file)

Your Environment

Since I am using the Gist, I hope my particular environment doesn't matter much.

@MaxG87 MaxG87 added the bug mypy got something wrong label Dec 12, 2023
@tmke8
Copy link
Contributor

tmke8 commented Dec 12, 2023

This is probably the same problem as in #15426 – mypy doesn't narrow tuples in the match statement.

EDIT: actually, #12364 is the better central issue

@MaxG87
Copy link
Author

MaxG87 commented Dec 12, 2023

Yes, it looks related. My issue just adds another nuance.

@rdozier-work
Copy link

I just ran into this as well (in this library we have homemade Result, Ok, and Err types where Result = Ok | Err):

    res: Result[tuple[A, B], MyException] = ...
    match res:
        case Ok((a, b)):
            ...
            return Ok(...)
        case Err(exc):
            return Err(exc)
        case _ as unreachable:
            assert_never(unreachable)
    #   ^^^^^^^^^Mypy: Argument 1 to "assert_never" has incompatible type "Ok[tuple[A, B]]"; expected "Never" [arg-type]

@ncoghlan
Copy link

Another variant on this shows up when differentiating 2-tuples via explicit string literals :

        # event is declared as a union of 2-tuples, with literal strings in the first field
        match event:
            case ("download_progress", update):
                self._report_progress(update)
            case ("finalize_download", _):
                self._finalize_download()
            case ("finished", _):
                pass
            case _:
                assert_never(event) # Fails, reporting the full union type

Switching to matching on event[0] instead makes the exhaustiveness check work, but loses the convenience of correctly downcasting the event argument to the corresponding type.

In my case, since I control the event definitions, I'm going to switch from using 2-tuples to a dedicated event class which MyPy can both exhaustiveness check and correctly downcast.

Since my string literal variation type checks in pyright, but fails in mypy, this issue is presumably due to the tuple-expansion (or lack thereof) mentioned in #16722 (comment)

This means that the OP's variation won't typecheck in pyright either (since it requires subtype expansion on both tuple elements)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-match-statement Python 3.10's match statement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants