Skip to content

Commit 0084195

Browse files
committed
Auto merge of #7506 - HKalbasi:add-xor-swap, r=camsteffen
Add xor case to `manual swap` lint Continue of #7153 closes #6598 changelog: Add "xor swap" case to [`manual_swap`]
2 parents 176df7c + 6d36d1a commit 0084195

File tree

5 files changed

+307
-138
lines changed

5 files changed

+307
-138
lines changed

clippy_lints/src/swap.rs

Lines changed: 101 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
use clippy_utils::diagnostics::span_lint_and_then;
1+
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
22
use clippy_utils::source::snippet_with_applicability;
33
use clippy_utils::sugg::Sugg;
44
use clippy_utils::ty::is_type_diagnostic_item;
5-
use clippy_utils::{differing_macro_contexts, eq_expr_value};
5+
use clippy_utils::{can_mut_borrow_both, differing_macro_contexts, eq_expr_value};
66
use if_chain::if_chain;
77
use rustc_errors::Applicability;
8-
use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, StmtKind};
8+
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
99
use rustc_lint::{LateContext, LateLintPass};
1010
use rustc_middle::ty;
1111
use rustc_session::{declare_lint_pass, declare_tool_lint};
12-
use rustc_span::sym;
12+
use rustc_span::source_map::Spanned;
13+
use rustc_span::{sym, Span};
1314

1415
declare_clippy_lint! {
1516
/// ### What it does
@@ -70,9 +71,67 @@ impl<'tcx> LateLintPass<'tcx> for Swap {
7071
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
7172
check_manual_swap(cx, block);
7273
check_suspicious_swap(cx, block);
74+
check_xor_swap(cx, block);
7375
}
7476
}
7577

