mypy icon indicating copy to clipboard operation
mypy copied to clipboard

Type is not being narrowed properly in match statement

Open bergkvist opened this issue 1 year ago • 2 comments

Bug Report

When using if-statements, the type is being properly narrowed depending on the conditions checked. mypy seems unable to do the same type of narrowing in a match statement

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

from typing import Literal, TypeAlias

Expr: TypeAlias = (
    tuple[Literal['lit'], int] |
    tuple[Literal['var'], str]
)

def square_lits(expr: Expr) -> Expr:
    match expr:
        case ('lit', x):
            return ('lit', square(x))
        case _:
            return expr

def square(x: int) -> int:
    return x * x

Here I get an error:

test.py:11: error: Argument 1 to "square" has incompatible type "int | str"; expected "int"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

While the following works fine: https://mypy-play.net/?mypy=latest&python=3.12&gist=ab7f28a259a5bff450413ce2f2924e74

from typing import Literal, TypeAlias

Expr: TypeAlias = (
    tuple[Literal['lit'], int] |
    tuple[Literal['var'], str]
)

def square_lits(expr: Expr) -> Expr:
    if expr[0] == 'lit':
        return ('lit', square(expr[1]))
    return expr

def square(x: int) -> int:
    return x * x

Expected Behavior

Both examples should work fine without any typechecking errors

Actual Behavior

Mypy is not able to narrow the type in the match statement case like it is able to when using if-conditions

Your Environment

The mypy playground links (mypy version 1.11.0, Python version 3.12).

Related:

  • https://github.com/python/mypy/pull/10191

bergkvist avatar Jul 20 '24 19:07 bergkvist

This seems to be where the if statement types are narrowed into tuple[Literal['lit'], int] and tuple[Literal['var'], str]: https://github.com/python/mypy/blob/8b74b5ab03742ba86348dfe14d0468a995f74ee7/mypy/checker.py#L4638

bergkvist avatar Jul 27 '24 16:07 bergkvist

Nice finding. match being a relatively new feature, i.e., in 3.10 Python, might be one reason for mypy to fail or not being mature enough to handle this. Also, it can be relatively complex to handle this, although you have come up with a very straightforward example above but I can imagine it should be quite a task to achieve

Prabhat-Thapa45 avatar Aug 26 '24 13:08 Prabhat-Thapa45

I encountered a similar bug. Do not know if current PR #17600 solves it. Below example shows errors from mypy 1.14.0

import typing

value: tuple[int, str] | None = 1, 'some_str'

match value:
    case item_1, item_2:
        typing.assert_type(item_1, int)  # error: Expression is of type "int | str", not "int"  [assert-type]
        typing.assert_type(item_2, str)  # error: Expression is of type "int | str", not "str"  [assert-type]

distrame avatar Dec 25 '24 12:12 distrame