Skip to content

Commit c66cca4

Browse files
committed
Auto merge of rust-lang#4877 - flip1995:manual_swap_4853, r=llogiq
Fix FP in manual_swap lint with slice-like types Fixes rust-lang#4853 changelog: Fix FP in [`manual_swap`] lint with slice-like types and make it auto applicable
2 parents f0fc18a + aa2381d commit c66cca4

File tree

4 files changed

+196
-58
lines changed

4 files changed

+196
-58
lines changed

clippy_lints/src/swap.rs

+94-51
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::utils::sugg::Sugg;
22
use crate::utils::{
3-
differing_macro_contexts, is_type_diagnostic_item, match_type, paths, snippet, span_lint_and_then, walk_ptrs_ty,
4-
SpanlessEq,
3+
differing_macro_contexts, is_type_diagnostic_item, match_type, paths, snippet_with_applicability,
4+
span_lint_and_then, walk_ptrs_ty, SpanlessEq,
55
};
66
use if_chain::if_chain;
77
use matches::matches;
@@ -97,29 +97,6 @@ fn check_manual_swap(cx: &LateContext<'_, '_>, block: &Block) {
9797
if SpanlessEq::new(cx).ignore_fn().eq_expr(tmp_init, lhs1);
9898
if SpanlessEq::new(cx).ignore_fn().eq_expr(rhs1, lhs2);
9999
then {
100-
fn check_for_slice<'a>(
101-
cx: &LateContext<'_, '_>,
102-
lhs1: &'a Expr,
103-
lhs2: &'a Expr,
104-
) -> Option<(&'a Expr, &'a Expr, &'a Expr)> {
105-
if let ExprKind::Index(ref lhs1, ref idx1) = lhs1.kind {
106-
if let ExprKind::Index(ref lhs2, ref idx2) = lhs2.kind {
107-
if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs1, lhs2) {
108-
let ty = walk_ptrs_ty(cx.tables.expr_ty(lhs1));
109-
110-
if matches!(ty.kind, ty::Slice(_)) ||
111-
matches!(ty.kind, ty::Array(_, _)) ||
112-
is_type_diagnostic_item(cx, ty, Symbol::intern("vec_type")) ||
113-
match_type(cx, ty, &paths::VEC_DEQUE) {
114-
return Some((lhs1, idx1, idx2));
115-
}
116-
}
117-
}
118-
}
119-
120-
None
121-
}
122-
123100
if let ExprKind::Field(ref lhs1, _) = lhs1.kind {
124101
if let ExprKind::Field(ref lhs2, _) = lhs2.kind {
125102
if lhs1.hir_id.owner_def_id() == lhs2.hir_id.owner_def_id() {
@@ -128,47 +105,113 @@ fn check_manual_swap(cx: &LateContext<'_, '_>, block: &Block) {
128105
}
129106
}
130107

131-
let (replace, what, sugg) = if let Some((slice, idx1, idx2)) = check_for_slice(cx, lhs1, lhs2) {
108+
let mut applicability = Applicability::MachineApplicable;
109+
110+
let slice = check_for_slice(cx, lhs1, lhs2);
111+
let (replace, what, sugg) = if let Slice::NotSwappable = slice {
112+
return;
113+
} else if let Slice::Swappable(slice, idx1, idx2) = slice {
132114
if let Some(slice) = Sugg::hir_opt(cx, slice) {
133-
(false,
134-
format!(" elements of `{}`", slice),
135-
format!("{}.swap({}, {})",
136-
slice.maybe_par(),
137-
snippet(cx, idx1.span, ".."),
138-
snippet(cx, idx2.span, "..")))
115+
(
116+
false,
117+
format!(" elements of `{}`", slice),
118+
format!(
119+
"{}.swap({}, {})",
120+
slice.maybe_par(),
121+
snippet_with_applicability(cx, idx1.span, "..", &mut applicability),
122+
snippet_with_applicability(cx, idx2.span, "..", &mut applicability),
123+
),
124+
)
139125
} else {
140126
(false, String::new(), String::new())
141127
}
142128
} else if let (Some(first), Some(second)) = (Sugg::hir_opt(cx, lhs1), Sugg::hir_opt(cx, rhs1)) {
143-
(true, format!(" `{}` and `{}`", first, second),
144-
format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()))
129+
(
130+
true,
131+
format!(" `{}` and `{}`", first, second),
132+
format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()),
133+
)
145134
} else {
146135
(true, String::new(), String::new())
147136
};
148137

149138
let span = w[0].span.to(second.span);
150139

151-
span_lint_and_then(cx,
152-
MANUAL_SWAP,
153-
span,
154-
&format!("this looks like you are swapping{} manually", what),
155-
|db| {
156-
if !sugg.is_empty() {
157-
db.span_suggestion(
158-
span,
159-
"try",
160-
sugg,
161-
Applicability::Unspecified,
162-
);
140+
span_lint_and_then(
141+
cx,
142+
MANUAL_SWAP,
143+
span,
144+
&format!("this looks like you are swapping{} manually", what),
145+
|db| {
146+
if !sugg.is_empty() {
147+
db.span_suggestion(
148+
span,
149+
"try",
150+
sugg,
151+
applicability,
152+
);
163153

164-
if replace {
165-
db.note("or maybe you should use `std::mem::replace`?");
166-
}
167-
}
168-
});
154+
if replace {
155+
db.note("or maybe you should use `std::mem::replace`?");
156+
}
157+
}
158+
}
159+
);
160+
}
161+
}
162+
}
163+
}
164+
165+
enum Slice<'a> {
166+
/// `slice.swap(idx1, idx2)` can be used
167+
///
168+
/// ## Example
169+
///
170+
/// ```rust
171+
/// # let mut a = vec![0, 1];
172+
/// let t = a[1];
173+
/// a[1] = a[0];
174+
/// a[0] = t;
175+
/// // can be written as
176+
/// a.swap(0, 1);
177+
/// ```
178+
Swappable(&'a Expr, &'a Expr, &'a Expr),
179+
/// The `swap` function cannot be used.
180+
///
181+
/// ## Example
182+
///
183+
/// ```rust
184+
/// # let mut a = [vec![1, 2], vec![3, 4]];
185+
/// let t = a[0][1];
186+
/// a[0][1] = a[1][0];
187+
/// a[1][0] = t;
188+
/// ```
189+
NotSwappable,
190+
/// Not a slice
191+
None,
192+
}
193+
194+
/// Checks if both expressions are index operations into "slice-like" types.
195+
fn check_for_slice<'a>(cx: &LateContext<'_, '_>, lhs1: &'a Expr, lhs2: &'a Expr) -> Slice<'a> {
196+
if let ExprKind::Index(ref lhs1, ref idx1) = lhs1.kind {
197+
if let ExprKind::Index(ref lhs2, ref idx2) = lhs2.kind {
198+
if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs1, lhs2) {
199+
let ty = walk_ptrs_ty(cx.tables.expr_ty(lhs1));
200+
201+
if matches!(ty.kind, ty::Slice(_))
202+
|| matches!(ty.kind, ty::Array(_, _))
203+
|| is_type_diagnostic_item(cx, ty, Symbol::intern("vec_type"))
204+
|| match_type(cx, ty, &paths::VEC_DEQUE)
205+
{
206+
return Slice::Swappable(lhs1, idx1, idx2);
207+
}
208+
} else {
209+
return Slice::NotSwappable;
169210
}
170211
}
171212
}
213+
214+
Slice::None
172215
}
173216

