Skip to content

Commit 05448bd

Browse files
committed
New lint: swap_with_temporary
1 parent 9f9a822 commit 05448bd

9 files changed

+612
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6155,6 +6155,7 @@ Released 2018-09-13
61556155
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
61566156
[`suspicious_xor_used_as_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_xor_used_as_pow
61576157
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
6158+
[`swap_with_temporary`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_with_temporary
61586159
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
61596160
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
61606161
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
487487
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
488488
crate::methods::SUSPICIOUS_SPLITN_INFO,
489489
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
490+
crate::methods::SWAP_WITH_TEMPORARY_INFO,
490491
crate::methods::TYPE_ID_ON_BOX_INFO,
491492
crate::methods::UNBUFFERED_BYTES_INFO,
492493
crate::methods::UNINIT_ASSUMED_INIT_INFO,

clippy_lints/src/methods/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ mod suspicious_command_arg_space;
114114
mod suspicious_map;
115115
mod suspicious_splitn;
116116
mod suspicious_to_owned;
117+
mod swap_with_temporary;
117118
mod type_id_on_box;
118119
mod unbuffered_bytes;
119120
mod uninit_assumed_init;
@@ -4484,6 +4485,53 @@ declare_clippy_lint! {
44844485
"calling `std::io::Error::new(std::io::ErrorKind::Other, _)`"
44854486
}
44864487

4488+
declare_clippy_lint! {
4489+
/// ### What it does
4490+
/// Checks for usage of `std::mem::swap` with temporary values.
4491+
///
4492+
/// ### Why is this bad?
4493+
/// Storing a new value in place of a temporary value which will
4494+
/// be dropped right after the `swap` is an inefficient way of performing
4495+
/// an assignment. The same result can be achieved by using a regular
4496+
/// assignment.
4497+
///
4498+
/// ### Examples
4499+
/// ```no_run
4500+
/// fn replace_string(s: &mut String) {
4501+
/// std::mem::swap(s, &mut String::from("replaced"));
4502+
/// }
4503+
/// ```
4504+
/// Use instead:
4505+
/// ```no_run
4506+
/// fn replace_string(s: &mut String) {
4507+
/// *s = String::from("replaced");
4508+
/// }
4509+
/// ```
4510+
///
4511+
/// Also, swapping two temporary values has no effect, as they will
4512+
/// both be dropped right after swapping them. This is likely an indication
4513+
/// of a bug. For example, the following code swaps the references to
4514+
/// the last element of the vectors, instead of swapping the elements
4515+
/// themselves:
4516+
///
4517+
/// ```no_run
4518+
/// fn bug(v1: &mut [i32], v2: &mut [i32]) {
4519+
/// // Incorrect: swapping temporary references (`&mut &mut` passed to swap)
4520+
/// std::mem::swap(&mut v1.last_mut().unwrap(), &mut v2.last_mut().unwrap());
4521+
/// }
4522+
/// ```
4523+
/// Use instead:
4524+
/// ```no_run
4525+
/// fn correct(v1: &mut [i32], v2: &mut [i32]) {
4526+
/// std::mem::swap(v1.last_mut().unwrap(), v2.last_mut().unwrap());
4527+
/// }
4528+
/// ```
4529+
#[clippy::version = "1.88.0"]
4530+
pub SWAP_WITH_TEMPORARY,
4531+
complexity,
4532+
"detect swap with a temporary value"
4533+
}
4534+
44874535
#[expect(clippy::struct_excessive_bools)]
44884536
pub struct Methods {
44894537
avoid_breaking_exported_api: bool,
@@ -4661,6 +4709,7 @@ impl_lint_pass!(Methods => [
46614709
UNBUFFERED_BYTES,
46624710
MANUAL_CONTAINS,
46634711
IO_OTHER_ERROR,
4712+
SWAP_WITH_TEMPORARY,
46644713
]);
46654714

46664715
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4691,6 +4740,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
46914740
manual_c_str_literals::check(cx, expr, func, args, self.msrv);
46924741
useless_nonzero_new_unchecked::check(cx, expr, func, args, self.msrv);
46934742
io_other_error::check(cx, expr, func, args, self.msrv);
4743+
swap_with_temporary::check(cx, expr, func, args);
46944744
},
46954745
ExprKind::MethodCall(method_call, receiver, args, _) => {
46964746
let method_span = method_call.ident.span;
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::sugg::Sugg;
3+
use rustc_ast::BorrowKind;
4+
use rustc_errors::{Applicability, Diag};
5+
use rustc_hir::{Expr, ExprKind, Node, QPath};
6+
use rustc_lint::LateContext;
7+
use rustc_span::sym;
8+
9+
use super::SWAP_WITH_TEMPORARY;
10+
11+
const MSG_TEMPORARY: &str = "this expression returns a temporary value";
12+
const MSG_TEMPORARY_REFMUT: &str = "this is a mutable reference to a temporary value";
13+
14+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>]) {
15+
if let ExprKind::Path(QPath::Resolved(_, func_path)) = func.kind
16+
&& let Some(func_def_id) = func_path.res.opt_def_id()
17+
&& cx.tcx.is_diagnostic_item(sym::mem_swap, func_def_id)
18+
{
19+
match (ArgKind::new(&args[0]), ArgKind::new(&args[1])) {
20+
(ArgKind::RefMutToTemp(left_temp), ArgKind::RefMutToTemp(right_temp)) => {
21+
emit_lint_useless(cx, expr, &args[0], &args[1], left_temp, right_temp);
22+
},
23+
(ArgKind::RefMutToTemp(left_temp), right) => emit_lint_assign(cx, expr, &right, &args[0], left_temp),
24+
(left, ArgKind::RefMutToTemp(right_temp)) => emit_lint_assign(cx, expr, &left, &args[1], right_temp),
25+
_ => {},
26+
}
27+
}
28+
}
29+
30+
enum ArgKind<'tcx> {
31+
// Mutable reference to a place, coming from a macro
32+
RefMutToPlaceAsMacro(&'tcx Expr<'tcx>),
33+
// Place behind a mutable reference
34+
RefMutToPlace(&'tcx Expr<'tcx>),
35+
// Temporary value behind a mutable reference
36+
RefMutToTemp(&'tcx Expr<'tcx>),
37+
// Any other case
38+
Expr(&'tcx Expr<'tcx>),
39+
}
40+
41+
impl<'tcx> ArgKind<'tcx> {
42+
fn new(arg: &'tcx Expr<'tcx>) -> Self {
43+
if let ExprKind::AddrOf(BorrowKind::Ref, _, target) = arg.kind {
44+
if target.is_syntactic_place_expr() {
45+
if arg.span.from_expansion() {
46+
ArgKind::RefMutToPlaceAsMacro(arg)
47+
} else {
48+
ArgKind::RefMutToPlace(target)
49+
}
50+
} else {
51+
ArgKind::RefMutToTemp(target)
52+
}
53+
} else {
54+
ArgKind::Expr(arg)
55+
}
56+
}
57+
}
58+
59+
// Emits a note either on the temporary expression if it can be found in the same context as the
60+
// base and returns `true`, or on the mutable reference to the temporary expression otherwise and
61+
// returns `false`.
62+
fn emit_note(diag: &mut Diag<'_, ()>, base: &Expr<'_>, expr: &Expr<'_>, expr_temp: &Expr<'_>) -> bool {
63+
if base.span.eq_ctxt(expr.span) {
64+
diag.span_note(expr_temp.span.source_callsite(), MSG_TEMPORARY);
65+
true
66+
} else {
67+
diag.span_note(expr.span.source_callsite(), MSG_TEMPORARY_REFMUT);
68+
false
69+
}
70+
}
71+
72+
fn emit_lint_useless(
73+
cx: &LateContext<'_>,
74+
expr: &Expr<'_>,
75+
left: &Expr<'_>,
76+
right: &Expr<'_>,
77+
left_temp: &Expr<'_>,
78+
right_temp: &Expr<'_>,
79+
) {
80+
span_lint_and_then(
81+
cx,
82+
SWAP_WITH_TEMPORARY,
83+
expr.span,
84+
"swapping temporary values has no effect",
85+
|diag| {
86+
emit_note(diag, expr, left, left_temp);
87+
emit_note(diag, expr, right, right_temp);
88+
},
89+
);
90+
}
91+
92+
fn emit_lint_assign(cx: &LateContext<'_>, expr: &Expr<'_>, target: &ArgKind<'_>, reftemp: &Expr<'_>, temp: &Expr<'_>) {
93+
span_lint_and_then(
94+
cx,
95+
SWAP_WITH_TEMPORARY,
96+
expr.span,
97+
"swapping with a temporary value is inefficient",
98+
|diag| {
99+
if !emit_note(diag, expr, reftemp, temp) {
100+
return;
101+
}
102+
103+
// Make the suggestion only when the original `swap()` call is a statement
104+
// or the last expression in a block.
105+
if matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(..) | Node::Block(..)) {
106+
let mut applicability = Applicability::MachineApplicable;
107+
let ctxt = expr.span.ctxt();
108+
let assign_target = match target {
109+
ArgKind::Expr(target) | ArgKind::RefMutToPlaceAsMacro(target) => {
110+
Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability).deref()
111+
},
112+
ArgKind::RefMutToPlace(target) => Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability),
113+
ArgKind::RefMutToTemp(_) => unreachable!(),
114+
};
115+
let assign_source = Sugg::hir_with_context(cx, temp, ctxt, "_", &mut applicability);
116+
diag.span_suggestion(
117+
expr.span,
118+
"use assignment instead",
119+
format!("{assign_target} = {assign_source}"),
120+
applicability,
121+
);
122+
}
123+
},
124+
);
125+
}

