1
- use crate :: checkers:: ast:: Checker ;
2
1
use ruff_diagnostics:: { AlwaysFixableViolation , Diagnostic , Edit , Fix } ;
3
2
use ruff_macros:: { derive_message_formats, violation} ;
4
3
use ruff_python_ast:: {
5
4
Expr , ExprCall , ExprName , ExprSlice , ExprSubscript , ExprUnaryOp , Int , StmtAssign , UnaryOp ,
6
5
} ;
7
6
use ruff_python_semantic:: analyze:: typing;
7
+ use ruff_python_semantic:: SemanticModel ;
8
+ use ruff_text_size:: Ranged ;
9
+
10
+ use crate :: checkers:: ast:: Checker ;
8
11
9
12
/// ## What it does
10
- /// Checks for uses of assignment of "reversed" expression on the list to the same binging.
13
+ /// Checks for list reversals that can be performed in-place in lieu of
14
+ /// creating a new list.
11
15
///
12
16
/// ## Why is this bad?
13
- ///
14
- /// Use of in-place method `.reverse()` is faster and allows to avoid copying the name of variable.
17
+ /// When reversing a list, it's more efficient to use the in-place method
18
+ /// `.reverse()` instead of creating a new list, if the original list is
19
+ /// no longer needed.
15
20
///
16
21
/// ## Example
17
22
/// ```python
18
23
/// l = [1, 2, 3]
19
24
/// l = reversed(l)
25
+ ///
26
+ /// l = [1, 2, 3]
20
27
/// l = list(reversed(l))
28
+ ///
29
+ /// l = [1, 2, 3]
21
30
/// l = l[::-1]
22
31
/// ```
23
32
///
24
33
/// Use instead:
25
34
/// ```python
26
35
/// l = [1, 2, 3]
27
36
/// l.reverse()
28
- /// l.reverse()
29
- /// l.reverse()
30
37
/// ```
31
38
///
32
39
/// ## References
@@ -39,52 +46,116 @@ pub struct ListAssignReversed {
39
46
impl AlwaysFixableViolation for ListAssignReversed {
40
47
#[ derive_message_formats]
41
48
fn message ( & self ) -> String {
42
- format ! ( "Use of assignment of `reversed` on list `{}`" , self . name)
49
+ let ListAssignReversed { name } = self ;
50
+ format ! ( "Use of assignment of `reversed` on list `{name}`" )
43
51
}
44
52
45
53
fn fix_title ( & self ) -> String {
46
- format ! ( "Use `{}.reverse()` instead" , self . name)
54
+ let ListAssignReversed { name } = self ;
55
+ format ! ( "Replace with `{name}.reverse()`" )
47
56
}
48
57
}
49
58
50
- fn extract_name_from_reversed ( expr : & Expr ) -> Option < & ExprName > {
51
- let ExprCall {
52
- func, arguments, ..
53
- } = expr. as_call_expr ( ) ?;
54
- if !arguments. keywords . is_empty ( ) {
55
- return None ;
59
+ /// FURB187
60
+ pub ( crate ) fn list_assign_reversed ( checker : & mut Checker , assign : & StmtAssign ) {
61
+ let [ Expr :: Name ( target_expr) ] = assign. targets . as_slice ( ) else {
62
+ return ;
63
+ } ;
64
+
65
+ let Some ( reversed_expr) = extract_reversed ( assign. value . as_ref ( ) , checker. semantic ( ) ) else {
66
+ return ;
67
+ } ;
68
+
69
+ if reversed_expr. id != target_expr. id {
70
+ return ;
56
71
}
57
- let [ arg] = arguments. args . as_ref ( ) else {
58
- return None ;
72
+
73
+ let Some ( binding) = checker
74
+ . semantic ( )
75
+ . only_binding ( reversed_expr)
76
+ . map ( |id| checker. semantic ( ) . binding ( id) )
77
+ else {
78
+ return ;
59
79
} ;
80
+ if !typing:: is_list ( binding, checker. semantic ( ) ) {
81
+ return ;
82
+ }
60
83
61
- func. as_name_expr ( )
62
- . is_some_and ( |name_expr| name_expr. id == "reversed" )
63
- . then ( || arg. as_name_expr ( ) )
64
- . flatten ( )
84
+ checker. diagnostics . push (
85
+ Diagnostic :: new (
86
+ ListAssignReversed {
87
+ name : target_expr. id . to_string ( ) ,
88
+ } ,
89
+ assign. range ( ) ,
90
+ )
91
+ . with_fix ( Fix :: safe_edit ( Edit :: range_replacement (
92
+ format ! ( "{}.reverse()" , target_expr. id) ,
93
+ assign. range ( ) ,
94
+ ) ) ) ,
95
+ ) ;
65
96
}
66
97
98
+ /// Recursively removes any `list` wrappers from the expression.
99
+ ///
100
+ /// For example, given `list(list(list([1, 2, 3])))`, this function
101
+ /// would return the inner `[1, 2, 3]` expression.
67
102
fn peel_lists ( expr : & Expr ) -> & Expr {
68
103
let Some ( ExprCall {
69
104
func, arguments, ..
70
105
} ) = expr. as_call_expr ( )
71
106
else {
72
107
return expr;
73
108
} ;
74
- if !arguments. keywords . is_empty ( )
75
- || func
76
- . as_name_expr ( )
77
- . map_or ( true , |expr_name| expr_name. id != "list" )
78
- {
109
+
110
+ if !arguments. keywords . is_empty ( ) {
111
+ return expr;
112
+ }
113
+
114
+ if !func. as_name_expr ( ) . is_some_and ( |name| name. id == "list" ) {
115
+ return expr;
116
+ }
117
+
118
+ let [ arg] = arguments. args . as_ref ( ) else {
79
119
return expr;
120
+ } ;
121
+
122
+ peel_lists ( arg)
123
+ }
124
+
125
+ /// Given a call to `reversed`, returns the inner argument.
126
+ ///
127
+ /// For example, given `reversed(l)`, this function would return `l`.
128
+ fn extract_name_from_reversed < ' a > (
129
+ expr : & ' a Expr ,
130
+ semantic : & SemanticModel ,
131
+ ) -> Option < & ' a ExprName > {
132
+ let ExprCall {
133
+ func, arguments, ..
134
+ } = expr. as_call_expr ( ) ?;
135
+ if !arguments. keywords . is_empty ( ) {
136
+ return None ;
80
137
}
81
- if let [ arg] = arguments. args . as_ref ( ) {
82
- peel_lists ( arg)
83
- } else {
84
- expr
138
+ let [ arg] = arguments. args . as_ref ( ) else {
139
+ return None ;
140
+ } ;
141
+ let Some ( arg) = func
142
+ . as_name_expr ( )
143
+ . is_some_and ( |name| name. id == "reversed" )
144
+ . then ( || arg. as_name_expr ( ) )
145
+ . flatten ( )
146
+ else {
147
+ return None ;
148
+ } ;
149
+ if !semantic. is_builtin ( "reversed" ) {
150
+ return None ;
85
151
}
152
+
153
+ Some ( arg)
86
154
}
87
155
156
+ /// Given a slice expression, returns the inner argument if it's a reversed slice.
157
+ ///
158
+ /// For example, given `l[::-1]`, this function would return `l`.
88
159
fn extract_name_from_sliced_reversed ( expr : & Expr ) -> Option < & ExprName > {
89
160
let ExprSubscript { value, slice, .. } = expr. as_subscript_expr ( ) ?;
90
161
let ExprSlice {
@@ -101,57 +172,18 @@ fn extract_name_from_sliced_reversed(expr: &Expr) -> Option<&ExprName> {
101
172
else {
102
173
return None ;
103
174
} ;
104
- if operand
175
+ if ! operand
105
176
. as_number_literal_expr ( )
106
177
. and_then ( |num| num. value . as_int ( ) )
107
178
. and_then ( Int :: as_u8)
108
- != Some ( 1 )
179
+ . is_some_and ( |value| value == 1 )
109
180
{
110
181
return None ;
111
182
} ;
112
183
value. as_name_expr ( )
113
184
}
114
185
115
- fn extract_name_from_general_reversed ( expr : & Expr ) -> Option < & ExprName > {
186
+ fn extract_reversed < ' a > ( expr : & ' a Expr , semantic : & SemanticModel ) -> Option < & ' a ExprName > {
116
187
let expr = peel_lists ( expr) ;
117
- extract_name_from_reversed ( expr) . or_else ( || extract_name_from_sliced_reversed ( expr) )
118
- }
119
-
120
- // FURB187
121
- pub ( crate ) fn list_assign_reversed ( checker : & mut Checker , assign : & StmtAssign ) {
122
- let [ Expr :: Name ( target_name_expr) ] = assign. targets . as_slice ( ) else {
123
- return ;
124
- } ;
125
-
126
- let Some ( arg_name_expr) = extract_name_from_general_reversed ( assign. value . as_ref ( ) ) else {
127
- return ;
128
- } ;
129
-
130
- if arg_name_expr. id != target_name_expr. id {
131
- return ;
132
- }
133
-
134
- let Some ( binding) = checker
135
- . semantic ( )
136
- . only_binding ( arg_name_expr)
137
- . map ( |id| checker. semantic ( ) . binding ( id) )
138
- else {
139
- return ;
140
- } ;
141
- if !typing:: is_list ( binding, checker. semantic ( ) ) {
142
- return ;
143
- }
144
-
145
- checker. diagnostics . push (
146
- Diagnostic :: new (
147
- ListAssignReversed {
148
- name : target_name_expr. id . to_string ( ) ,
149
- } ,
150
- assign. range ,
151
- )
152
- . with_fix ( Fix :: safe_edit ( Edit :: range_replacement (
153
- format ! ( "{}.reverse()" , target_name_expr. id) ,
154
- assign. range ,
155
- ) ) ) ,
156
- ) ;
188
+ extract_name_from_reversed ( expr, semantic) . or_else ( || extract_name_from_sliced_reversed ( expr) )
157
189
}
0 commit comments