174217
/// Implementation of the `ALMOST_SWAPPED` lint.

tests/ui/swap.fixed

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::all)]
4+
#![allow(
5+
clippy::blacklisted_name,
6+
clippy::no_effect,
7+
clippy::redundant_clone,
8+
redundant_semicolon,
9+
unused_assignments
10+
)]
11+
12+
struct Foo(u32);
13+
14+
#[derive(Clone)]
15+
struct Bar {
16+
a: u32,
17+
b: u32,
18+
}
19+
20+
fn field() {
21+
let mut bar = Bar { a: 1, b: 2 };
22+
23+
let temp = bar.a;
24+
bar.a = bar.b;
25+
bar.b = temp;
26+
27+
let mut baz = vec![bar.clone(), bar.clone()];
28+
let temp = baz[0].a;
29+
baz[0].a = baz[1].a;
30+
baz[1].a = temp;
31+
}
32+
33+
fn array() {
34+
let mut foo = [1, 2];
35+
foo.swap(0, 1);
36+
37+
foo.swap(0, 1);
38+
}
39+
40+
fn slice() {
41+
let foo = &mut [1, 2];
42+
foo.swap(0, 1);
43+
44+
foo.swap(0, 1);
45+
}
46+
47+
fn unswappable_slice() {
48+
let foo = &mut [vec![1, 2], vec![3, 4]];
49+
let temp = foo[0][1];
50+
foo[0][1] = foo[1][0];
51+
foo[1][0] = temp;
52+
53+
// swap(foo[0][1], foo[1][0]) would fail
54+
}
55+
56+
fn vec() {
57+
let mut foo = vec![1, 2];
58+
foo.swap(0, 1);
59+
60+
foo.swap(0, 1);
61+
}
62+
63+
#[rustfmt::skip]
64+
fn main() {
65+
field();
66+
array();
67+
slice();
68+
unswappable_slice();
69+
vec();
70+
71+
let mut a = 42;
72+
let mut b = 1337;
73+
74+
std::mem::swap(&mut a, &mut b);
75+
76+
; std::mem::swap(&mut a, &mut b);
77+
78+
let mut c = Foo(42);
79+
80+
std::mem::swap(&mut c.0, &mut a);
81+
82+
; std::mem::swap(&mut c.0, &mut a);
83+
}

