Skip to content

Commit ac88357

Browse files
authored
New lint: swap_with_temporary (#14046)
This lint detects inefficient or useless `{std,core}::mem::swap()` calls such as: ```rust // Should be `a = temp();` swap(&mut a, &mut temp()); // Should be `*b = temp();` swap(b, &mut temp()); // Should be `temp1(); temp2();` if we want to keep the side effects swap(&mut temp1(), &mut temp2()); ``` It also takes care of using a form appropriate for a `()` context if `swap()` is part of a larger expression (don't ask me why this wouldn't happen, I have no idea), by suggesting `{ x = y; }` (statement in block) or `{std,core}::mem::drop((temp1(), temp2())`. changelog: [`swap_with_temporary`]: new lint Close #1968
2 parents 30e9cd5 + 05448bd commit ac88357

9 files changed

+612
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6218,6 +6218,7 @@ Released 2018-09-13
62186218
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
62196219
[`suspicious_xor_used_as_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_xor_used_as_pow
62206220
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
6221+
[`swap_with_temporary`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_with_temporary
62216222
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
62226223
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
62236224
[`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
@@ -491,6 +491,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
491491
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
492492
crate::methods::SUSPICIOUS_SPLITN_INFO,
493493
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
494+
crate::methods::SWAP_WITH_TEMPORARY_INFO,
494495
crate::methods::TYPE_ID_ON_BOX_INFO,
495496
crate::methods::UNBUFFERED_BYTES_INFO,
496497
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;
@@ -4481,6 +4482,53 @@ declare_clippy_lint! {
44814482
"calling `std::io::Error::new(std::io::ErrorKind::Other, _)`"
44824483
}
44834484

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

46634712
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4689,6 +4738,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
46894738
manual_c_str_literals::check(cx, expr, func, args, self.msrv);
46904739
useless_nonzero_new_unchecked::check(cx, expr, func, args, self.msrv);
46914740
io_other_error::check(cx, expr, func, args, self.msrv);
4741+
swap_with_temporary::check(cx, expr, func, args);
46924742
},
46934743
ExprKind::MethodCall(method_call, receiver, args, _) => {
46944744
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)