Skip to content

Commit 3016c0b

Browse files
committed
New lint: swap_with_temporary
1 parent e479a9f commit 3016c0b

File tree

7 files changed

+493
-0
lines changed

7 files changed

+493
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6153,6 +6153,7 @@ Released 2018-09-13
61536153
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
61546154
[`suspicious_xor_used_as_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_xor_used_as_pow
61556155
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
6156+
[`swap_with_temporary`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_with_temporary
61566157
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
61576158
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
61586159
[`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
@@ -485,6 +485,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
485485
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
486486
crate::methods::SUSPICIOUS_SPLITN_INFO,
487487
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
488+
crate::methods::SWAP_WITH_TEMPORARY_INFO,
488489
crate::methods::TYPE_ID_ON_BOX_INFO,
489490
crate::methods::UNBUFFERED_BYTES_INFO,
490491
crate::methods::UNINIT_ASSUMED_INIT_INFO,

clippy_lints/src/methods/mod.rs

Lines changed: 31 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,34 @@ 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 inefficient. The same
4495+
/// result can be achieved by using an assignment, or dropping
4496+
/// the swap arguments.
4497+
///
4498+
/// ### Example
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+
#[clippy::version = "1.86.0"]
4511+
pub SWAP_WITH_TEMPORARY,
4512+
complexity,
4513+
"detect swap with a temporary value"
4514+
}
4515+
44874516
#[expect(clippy::struct_excessive_bools)]
44884517
pub struct Methods {
44894518
avoid_breaking_exported_api: bool,
@@ -4661,6 +4690,7 @@ impl_lint_pass!(Methods => [
46614690
UNBUFFERED_BYTES,
46624691
MANUAL_CONTAINS,
46634692
IO_OTHER_ERROR,
4693+
SWAP_WITH_TEMPORARY,
46644694
]);
46654695

46664696
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4691,6 +4721,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
46914721
manual_c_str_literals::check(cx, expr, func, args, &self.msrv);
46924722
useless_nonzero_new_unchecked::check(cx, expr, func, args, &self.msrv);
46934723
io_other_error::check(cx, expr, func, args, &self.msrv);
4724+
swap_with_temporary::check(cx, expr, func, args);
46944725
},
46954726
ExprKind::MethodCall(method_call, receiver, args, _) => {
46964727
let method_span = method_call.ident.span;
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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 (ArgKind::new(&args[0]), ArgKind::new(&args[1])) {
20+
(ArgKind::RefMutToTemp(left_temp), ArgKind::RefMutToTemp(right_temp)) => {
21+
emit_lint_useless(cx, expr, func, &args[0], &args[1], left_temp, right_temp);
22+
},
23+
(ArgKind::RefMutToTemp(left_temp), right) => emit_lint_assign(cx, expr, &right, left_temp),
24+
(left, ArgKind::RefMutToTemp(right_temp)) => emit_lint_assign(cx, expr, &left, 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+
fn emit_lint_useless(
60+
cx: &LateContext<'_>,
61+
expr: &Expr<'_>,
62+
func: &Expr<'_>,
63+
left: &Expr<'_>,
64+
right: &Expr<'_>,
65+
left_temp: &Expr<'_>,
66+
right_temp: &Expr<'_>,
67+
) {
68+
span_lint_and_then(
69+
cx,
70+
SWAP_WITH_TEMPORARY,
71+
expr.span,
72+
"swapping temporary values has no effect",
73+
|diag| {
74+
const DROP_MSG: &str = "drop them if creating these temporary expressions is necessary";
75+
76+
diag.span_note(left_temp.span, MSG_TEMPORARY);
77+
diag.span_note(right_temp.span, MSG_TEMPORARY);
78+
79+
// If the `swap()` is a statement by itself, just propose to replace `swap(&mut a, &mut b)` by `a;
80+
// b;` in order to drop `a` and `b` while acknowledging their side effects. If the
81+
// `swap()` call is part of a larger expression, replace it by `{core,
82+
// std}::mem::drop((a, b))`.
83+
if matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(..)) {
84+
diag.multipart_suggestion(
85+
DROP_MSG,
86+
vec![
87+
(func.span.with_hi(left_temp.span.lo()), String::new()),
88+
(left_temp.span.between(right_temp.span), String::from("; ")),
89+
(expr.span.with_lo(right_temp.span.hi()), String::new()),
90+
],
91+
Applicability::MachineApplicable,
92+
);
93+
} else {
94+
diag.multipart_suggestion(
95+
DROP_MSG,
96+
vec![
97+
(
98+
func.span,
99+
format!("{}::mem::drop(", if is_no_std_crate(cx) { "core" } else { "std" }),
100+
),
101+
(left.span.with_hi(left_temp.span.lo()), String::new()),
102+
(right.span.with_hi(right_temp.span.lo()), String::new()),
103+
(expr.span.shrink_to_hi(), String::from(")")),
104+
],
105+
Applicability::MachineApplicable,
106+
);
107+
}
108+
},
109+
);
110+
}
111+
112+
fn emit_lint_assign(cx: &LateContext<'_>, expr: &Expr<'_>, target: &ArgKind<'_>, temp: &Expr<'_>) {
113+
span_lint_and_then(
114+
cx,
115+
SWAP_WITH_TEMPORARY,
116+
expr.span,
117+
"swapping with a temporary value is inefficient",
118+
|diag| {
119+
diag.span_note(temp.span, MSG_TEMPORARY);
120+
let mut applicability = Applicability::MachineApplicable;
121+
let ctxt = expr.span.ctxt();
122+
let assign_target = match target {
123+
ArgKind::Expr(target) | ArgKind::RefMutToPlaceAsMacro(target) => {
124+
Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability).deref()
125+
},
126+
ArgKind::RefMutToPlace(target) => Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability),
127+
ArgKind::RefMutToTemp(_) => unreachable!(),
128+
};
129+
let assign_source = Sugg::hir_with_context(cx, temp, ctxt, "_", &mut applicability);
130+
131+
// If the `swap()` is a statement by itself, propose to replace it by `a = b`. Otherwise, when part
132+
// of a larger expression, surround the assignment with a block to make it `()`.
133+
let suggestion = format!("{assign_target} = {assign_source}");
134+
let suggestion = if matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(..)) {
135+
suggestion
136+
} else {
137+
format!("{{ {suggestion}; }}")
138+
};
139+
diag.span_suggestion(expr.span, "use assignment instead", suggestion, applicability);
140+
},
141+
);
142+
}

