python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.44k stars 2.82k forks source link

Cannot use `match case` to destructure any object using `case object(x=y)` #16912

Open RenDelaCruz opened 8 months ago

RenDelaCruz commented 8 months ago

Bug Report

In the global scope, mypy allows destructuring any object in match case with object(x=y):

@dataclass
class A:
    id: int

# SUCCESS
match A(id=5):
    case object(id=object_id):
        print(object_id)

However, in a method, mypy errors out trying to use object(x=y) in match case:

# FAILURE
def get_id_from_any_object(obj: object) -> int | None:
    match obj:
        # main.py:19: error: Class "builtins.object" has no attribute "id"
        case object(id=object_id):
            return object_id
        case _:
            return None

Edit: Turns out, it is not due to scoping, but due to the type of the match <subject>. See https://github.com/python/mypy/issues/16912#issuecomment-1939638659

To Reproduce

mypy playground: https://mypy-play.net/?mypy=latest&python=3.12&gist=c5799b1b128c52894f2879f9fc8d0115

Expected Behavior

Should allow using case object(x=y) in match case syntax.

Actual Behavior

Errors out trying case object(x=y) in match case syntax, inside a method. Does not error out in global scope.

Your Environment

Hnasar commented 8 months ago

I poked at it a little and there is a bug here but I wanted to clarify that this is not influenced by the scope: it has to do with the type of the match "subject".

In your global scope example the subject has type A but in the function, the subject, obj has type object.

If we remove the match statement:

However, with a match statment it's a bit different

match object():
    case object(id=object_id):
        print("match")
    case _:
        print("no match")
TeamSpen210 commented 8 months ago

The match statement is more like a hasattr() check, which mypy does have the ability to handle. But changing the behaviour to do that then opens up false negatives where mypy fails to warn you about typos etc.

erictraut commented 8 months ago

A similar question was recently posted to the pyright issue tracker. From my perspective, this is a legitimate use of pattern matching. Pyright therefore does not generate a type error for the code at the top of this thread. As @TeamSpen210 notes, this comes at the expense of some potential false negatives.

RenDelaCruz commented 8 months ago

I agree, using object() in this case is a legitimate use of pattern matching. Since this works with regular Python, and reduces excessive use of hasattr checks.

Perhaps mypy should make an exception for subjects with an object type, when used in match case statements only