78+
fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, span: Span, is_xor_based: bool) {
79+
let mut applicability = Applicability::MachineApplicable;
80+
81+
if !can_mut_borrow_both(cx, e1, e2) {
82+
if let ExprKind::Index(lhs1, idx1) = e1.kind {
83+
if let ExprKind::Index(lhs2, idx2) = e2.kind {
84+
if eq_expr_value(cx, lhs1, lhs2) {
85+
let ty = cx.typeck_results().expr_ty(lhs1).peel_refs();
86+
87+
if matches!(ty.kind(), ty::Slice(_))
88+
|| matches!(ty.kind(), ty::Array(_, _))
89+
|| is_type_diagnostic_item(cx, ty, sym::vec_type)
90+
|| is_type_diagnostic_item(cx, ty, sym::vecdeque_type)
91+
{
92+
let slice = Sugg::hir_with_applicability(cx, lhs1, "<slice>", &mut applicability);
93+
span_lint_and_sugg(
94+
cx,
95+
MANUAL_SWAP,
96+
span,
97+
&format!("this looks like you are swapping elements of `{}` manually", slice),
98+
"try",
99+
format!(
100+
"{}.swap({}, {})",
101+
slice.maybe_par(),
102+
snippet_with_applicability(cx, idx1.span, "..", &mut applicability),
103+
snippet_with_applicability(cx, idx2.span, "..", &mut applicability),
104+
),
105+
applicability,
106+
);
107+
}
108+
}
109+
}
110+
}
111+
return;
112+
}
113+
114+
let first = Sugg::hir_with_applicability(cx, e1, "..", &mut applicability);
115+
let second = Sugg::hir_with_applicability(cx, e2, "..", &mut applicability);
116+
span_lint_and_then(
117+
cx,
118+
MANUAL_SWAP,
119+
span,
120+
&format!("this looks like you are swapping `{}` and `{}` manually", first, second),
121+
|diag| {
122+
diag.span_suggestion(
123+
span,
124+
"try",
125+
format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()),
126+
applicability,
127+
);
128+
if !is_xor_based {
129+
diag.note("or maybe you should use `std::mem::replace`?");
130+
}
131+
},
132+
);
133+
}
134+
76135
/// Implementation of the `MANUAL_SWAP` lint.
77136
fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
78137
for w in block.stmts.windows(3) {
@@ -96,121 +155,11 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
96155
if eq_expr_value(cx, tmp_init, lhs1);
97156
if eq_expr_value(cx, rhs1, lhs2);
98157
then {
99-
if let ExprKind::Field(lhs1, _) = lhs1.kind {
100-
if let ExprKind::Field(lhs2, _) = lhs2.kind {
101-
if lhs1.hir_id.owner == lhs2.hir_id.owner {
102-
return;
103-
}
104-
}
105-
}
106-
107-
let mut applicability = Applicability::MachineApplicable;
108-
109-
let slice = check_for_slice(cx, lhs1, lhs2);
110-
let (replace, what, sugg) = if let Slice::NotSwappable = slice {
111-
return;
112-
} else if let Slice::Swappable(slice, idx1, idx2) = slice {
113-
if let Some(slice) = Sugg::hir_opt(cx, slice) {
114-
(
115-
false,
116-
format!(" elements of `{}`", slice),
117-
format!(
118-
"{}.swap({}, {})",
119-
slice.maybe_par(),
120-
snippet_with_applicability(cx, idx1.span, "..", &mut applicability),
121-
snippet_with_applicability(cx, idx2.span, "..", &mut applicability),
122-
),
123-
)
124-
} else {
125-
(false, String::new(), String::new())
126-
}
127-
} else if let (Some(first), Some(second)) = (Sugg::hir_opt(cx, lhs1), Sugg::hir_opt(cx, rhs1)) {
128-
(
129-
true,
130-
format!(" `{}` and `{}`", first, second),
131-
format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()),
132-
)
133-
} else {
134-
(true, String::new(), String::new())
135-
};
136-
137158
let span = w[0].span.to(second.span);
138-
139-
span_lint_and_then(
140-
cx,
141-
MANUAL_SWAP,
142-
span,
143-
&format!("this looks like you are swapping{} manually", what),
144-
|diag| {
145-
if !sugg.is_empty() {
146-
diag.span_suggestion(
147-
span,
148-
"try",
149-
sugg,
150-
applicability,
151-
);
152-
153-
if replace {
154-
diag.note("or maybe you should use `std::mem::replace`?");
155-
}
156-
}
157-
}
158-
);
159-
}
160-
}
161-
}
162-
}
163-
164-
enum Slice<'a> {
165-
/// `slice.swap(idx1, idx2)` can be used
166-
///
167-
/// ## Example
168-
///
169-
/// ```rust
170-
/// # let mut a = vec![0, 1];
171-
/// let t = a[1];
172-
/// a[1] = a[0];
173-
/// a[0] = t;
174-
/// // can be written as
175-
/// a.swap(0, 1);
176-
/// ```
177-
Swappable(&'a Expr<'a>, &'a Expr<'a>, &'a Expr<'a>),
178-
/// The `swap` function cannot be used.
179-
///
180-
/// ## Example
181-
///
182-
/// ```rust
183-
/// # let mut a = [vec![1, 2], vec![3, 4]];
184-
/// let t = a[0][1];
185-
/// a[0][1] = a[1][0];
186-
/// a[1][0] = t;
187-
/// ```
188-
NotSwappable,
189-
/// Not a slice
190-
None,
191-
}
192-
193-
/// Checks if both expressions are index operations into "slice-like" types.
194-
fn check_for_slice<'a>(cx: &LateContext<'_>, lhs1: &'a Expr<'_>, lhs2: &'a Expr<'_>) -> Slice<'a> {
195-
if let ExprKind::Index(lhs1, idx1) = lhs1.kind {
196-
if let ExprKind::Index(lhs2, idx2) = lhs2.kind {
197-
if eq_expr_value(cx, lhs1, lhs2) {
198-
let ty = cx.typeck_results().expr_ty(lhs1).peel_refs();
199-
200-
if matches!(ty.kind(), ty::Slice(_))
201-
|| matches!(ty.kind(), ty::Array(_, _))
202-
|| is_type_diagnostic_item(cx, ty, sym::vec_type)
203-
|| is_type_diagnostic_item(cx, ty, sym::vecdeque_type)
204-
{
205-
return Slice::Swappable(lhs1, idx1, idx2);
206-
}
207-
} else {
208-
return Slice::NotSwappable;
159+
generate_swap_warning(cx, lhs1, lhs2, span, false);
209160
}
210161
}
211162
}
212-
213-
Slice::None
214163
}
215164

216165
/// Implementation of the `ALMOST_SWAPPED` lint.
@@ -262,3 +211,40 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
262211
}
263212
}
264213
}
214+
215+
/// Implementation of the xor case for `MANUAL_SWAP` lint.
216+
fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) {
217+
for window in block.stmts.windows(3) {
218+
if_chain! {
219+
if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(&window[0]);
220+
if let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(&window[1]);
221+
if let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(&window[2]);
222+
if eq_expr_value(cx, lhs0, rhs1);
223+
if eq_expr_value(cx, lhs2, rhs1);
224+
if eq_expr_value(cx, lhs1, rhs0);
225+
if eq_expr_value(cx, lhs1, rhs2);
226+
then {
227+
let span = window[0].span.to(window[2].span);
228+
generate_swap_warning(cx, lhs0, rhs0, span, true);
229+
}
230+
};
231+
}
232+
}
233+
234+
/// Returns the lhs and rhs of an xor assignment statement.
235+
fn extract_sides_of_xor_assign<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(&'a Expr<'hir>, &'a Expr<'hir>)> {
236+
if let StmtKind::Semi(expr) = stmt.kind {
237+
if let ExprKind::AssignOp(
238+
Spanned {
239+
node: BinOpKind::BitXor,
240+
..
241+
},
242+
lhs,
243+
rhs,
244+
) = expr.kind
245+
{
246+
return Some((lhs, rhs));
247+
}
248+
}
249+
None
250+
}

clippy_utils/src/lib.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,54 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
558558
None
559559
}
560560

