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

Exhaustiveness checking for match statements #12267

Merged
merged 23 commits into from Mar 7, 2022
Merged

Exhaustiveness checking for match statements #12267

merged 23 commits into from Mar 7, 2022

Conversation

JukkaL
Copy link
Collaborator

@JukkaL JukkaL commented Mar 1, 2022

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

@github-actions

This comment has been minimized.

mypy/checker.py Outdated

def remove_capture_conflicts(self, type_map: TypeMap, inferred_types: Dict[Var, Type]) -> None:
if type_map:
for expr, typ in type_map.copy().items():
Copy link
Collaborator

@jhance jhance Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think list(.items()) is more idiomatic (and probably cheaper) python

mypy/subtypes.py Outdated
@@ -1237,11 +1237,13 @@ def _is_proper_subtype(left: Type, right: Type, *,
class ProperSubtypeVisitor(TypeVisitor[bool]):
def __init__(self, right: Type, *,
ignore_promotions: bool = False,
ignore_last_known_value: bool = False,
Copy link
Collaborator

@jhance jhance Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nobody ever passes True here. Is this deadcode?

jhance
jhance approved these changes Mar 2, 2022
Copy link
Collaborator

@jhance jhance left a comment

Seems fine, but some minor tweaks.

@github-actions
Copy link

@github-actions github-actions bot commented Mar 3, 2022

Diff from mypy_primer, showing the effect of this PR on open source code:

pylox (https://github.com/sco1/pylox)
- pylox/containers/base.py:62: error: Missing return statement  [return]
- pylox/containers/array.py:163: error: Missing return statement  [return]
- pylox/interpreter.py:230: error: Missing return statement  [return]
- pylox/interpreter.py:258: error: Missing return statement  [return]

@thomkeh
Copy link

@thomkeh thomkeh commented Mar 3, 2022

This doesn't seem to work:

from enum import Enum, auto

class E1(Enum):
    a = auto()
    b = auto()

class E2(Enum):
    x = auto()
    y = auto()

def f(x: tuple[E1, E2]) -> int:
    match x:
        case (E1.a, E2.x):
            return 0
        case (E1.a, E2.y):
            return 1
        case (E1.b, E2.x):
            return 2
        case (E1.b, E2.y):
            return 3

output:

match_test.py:11: error: Missing return statement

but it's probably too difficult to support...

@JukkaL
Copy link
Collaborator Author

@JukkaL JukkaL commented Mar 7, 2022

but it's probably too difficult to support...

Yeah, this seems a bit too complex for now. Since enums are internally expanded into unions of literal types to support exhaustiveness checking, an N-tuple of enums with M items each could be expanded a union of M**N items, which could become impractically large (and slow to process).

@JukkaL JukkaL merged commit 226661f into master Mar 7, 2022
15 checks passed
@JukkaL JukkaL deleted the match-exhaustiveness branch Mar 7, 2022
cdce8p pushed a commit to cdce8p/mypy that referenced this issue Mar 10, 2022
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 <adrian@freund.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants