1
1
use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
2
- use clippy_utils:: source:: snippet_with_context;
2
+ use clippy_utils:: source:: { snippet_indent , snippet_with_context} ;
3
3
use clippy_utils:: sugg:: Sugg ;
4
4
use clippy_utils:: ty:: is_type_diagnostic_item;
5
+
5
6
use clippy_utils:: { can_mut_borrow_both, eq_expr_value, in_constant, std_or_core} ;
7
+ use itertools:: Itertools ;
8
+
9
+ use rustc_hir:: intravisit:: { walk_expr, Visitor } ;
10
+
11
+ use crate :: FxHashSet ;
6
12
use rustc_errors:: Applicability ;
7
13
use rustc_hir:: { BinOpKind , Block , Expr , ExprKind , PatKind , QPath , Stmt , StmtKind } ;
8
14
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
@@ -80,7 +86,17 @@ impl<'tcx> LateLintPass<'tcx> for Swap {
80
86
}
81
87
}
82
88
83
- fn generate_swap_warning ( cx : & LateContext < ' _ > , e1 : & Expr < ' _ > , e2 : & Expr < ' _ > , span : Span , is_xor_based : bool ) {
89
+ #[ allow( clippy:: too_many_arguments) ]
90
+ fn generate_swap_warning < ' tcx > (
91
+ block : & ' tcx Block < ' tcx > ,
92
+ cx : & LateContext < ' tcx > ,
93
+ e1 : & ' tcx Expr < ' tcx > ,
94
+ e2 : & ' tcx Expr < ' tcx > ,
95
+ rhs1 : & ' tcx Expr < ' tcx > ,
96
+ rhs2 : & ' tcx Expr < ' tcx > ,
97
+ span : Span ,
98
+ is_xor_based : bool ,
99
+ ) {
84
100
let ctxt = span. ctxt ( ) ;
85
101
let mut applicability = Applicability :: MachineApplicable ;
86
102
@@ -99,14 +115,25 @@ fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, spa
99
115
|| is_type_diagnostic_item ( cx, ty, sym:: VecDeque )
100
116
{
101
117
let slice = Sugg :: hir_with_applicability ( cx, lhs1, "<slice>" , & mut applicability) ;
118
+
102
119
span_lint_and_sugg (
103
120
cx,
104
121
MANUAL_SWAP ,
105
122
span,
106
123
format ! ( "this looks like you are swapping elements of `{slice}` manually" ) ,
107
124
"try" ,
108
125
format ! (
109
- "{}.swap({}, {});" ,
126
+ "{}{}.swap({}, {});" ,
127
+ IndexBinding {
128
+ block,
129
+ swap1_idx: idx1,
130
+ swap2_idx: idx2,
131
+ suggest_span: span,
132
+ cx,
133
+ ctxt,
134
+ applicability: & mut applicability,
135
+ }
136
+ . snippet_index_bindings( & [ idx1, idx2, rhs1, rhs2] ) ,
110
137
slice. maybe_par( ) ,
111
138
snippet_with_context( cx, idx1. span, ctxt, ".." , & mut applicability) . 0 ,
112
139
snippet_with_context( cx, idx2. span, ctxt, ".." , & mut applicability) . 0 ,
@@ -142,7 +169,7 @@ fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, spa
142
169
}
143
170
144
171
/// Implementation of the `MANUAL_SWAP` lint.
145
- fn check_manual_swap ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) {
172
+ fn check_manual_swap < ' tcx > ( cx : & LateContext < ' tcx > , block : & ' tcx Block < ' tcx > ) {
146
173
if in_constant ( cx, block. hir_id ) {
147
174
return ;
148
175
}
@@ -160,10 +187,10 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
160
187
// bar() = t;
161
188
&& let StmtKind :: Semi ( second) = s3. kind
162
189
&& let ExprKind :: Assign ( lhs2, rhs2, _) = second. kind
163
- && let ExprKind :: Path ( QPath :: Resolved ( None , rhs2 ) ) = rhs2. kind
164
- && rhs2 . segments . len ( ) == 1
190
+ && let ExprKind :: Path ( QPath :: Resolved ( None , rhs2_path ) ) = rhs2. kind
191
+ && rhs2_path . segments . len ( ) == 1
165
192
166
- && ident. name == rhs2 . segments [ 0 ] . ident . name
193
+ && ident. name == rhs2_path . segments [ 0 ] . ident . name
167
194
&& eq_expr_value ( cx, tmp_init, lhs1)
168
195
&& eq_expr_value ( cx, rhs1, lhs2)
169
196
@@ -174,7 +201,7 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
174
201
&& second. span . ctxt ( ) == ctxt
175
202
{
176
203
let span = s1. span . to ( s3. span ) ;
177
- generate_swap_warning ( cx, lhs1, lhs2, span, false ) ;
204
+ generate_swap_warning ( block , cx, lhs1, lhs2, rhs1 , rhs2 , span, false ) ;
178
205
}
179
206
}
180
207
}
@@ -254,7 +281,7 @@ fn parse<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(ExprOrIdent<'hir>, &'a Expr<
254
281
}
255
282
256
283
/// Implementation of the xor case for `MANUAL_SWAP` lint.
257
- fn check_xor_swap ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) {
284
+ fn check_xor_swap < ' tcx > ( cx : & LateContext < ' tcx > , block : & ' tcx Block < ' tcx > ) {
258
285
for [ s1, s2, s3] in block. stmts . array_windows :: < 3 > ( ) {
259
286
let ctxt = s1. span . ctxt ( ) ;
260
287
if let Some ( ( lhs0, rhs0) ) = extract_sides_of_xor_assign ( s1, ctxt)
@@ -268,7 +295,7 @@ fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) {
268
295
&& s3. span . ctxt ( ) == ctxt
269
296
{
270
297
let span = s1. span . to ( s3. span ) ;
271
- generate_swap_warning ( cx, lhs0, rhs0, span, true ) ;
298
+ generate_swap_warning ( block , cx, lhs0, rhs0, rhs1 , rhs2 , span, true ) ;
272
299
} ;
273
300
}
274
301
}
@@ -294,3 +321,130 @@ fn extract_sides_of_xor_assign<'a, 'hir>(
294
321
None
295
322
}
296
323
}
324
+
325
+ struct IndexBinding < ' a , ' tcx > {
326
+ block : & ' a Block < ' a > ,
327
+ swap1_idx : & ' a Expr < ' a > ,
328
+ swap2_idx : & ' a Expr < ' a > ,
329
+ suggest_span : Span ,
330
+ cx : & ' a LateContext < ' tcx > ,
331
+ ctxt : SyntaxContext ,
332
+ applicability : & ' a mut Applicability ,
333
+ }
334
+
335
+ impl < ' a , ' tcx > IndexBinding < ' a , ' tcx > {
336
+ fn snippet_index_bindings ( & mut self , exprs : & [ & ' tcx Expr < ' tcx > ] ) -> String {
337
+ let mut bindings = FxHashSet :: default ( ) ;
338
+ for expr in exprs {
339
+ bindings. insert ( self . snippet_index_binding ( expr) ) ;
340
+ }
341
+ bindings. into_iter ( ) . join ( "" )
342
+ }
343
+
344
+ fn snippet_index_binding ( & mut self , expr : & ' tcx Expr < ' tcx > ) -> String {
345
+ match expr. kind {
346
+ ExprKind :: Binary ( _, lhs, rhs) => {
347
+ if matches ! ( lhs. kind, ExprKind :: Lit ( _) ) && matches ! ( rhs. kind, ExprKind :: Lit ( _) ) {
348
+ return String :: new ( ) ;
349
+ }
350
+ let lhs_snippet = self . snippet_index_binding ( lhs) ;
351
+ let rhs_snippet = self . snippet_index_binding ( rhs) ;
352
+ format ! ( "{lhs_snippet}{rhs_snippet}" )
353
+ } ,
354
+ ExprKind :: Path ( QPath :: Resolved ( _, path) ) => {
355
+ let init = self . cx . expr_or_init ( expr) ;
356
+
357
+ let Some ( first_segment) = path. segments . first ( ) else {
358
+ return String :: new ( ) ;
359
+ } ;
360
+ if !self . suggest_span . contains ( init. span ) || !self . is_used_other_than_swapping ( first_segment. ident ) {
361
+ return String :: new ( ) ;
362
+ }
363
+
364
+ let init_str = snippet_with_context ( self . cx , init. span , self . ctxt , "" , self . applicability )
365
+ . 0
366
+ . to_string ( ) ;
367
+ let indent_str = snippet_indent ( self . cx , init. span ) ;
368
+ let indent_str = indent_str. as_deref ( ) . unwrap_or ( "" ) ;
369
+
370
+ format ! ( "let {} = {init_str};\n {indent_str}" , first_segment. ident)
371
+ } ,
372
+ _ => String :: new ( ) ,
373
+ }
374
+ }
375
+
376
+ fn is_used_other_than_swapping ( & mut self , idx_ident : Ident ) -> bool {
377
+ if Self :: is_used_slice_indexed ( self . swap1_idx , idx_ident)
378
+ || Self :: is_used_slice_indexed ( self . swap2_idx , idx_ident)
379
+ {
380
+ return true ;
381
+ }
382
+ self . is_used_after_swap ( idx_ident)
383
+ }
384
+
385
+ fn is_used_after_swap ( & mut self , idx_ident : Ident ) -> bool {
386
+ let mut v = IndexBindingVisitor {
387
+ found_used : false ,
388
+ suggest_span : self . suggest_span ,
389
+ idx : idx_ident,
390
+ } ;
391
+
392
+ for stmt in self . block . stmts {
393
+ match stmt. kind {
394
+ StmtKind :: Expr ( expr) | StmtKind :: Semi ( expr) => v. visit_expr ( expr) ,
395
+ StmtKind :: Let ( rustc_hir:: Local { ref init, .. } ) => {
396
+ if let Some ( init) = init. as_ref ( ) {
397
+ v. visit_expr ( init) ;
398
+ }
399
+ } ,
400
+ StmtKind :: Item ( _) => { } ,
401
+ }
402
+ }
403
+
404
+ v. found_used
405
+ }
406
+
407
+ fn is_used_slice_indexed ( swap_index : & Expr < ' _ > , idx_ident : Ident ) -> bool {
408
+ match swap_index. kind {
409
+ ExprKind :: Binary ( _, lhs, rhs) => {
410
+ if matches ! ( lhs. kind, ExprKind :: Lit ( _) ) && matches ! ( rhs. kind, ExprKind :: Lit ( _) ) {
411
+ return false ;
412
+ }
413
+ Self :: is_used_slice_indexed ( lhs, idx_ident) || Self :: is_used_slice_indexed ( rhs, idx_ident)
414
+ } ,
415
+ ExprKind :: Path ( QPath :: Resolved ( _, path) ) => {
416
+ path. segments . first ( ) . map_or ( false , |idx| idx. ident == idx_ident)
417
+ } ,
418
+ _ => false ,
419
+ }
420
+ }
421
+ }
422
+
423
+ struct IndexBindingVisitor {
424
+ idx : Ident ,
425
+ suggest_span : Span ,
426
+ found_used : bool ,
427
+ }
428
+
429
+ impl < ' tcx > Visitor < ' tcx > for IndexBindingVisitor {
430
+ fn visit_path_segment ( & mut self , path_segment : & ' tcx rustc_hir:: PathSegment < ' tcx > ) -> Self :: Result {
431
+ if path_segment. ident == self . idx {
432
+ self . found_used = true ;
433
+ }
434
+ }
435
+
436
+ fn visit_expr ( & mut self , expr : & ' tcx Expr < ' tcx > ) -> Self :: Result {
437
+ if expr. span . hi ( ) <= self . suggest_span . hi ( ) {
438
+ return ;
439
+ }
440
+
441
+ match expr. kind {
442
+ ExprKind :: Path ( QPath :: Resolved ( _, path) ) => {
443
+ for segment in path. segments {
444
+ self . visit_path_segment ( segment) ;
445
+ }
446
+ } ,
447
+ _ => walk_expr ( self , expr) ,
448
+ }
449
+ }
450
+ }
0 commit comments