1
- use ruff_python_ast:: { self as ast, Decorator , Expr } ;
2
-
3
1
use ruff_diagnostics:: { Diagnostic , Violation } ;
4
2
use ruff_macros:: { derive_message_formats, violation} ;
5
- use ruff_python_semantic:: SemanticModel ;
3
+ use ruff_python_ast:: helpers:: map_callable;
4
+ use ruff_python_ast:: { self as ast, Expr } ;
5
+ use ruff_python_semantic:: analyze:: { class, function_type} ;
6
+ use ruff_python_semantic:: { ScopeKind , SemanticModel } ;
6
7
use ruff_text_size:: Ranged ;
7
8
8
9
use crate :: checkers:: ast:: Checker ;
@@ -20,6 +21,9 @@ use crate::checkers::ast::Checker;
20
21
/// instance of the class, or use the `@lru_cache` decorator on a function
21
22
/// outside of the class.
22
23
///
24
+ /// This rule ignores instance methods on enumeration classes, as enum members
25
+ /// are singletons.
26
+ ///
23
27
/// ## Example
24
28
/// ```python
25
29
/// from functools import lru_cache
@@ -70,42 +74,50 @@ impl Violation for CachedInstanceMethod {
70
74
}
71
75
}
72
76
73
- fn is_cache_func ( expr : & Expr , semantic : & SemanticModel ) -> bool {
74
- semantic
75
- . resolve_qualified_name ( expr)
76
- . is_some_and ( |qualified_name| {
77
- matches ! (
78
- qualified_name. segments( ) ,
79
- [ "functools" , "lru_cache" | "cache" ]
80
- )
81
- } )
82
- }
83
-
84
77
/// B019
85
- pub ( crate ) fn cached_instance_method ( checker : & mut Checker , decorator_list : & [ Decorator ] ) {
86
- if !checker. semantic ( ) . current_scope ( ) . kind . is_class ( ) {
78
+ pub ( crate ) fn cached_instance_method ( checker : & mut Checker , function_def : & ast:: StmtFunctionDef ) {
79
+ let scope = checker. semantic ( ) . current_scope ( ) ;
80
+
81
+ // Parent scope _must_ be a class.
82
+ let ScopeKind :: Class ( class_def) = scope. kind else {
83
+ return ;
84
+ } ;
85
+
86
+ // The function must be an _instance_ method.
87
+ let type_ = function_type:: classify (
88
+ & function_def. name ,
89
+ & function_def. decorator_list ,
90
+ scope,
91
+ checker. semantic ( ) ,
92
+ & checker. settings . pep8_naming . classmethod_decorators ,
93
+ & checker. settings . pep8_naming . staticmethod_decorators ,
94
+ ) ;
95
+ if !matches ! ( type_, function_type:: FunctionType :: Method ) {
87
96
return ;
88
97
}
89
- for decorator in decorator_list {
90
- // TODO(charlie): This should take into account `classmethod-decorators` and
91
- // `staticmethod-decorators`.
92
- if let Expr :: Name ( ast :: ExprName { id , .. } ) = & decorator . expression {
93
- if id == "classmethod" || id == "staticmethod" {
98
+
99
+ for decorator in & function_def . decorator_list {
100
+ if is_cache_func ( map_callable ( & decorator . expression ) , checker . semantic ( ) ) {
101
+ // If we found a cached instance method, validate (lazily) that the class is not an enum.
102
+ if class :: is_enumeration ( class_def , checker . semantic ( ) ) {
94
103
return ;
95
104
}
96
- }
97
- }
98
- for decorator in decorator_list {
99
- if is_cache_func (
100
- match & decorator. expression {
101
- Expr :: Call ( ast:: ExprCall { func, .. } ) => func,
102
- _ => & decorator. expression ,
103
- } ,
104
- checker. semantic ( ) ,
105
- ) {
105
+
106
106
checker
107
107
. diagnostics
108
108
. push ( Diagnostic :: new ( CachedInstanceMethod , decorator. range ( ) ) ) ;
109
109
}
110
110
}
111
111
}
112
+
113
+ /// Returns `true` if the given expression is a call to `functools.lru_cache` or `functools.cache`.
114
+ fn is_cache_func ( expr : & Expr , semantic : & SemanticModel ) -> bool {
115
+ semantic
116
+ . resolve_qualified_name ( expr)
117
+ . is_some_and ( |qualified_name| {
118
+ matches ! (
119
+ qualified_name. segments( ) ,
120
+ [ "functools" , "lru_cache" | "cache" ]
121
+ )
122
+ } )
123
+ }
0 commit comments