-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Reachability/exhaustiveness checking for match statements #12010
Comments
@freundTech Did you have a work-in-progress implementation of exhaustiveness checking? If you have, it would be great if you could create a PR -- maybe I or somebody else can help finish it? |
There is a working exhaustiveness checking implementation in https://github.com/freundTech/mypy (feature-match-stmt-exhaustiveness branch. I made it the default branch) I could make a PR from that branch, but it would need major rebasing. It's probably better to cherry-pick the relevant commits to a new branch instead of rebasing all of it. The implementation works, but I'm not really happy with it's design. It checks all patterns twice. First ignoring guard statements in order to infer rough types for capture patterns (capture patterns need to be inferred before a guard statement can use them) and then a second time with guard patterns to infer more accurate types and narrow the capture patterns. I did this because I thought that mypy couldn't broaden a variable's type after it was inferred. Later I found out that it can do that, but didn't have enough time to redo my implementation. An example to illustrate the problem:
|
Thanks! Cherry-picking from your branch and potentially tweaking parts of the implementation sounds reasonable. The branch also suggests that we could split this into at least two separate PRs: first one adds basic support and second one adds better support for enums (and literal types). |
There's some relevant discussion starting from this comment: #10191 (comment) Here's my summary of the ideas discussed above (these can be moved to follow-up issues):
|
For what it's worth, here's another discussion on this topic: microsoft/pyright#2569. I'm personally not a fan of using |
I'm wondering if this is the same issue as what I am experiencing? def testing_return(test: str) -> str:
match test:
case "test":
return test
case "testing":
return test
if __name__ == "__main__":
print(testing_return("test")) results in: it seems even valid match case blocks in python 3.10 results in mypy giving the missing return statement error. |
@Pavocracy that error is correct. The function does not return a value if (It doesn't matter that you only call the function with |
@JelleZijlstra so mypy will always complain at match blocks that dont contain I did not know that, thank you for the information! Just to clarify incase this ever shows up in a search result. Simply adding a |
@Pavocracy Once this issue is solved using a |
I have been following this discussion with great interest; I think "safe by default" should win. It also seems to jive better with PEP 20's "Zen of Python": Explicit is better than implicit, Special cases aren't special enough to break the rules and, most importantly, Errors should never pass silently. IMO, not handling exhaustiveness is equivalent to the required The overwhelming majority of languages (all?) I am familiar with that support structural pattern matching have exhaustiveness checking - with good reasons. Unless there are strong technical issues, it may be reasonable to go with what's familiar/established. It will also make Python more attractive to domains for which other languages have typically "dominated", such as compilers.
Finally, |
Closes #12010. Mypy can now detect if a match statement covers all the possible values. Example: ``` def f(x: int | str) -> int: match x: case str(): return 0 case int(): return 1 # Mypy knows that we can't reach here ``` Most of the work was done by @freundTech. I did various minor updates and changes to tests. This doesn't handle some cases properly, including these: 1. We don't recognize that `match [*args]` fully covers a list type 2. Fake intersections don't work quite right (some tests are skipped) 3. We assume enums don't have custom `__eq__` methods Co-authored-by: Adrian Freund <[email protected]>
Closes python#12010. Mypy can now detect if a match statement covers all the possible values. Example: ``` def f(x: int | str) -> int: match x: case str(): return 0 case int(): return 1 # Mypy knows that we can't reach here ``` Most of the work was done by @freundTech. I did various minor updates and changes to tests. This doesn't handle some cases properly, including these: 1. We don't recognize that `match [*args]` fully covers a list type 2. Fake intersections don't work quite right (some tests are skipped) 3. We assume enums don't have custom `__eq__` methods Co-authored-by: Adrian Freund <[email protected]>
Is this deficiency (from the commit message of 226661f) tracked somewhere?
|
Currently the following code still fails with from dataclasses import dataclass
@dataclass
class Foo:
value: int | str
def f(x: Foo) -> int:
match x:
case Foo(str(v)):
return 0
case Foo(int(v)):
return 1 |
@alexandermalyga, I wouldn't expect that example to work. Type exhaustion for match statements is implemented using type narrowing. The initial type of the subject expression If you want type exhaustion to work with this code, you will need to make a slight modification: def f(x: Foo) -> int:
match x.value:
case str(v):
return 0
case int(v):
return 1 This works because the type of the subject expression |
Fair, but do you expect it to give an error?
Would it be possible to expand on this a little? When you write that the Python type system has no way of representing, do you really mean expressing? It's not clear to me why there would need to be a way to express this in order for a type checker to implement an understanding of it. How does this differ from the situation with intersections for instance, where there is a representation in type checkers but no way to express them? My understanding is that a Python runtime implementation is free to optimize the given pattern into this: if isinstance(x, Foo):
if isinstance(x.value, str):
return 0
elif isinstance(x.value, int):
return 1 Is there some fundamental reason that a type checker couldn't do the same, and apply exhaustiveness checking to that? I think it's completely fair for a type checker to dismiss this as too complex to implement, or on grounds of being too slow, but is there some inherently unsafe or other (perhaps type-theory-based) reason not to have this functionality in type checkers? |
Given how type exhaustion detection works, yes I'd expect it to not detect exhaustion in this case.
The logic in a type checker works on types that are expressible within the type system. It's theoretically possible for a type checker to internally create constructs that extend the type system, but the logic throughout the entire type checker would need to understand these new constructs. There would also need to be some way of expressing these constructs to users in error messages and |
Currently this generates a false positive, since mypy doesn't recognize that all the possible values are processed by the match statement:
This has the same issue as well:
The text was updated successfully, but these errors were encountered: