Skip to content

Commit 57f3cfb

Browse files
committed
New lint: swap_with_temporary
1 parent 83bde36 commit 57f3cfb

File tree

7 files changed

+409
-0
lines changed

7 files changed

+409
-0
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6100,6 +6100,7 @@ Released 2018-09-13
61006100
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
61016101
[`suspicious_xor_used_as_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_xor_used_as_pow
61026102
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
6103+
[`swap_with_temporary`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_with_temporary
61036104
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
61046105
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
61056106
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr

Diff for: clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
478478
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
479479
crate::methods::SUSPICIOUS_SPLITN_INFO,
480480
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
481+
crate::methods::SWAP_WITH_TEMPORARY_INFO,
481482
crate::methods::TYPE_ID_ON_BOX_INFO,
482483
crate::methods::UNINIT_ASSUMED_INIT_INFO,
483484
crate::methods::UNIT_HASH_INFO,

Diff for: clippy_lints/src/methods/mod.rs

+31
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ mod suspicious_command_arg_space;
112112
mod suspicious_map;
113113
mod suspicious_splitn;
114114
mod suspicious_to_owned;
115+
mod swap_with_temporary;
115116
mod type_id_on_box;
116117
mod uninit_assumed_init;
117118
mod unit_hash;
@@ -4392,6 +4393,34 @@ declare_clippy_lint! {
43924393
"slicing a string and immediately calling as_bytes is less efficient and can lead to panics"
43934394
}
43944395

4396+
declare_clippy_lint! {
4397+
/// ### What it does
4398+
/// Checks for usage of `std::mem::swap` with temporary values.
4399+
///
4400+
/// ### Why is this bad?
4401+
/// Storing a new value in place of a temporary value which will
4402+
/// be dropped right after the `swap` is inefficient. The same
4403+
/// result can be achieved by using an assignment, or dropping
4404+
/// the swap arguments.
4405+
///
4406+
/// ### Example
4407+
/// ```no_run
4408+
/// fn replace_string(s: &mut String) {
4409+
/// std::mem::swap(s, &mut String::from("replaced"));
4410+
/// }
4411+
/// ```
4412+
/// Use instead:
4413+
/// ```no_run
4414+
/// fn replace_string(s: &mut String) {
4415+
/// *s = String::from("replaced");
4416+
/// }
4417+
/// ```
4418+
#[clippy::version = "1.86.0"]
4419+
pub SWAP_WITH_TEMPORARY,
4420+
perf,
4421+
"detect swap with a temporary value"
4422+
}
4423+
43954424
pub struct Methods {
43964425
avoid_breaking_exported_api: bool,
43974426
msrv: Msrv,
@@ -4561,6 +4590,7 @@ impl_lint_pass!(Methods => [
45614590
USELESS_NONZERO_NEW_UNCHECKED,
45624591
MANUAL_REPEAT_N,
45634592
SLICED_STRING_AS_BYTES,
4593+
SWAP_WITH_TEMPORARY,
45644594
]);
45654595

45664596
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4590,6 +4620,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
45904620
unnecessary_fallible_conversions::check_function(cx, expr, func);
45914621
manual_c_str_literals::check(cx, expr, func, args, &self.msrv);
45924622
useless_nonzero_new_unchecked::check(cx, expr, func, args, &self.msrv);
4623+
swap_with_temporary::check(cx, expr, func, args);
45934624
},
45944625
ExprKind::MethodCall(method_call, receiver, args, _) => {
45954626
let method_span = method_call.ident.span;

Diff for: clippy_lints/src/methods/swap_with_temporary.rs

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::is_no_std_crate;
3+
use clippy_utils::sugg::Sugg;
4+
use rustc_ast::BorrowKind;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{Expr, ExprKind, Node, QPath};
7+
use rustc_lint::LateContext;
8+
use rustc_span::sym;
9+
10+
use super::SWAP_WITH_TEMPORARY;
11+
12+
const MSG_TEMPORARY: &str = "this expression returns 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 (arg_kind(&args[0]), arg_kind(&args[1])) {
20+
(ArgKind::FromExpansion, _) | (_, ArgKind::FromExpansion) => {},
21+
(ArgKind::RefMutToTemp(left_temp), ArgKind::RefMutToTemp(right_temp)) => {
22+
emit_lint_useless(cx, expr, func, &args[0], &args[1], left_temp, right_temp);
23+
},
24+
(ArgKind::RefMutToTemp(left_temp), right) => emit_lint_assign(cx, expr, &right, left_temp),
25+
(left, ArgKind::RefMutToTemp(right_temp)) => emit_lint_assign(cx, expr, &left, right_temp),
26+
_ => {},
27+
}
28+
}
29+
}
30+
31+
enum ArgKind<'tcx> {
32+
Expr(&'tcx Expr<'tcx>),
33+
RefMutToPlace(&'tcx Expr<'tcx>),
34+
RefMutToTemp(&'tcx Expr<'tcx>),
35+
FromExpansion,
36+
}
37+
38+
fn arg_kind<'tcx>(arg: &'tcx Expr<'tcx>) -> ArgKind<'tcx> {
39+
if arg.span.from_expansion() {
40+
ArgKind::FromExpansion
41+
} else if let ExprKind::AddrOf(BorrowKind::Ref, _, target) = arg.kind {
42+
if target.span.from_expansion() {
43+
ArgKind::FromExpansion
44+
} else if target.is_syntactic_place_expr() {
45+
ArgKind::RefMutToPlace(target)
46+
} else {
47+
ArgKind::RefMutToTemp(target)
48+
}
49+
} else {
50+
ArgKind::Expr(arg)
51+
}
52+
}
53+
54+
fn emit_lint_useless(
55+
cx: &LateContext<'_>,
56+
expr: &Expr<'_>,
57+
func: &Expr<'_>,
58+
left: &Expr<'_>,
59+
right: &Expr<'_>,
60+
left_temp: &Expr<'_>,
61+
right_temp: &Expr<'_>,
62+
) {
63+
span_lint_and_then(
64+
cx,
65+
SWAP_WITH_TEMPORARY,
66+
expr.span,
67+
"swapping temporary values has no effect",
68+
|diag| {
69+
const DROP_MSG: &str = "drop them if creating these temporary expressions is necessary";
70+
71+
diag.span_note(left_temp.span, MSG_TEMPORARY);
72+
diag.span_note(right_temp.span, MSG_TEMPORARY);
73+
74+
// If the `swap()` is a statement by itself, just propose to replace `swap(&mut a, &mut b)` by `a;
75+
// b;` in order to drop `a` and `b` while acknowledging their side effects. If the
76+
// `swap()` call is part of a larger expression, replace it by `{core,
77+
// std}::mem::drop((a, b))`.
78+
if matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(..)) {
79+
diag.multipart_suggestion(
80+
DROP_MSG,
81+
vec![
82+
(func.span.with_hi(left_temp.span.lo()), String::new()),
83+
(left_temp.span.between(right_temp.span), String::from("; ")),
84+
(expr.span.with_lo(right_temp.span.hi()), String::new()),
85+
],
86+
Applicability::MachineApplicable,
87+
);
88+
} else {
89+
diag.multipart_suggestion(
90+
DROP_MSG,
91+
vec![
92+
(
93+
func.span,
94+
format!("{}::mem::drop(", if is_no_std_crate(cx) { "core" } else { "std" }),
95+
),
96+
(left.span.with_hi(left_temp.span.lo()), String::new()),
97+
(right.span.with_hi(right_temp.span.lo()), String::new()),
98+
(expr.span.shrink_to_hi(), String::from(")")),
99+
],
100+
Applicability::MachineApplicable,
101+
);
102+
}
103+
},
104+
);
105+
}
106+
107+
fn emit_lint_assign(cx: &LateContext<'_>, expr: &Expr<'_>, target: &ArgKind<'_>, temp: &Expr<'_>) {
108+
span_lint_and_then(
109+
cx,
110+
SWAP_WITH_TEMPORARY,
111+
expr.span,
112+
"swapping with a temporary value is inefficient",
113+
|diag| {
114+
diag.span_note(temp.span, MSG_TEMPORARY);
115+
let mut applicability = Applicability::MachineApplicable;
116+
let assign_target = match target {
117+
ArgKind::Expr(expr) => Sugg::hir_with_applicability(cx, expr, "_", &mut applicability).deref(),
118+
ArgKind::RefMutToPlace(expr) => Sugg::hir_with_applicability(cx, expr, "_", &mut applicability),
119+
ArgKind::RefMutToTemp(_) | ArgKind::FromExpansion => unreachable!(),
120+
};
121+
let assign_source = Sugg::hir_with_applicability(cx, temp, "_", &mut applicability);
122+
123+
// If the `swap()` is a statement by itself, propose to replace it by `a = b`. Otherwise, when part
124+
// of a larger expression, surround the assignment with a block to make it `()`.
125+
let suggestion = format!("{assign_target} = {assign_source}");
126+
let suggestion = if matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(..)) {
127+
suggestion
128+
} else {
129+
format!("{{ {suggestion}; }}")
130+
};
131+
diag.span_suggestion(expr.span, "use assignment instead", suggestion, applicability);
132+
},
133+
);
134+
}

