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

Support for python 3.10 match statement #10191

Merged
merged 84 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
3847124
Add Nodes needed for match statement support
freundTech Mar 9, 2021
f9e9277
Added match statement support to StrConv visitor
freundTech Mar 9, 2021
6c4109f
Added match statement support to TraverserVisitor
freundTech Mar 9, 2021
289d014
Move MatchAs and MatchOr to patterns and add literal patterns
freundTech Mar 10, 2021
1973a88
Added nodes for capture and wildcard patterns
freundTech Mar 10, 2021
cc92db4
Added nodes for value, sequence and mapping patterns
freundTech Mar 10, 2021
057716c
Added nodes for class patterns
freundTech Mar 10, 2021
6bfb098
Added parser tests for pattern matching and tweaks to pattern classes
freundTech Mar 10, 2021
a74b0e6
Added patterns to NodeVisitor and TraverserVisitor
freundTech Mar 10, 2021
fc3c1d2
Add missing parse test and prevent tests from running on < 3.10
freundTech Mar 10, 2021
db04186
Add parse tests for open sequence patterns
freundTech Mar 10, 2021
eeca25b
Added match statement support to SemanticAnalyzerPreAnalysis
freundTech Mar 12, 2021
cc76f59
Also consider pattern in SemanticAnalyzerPreAnalysis
freundTech Mar 12, 2021
36a45b3
Use common base classes instead of unions for patterns
freundTech Mar 12, 2021
7facb0d
Added match statement support for variable renaming (allow-redefinition)
freundTech Mar 12, 2021
1bc8a4b
Added match statement support for SemanticAnalyzer
freundTech Mar 13, 2021
d26d81c
Moved int from types to builtin stubs in order to extend int
freundTech Apr 5, 2021
3effadc
Added initial version of match statement type checking
freundTech Apr 6, 2021
3aab780
Use double quotes in error messages
freundTech Apr 6, 2021
3a71c32
Add support for match statement self matching class patterns
freundTech Apr 6, 2021
b7468d9
Replace more single quotes in tests with double quotes
freundTech Apr 7, 2021
943f2ff
Add support for __match_args__ to dataclass
freundTech Apr 7, 2021
b41fa30
Minor code cleanup
freundTech Apr 7, 2021
037a140
Add support __match_args__ to namedtuple
freundTech Apr 7, 2021
fb9ae47
Add support for match statement as pattern and change type_stack to Type
freundTech Apr 7, 2021
99c1bbf
Make PatternChecker infer unions for patterns with the same name
freundTech Apr 8, 2021
0e47d6f
Implemented match_args parameter for dataclasses (bpo-43764)
freundTech Apr 12, 2021
77b997b
Refactor PatternChecker
freundTech Apr 14, 2021
5228bc7
Add match statement support for or pattern
freundTech Apr 20, 2021
bba61fa
Fix make_simplified_union for instances with last_known_value
freundTech Apr 26, 2021
39478fd
Infer literals from literal patterns in match statements
freundTech Apr 27, 2021
a865882
Infer the rest pattern for mapping patterns
freundTech Apr 27, 2021
c35d921
Adjust ASTConverter to use dedicated pattern nodes
freundTech Apr 30, 2021
1aae2c1
Improve type inference for tuples checked against sequence patterns
freundTech May 1, 2021
1364a67
Merge remote-tracking branch 'upstream/master' into feature-match-stmt
freundTech May 1, 2021
32a7b71
Fix linter errors
freundTech May 3, 2021
25fe496
Sync typeshed
freundTech May 10, 2021
86a6ce9
Fix sequence pattern outer type check to be in line with PEP 634
freundTech May 10, 2021
9445c10
Merge remote-tracking branch 'upstream/master' into feature-match-stmt
freundTech May 10, 2021
74c7040
Remove accidentally commited changes
freundTech May 10, 2021
c964027
Fix failing TypeExport tests
freundTech May 10, 2021
f6b1752
Improve type inference for mapping pattern rest
freundTech May 11, 2021
d2b7d1e
Improve class pattern subpattern type inference
freundTech May 11, 2021
5c47852
Fix selftest failing
freundTech May 18, 2021
0ac21d0
Fix mypyc build failing
freundTech May 18, 2021
7db936b
Merge remote-tracking branch 'upstream/master' into feature-match-stmt
freundTech May 18, 2021
5f98cdd
Fix another mypyc build error
freundTech May 18, 2021
423b04c
Merge remote-tracking branch 'upstream/master' into feature-match-stmt
freundTech Sep 27, 2021
f2e12f8
Fix failing tests after merge (Caused by #8578)
freundTech Sep 27, 2021
a5eb480
Fixed more failing tests after merge (Caused by #10685)
freundTech Sep 27, 2021
9d5d600
Adjust some tests and add new ones
freundTech Sep 28, 2021
a68ea59
Fixed types staying narrowed after match statement
freundTech Sep 29, 2021
43fd6e5
Run com2ann and fix errors
freundTech Sep 29, 2021
d7baadf
Split conditional_type_maps into two functions
freundTech Sep 29, 2021
8a3ba8e
Readd removed workaround as the problem is still present
freundTech Sep 29, 2021
90aa1a7
Disable Exhaustiveness test for now
freundTech Oct 4, 2021
de723e1
Fixed checking of nested list statements failing
freundTech Oct 6, 2021
3fb2cf2
Fixed mypy crashing when list statement can't match
freundTech Oct 6, 2021
ab7a2e3
Moved test to correct category
freundTech Oct 6, 2021
45e2abe
Simplify conditional_types
freundTech Sep 30, 2021
44ca265
Generate intersection types in match statements and add guard tests
freundTech Oct 11, 2021
21f7b59
Fixed broken tests
freundTech Oct 11, 2021
bfb578d
Fix mypy_primer differences
freundTech Oct 12, 2021
909fc55
Merge remote-tracking branch 'upstream/master' into feature-match-stmt
freundTech Oct 12, 2021
c0a43e8
Fix type errors
freundTech Oct 12, 2021
f768993
Fixed copy-paste error causing mypyc crash
freundTech Oct 12, 2021
7b60596
Added semanal tests for undefined value and class patterns
freundTech Oct 20, 2021
6cf9f58
Removed redundant bool from lib-stubs
freundTech Oct 20, 2021
268e181
Removed tuple from lib-stub
freundTech Oct 20, 2021
dbcd886
Fix typo in test
freundTech Oct 20, 2021
bd7e777
Fixed testcase and marked it as skip
freundTech Oct 20, 2021
f51c83f
Moved messages to message_registry
freundTech Oct 20, 2021
4d87ca1
Removed outdated comment
freundTech Oct 20, 2021
9f9efcc
Removed full stop from error message
freundTech Oct 20, 2021
68bd2ba
Change test case to new error message
freundTech Oct 20, 2021
ff5123c
Report error for non-existing keywords on class patterns
freundTech Oct 20, 2021
13b675b
Merge remote-tracking branch 'upstream/master' into feature-match-stmt
freundTech Oct 20, 2021
e437323
Fixed dataclasses plugin after merging master.
freundTech Oct 20, 2021
68a09ee
Added more comments and asserts to pattern classes
freundTech Oct 21, 2021
a5baf60
Readd tuple missing tests
freundTech Oct 21, 2021
4add6b7
Merge remote-tracking branch 'upstream/master' into feature-match-stmt
freundTech Dec 18, 2021
eabeb89
Merge remote-tracking branch 'upstream/master' into feature-match-stmt
freundTech Dec 18, 2021
53d40f8
Added missing Final annotations
freundTech Dec 19, 2021
52dc066
Add comments and clarify some names
freundTech Dec 19, 2021
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
2 changes: 1 addition & 1 deletion mypy-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
typing_extensions>=3.7.4
mypy_extensions>=0.4.3,<0.5.0
typed_ast>=1.4.0,<1.5.0
typed_ast>=1.4.0,<1.5.0; python_version<'3.8'
freundTech marked this conversation as resolved.
Show resolved Hide resolved
types-typing-extensions>=3.7.0
types-mypy-extensions>=0.4.0
toml
93 changes: 91 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import itertools
import fnmatch
from collections import defaultdict
from contextlib import contextmanager

from typing import (
Expand All @@ -24,8 +25,7 @@
Import, ImportFrom, ImportAll, ImportBase, TypeAlias,
ARG_POS, ARG_STAR, LITERAL_TYPE, MDEF, GDEF,
CONTRAVARIANT, COVARIANT, INVARIANT, TypeVarExpr, AssignmentExpr,
is_final_node,
ARG_NAMED)
is_final_node, ARG_NAMED, MatchStmt)
from mypy import nodes
from mypy.literals import literal, literal_hash, Key
from mypy.typeanal import has_any_from_unimported_type, check_for_explicit_any
Expand All @@ -45,6 +45,7 @@
from mypy.checkmember import (
analyze_member_access, analyze_descriptor_access, type_object_type,
)
from mypy.checkpattern import PatternChecker
from mypy.typeops import (
map_type_from_supertype, bind_self, erase_to_bound, make_simplified_union,
erase_def_to_union_or_bound, erase_to_union_or_bound, coerce_to_literal,
Expand Down Expand Up @@ -164,6 +165,8 @@ class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
# Helper for type checking expressions
expr_checker = None # type: mypy.checkexpr.ExpressionChecker

pattern_checker = None # type: PatternChecker

tscope = None # type: Scope
scope = None # type: CheckerScope
# Stack of function return types
Expand Down Expand Up @@ -220,6 +223,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Option
self.msg = MessageBuilder(errors, modules)
self.plugin = plugin
self.expr_checker = mypy.checkexpr.ExpressionChecker(self, self.msg, self.plugin)
self.pattern_checker = PatternChecker(self, self.msg, self.plugin)
self.tscope = Scope()
self.scope = CheckerScope(tree)
self.binder = ConditionalTypeBinder()
Expand Down Expand Up @@ -1385,6 +1389,19 @@ def check_setattr_method(self, typ: Type, context: Context) -> None:
if not is_subtype(typ, method_type):
self.msg.invalid_signature_for_special_method(typ, context, '__setattr__')

def check_match_args(self, var: Var, typ: Type, context: Context) -> None:
"""Check that __match_args__ is final and contains literal strings"""

if not var.is_final:
self.note("__match_args__ must be final for checking of match statements to work",
context, code=codes.LITERAL_REQ)
Copy link
Collaborator

Choose a reason for hiding this comment

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

For consistency with __slots__, __all__ and __deletable__, we shouldn't require this to be final. You can just drop this check.


typ = get_proper_type(typ)
if not isinstance(typ, TupleType) or \
not all([is_string_literal(item) for item in typ.items]):
self.msg.note("__match_args__ must be a tuple containing string literals for checking "
"of match statements to work", context, code=codes.LITERAL_REQ)
Copy link
Collaborator

Choose a reason for hiding this comment

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

As mentioned elsewhere, we prefer not to generate "bare notes". Can you make this report an error instead?


def expand_typevars(self, defn: FuncItem,
typ: CallableType) -> List[Tuple[FuncItem, CallableType]]:
# TODO use generator
Expand Down Expand Up @@ -2066,6 +2083,10 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
else:
self.check_getattr_method(signature, lvalue, name)

if name == '__match_args__' and inferred is not None:
typ = self.expr_checker.accept(rvalue)
self.check_match_args(inferred, typ, lvalue)

# Defer PartialType's super type checking.
if (isinstance(lvalue, RefExpr) and
not (isinstance(lvalue_type, PartialType) and lvalue_type.type is None)):
Expand Down Expand Up @@ -3704,6 +3725,69 @@ def visit_continue_stmt(self, s: ContinueStmt) -> None:
self.binder.handle_continue()
return None

def visit_match_stmt(self, s: MatchStmt) -> None:
with self.binder.frame_context(can_skip=False, fall_through=0):
subject_type = get_proper_type(self.expr_checker.accept(s.subject))

if isinstance(subject_type, DeletedType):
self.msg.deleted_as_rvalue(subject_type, s)

pattern_types = [self.pattern_checker.accept(p, subject_type) for p in s.patterns]

type_maps = [t.captures for t in pattern_types] # type: List[TypeMap]
self.infer_names_from_type_maps(type_maps)

for pattern_type, g, b in zip(pattern_types, s.guards, s.bodies):
with self.binder.frame_context(can_skip=True, fall_through=2):
if b.is_unreachable or pattern_type.type is None:
self.push_type_map(None)
else:
self.binder.put(s.subject, pattern_type.type)
self.push_type_map(pattern_type.captures)
if g is not None:
gt = get_proper_type(self.expr_checker.accept(g))

if isinstance(gt, DeletedType):
self.msg.deleted_as_rvalue(gt, s)

if_map, _ = self.find_isinstance_check(g)

self.push_type_map(if_map)
self.accept(b)

def infer_names_from_type_maps(self, type_maps: List[TypeMap]) -> None:
freundTech marked this conversation as resolved.
Show resolved Hide resolved
all_captures = defaultdict(list) # type: Dict[Var, List[Tuple[NameExpr, Type]]]
for tm in type_maps:
if tm is not None:
for expr, typ in tm.items():
if isinstance(expr, NameExpr):
node = expr.node
assert isinstance(node, Var)
all_captures[node].append((expr, typ))

for var, captures in all_captures.items():
conflict = False
types = [] # type: List[Type]
for expr, typ in captures:
types.append(typ)

previous_type, _, inferred = self.check_lvalue(expr)
if previous_type is not None:
conflict = True
self.check_subtype(typ, previous_type, expr,
msg=message_registry.INCOMPATIBLE_TYPES_IN_CAPTURE,
subtype_label="pattern captures type",
supertype_label="variable has type")
for type_map in type_maps:
if type_map is not None and expr in type_map:
del type_map[expr]

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

def make_fake_typeinfo(self,
curr_module_fullname: str,
class_gen_name: str,
Expand Down Expand Up @@ -5801,6 +5885,11 @@ def is_private(node_name: str) -> bool:
return node_name.startswith('__') and not node_name.endswith('__')


def is_string_literal(typ: Type) -> bool:
strs = try_getting_str_literals_from_type(typ)
return strs is not None and len(strs) == 1


def has_bool_item(typ: ProperType) -> bool:
"""Return True if type is 'bool' or a union with a 'bool' item."""
if is_named_instance(typ, 'builtins.bool'):
Expand Down
10 changes: 7 additions & 3 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3015,7 +3015,11 @@ def nonliteral_tuple_index_helper(self, left_type: TupleType, index: Expression)
else:
return union

def visit_typeddict_index_expr(self, td_type: TypedDictType, index: Expression) -> Type:
def visit_typeddict_index_expr(self, td_type: TypedDictType,
index: Expression,
local_errors: Optional[MessageBuilder] = None
) -> Type:
local_errors = local_errors or self.msg
if isinstance(index, (StrExpr, UnicodeExpr)):
key_names = [index.value]
else:
Expand All @@ -3035,14 +3039,14 @@ def visit_typeddict_index_expr(self, td_type: TypedDictType, index: Expression)
and key_type.fallback.type.fullname != 'builtins.bytes'):
key_names.append(key_type.value)
else:
self.msg.typeddict_key_must_be_string_literal(td_type, index)
local_errors.typeddict_key_must_be_string_literal(td_type, index)
return AnyType(TypeOfAny.from_error)

value_types = []
for key_name in key_names:
value_type = td_type.items.get(key_name)
if value_type is None:
self.msg.typeddict_key_not_found(td_type, key_name, index)
local_errors.typeddict_key_not_found(td_type, key_name, index)
return AnyType(TypeOfAny.from_error)
else:
value_types.append(value_type)
Expand Down
Loading