Skip to content
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

Restrictions on class patterns (match statement) are too strict / (possible cause: too eager to alias types) #17133

Open
dmoisset opened this issue Apr 17, 2024 · 1 comment
Labels
bug mypy got something wrong

Comments

@dmoisset
Copy link
Contributor

Bug Report

Considering the following piece of code:

NestedLines = tuple

def print_lines(ls: NestedLines, indent: int = 0) -> None:
    for line in ls:
        match line:
            case NestedLines():
                print_lines(line, indent+4)
            case _:
                print(" "*indent + str(line))

print_lines((1, [2,3,4], (5, [6,7]), 8))

mypy --strict complains at the case NestedLines() saying "Class pattern class must not be a type alias with type parameters". However, given that a class pattern has to be a type object (and not a genericalias object), there's no way to resolve this that makes both the runtime and the typechecker happy.

For example, if you define

from typing import TypeAlias
NestedLines: TypeAlias = tuple["NestedLines | object"]

Running this will result in a TypeError: called match pattern must be a class.

Expected Behavior

Program should both typecheck AND print the following when run:

1
[2, 3, 4]
    5
    [6, 7]
8

Not sure what's the best solution... pyright is happy about this example, so perhaps some checks need to be removed/relaxed. In the (more complicated real usecase) code where I came accross this, the x = tuple was not intended as an alias, but there's no way to tell mypy "this is just an assignment", which I think would cmake more explicit that its usage in the match statement is not related to aliases.

The documentation says "Because the distinction between an unannotated variable and a type alias is implicit, ambiguous or incorrect type alias declarations default to defining a normal variable instead of a type alias."; I would say this case is ambiguous/incorrect as a typealias, so it should be considered a regular variable definition. But I even tried NestedLines: type = tuple, and it is still taken as an alias.

A possible desired behaviour would be: if the value used in a match statement is an "ambiguous" typealias (not defined with a TypeAlias/TypeStatement), and has actually been set to a type object (not a generic alias like tuple[Any]), do not emit an error

Actual Behavior

$ python3.12 -m mypy --strict example.py 
example.py:1: error: Missing type parameters for generic type "tuple"  [type-arg]
example.py:6: error: Class pattern class must not be a type alias with type parameters  [misc]
Found 2 errors in 1 file (checked 1 source file)

If I change it to a typealias

$ python3.12 example.py 
Traceback (most recent call last):
  File "/home/dmoissetdees/training-materials/unix/labs/whale/template/example.py", line 12, in <module>
    print_lines((1, [2,3,4], (5, [6,7]), 8))
  File "/home/dmoissetdees/training-materials/unix/labs/whale/template/example.py", line 7, in print_lines
    case NestedLines():
         ^^^^^^^^^^^^^
TypeError: called match pattern must be a class

And mypy still complains (in this case I think correctly):

example.py:7: error: Class pattern class must not be a type alias with type parameters  [misc]

Your Environment

  • Mypy version used: mypy 1.8.0 (compiled: yes)
  • Mypy command-line flags: --strict
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.12.2
@dmoisset dmoisset added the bug mypy got something wrong label Apr 17, 2024
@dmoisset
Copy link
Contributor Author

As an aditional examples where the check on match statements is overly strict:

def match_type(value: object, t: type[object]) -> bool:
    match value:
        case t():
            return True
        case _:
            return False

print(match_type(3, int))
print(match_type("3", int))

Result:

$ python3.12 -m mypy --strict example2.py 
example2.py:3: error: Expected type in class pattern; found "Type[builtins.object]"  [misc]

That looks like something that would clearly be allowed (even the error message "expected a type, found a type instead", sounds confusing). pyright accepts this one too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

1 participant