tests/ui/swap.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// run-rustfix
2+
13
#![warn(clippy::all)]
24
#![allow(
35
clippy::blacklisted_name,
@@ -46,6 +48,15 @@ fn slice() {
4648
foo.swap(0, 1);
4749
}
4850

51+
fn unswappable_slice() {
52+
let foo = &mut [vec![1, 2], vec![3, 4]];
53+
let temp = foo[0][1];
54+
foo[0][1] = foo[1][0];
55+
foo[1][0] = temp;
56+
57+
// swap(foo[0][1], foo[1][0]) would fail
58+
}
59+
4960
fn vec() {
5061
let mut foo = vec![1, 2];
5162
let temp = foo[0];
@@ -60,6 +71,7 @@ fn main() {
6071
field();
6172
array();
6273
slice();
74+
unswappable_slice();
6375
vec();
6476

6577
let mut a = 42;

tests/ui/swap.stderr

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: this looks like you are swapping elements of `foo` manually
2-
--> $DIR/swap.rs:33:5
2+
--> $DIR/swap.rs:35:5
33
|
44
LL | / let temp = foo[0];
55
LL | | foo[0] = foo[1];
@@ -9,23 +9,23 @@ LL | | foo[1] = temp;
99
= note: `-D clippy::manual-swap` implied by `-D warnings`
1010

1111
error: this looks like you are swapping elements of `foo` manually
12-
--> $DIR/swap.rs:42:5
12+
--> $DIR/swap.rs:44:5
1313
|
1414
LL | / let temp = foo[0];
1515
LL | | foo[0] = foo[1];
1616
LL | | foo[1] = temp;
1717
| |_________________^ help: try: `foo.swap(0, 1)`
1818

1919
error: this looks like you are swapping elements of `foo` manually
20-
--> $DIR/swap.rs:51:5
20+
--> $DIR/swap.rs:62:5
2121
|
2222
LL | / let temp = foo[0];
2323
LL | | foo[0] = foo[1];
2424
LL | | foo[1] = temp;
2525
| |_________________^ help: try: `foo.swap(0, 1)`
2626

2727
error: this looks like you are swapping `a` and `b` manually
28-
--> $DIR/swap.rs:71:7
28+
--> $DIR/swap.rs:83:7
2929
|
3030
LL | ; let t = a;
3131
| _______^
@@ -36,7 +36,7 @@ LL | | b = t;
3636
= note: or maybe you should use `std::mem::replace`?
3737

3838
error: this looks like you are swapping `c.0` and `a` manually
39-
--> $DIR/swap.rs:80:7
39+
--> $DIR/swap.rs:92:7
4040
|
4141
LL | ; let t = c.0;
4242
| _______^
@@ -47,7 +47,7 @@ LL | | a = t;
4747
= note: or maybe you should use `std::mem::replace`?
4848

4949
error: this looks like you are trying to swap `a` and `b`
50-
--> $DIR/swap.rs:68:5
50+
--> $DIR/swap.rs:80:5
5151
|
5252
LL | / a = b;
5353
LL | | b = a;
@@ -57,7 +57,7 @@ LL | | b = a;
5757
= note: or maybe you should use `std::mem::replace`?
5858

5959
error: this looks like you are trying to swap `c.0` and `a`
60-
--> $DIR/swap.rs:77:5
60+
--> $DIR/swap.rs:89:5
6161
|
6262
LL | / c.0 = a;
6363
LL | | a = c.0;

0 commit comments

Comments
 (0)