561+
/// This method will return tuple of projection stack and root of the expression,
562+
/// used in `can_mut_borrow_both`.
563+
///
564+
/// For example, if `e` represents the `v[0].a.b[x]`
565+
/// this method will return a tuple, composed of a `Vec`
566+
/// containing the `Expr`s for `v[0], v[0].a, v[0].a.b, v[0].a.b[x]`
567+
/// and a `Expr` for root of them, `v`
568+
fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'a Expr<'hir>) {
569+
let mut result = vec![];
570+
let root = loop {
571+
match e.kind {
572+
ExprKind::Index(ep, _) | ExprKind::Field(ep, _) => {
573+
result.push(e);
574+
e = ep;
575+
},
576+
_ => break e,
577+
};
578+
};
579+
result.reverse();
580+
(result, root)
581+
}
582+
583+
/// Checks if two expressions can be mutably borrowed simultaneously
584+
/// and they aren't dependent on borrowing same thing twice
585+
pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -> bool {
586+
let (s1, r1) = projection_stack(e1);
587+
let (s2, r2) = projection_stack(e2);
588+
if !eq_expr_value(cx, r1, r2) {
589+
return true;
590+
}
591+
for (x1, x2) in s1.iter().zip(s2.iter()) {
592+
match (&x1.kind, &x2.kind) {
593+
(ExprKind::Field(_, i1), ExprKind::Field(_, i2)) => {
594+
if i1 != i2 {
595+
return true;
596+
}
597+
},
598+
(ExprKind::Index(_, i1), ExprKind::Index(_, i2)) => {
599+
if !eq_expr_value(cx, i1, i2) {
600+
return false;
601+
}
602+
},
603+
_ => return false,
604+
}
605+
}
606+
false
607+
}
608+
561609
/// Checks if the top level expression can be moved into a closure as is.
562610
pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, jump_targets: &[HirId]) -> bool {
563611
match expr.kind {

tests/ui/swap.fixed

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
clippy::no_effect,
77
clippy::redundant_clone,
88
redundant_semicolons,
9+
dead_code,
910
unused_assignments
1011
)]
1112

@@ -20,9 +21,7 @@ struct Bar {
2021
fn field() {
2122
let mut bar = Bar { a: 1, b: 2 };
2223

23-
let temp = bar.a;
24-
bar.a = bar.b;
25-
bar.b = temp;
24+
std::mem::swap(&mut bar.a, &mut bar.b);
2625

2726
let mut baz = vec![bar.clone(), bar.clone()];
2827
let temp = baz[0].a;
@@ -51,6 +50,7 @@ fn unswappable_slice() {
5150
foo[1][0] = temp;
5251

5352
// swap(foo[0][1], foo[1][0]) would fail
53+
// this could use split_at_mut and mem::swap, but that is not much simpler.
5454
}
5555

5656
fn vec() {
@@ -60,13 +60,54 @@ fn vec() {
6060
foo.swap(0, 1);
6161
}
6262

63+
fn xor_swap_locals() {
64+
// This is an xor-based swap of local variables.
65+
let mut a = 0;
66+
let mut b = 1;
67+
std::mem::swap(&mut a, &mut b)
68+
}
69+
70+
fn xor_field_swap() {
71+
// This is an xor-based swap of fields in a struct.
72+
let mut bar = Bar { a: 0, b: 1 };
73+
std::mem::swap(&mut bar.a, &mut bar.b)
74+
}
75+
76+
fn xor_slice_swap() {
77+
// This is an xor-based swap of a slice
78+
let foo = &mut [1, 2];
79+
foo.swap(0, 1)
80+
}
81+
82+
fn xor_no_swap() {
83+
// This is a sequence of xor-assignment statements that doesn't result in a swap.
84+
let mut a = 0;
85+
let mut b = 1;
86+
let mut c = 2;
87+
a ^= b;
88+
b ^= c;
89+
a ^= c;
90+
c ^= a;
91+
}
92+
93+
fn xor_unswappable_slice() {
94+
let foo = &mut [vec![1, 2], vec![3, 4]];
95+
foo[0][1] ^= foo[1][0];
96+
foo[1][0] ^= foo[0][0];
97+
foo[0][1] ^= foo[1][0];
98+
99+
// swap(foo[0][1], foo[1][0]) would fail
100+
// this could use split_at_mut and mem::swap, but that is not much simpler.
101+
}
102+
103+
fn distinct_slice() {
104+
let foo = &mut [vec![1, 2], vec![3, 4]];
105+
let bar = &mut [vec![1, 2], vec![3, 4]];
106+
std::mem::swap(&mut foo[0][1], &mut bar[1][0]);
107+
}
108+
63109
#[rustfmt::skip]
64110
fn main() {
65-
field();
66-
array();
67-
slice();
68-
unswappable_slice();
69-
vec();
70111

71112
let mut a = 42;
72113
let mut b = 1337;

0 commit comments

Comments
 (0)