mypy icon indicating copy to clipboard operation
mypy copied to clipboard

Match exhaustiveness with class inheriting from tuple

Open tobiasBora opened this issue 1 year ago • 1 comments

Bug Report

If I run:

# Run the type checking with "mypy --strict foo.py"

class Add(tuple[int,int]):
    pass

class Const(int):
    pass

Expr = Add | Const

def my_eval(e : Expr) -> int:
    match e:
        case Add((a,b)):
            return a + b
        case Const(x):
            return x

print(my_eval(Const(42)))
print(my_eval(Add((42,45))))

with python 11, it runs just fine. But if I run mypy on this file (with or without strict), I get an error:

$ mypy --strict foo.py
foo.py:11: error: Missing return statement  [return]
Found 1 error in 1 file (checked 1 source file)

Usually I get this error when a branch in a match is forgotten… but here I have covered all cases.

Note that the following code does work, but I would prefer the above code that is less verbose and error prone:

# Run the type checking with "mypy --strict foo.py"

class Add:
    left: int
    right: int
    def __init__(self, left:int, right:int):
        self.left = left
        self.right = right

class Const:
    val: int
    def __init__(self, val:int):
        self.val = val

Expr = Add | Const

def my_eval(e : Expr) -> int:
    match e:
        case Add():
            return e.left + e.right
        case Const():
            return e.val

print(my_eval(Const(42)))
print(my_eval(Add(42,45)))

To Reproduce

Gist URL: https://gist.github.com/mypy-play/5e61f50c5c61bf4c90b5e1403f2b6be1 Playground URL: https://mypy-play.net/?mypy=latest&python=3.12&gist=5e61f50c5c61bf4c90b5e1403f2b6be1

Expected Behavior

I would expect this to type fine. I need this for instance in this context https://stackoverflow.com/questions/78345055/python-static-type-checking-for-union-type-and-pattern-maching/78345797#78345797

I would also expect this to work when using forward reference:

# Run the type checking with "mypy --strict foo.py"

class Add(tuple['Expr','Expr']):
    pass

class Const(int):
    pass

Expr = Add | Const

def my_eval(e : Expr) -> int:
    match e:
        case Add((a,b)):
            return my_eval(a) + my_eval(b)
        case Const(x):
            return x

print(my_eval(Const(42)))
print(my_eval(Add((Const(42),Const(45)))))

Actual Behavior

I get an error.

Your Environment

  • Mypy version used: 1.5.1
  • Mypy command-line flags: None or strict
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.11.8

tobiasBora avatar Apr 18 '24 07:04 tobiasBora

The problem isn't the Foo(int), but the Add(tuple[int, int]). This works fine (https://mypy-play.net/?mypy=latest&python=3.12&gist=5e61f50c5c61bf4c90b5e1403f2b6be1):

# Run the type checking with "mypy --strict foo.py"

class Add(int):
    pass

class Const(int):
    pass

Expr = Add | Const

def my_eval(e : Expr) -> int:
    match e:
        case Add(a):
            return a
        case Const(x):
            return x

print(my_eval(Const(42)))
print(my_eval(Add(4)))

As for the solution, tuple types are often special; something will have to change in mypy's handling of them but I'm not sure where.

JelleZijlstra avatar Apr 18 '24 09:04 JelleZijlstra