tests/ui/swap_with_temporary.fixed

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
macro_rules! mac {
53+
(refmut $x:expr) => {
54+
&mut $x
55+
};
56+
(funcall $f:ident) => {
57+
$f()
58+
};
59+
(wholeexpr) => {
60+
swap(&mut 42, &mut 0)
61+
};
62+
(ident $v:ident) => {
63+
$v
64+
};
65+
}
66+
*z = func();
67+
//~^ ERROR: swapping with a temporary value is inefficient
68+
*z = mac!(funcall func);
69+
//~^ ERROR: swapping with a temporary value is inefficient
70+
*mac!(ident z) = mac!(funcall func);
71+
//~^ ERROR: swapping with a temporary value is inefficient
72+
*mac!(refmut y) = func();
73+
//~^ ERROR: swapping with a temporary value is inefficient
74+
75+
// No lint if it comes from a macro as it may depend on the arguments
76+
mac!(wholeexpr);
77+
}

tests/ui/swap_with_temporary.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
macro_rules! mac {
53+
(refmut $x:expr) => {
54+
&mut $x
55+
};
56+
(funcall $f:ident) => {
57+
$f()
58+
};
59+
(wholeexpr) => {
60+
swap(&mut 42, &mut 0)
61+
};
62+
(ident $v:ident) => {
63+
$v
64+
};
65+
}
66+
swap(mac!(refmut func()), z);
67+
//~^ ERROR: swapping with a temporary value is inefficient
68+
swap(&mut mac!(funcall func), z);
69+
//~^ ERROR: swapping with a temporary value is inefficient
70+
swap(&mut mac!(funcall func), mac!(ident z));
71+
//~^ ERROR: swapping with a temporary value is inefficient
72+
swap(mac!(refmut y), &mut func());
73+
//~^ ERROR: swapping with a temporary value is inefficient
74+
75+
// No lint if it comes from a macro as it may depend on the arguments
76+
mac!(wholeexpr);
77+
}

0 commit comments

Comments
 (0)