1919 from pylint .lint import PyLinter
2020
2121
22+ # List of builtin classes which match self
23+ # https://docs.python.org/3/reference/compound_stmts.html#class-patterns
24+ MATCH_CLASS_SELF_NAMES = {
25+ "builtins.bool" ,
26+ "builtins.bytearray" ,
27+ "builtins.bytes" ,
28+ "builtins.dict" ,
29+ "builtins.float" ,
30+ "builtins.frozenset" ,
31+ "builtins.int" ,
32+ "builtins.list" ,
33+ "builtins.set" ,
34+ "builtins.str" ,
35+ "builtins.tuple" ,
36+ }
37+
38+
2239class MatchStatementChecker (BaseChecker ):
2340 name = "match_statements"
2441 msgs = {
@@ -46,6 +63,19 @@ class MatchStatementChecker(BaseChecker):
4663 "Emitted when there is more than one sub-pattern for a specific "
4764 "attribute in a class pattern." ,
4865 ),
66+ "R1905" : (
67+ "Use '%s' instead" ,
68+ "match-class-bind-self" ,
69+ "Match class patterns are faster if the name binding happens "
70+ "for the whole pattern and any lookup for `__match_args__` "
71+ "can be avoided." ,
72+ ),
73+ "R1906" : (
74+ "Use keyword attributes instead of positional ones" ,
75+ "match-class-positional-attributes" ,
76+ "Keyword attributes are more explicit and slightly faster "
77+ "since CPython can skip the `__match_args__` lookup." ,
78+ ),
4979 }
5080
5181 @only_required_for_messages ("invalid-match-args-definition" )
@@ -86,6 +116,26 @@ def visit_match(self, node: nodes.Match) -> None:
86116 confidence = HIGH ,
87117 )
88118
119+ @only_required_for_messages ("match-class-bind-self" )
120+ def visit_matchas (self , node : nodes .MatchAs ) -> None :
121+ match node :
122+ case nodes .MatchAs (
123+ parent = nodes .MatchClass (cls = nodes .Name () as cls_name ),
124+ name = nodes .AssignName (name = name ),
125+ pattern = None ,
126+ ):
127+ inferred = safe_infer (cls_name )
128+ if (
129+ isinstance (inferred , nodes .ClassDef )
130+ and inferred .qname () in MATCH_CLASS_SELF_NAMES
131+ ):
132+ self .add_message (
133+ "match-class-bind-self" ,
134+ node = node ,
135+ args = (f"{ cls_name .name } () as { name } " ,),
136+ confidence = HIGH ,
137+ )
138+
89139 @staticmethod
90140 def get_match_args_for_class (node : nodes .NodeNG ) -> list [str ] | None :
91141 """Infer __match_args__ from class name."""
@@ -95,6 +145,8 @@ def get_match_args_for_class(node: nodes.NodeNG) -> list[str] | None:
95145 try :
96146 match_args = inferred .getattr ("__match_args__" )
97147 except astroid .exceptions .NotFoundError :
148+ if inferred .qname () in MATCH_CLASS_SELF_NAMES :
149+ return ["<self>" ]
98150 return None
99151
100152 match match_args :
@@ -124,13 +176,27 @@ def check_duplicate_sub_patterns(
124176 attrs .add (name )
125177
126178 @only_required_for_messages (
179+ "match-class-positional-attributes" ,
127180 "multiple-class-sub-patterns" ,
128181 "too-many-positional-sub-patterns" ,
129182 )
130183 def visit_matchclass (self , node : nodes .MatchClass ) -> None :
131184 attrs : set [str ] = set ()
132185 dups : set [str ] = set ()
133186
187+ if node .patterns :
188+ if isinstance (node , nodes .MatchClass ) and isinstance (node .cls , nodes .Name ):
189+ inferred = safe_infer (node .cls )
190+ if not (
191+ isinstance (inferred , nodes .ClassDef )
192+ and inferred .qname () in MATCH_CLASS_SELF_NAMES
193+ ):
194+ self .add_message (
195+ "match-class-positional-attributes" ,
196+ node = node ,
197+ confidence = HIGH ,
198+ )
199+
134200 if (
135201 node .patterns
136202 and (match_args := self .get_match_args_for_class (node .cls )) is not None
0 commit comments