From 87822430f65acc316e10610c2e4c7875b3ba00ac Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 21 Mar 2022 16:12:07 +0000 Subject: [PATCH 1/4] Don't complain about incompatible overrides of __match_args__ It's okay to modify the definition of `__match_args__` in subclasses. --- mypy/checker.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e0f2398b67fc..5491df22fe6c 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1439,12 +1439,7 @@ def check_setattr_method(self, typ: Type, context: Context) -> None: 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) - + """Check that __match_args__ contains literal strings""" typ = get_proper_type(typ) if not isinstance(typ, TupleType) or \ not all([is_string_literal(item) for item in typ.items]): @@ -2276,7 +2271,9 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type # Defer PartialType's super type checking. if (isinstance(lvalue, RefExpr) and - not (isinstance(lvalue_type, PartialType) and lvalue_type.type is None)): + not (isinstance(lvalue_type, PartialType) and + lvalue_type.type is None) and + lvalue.name != '__match_args__'): if self.check_compatibility_all_supers(lvalue, lvalue_type, rvalue): # We hit an error on this line; don't check for any others return From 09bd9be980ac70a1cb00b52251a38190b993e04a Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 21 Mar 2022 16:17:32 +0000 Subject: [PATCH 2/4] Prevent assignment to __match_args__ --- mypy/checker.py | 3 +++ mypy/message_registry.py | 1 + test-data/unit/check-python310.test | 17 +++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 5491df22fe6c..f52f912a9f7c 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2278,6 +2278,9 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type # We hit an error on this line; don't check for any others return + if isinstance(lvalue, MemberExpr) and lvalue.name == '__match_args__': + self.fail(message_registry.CANNOT_MODIFY_MATCH_ARGS, lvalue) + if lvalue_type: if isinstance(lvalue_type, PartialType) and lvalue_type.type is None: # Try to infer a proper type for a variable with a partial None type. diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 93a0258cf07a..4d2569587c2d 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -247,3 +247,4 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": CLASS_PATTERN_DUPLICATE_KEYWORD_PATTERN: Final = 'Duplicate keyword pattern "{}"' CLASS_PATTERN_UNKNOWN_KEYWORD: Final = 'Class "{}" has no attribute "{}"' MULTIPLE_ASSIGNMENTS_IN_PATTERN: Final = 'Multiple assignments to name "{}" in pattern' +CANNOT_MODIFY_MATCH_ARGS: Final = 'Cannot assign to "__match_args__"' diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 9d56aeb468f7..8835d33ccfe3 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1528,3 +1528,20 @@ class A: class B: def __enter__(self) -> B: ... def __exit__(self, x, y, z) -> None: ... + +[case testOverrideMatchArgs] +class AST: + __match_args__ = () + +class stmt(AST): ... + +class AnnAssign(stmt): + __match_args__ = ('target', 'annotation', 'value', 'simple') + +reveal_type(AST.__match_args__) # N: Revealed type is "Tuple[]" +reveal_type(stmt.__match_args__) # N: Revealed type is "Tuple[]" +reveal_type(AnnAssign.__match_args__) # N: Revealed type is "Tuple[builtins.str, builtins.str, builtins.str, builtins.str]" + +AnnAssign.__match_args__ = ('a', 'b', 'c', 'd') # E: Cannot assign to "__match_args__" +__match_args__ = 0 +[builtins fixtures/tuple.pyi] From 924ebbddb45c2b53bb003d414378f8632b14712d Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 21 Mar 2022 16:29:43 +0000 Subject: [PATCH 3/4] Fix type inference --- mypy/checker.py | 5 +++-- test-data/unit/check-python310.test | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index f52f912a9f7c..de33f6259297 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2273,7 +2273,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type if (isinstance(lvalue, RefExpr) and not (isinstance(lvalue_type, PartialType) and lvalue_type.type is None) and - lvalue.name != '__match_args__'): + not (isinstance(lvalue, NameExpr) and lvalue.name == '__match_args__')): if self.check_compatibility_all_supers(lvalue, lvalue_type, rvalue): # We hit an error on this line; don't check for any others return @@ -2377,7 +2377,8 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type if inferred: rvalue_type = self.expr_checker.accept(rvalue) - if not inferred.is_final: + if not (inferred.is_final or (isinstance(lvalue, NameExpr) and + lvalue.name == '__match_args__')): rvalue_type = remove_instance_last_known_values(rvalue_type) self.infer_variable_type(inferred, lvalue, rvalue_type, rvalue) self.check_assignment_to_slots(lvalue) diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 8835d33ccfe3..c65e167bc52f 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -881,7 +881,7 @@ reveal_type(z) # N: Revealed type is "builtins.int*" [case testMatchNonFinalMatchArgs] class A: - __match_args__ = ("a", "b") # N: __match_args__ must be final for checking of match statements to work + __match_args__ = ("a", "b") a: str b: int @@ -889,8 +889,8 @@ m: object match m: case A(i, j): - reveal_type(i) # N: Revealed type is "Any" - reveal_type(j) # N: Revealed type is "Any" + reveal_type(i) # N: Revealed type is "builtins.str" + reveal_type(j) # N: Revealed type is "builtins.int" [builtins fixtures/tuple.pyi] [case testMatchAnyTupleMatchArgs] From afab611383b11dc3d3d8e8e8dc68c06d448ee16c Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 21 Mar 2022 16:49:42 +0000 Subject: [PATCH 4/4] Fix test case --- test-data/unit/check-python310.test | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index c65e167bc52f..10a3e56c6369 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1537,11 +1537,28 @@ class stmt(AST): ... class AnnAssign(stmt): __match_args__ = ('target', 'annotation', 'value', 'simple') + target: str + annotation: int + value: str + simple: int reveal_type(AST.__match_args__) # N: Revealed type is "Tuple[]" reveal_type(stmt.__match_args__) # N: Revealed type is "Tuple[]" -reveal_type(AnnAssign.__match_args__) # N: Revealed type is "Tuple[builtins.str, builtins.str, builtins.str, builtins.str]" +reveal_type(AnnAssign.__match_args__) # N: Revealed type is "Tuple[Literal['target']?, Literal['annotation']?, Literal['value']?, Literal['simple']?]" AnnAssign.__match_args__ = ('a', 'b', 'c', 'd') # E: Cannot assign to "__match_args__" __match_args__ = 0 + +def f(x: AST) -> None: + match x: + case AST(): + reveal_type(x) # N: Revealed type is "__main__.AST" + match x: + case stmt(): + reveal_type(x) # N: Revealed type is "__main__.stmt" + match x: + case AnnAssign(a, b, c, d): + reveal_type(a) # N: Revealed type is "builtins.str" + reveal_type(b) # N: Revealed type is "builtins.int" + reveal_type(c) # N: Revealed type is "builtins.str" [builtins fixtures/tuple.pyi]