Skip to content

Commit 398a52a

Browse files
committed
Auto merge of rust-lang#12340 - not-elm:fix/issue-12334, r=llogiq
FIX(12334): manual_swap auto fix Fixed: rust-lang#12334 Initialization expressions are now generated as needed if the slice index is bound to a variable. ---- changelog: Fix [`manual_swap`]
2 parents e80ca2f + 0478d26 commit 398a52a

File tree

4 files changed

+381
-10
lines changed

4 files changed

+381
-10
lines changed

clippy_lints/src/swap.rs

+164-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
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};
33
use clippy_utils::sugg::Sugg;
44
use clippy_utils::ty::is_type_diagnostic_item;
5+
56
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;
612
use rustc_errors::Applicability;
713
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
814
use 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+
}

tests/ui/manual_swap_auto_fix.fixed

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#![warn(clippy::manual_swap)]
2+
#![no_main]
3+
4+
fn swap1() {
5+
let mut v = [3, 2, 1, 0];
6+
let index = v[0];
7+
v.swap(0, index);
8+
}
9+
10+
fn swap2() {
11+
let mut v = [3, 2, 1, 0];
12+
let tmp = v[0];
13+
v.swap(0, 1);
14+
// check not found in this scope.
15+
let _ = tmp;
16+
}
17+
18+
fn swap3() {
19+
let mut v = [3, 2];
20+
let i1 = 0;
21+
let i2 = 1;
22+
v.swap(i1, i2);
23+
}
24+
25+
fn swap4() {
26+
let mut v = [3, 2, 1];
27+
let i1 = 0;
28+
let i2 = 1;
29+
v.swap(i1, i2 + 1);
30+
}
31+
32+
fn swap5() {
33+
let mut v = [0, 1, 2, 3];
34+
let i1 = 0;
35+
let i2 = 1;
36+
v.swap(i1, i2 + 1);
37+
}
38+
39+
fn swap6() {
40+
let mut v = [0, 1, 2, 3];
41+
let index = v[0];
42+
v.swap(0, index + 1);
43+
}
44+
45+
fn swap7() {
46+
let mut v = [0, 1, 2, 3];
47+
let i1 = 0;
48+
let i2 = 6;
49+
v.swap(i1 * 3, i2 / 2);
50+
}
51+
52+
fn swap8() {
53+
let mut v = [1, 2, 3, 4];
54+
let i1 = 1;
55+
let i2 = 1;
56+
v.swap(i1 + i2, i2);
57+
}

tests/ui/manual_swap_auto_fix.rs

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#![warn(clippy::manual_swap)]
2+
#![no_main]
3+
4+
fn swap1() {
5+
let mut v = [3, 2, 1, 0];
6+
let index = v[0];
7+
//~^ ERROR: this looks like you are swapping elements of `v` manually
8+
v[0] = v[index];
9+
v[index] = index;
10+
}
11+
12+
fn swap2() {
13+
let mut v = [3, 2, 1, 0];
14+
let tmp = v[0];
15+
v[0] = v[1];
16+
v[1] = tmp;
17+
// check not found in this scope.
18+
let _ = tmp;
19+
}
20+
21+
fn swap3() {
22+
let mut v = [3, 2];
23+
let i1 = 0;
24+
let i2 = 1;
25+
let temp = v[i1];
26+
v[i1] = v[i2];
27+
v[i2] = temp;
28+
}
29+
30+
fn swap4() {
31+
let mut v = [3, 2, 1];
32+
let i1 = 0;
33+
let i2 = 1;
34+
let temp = v[i1];
35+
v[i1] = v[i2 + 1];
36+
v[i2 + 1] = temp;
37+
}
38+
39+
fn swap5() {
40+
let mut v = [0, 1, 2, 3];
41+
let i1 = 0;
42+
let i2 = 1;
43+
let temp = v[i1];
44+
v[i1] = v[i2 + 1];
45+
v[i2 + 1] = temp;
46+
}
47+
48+
fn swap6() {
49+
let mut v = [0, 1, 2, 3];
50+
let index = v[0];
51+
//~^ ERROR: this looks like you are swapping elements of `v` manually
52+
v[0] = v[index + 1];
53+
v[index + 1] = index;
54+
}
55+
56+
fn swap7() {
57+
let mut v = [0, 1, 2, 3];
58+
let i1 = 0;
59+
let i2 = 6;
60+
let tmp = v[i1 * 3];
61+
v[i1 * 3] = v[i2 / 2];
62+
v[i2 / 2] = tmp;
63+
}
64+
65+
fn swap8() {
66+
let mut v = [1, 2, 3, 4];
67+
let i1 = 1;
68+
let i2 = 1;
69+
let tmp = v[i1 + i2];
70+
v[i1 + i2] = v[i2];
71+
v[i2] = tmp;
72+
}

0 commit comments

Comments
 (0)