Skip to content

Commit 6606dbe

Browse files
committed
Allow non-final __match_args__ and overriding (#12415)
Allow subclasses to override `__match_args__` freely, and don't require `__match_args__` to be final. This matches runtime behavior. For example, if `B` subclasses `A`, `case A(...)` also matches instances of `B`, using the `__match_args__` from `A`. The issue was brough up by @AlexWaygood in #12411 (comment).
1 parent 626147a commit 6606dbe

File tree

3 files changed

+47
-11
lines changed

3 files changed

+47
-11
lines changed

mypy/checker.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -1439,12 +1439,7 @@ def check_setattr_method(self, typ: Type, context: Context) -> None:
14391439
self.msg.invalid_signature_for_special_method(typ, context, '__setattr__')
14401440

14411441
def check_match_args(self, var: Var, typ: Type, context: Context) -> None:
1442-
"""Check that __match_args__ is final and contains literal strings"""
1443-
1444-
if not var.is_final:
1445-
self.note("__match_args__ must be final for checking of match statements to work",
1446-
context, code=codes.LITERAL_REQ)
1447-
1442+
"""Check that __match_args__ contains literal strings"""
14481443
typ = get_proper_type(typ)
14491444
if not isinstance(typ, TupleType) or \
14501445
not all([is_string_literal(item) for item in typ.items]):
@@ -2276,11 +2271,16 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
22762271

22772272
# Defer PartialType's super type checking.
22782273
if (isinstance(lvalue, RefExpr) and
2279-
not (isinstance(lvalue_type, PartialType) and lvalue_type.type is None)):
2274+
not (isinstance(lvalue_type, PartialType) and
2275+
lvalue_type.type is None) and
2276+
not (isinstance(lvalue, NameExpr) and lvalue.name == '__match_args__')):
22802277
if self.check_compatibility_all_supers(lvalue, lvalue_type, rvalue):
22812278
# We hit an error on this line; don't check for any others
22822279
return
22832280

2281+
if isinstance(lvalue, MemberExpr) and lvalue.name == '__match_args__':
2282+
self.fail(message_registry.CANNOT_MODIFY_MATCH_ARGS, lvalue)
2283+
22842284
if lvalue_type:
22852285
if isinstance(lvalue_type, PartialType) and lvalue_type.type is None:
22862286
# Try to infer a proper type for a variable with a partial None type.
@@ -2377,7 +2377,8 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
23772377

23782378
if inferred:
23792379
rvalue_type = self.expr_checker.accept(rvalue)
2380-
if not inferred.is_final:
2380+
if not (inferred.is_final or (isinstance(lvalue, NameExpr) and
2381+
lvalue.name == '__match_args__')):
23812382
rvalue_type = remove_instance_last_known_values(rvalue_type)
23822383
self.infer_variable_type(inferred, lvalue, rvalue_type, rvalue)
23832384
self.check_assignment_to_slots(lvalue)

mypy/message_registry.py

+1
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,4 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage":
245245
CLASS_PATTERN_DUPLICATE_KEYWORD_PATTERN: Final = 'Duplicate keyword pattern "{}"'
246246
CLASS_PATTERN_UNKNOWN_KEYWORD: Final = 'Class "{}" has no attribute "{}"'
247247
MULTIPLE_ASSIGNMENTS_IN_PATTERN: Final = 'Multiple assignments to name "{}" in pattern'
248+
CANNOT_MODIFY_MATCH_ARGS: Final = 'Cannot assign to "__match_args__"'

test-data/unit/check-python310.test

+37-3
Original file line numberDiff line numberDiff line change
@@ -881,16 +881,16 @@ reveal_type(z) # N: Revealed type is "builtins.int*"
881881

882882
[case testMatchNonFinalMatchArgs]
883883
class A:
884-
__match_args__ = ("a", "b") # N: __match_args__ must be final for checking of match statements to work
884+
__match_args__ = ("a", "b")
885885
a: str
886886
b: int
887887

888888
m: object
889889

890890
match m:
891891
case A(i, j):
892-
reveal_type(i) # N: Revealed type is "Any"
893-
reveal_type(j) # N: Revealed type is "Any"
892+
reveal_type(i) # N: Revealed type is "builtins.str"
893+
reveal_type(j) # N: Revealed type is "builtins.int"
894894
[builtins fixtures/tuple.pyi]
895895

896896
[case testMatchAnyTupleMatchArgs]
@@ -1528,3 +1528,37 @@ class A:
15281528
class B:
15291529
def __enter__(self) -> B: ...
15301530
def __exit__(self, x, y, z) -> None: ...
1531+
1532+
[case testOverrideMatchArgs]
1533+
class AST:
1534+
__match_args__ = ()
1535+
1536+
class stmt(AST): ...
1537+
1538+
class AnnAssign(stmt):
1539+
__match_args__ = ('target', 'annotation', 'value', 'simple')
1540+
target: str
1541+
annotation: int
1542+
value: str
1543+
simple: int
1544+
1545+
reveal_type(AST.__match_args__) # N: Revealed type is "Tuple[]"
1546+
reveal_type(stmt.__match_args__) # N: Revealed type is "Tuple[]"
1547+
reveal_type(AnnAssign.__match_args__) # N: Revealed type is "Tuple[Literal['target']?, Literal['annotation']?, Literal['value']?, Literal['simple']?]"
1548+
1549+
AnnAssign.__match_args__ = ('a', 'b', 'c', 'd') # E: Cannot assign to "__match_args__"
1550+
__match_args__ = 0
1551+
1552+
def f(x: AST) -> None:
1553+
match x:
1554+
case AST():
1555+
reveal_type(x) # N: Revealed type is "__main__.AST"
1556+
match x:
1557+
case stmt():
1558+
reveal_type(x) # N: Revealed type is "__main__.stmt"
1559+
match x:
1560+
case AnnAssign(a, b, c, d):
1561+
reveal_type(a) # N: Revealed type is "builtins.str"
1562+
reveal_type(b) # N: Revealed type is "builtins.int"
1563+
reveal_type(c) # N: Revealed type is "builtins.str"
1564+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)