Diff for: tests/ui/swap_with_temporary.fixed

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
func(); func();
23+
//~^ ERROR: swapping temporary values has no effect
24+
25+
y = func();
26+
//~^ ERROR: swapping with a temporary value is inefficient
27+
28+
x = func();
29+
//~^ ERROR: swapping with a temporary value is inefficient
30+
31+
*z = func();
32+
//~^ ERROR: swapping with a temporary value is inefficient
33+
34+
// No lint
35+
swap(z, func_returning_refmut(&mut x));
36+
37+
swap(&mut y, z);
38+
39+
*z = func();
40+
//~^ ERROR: swapping with a temporary value is inefficient
41+
42+
// Check rewrites as part of a larger expression
43+
if matches!(std::mem::drop((func(), func())), ()) {
44+
//~^ ERROR: swapping temporary values has no effect
45+
println!("Yeah");
46+
}
47+
if matches!({ *z = func(); }, ()) {
48+
//~^ ERROR: swapping with a temporary value is inefficient
49+
println!("Yeah");
50+
}
51+
52+
// Check that macros won't trigger the lint
53+
macro_rules! mac {
54+
(refmut $x:expr) => {
55+
&mut $x
56+
};
57+
(funcall $f:ident) => {
58+
$f()
59+
};
60+
(wholeexpr) => {
61+
swap(&mut 42, &mut 0)
62+
};
63+
}
64+
swap(mac!(refmut func()), z);
65+
swap(&mut mac!(funcall func), z);
66+
mac!(wholeexpr);
67+
}