tests/ui/swap_with_temporary.fixed

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#![warn(clippy::swap_with_temporary)]
2+
3+
use std::mem::swap;
4+
5+
fn func() -> String {
6+
String::from("func")
7+
}
8+
9+
fn func_returning_refmut(s: &mut String) -> &mut String {
10+
s
11+
}
12+
13+
fn main() {
14+
let mut x = String::from("x");
15+
let mut y = String::from("y");
16+
let mut zz = String::from("zz");
17+
let z = &mut zz;
18+
19+
// No lint
20+
swap(&mut x, &mut y);
21+
22+
y = func();
23+
//~^ ERROR: swapping with a temporary value is inefficient
24+
25+
x = func();
26+
//~^ ERROR: swapping with a temporary value is inefficient
27+
28+
*z = func();
29+
//~^ ERROR: swapping with a temporary value is inefficient
30+
31+
// No lint
32+
swap(z, func_returning_refmut(&mut x));
33+
34+
swap(&mut y, z);
35+
36+
*z = func();
37+
//~^ ERROR: swapping with a temporary value is inefficient
38+
39+
macro_rules! mac {
40+
(refmut $x:expr) => {
41+
&mut $x
42+
};
43+
(funcall $f:ident) => {
44+
$f()
45+
};
46+
(wholeexpr) => {
47+
swap(&mut 42, &mut 0)
48+
};
49+
(ident $v:ident) => {
50+
$v
51+
};
52+
}
53+
*z = mac!(funcall func);
54+
//~^ ERROR: swapping with a temporary value is inefficient
55+
*mac!(ident z) = mac!(funcall func);
56+
//~^ ERROR: swapping with a temporary value is inefficient
57+
*mac!(ident z) = mac!(funcall func);
58+
//~^ ERROR: swapping with a temporary value is inefficient
59+
*mac!(refmut y) = func();
60+
//~^ ERROR: swapping with a temporary value is inefficient
61+
62+
// No lint if it comes from a macro as it may depend on the arguments
63+
mac!(wholeexpr);
64+
}
65+
66+
struct S {
67+
t: String,
68+
}
69+
70+
fn dont_lint_those(s: &mut S, v: &mut [String], w: Option<&mut String>) {
71+
swap(&mut s.t, &mut v[0]);
72+
swap(&mut s.t, v.get_mut(0).unwrap());
73+
swap(w.unwrap(), &mut s.t);
74+
}

