Skip to content

Commit 4e6fe44

Browse files
committed
New lint: swap_with_temporary
1 parent 8cef0b6 commit 4e6fe44

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
@@ -6109,6 +6109,7 @@ Released 2018-09-13
61096109
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
61106110
[`suspicious_xor_used_as_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_xor_used_as_pow
61116111
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
6112+
[`swap_with_temporary`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_with_temporary
61126113
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
61136114
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
61146115
[`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
@@ -484,6 +484,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
484484
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
485485
crate::methods::SUSPICIOUS_SPLITN_INFO,
486486
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
487+
crate::methods::SWAP_WITH_TEMPORARY_INFO,
487488
crate::methods::TYPE_ID_ON_BOX_INFO,
488489
crate::methods::UNBUFFERED_BYTES_INFO,
489490
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
@@ -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 unbuffered_bytes;
118119
mod uninit_assumed_init;
@@ -4460,6 +4461,34 @@ declare_clippy_lint! {
44604461
"unnecessary `iter().any()` on slices that can be replaced with `contains()`"
44614462
}
44624463

4464+
declare_clippy_lint! {
4465+
/// ### What it does
4466+
/// Checks for usage of `std::mem::swap` with temporary values.
4467+
///
4468+
/// ### Why is this bad?
4469+
/// Storing a new value in place of a temporary value which will
4470+
/// be dropped right after the `swap` is inefficient. The same
4471+
/// result can be achieved by using an assignment, or dropping
4472+
/// the swap arguments.
4473+
///
4474+
/// ### Example
4475+
/// ```no_run
4476+
/// fn replace_string(s: &mut String) {
4477+
/// std::mem::swap(s, &mut String::from("replaced"));
4478+
/// }
4479+
/// ```
4480+
/// Use instead:
4481+
/// ```no_run
4482+
/// fn replace_string(s: &mut String) {
4483+
/// *s = String::from("replaced");
4484+
/// }
4485+
/// ```
4486+
#[clippy::version = "1.86.0"]
4487+
pub SWAP_WITH_TEMPORARY,
4488+
complexity,
4489+
"detect swap with a temporary value"
4490+
}
4491+
44634492
#[expect(clippy::struct_excessive_bools)]
44644493
pub struct Methods {
44654494
avoid_breaking_exported_api: bool,
@@ -4636,6 +4665,7 @@ impl_lint_pass!(Methods => [
46364665
RETURN_AND_THEN,
46374666
UNBUFFERED_BYTES,
46384667
MANUAL_CONTAINS,
4668+
SWAP_WITH_TEMPORARY,
46394669
]);
46404670

46414671
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4665,6 +4695,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
46654695
unnecessary_fallible_conversions::check_function(cx, expr, func);
46664696
manual_c_str_literals::check(cx, expr, func, args, &self.msrv);
46674697
useless_nonzero_new_unchecked::check(cx, expr, func, args, &self.msrv);
4698+
swap_with_temporary::check(cx, expr, func, args);
46684699
},
46694700
ExprKind::MethodCall(method_call, receiver, args, _) => {
46704701
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)