Diff for: tests/ui/swap_with_temporary.rs

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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 func());
23+
//~^ ERROR: swapping temporary values has no effect
24+
25+
swap(&mut func(), &mut y);
26+
//~^ ERROR: swapping with a temporary value is inefficient
27+
28+
swap(&mut x, &mut func());
29+
//~^ ERROR: swapping with a temporary value is inefficient
30+
31+
swap(z, &mut func());
32+
//~^ ERROR: swapping with a temporary value is inefficient
33+
34+
// No lint
35+
swap(z, func_returning_refmut(&mut x));
36+
37+
swap(&mut y, z);
38+
39+
swap(&mut func(), z);
40+
//~^ ERROR: swapping with a temporary value is inefficient
41+
42+
// Check rewrites as part of a larger expression
43+
if matches!(swap(&mut func(), &mut func()), ()) {
44+
//~^ ERROR: swapping temporary values has no effect
45+
println!("Yeah");
46+
}
47+
if matches!(swap(z, &mut func()), ()) {
48+
//~^ ERROR: swapping with a temporary value is inefficient
49+
println!("Yeah");
50+
}
51+
52+
// Check that macros won't trigger the lint
53+
macro_rules! mac {
54+
(refmut $x:expr) => {
55+
&mut $x
56+
};
57+
(funcall $f:ident) => {
58+
$f()
59+
};
60+
(wholeexpr) => {
61+
swap(&mut 42, &mut 0)
62+
};
63+
}
64+
swap(mac!(refmut func()), z);
65+
swap(&mut mac!(funcall func), z);
66+
mac!(wholeexpr);
67+
}

0 commit comments

Comments
 (0)