tests/ui/swap_with_temporary.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#![warn(clippy::swap_with_temporary)]
2+
3+
use std::mem::swap;
4+
5+
fn func() -> String {
6+
String::from("func")
7+
}
8+
9+
fn func_returning_refmut(s: &mut String) -> &mut String {
10+
s
11+
}
12+
13+
fn main() {
14+
let mut x = String::from("x");
15+
let mut y = String::from("y");
16+
let mut zz = String::from("zz");
17+
let z = &mut zz;
18+
19+
// No lint
20+
swap(&mut x, &mut y);
21+
22+
swap(&mut func(), &mut y);
23+
//~^ ERROR: swapping with a temporary value is inefficient
24+
25+
swap(&mut x, &mut func());
26+
//~^ ERROR: swapping with a temporary value is inefficient
27+
28+
swap(z, &mut func());
29+
//~^ ERROR: swapping with a temporary value is inefficient
30+
31+
// No lint
32+
swap(z, func_returning_refmut(&mut x));
33+
34+
swap(&mut y, z);
35+
36+
swap(&mut func(), z);
37+
//~^ ERROR: swapping with a temporary value is inefficient
38+
39+
macro_rules! mac {
40+
(refmut $x:expr) => {
41+
&mut $x
42+
};
43+
(funcall $f:ident) => {
44+
$f()
45+
};
46+
(wholeexpr) => {
47+
swap(&mut 42, &mut 0)
48+
};
49+
(ident $v:ident) => {
50+
$v
51+
};
52+
}
53+
swap(&mut mac!(funcall func), z);
54+
//~^ ERROR: swapping with a temporary value is inefficient
55+
swap(&mut mac!(funcall func), mac!(ident z));
56+
//~^ ERROR: swapping with a temporary value is inefficient
57+
swap(mac!(ident z), &mut mac!(funcall func));
58+
//~^ ERROR: swapping with a temporary value is inefficient
59+
swap(mac!(refmut y), &mut func());
60+
//~^ ERROR: swapping with a temporary value is inefficient
61+
62+
// No lint if it comes from a macro as it may depend on the arguments
63+
mac!(wholeexpr);
64+
}
65+
66+
struct S {
67+
t: String,
68+
}
69+
70+
fn dont_lint_those(s: &mut S, v: &mut [String], w: Option<&mut String>) {
71+
swap(&mut s.t, &mut v[0]);
72+
swap(&mut s.t, v.get_mut(0).unwrap());
73+
swap(w.unwrap(), &mut s.t);
74+
}

0 commit comments

Comments
 (0)