11use 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} ;
33use clippy_utils:: sugg:: Sugg ;
44use clippy_utils:: ty:: is_type_diagnostic_item;
5+
56use 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 ;
612use rustc_errors:: Applicability ;
713use rustc_hir:: { BinOpKind , Block , Expr , ExprKind , PatKind , QPath , Stmt , StmtKind } ;
814use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
@@ -80,7 +86,17 @@ impl<'tcx> LateLintPass<'tcx> for Swap {
8086 }
8187}
8288
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+ ) {
84100 let ctxt = span. ctxt ( ) ;
85101 let mut applicability = Applicability :: MachineApplicable ;
86102
@@ -99,14 +115,25 @@ fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, spa
99115 || is_type_diagnostic_item ( cx, ty, sym:: VecDeque )
100116 {
101117 let slice = Sugg :: hir_with_applicability ( cx, lhs1, "<slice>" , & mut applicability) ;
118+
102119 span_lint_and_sugg (
103120 cx,
104121 MANUAL_SWAP ,
105122 span,
106123 format ! ( "this looks like you are swapping elements of `{slice}` manually" ) ,
107124 "try" ,
108125 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] ) ,
110137 slice. maybe_par( ) ,
111138 snippet_with_context( cx, idx1. span, ctxt, ".." , & mut applicability) . 0 ,
112139 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
142169}
143170
144171/// 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 > ) {
146173 if in_constant ( cx, block. hir_id ) {
147174 return ;
148175 }
@@ -160,10 +187,10 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
160187 // bar() = t;
161188 && let StmtKind :: Semi ( second) = s3. kind
162189 && 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
165192
166- && ident. name == rhs2 . segments [ 0 ] . ident . name
193+ && ident. name == rhs2_path . segments [ 0 ] . ident . name
167194 && eq_expr_value ( cx, tmp_init, lhs1)
168195 && eq_expr_value ( cx, rhs1, lhs2)
169196
@@ -174,7 +201,7 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
174201 && second. span . ctxt ( ) == ctxt
175202 {
176203 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 ) ;
178205 }
179206 }
180207}
@@ -254,7 +281,7 @@ fn parse<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(ExprOrIdent<'hir>, &'a Expr<
254281}
255282
256283/// 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 > ) {
258285 for [ s1, s2, s3] in block. stmts . array_windows :: < 3 > ( ) {
259286 let ctxt = s1. span . ctxt ( ) ;
260287 if let Some ( ( lhs0, rhs0) ) = extract_sides_of_xor_assign ( s1, ctxt)
@@ -268,7 +295,7 @@ fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) {
268295 && s3. span . ctxt ( ) == ctxt
269296 {
270297 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 ) ;
272299 } ;
273300 }
274301}
@@ -294,3 +321,130 @@ fn extract_sides_of_xor_assign<'a, 'hir>(
294321 None
295322 }
296323}
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