8000 Exhaustiveness checking for match statements by JukkaL · Pull Request #12267 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Exhaustiveness checking for match statements #12267

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

Merged
merged 23 commits into from
Mar 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Some clean-up
  • Loading branch information
JukkaL committed Mar 1, 2022
commit 8eb2dc9e41d9661876b01a50155fdfaabb85e32c
24 changes: 8 additions & 16 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4089,18 +4089,10 @@ def visit_match_stmt(self, s: MatchStmt) -> None:
if isinstance(subject_type, DeletedType):
self.msg.deleted_as_rvalue(subject_type, s)

# We have to check each pattern twice. Once ignoring the guard statement to infer
# the capture types and once with then to narrow the subject.
# In addition PatternChecker adds intersection types to the scope. We only want that
# to happen on the second pass, so we copy the SymbolTable beforehand.
curr_module = self.scope.stack[0]
assert isinstance(curr_module, MypyFile)
names = curr_module.names.copy()
pattern_types = [self.pattern_checker.accept(p, subject_type) for p in s.patterns]
curr_module.names = names

type_maps: List[TypeMap] = [t.captures for t in pattern_types]
inferred_names = self.infer_variable_types_from_type_maps(type_maps)
inferred_types = self.infer_variable_types_from_type_maps(type_maps)

for p, g, b in zip(s.patterns, s.guards, s.bodies):
current_subject_type = self.expr_checker.narrow_type_from_binder(s.subject,
Expand All @@ -4118,7 +4110,7 @@ def visit_match_stmt(self, s: MatchStmt) -> None:
pattern_type.rest_type
)
self.remove_capture_conflicts(pattern_type.captures,
inferred_names)
inferred_types)
self.push_type_map(pattern_map)
self.push_type_map(pattern_type.captures)
if g is not None:
Expand All @@ -4144,7 +4136,7 @@ def visit_match_stmt(self, s: MatchStmt) -> None:

def infer_variable_types_from_type_maps(self, type_maps: List[TypeMap]) -> Dict[Var, Type]:
all_captures: Dict[Var, List[Tuple[NameExpr, Type]]] = defaultdict(list)
inferred_names: Dict[Var, Type] = {}
inferred_ty 8000 pes: Dict[Var, Type] = {}
for tm in type_maps:
if tm is not None:
for expr, typ in tm.items():
Expand All @@ -4166,23 +4158,23 @@ def infer_variable_types_from_type_maps(self, type_maps: List[TypeMap]) -> Dict[
msg=message_registry.INCOMPATIBLE_TYPES_IN_CAPTURE,
subtype_label="pattern captures type",
supertype_label="variable has type"):
inferred_names[var] = previous_type
inferred_types[var] = previous_type

if not already_exists:
new_type = UnionType.make_union(types)
# Infer the union type at the first occurrence
first_occurrence, _ = captures[0]
inferred_names[var] = new_type
inferred_types[var] = new_type
self.infer_variable_type(var, first_occurrence, new_type, first_occurrence)
return inferred_names
return inferred_types

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

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

if isinstance(expr, NameExpr):
node = expr.node
assert isinstance(node, Var)
if node not in inferred_names or not is_subtype(typ, inferred_names[node]):
if node not in inferred_types or not is_subtype(typ, inferred_types[node]):
del type_map[expr]

def make_fake_typeinfo(self,
Expand Down
3 changes: 2 additions & 1 deletion mypy/checkpattern.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Pattern checker. This file is conceptually part of TypeChecker."""

from collections import defaultdict
from typing import List, Optional, Tuple, Dict, NamedTuple, Set, Union
from typing_extensions import Final
Expand Down Expand Up @@ -56,7 +57,7 @@
'PatternType',
[
('type', Type), # The type the match subject can be narrowed to
('rest_type', Type),
('rest_type', Type), # The remaining type if the pattern didn't match
('captures', Dict[Expression, Type]), # The variables captured by the pattern
])

Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-python310.test
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ m: A

match m:
case b.b:
reveal_type(m) # N: Revealed type is "__main__.<subclass of "A" and "B">"
reveal_type(m) # N: Revealed type is "__main__.<subclass of "A" and "B">1"
[file b.py]
class B: ...
b: B
Expand Down Expand Up @@ -804,9 +804,9 @@ m: B

match m:
case A():
reveal_type(m) # N: Revealed type is "__main__.<subclass of "B" and "A">"
reveal_type(m) # N: Revealed type is "__main__.<subclass of "B" and "A">2"
case A(i, j):
reveal_type(m) # N: Revealed type is "__main__.<subclass of "B" and "A">1"
reveal_type(m) # N: Revealed type is "__main__.<subclass of "B" and "A">3"
[builtins fixtures/tuple.pyi]

[case testMatchClassPatternNonexistentKeyword]
Expand Down
0