Skip to content

Commit ad7de94

Browse files
committed
Consider deref'ed argument as non-temporary
If there are more than one dereference (there is one corresponding matched with a borrow in any case), consider that the argument might point to a place expression, which is the safest choice. Also, use an appropriate number of dereferences in suggestions involving arguments using themselves multiple dereferences.
1 parent 428208e commit ad7de94

File tree

4 files changed

+166
-16
lines changed

4 files changed

+166
-16
lines changed

clippy_lints/src/methods/swap_with_temporary.rs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ use rustc_ast::BorrowKind;
44
use rustc_errors::{Applicability, Diag};
55
use rustc_hir::{Expr, ExprKind, Node, QPath};
66
use rustc_lint::LateContext;
7+
use rustc_middle::ty::adjustment::Adjust;
78
use rustc_span::sym;
89

910
use super::SWAP_WITH_TEMPORARY;
1011

1112
const MSG_TEMPORARY: &str = "this expression returns a temporary value";
1213
const MSG_TEMPORARY_REFMUT: &str = "this is a mutable reference to a temporary value";
1314

14-
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>]) {
15+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'_>, args: &'tcx [Expr<'_>]) {
1516
if let ExprKind::Path(QPath::Resolved(_, func_path)) = func.kind
1617
&& let Some(func_def_id) = func_path.res.opt_def_id()
1718
&& cx.tcx.is_diagnostic_item(sym::mem_swap, func_def_id)
1819
{
19-
match (ArgKind::new(&args[0]), ArgKind::new(&args[1])) {
20+
match (ArgKind::new(cx, &args[0]), ArgKind::new(cx, &args[1])) {
2021
(ArgKind::RefMutToTemp(left_temp), ArgKind::RefMutToTemp(right_temp)) => {
2122
emit_lint_useless(cx, expr, &args[0], &args[1], left_temp, right_temp);
2223
},
@@ -28,24 +29,40 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args
2829
}
2930

3031
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>),
32+
// Mutable reference to a place, coming from a macro, and number of dereferences to use
33+
RefMutToPlaceAsMacro(&'tcx Expr<'tcx>, usize),
34+
// Place behind a mutable reference, and number of dereferences to use
35+
RefMutToPlace(&'tcx Expr<'tcx>, usize),
3536
// Temporary value behind a mutable reference
3637
RefMutToTemp(&'tcx Expr<'tcx>),
3738
// Any other case
3839
Expr(&'tcx Expr<'tcx>),
3940
}
4041

4142
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() {
43+
/// Build a new `ArgKind` from `arg`. There must be no false positive when returning a
44+
/// `ArgKind::RefMutToTemp` variant, as this may cause a spurious lint to be emitted.
45+
fn new(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>) -> Self {
46+
if let ExprKind::AddrOf(BorrowKind::Ref, _, target) = arg.kind
47+
&& let adjustments = cx.typeck_results().expr_adjustments(arg)
48+
&& adjustments
49+
.first()
50+
.is_some_and(|adj| matches!(adj.kind, Adjust::Deref(None)))
51+
&& adjustments
52+
.last()
53+
.is_some_and(|adj| matches!(adj.kind, Adjust::Borrow(_)))
54+
{
55+
let extra_derefs = adjustments[1..adjustments.len() - 1]
56+
.iter()
57+
.filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
58+
.count();
59+
// If a deref is used, `arg` might be a place expression. For example, a mutex guard
60+
// would dereference into the mutex content which is probably not temporary.
61+
if target.is_syntactic_place_expr() || extra_derefs > 0 {
4562
if arg.span.from_expansion() {
46-
ArgKind::RefMutToPlaceAsMacro(arg)
63+
ArgKind::RefMutToPlaceAsMacro(arg, extra_derefs)
4764
} else {
48-
ArgKind::RefMutToPlace(target)
65+
ArgKind::RefMutToPlace(target, extra_derefs)
4966
}
5067
} else {
5168
ArgKind::RefMutToTemp(target)
@@ -106,10 +123,15 @@ fn emit_lint_assign(cx: &LateContext<'_>, expr: &Expr<'_>, target: &ArgKind<'_>,
106123
let mut applicability = Applicability::MachineApplicable;
107124
let ctxt = expr.span.ctxt();
108125
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),
126+
ArgKind::Expr(target) => Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability).deref(),
127+
ArgKind::RefMutToPlaceAsMacro(arg, derefs) => (0..*derefs).fold(
128+
Sugg::hir_with_context(cx, arg, ctxt, "_", &mut applicability).deref(),
129+
|sugg, _| sugg.deref(),
130+
),
131+
ArgKind::RefMutToPlace(target, derefs) => (0..*derefs).fold(
132+
Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability),
133+
|sugg, _| sugg.deref(),
134+
),
113135
ArgKind::RefMutToTemp(_) => unreachable!(),
114136
};
115137
let assign_source = Sugg::hir_with_context(cx, temp, ctxt, "_", &mut applicability);

tests/ui/swap_with_temporary.fixed

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,49 @@ fn dont_lint_those(s: &mut S, v: &mut [String], w: Option<&mut String>) {
7272
swap(&mut s.t, v.get_mut(0).unwrap());
7373
swap(w.unwrap(), &mut s.t);
7474
}
75+
76+
fn issue15166() {
77+
use std::sync::Mutex;
78+
79+
struct A {
80+
thing: Mutex<Vec<u8>>,
81+
}
82+
83+
impl A {
84+
fn a(&self) {
85+
let mut new_vec = vec![42];
86+
// Do not lint here, as neither `new_vec` nor the result of `.lock().unwrap()` are temporaries
87+
swap(&mut new_vec, &mut self.thing.lock().unwrap());
88+
for v in new_vec {
89+
// Do something with v
90+
}
91+
// Here `vec![42]` is temporary though, and a proper dereference will have to be used in the fix
92+
*self.thing.lock().unwrap() = vec![42];
93+
//~^ ERROR: swapping with a temporary value is inefficient
94+
}
95+
}
96+
}
97+
98+
fn multiple_deref() {
99+
let mut v1 = &mut &mut &mut vec![42];
100+
***v1 = vec![];
101+
//~^ ERROR: swapping with a temporary value is inefficient
102+
103+
struct Wrapper<T: ?Sized>(T);
104+
impl<T: ?Sized> std::ops::Deref for Wrapper<T> {
105+
type Target = T;
106+
fn deref(&self) -> &Self::Target {
107+
&self.0
108+
}
109+
}
110+
impl<T: ?Sized> std::ops::DerefMut for Wrapper<T> {
111+
fn deref_mut(&mut self) -> &mut Self::Target {
112+
&mut self.0
113+
}
114+
}
115+
116+
use std::sync::Mutex;
117+
let mut v1 = Mutex::new(Wrapper(Wrapper(vec![42])));
118+
***v1.lock().unwrap() = vec![];
119+
//~^ ERROR: swapping with a temporary value is inefficient
120+
}

tests/ui/swap_with_temporary.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,49 @@ fn dont_lint_those(s: &mut S, v: &mut [String], w: Option<&mut String>) {
7272
swap(&mut s.t, v.get_mut(0).unwrap());
7373
swap(w.unwrap(), &mut s.t);
7474
}
75+
76+
fn issue15166() {
77+
use std::sync::Mutex;
78+
79+
struct A {
80+
thing: Mutex<Vec<u8>>,
81+
}
82+
83+
impl A {
84+
fn a(&self) {
85+
let mut new_vec = vec![42];
86+
// Do not lint here, as neither `new_vec` nor the result of `.lock().unwrap()` are temporaries
87+
swap(&mut new_vec, &mut self.thing.lock().unwrap());
88+
for v in new_vec {
89+
// Do something with v
90+
}
91+
// Here `vec![42]` is temporary though, and a proper dereference will have to be used in the fix
92+
swap(&mut vec![42], &mut self.thing.lock().unwrap());
93+
//~^ ERROR: swapping with a temporary value is inefficient
94+
}
95+
}
96+
}
97+
98+
fn multiple_deref() {
99+
let mut v1 = &mut &mut &mut vec![42];
100+
swap(&mut ***v1, &mut vec![]);
101+
//~^ ERROR: swapping with a temporary value is inefficient
102+
103+
struct Wrapper<T: ?Sized>(T);
104+
impl<T: ?Sized> std::ops::Deref for Wrapper<T> {
105+
type Target = T;
106+
fn deref(&self) -> &Self::Target {
107+
&self.0
108+
}
109+
}
110+
impl<T: ?Sized> std::ops::DerefMut for Wrapper<T> {
111+
fn deref_mut(&mut self) -> &mut Self::Target {
112+
&mut self.0
113+
}
114+
}
115+
116+
use std::sync::Mutex;
117+
let mut v1 = Mutex::new(Wrapper(Wrapper(vec![42])));
118+
swap(&mut vec![], &mut v1.lock().unwrap());
119+
//~^ ERROR: swapping with a temporary value is inefficient
120+
}

tests/ui/swap_with_temporary.stderr

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,41 @@ note: this expression returns a temporary value
9696
LL | swap(mac!(refmut y), &mut func());
9797
| ^^^^^^
9898

99-
error: aborting due to 8 previous errors
99+
error: swapping with a temporary value is inefficient
100+
--> tests/ui/swap_with_temporary.rs:92:13
101+
|
102+
LL | swap(&mut vec![42], &mut self.thing.lock().unwrap());
103+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use assignment instead: `*self.thing.lock().unwrap() = vec![42]`
104+
|
105+
note: this expression returns a temporary value
106+
--> tests/ui/swap_with_temporary.rs:92:23
107+
|
108+
LL | swap(&mut vec![42], &mut self.thing.lock().unwrap());
109+
| ^^^^^^^^
110+
111+
error: swapping with a temporary value is inefficient
112+
--> tests/ui/swap_with_temporary.rs:100:5
113+
|
114+
LL | swap(&mut ***v1, &mut vec![]);
115+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use assignment instead: `***v1 = vec![]`
116+
|
117+
note: this expression returns a temporary value
118+
--> tests/ui/swap_with_temporary.rs:100:27
119+
|
120+
LL | swap(&mut ***v1, &mut vec![]);
121+
| ^^^^^^
122+
123+
error: swapping with a temporary value is inefficient
124+
--> tests/ui/swap_with_temporary.rs:118:5
125+
|
126+
LL | swap(&mut vec![], &mut v1.lock().unwrap());
127+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use assignment instead: `***v1.lock().unwrap() = vec![]`
128+
|
129+
note: this expression returns a temporary value
130+
--> tests/ui/swap_with_temporary.rs:118:15
131+
|
132+
LL | swap(&mut vec![], &mut v1.lock().unwrap());
133+
| ^^^^^^
134+
135+
error: aborting due to 11 previous errors
100136

0 commit comments

Comments
 (0)