Skip to content

Commit 19ddfe2

Browse files
committed
New lint: swap_with_temporary
1 parent 398a5c2 commit 19ddfe2

File tree

7 files changed

+409
-0
lines changed

7 files changed

+409
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6103,6 +6103,7 @@ Released 2018-09-13
61036103
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
61046104
[`suspicious_xor_used_as_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_xor_used_as_pow
61056105
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
6106+
[`swap_with_temporary`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_with_temporary
61066107
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
61076108
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
61086109
[`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
@@ -480,6 +480,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
480480
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
481481
crate::methods::SUSPICIOUS_SPLITN_INFO,
482482
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
483+
crate::methods::SWAP_WITH_TEMPORARY_INFO,
483484
crate::methods::TYPE_ID_ON_BOX_INFO,
484485
crate::methods::UNINIT_ASSUMED_INIT_INFO,
485486
crate::methods::UNIT_HASH_INFO,

clippy_lints/src/methods/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ mod suspicious_command_arg_space;
113113
mod suspicious_map;
114114
mod suspicious_splitn;
115115
mod suspicious_to_owned;
116+
mod swap_with_temporary;
116117
mod type_id_on_box;
117118
mod uninit_assumed_init;
118119
mod unit_hash;
@@ -4433,6 +4434,34 @@ declare_clippy_lint! {
44334434
"using `Option::and_then` or `Result::and_then` to chain a computation that returns an `Option` or a `Result`"
44344435
}
44354436

4437+
declare_clippy_lint! {
4438+
/// ### What it does
4439+
/// Checks for usage of `std::mem::swap` with temporary values.
4440+
///
4441+
/// ### Why is this bad?
4442+
/// Storing a new value in place of a temporary value which will
4443+
/// be dropped right after the `swap` is inefficient. The same
4444+
/// result can be achieved by using an assignment, or dropping
4445+
/// the swap arguments.
4446+
///
4447+
/// ### Example
4448+
/// ```no_run
4449+
/// fn replace_string(s: &mut String) {
4450+
/// std::mem::swap(s, &mut String::from("replaced"));
4451+
/// }
4452+
/// ```
4453+
/// Use instead:
4454+
/// ```no_run
4455+
/// fn replace_string(s: &mut String) {
4456+
/// *s = String::from("replaced");
4457+
/// }
4458+
/// ```
4459+
#[clippy::version = "1.86.0"]
4460+
pub SWAP_WITH_TEMPORARY,
4461+
perf,
4462+
"detect swap with a temporary value"
4463+
}
4464+
44364465
pub struct Methods {
44374466
avoid_breaking_exported_api: bool,
44384467
msrv: Msrv,
@@ -4603,6 +4632,7 @@ impl_lint_pass!(Methods => [
46034632
MANUAL_REPEAT_N,
46044633
SLICED_STRING_AS_BYTES,
46054634
RETURN_AND_THEN,
4635+
SWAP_WITH_TEMPORARY,
46064636
]);
46074637

46084638
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4632,6 +4662,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
46324662
unnecessary_fallible_conversions::check_function(cx, expr, func);
46334663
manual_c_str_literals::check(cx, expr, func, args, &self.msrv);
46344664
useless_nonzero_new_unchecked::check(cx, expr, func, args, &self.msrv);
4665+
swap_with_temporary::check(cx, expr, func, args);
46354666
},
46364667
ExprKind::MethodCall(method_call, receiver, args, _) => {
46374668
let method_span = method_call.ident.span;
Lines changed: 134 additions & 0 deletions
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+
}

tests/ui/swap_with_temporary.fixed

Lines changed: 67 additions & 0 deletions
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+
}

tests/ui/swap_with_temporary.rs

Lines changed: 67 additions & 0 deletions
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)