Skip to content

Commit 8485d40

Browse files
committed
Auto merge of rust-lang#5304 - sinkuu:redundant_clone_not_consumed, r=flip1995
Extend `redundant_clone` to the case that cloned value is not consumed Fixes rust-lang#5303. --- changelog: Extend `redundant_clone` to the case that cloned value is not consumed
2 parents b064ea8 + d9ad338 commit 8485d40

File tree

6 files changed

+136
-54
lines changed

6 files changed

+136
-54
lines changed

clippy_lints/src/redundant_clone.rs

+65-43
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use if_chain::if_chain;
66
use matches::matches;
77
use rustc::mir::{
88
self, traversal,
9-
visit::{MutatingUseContext, PlaceContext, Visitor as _},
9+
visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor as _},
1010
};
1111
use rustc::ty::{self, fold::TypeVisitor, Ty};
1212
use rustc_data_structures::{fx::FxHashMap, transitive_relation::TransitiveRelation};
@@ -110,7 +110,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantClone {
110110
continue;
111111
}
112112

113-
let (fn_def_id, arg, arg_ty, _) = unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind));
113+
let (fn_def_id, arg, arg_ty, clone_ret) =
114+
unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind));
114115

115116
let from_borrow = match_def_path(cx, fn_def_id, &paths::CLONE_TRAIT_METHOD)
116117
|| match_def_path(cx, fn_def_id, &paths::TO_OWNED_METHOD)
@@ -132,16 +133,16 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantClone {
132133
statement_index: bbdata.statements.len(),
133134
};
134135

135-
// Cloned local
136-
let local = if from_borrow {
136+
// `Local` to be cloned, and a local of `clone` call's destination
137+
let (local, ret_local) = if from_borrow {
137138
// `res = clone(arg)` can be turned into `res = move arg;`
138139
// if `arg` is the only borrow of `cloned` at this point.
139140

140141
if cannot_move_out || !possible_borrower.only_borrowers(&[arg], cloned, loc) {
141142
continue;
142143
}
143144

144-
cloned
145+
(cloned, clone_ret)
145146
} else {
146147
// `arg` is a reference as it is `.deref()`ed in the previous block.
147148
// Look into the predecessor block and find out the source of deref.
@@ -153,15 +154,15 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantClone {
153154
let pred_terminator = mir[ps[0]].terminator();
154155

155156
// receiver of the `deref()` call
156-
let pred_arg = if_chain! {
157-
if let Some((pred_fn_def_id, pred_arg, pred_arg_ty, Some(res))) =
157+
let (pred_arg, deref_clone_ret) = if_chain! {
158+
if let Some((pred_fn_def_id, pred_arg, pred_arg_ty, res)) =
158159
is_call_with_ref_arg(cx, mir, &pred_terminator.kind);
159-
if res.local == cloned;
160+
if res == cloned;
160161
if match_def_path(cx, pred_fn_def_id, &paths::DEREF_TRAIT_METHOD);
161162
if match_type(cx, pred_arg_ty, &paths::PATH_BUF)
162163
|| match_type(cx, pred_arg_ty, &paths::OS_STRING);
163164
then {
164-
pred_arg
165+
(pred_arg, res)
165166
} else {
166167
continue;
167168
}
@@ -188,25 +189,35 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantClone {
188189
continue;
189190
}
190191

191-
local
192+
(local, deref_clone_ret)
192193
};
193194

194-
// `local` cannot be moved out if it is used later
195-
let used_later = traversal::ReversePostorder::new(&mir, bb).skip(1).any(|(tbb, tdata)| {
196-
// Give up on loops
197-
if tdata.terminator().successors().any(|s| *s == bb) {
198-
return true;
199-
}
195+
let is_temp = mir_read_only.local_kind(ret_local) == mir::LocalKind::Temp;
196+
197+
// 1. `local` can be moved out if it is not used later.
198+
// 2. If `ret_local` is a temporary and is neither consumed nor mutated, we can remove this `clone`
199+
// call anyway.
200+
let (used, consumed_or_mutated) = traversal::ReversePostorder::new(&mir, bb).skip(1).fold(
201+
(false, !is_temp),
202+
|(used, consumed), (tbb, tdata)| {
203+
// Short-circuit
204+
if (used && consumed) ||
205+
// Give up on loops
206+
tdata.terminator().successors().any(|s| *s == bb)
207+
{
208+
return (true, true);
209+
}
200210

201-
let mut vis = LocalUseVisitor {
202-
local,
203-
used_other_than_drop: false,
204-
};
205-
vis.visit_basic_block_data(tbb, tdata);
206-
vis.used_other_than_drop
207-
});
211+
let mut vis = LocalUseVisitor {
212+
used: (local, false),
213+
consumed_or_mutated: (ret_local, false),
214+
};
215+
vis.visit_basic_block_data(tbb, tdata);
216+
(used || vis.used.1, consumed || vis.consumed_or_mutated.1)
217+
},
218+
);
208219

209-
if !used_later {
220+
if !used || !consumed_or_mutated {
210221
let span = terminator.source_info.span;
211222
let scope = terminator.source_info.scope;
212223
let node = mir.source_scopes[scope]
@@ -240,10 +251,17 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantClone {
240251
String::new(),
241252
app,
242253
);
243-
db.span_note(
244-
span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())),
245-
"this value is dropped without further use",
246-
);
254+
if used {
255+
db.span_note(
256+
span,
257+
"cloned value is neither consumed nor mutated",
258+
);
259+
} else {
260+
db.span_note(
261+
span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())),
262+
"this value is dropped without further use",
263+
);
264+
}
247265
});
248266
} else {
249267
span_lint_hir(cx, REDUNDANT_CLONE, node, span, "redundant clone");
@@ -259,7 +277,7 @@ fn is_call_with_ref_arg<'tcx>(
259277
cx: &LateContext<'_, 'tcx>,
260278
mir: &'tcx mir::Body<'tcx>,
261279
kind: &'tcx mir::TerminatorKind<'tcx>,
262-
) -> Option<(def_id::DefId, mir::Local, Ty<'tcx>, Option<&'tcx mir::Place<'tcx>>)> {
280+
) -> Option<(def_id::DefId, mir::Local, Ty<'tcx>, mir::Local)> {
263281
if_chain! {
264282
if let mir::TerminatorKind::Call { func, args, destination, .. } = kind;
265283
if args.len() == 1;
@@ -268,7 +286,7 @@ fn is_call_with_ref_arg<'tcx>(
268286
if let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].ty(&*mir, cx.tcx));
269287
if !is_copy(cx, inner_ty);
270288
then {
271-
Some((def_id, *local, inner_ty, destination.as_ref().map(|(dest, _)| dest)))
289+
Some((def_id, *local, inner_ty, destination.as_ref().map(|(dest, _)| dest)?.as_local()?))
272290
} else {
273291
None
274292
}
@@ -337,20 +355,15 @@ fn base_local_and_movability<'tcx>(
337355
}
338356

339357
struct LocalUseVisitor {
340-
local: mir::Local,
341-
used_other_than_drop: bool,
358+
used: (mir::Local, bool),
359+
consumed_or_mutated: (mir::Local, bool),
342360
}
343361

344362
impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor {
345363
fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
346364
let statements = &data.statements;
347365
for (statement_index, statement) in statements.iter().enumerate() {
348366
self.visit_statement(statement, mir::Location { block, statement_index });
349-
350-
// Once flagged, skip remaining statements
351-
if self.used_other_than_drop {
352-
return;
353-
}
354367
}
355368

356369
self.visit_terminator(
@@ -362,14 +375,23 @@ impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor {
362375
);
363376
}
364377

365-
fn visit_local(&mut self, local: &mir::Local, ctx: PlaceContext, _: mir::Location) {
366-
match ctx {
367-
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_) => return,
368-
_ => {},
378+
fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, _: mir::Location) {
379+
let local = place.local;
380+
381+
if local == self.used.0
382+
&& !matches!(ctx, PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_))
383+
{
384+
self.used.1 = true;
369385
}
370386

371-
if *local == self.local {
372-
self.used_other_than_drop = true;
387+
if local == self.consumed_or_mutated.0 {
388+
match ctx {
389+
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
390+
| PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
391+
self.consumed_or_mutated.1 = true;
392+
},
393+
_ => {},
394+
}
373395
}
374396
}
375397
}

tests/ui/format.fixed

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// run-rustfix
22

3-
#![allow(clippy::print_literal)]
3+
#![allow(clippy::print_literal, clippy::redundant_clone)]
44
#![warn(clippy::useless_format)]
55

66
struct Foo(pub String);

tests/ui/format.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// run-rustfix
22

3-
#![allow(clippy::print_literal)]
3+
#![allow(clippy::print_literal, clippy::redundant_clone)]
44
#![warn(clippy::useless_format)]
55

66
struct Foo(pub String);

tests/ui/redundant_clone.fixed

+24
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ fn main() {
5050
cannot_double_move(Alpha);
5151
cannot_move_from_type_with_drop();
5252
borrower_propagation();
53+
not_consumed();
5354
}
5455

5556
#[derive(Clone)]
@@ -136,3 +137,26 @@ fn borrower_propagation() {
136137
let _f = f.clone(); // ok
137138
}
138139
}
140+
141+
fn not_consumed() {
142+
let x = std::path::PathBuf::from("home");
143+
let y = x.join("matthias");
144+
// join() creates a new owned PathBuf, does not take a &mut to x variable, thus the .clone() is
145+
// redundant. (It also does not consume the PathBuf)
146+
147+
println!("x: {:?}, y: {:?}", x, y);
148+
149+
let mut s = String::new();
150+
s.clone().push_str("foo"); // OK, removing this `clone()` will change the behavior.
151+
s.push_str("bar");
152+
assert_eq!(s, "bar");
153+
154+
let t = Some(s);
155+
// OK
156+
if let Some(x) = t.clone() {
157+
println!("{}", x);
158+
}
159+
if let Some(x) = t {
160+
println!("{}", x);
161+
}
162+
}

tests/ui/redundant_clone.rs

+24
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ fn main() {
5050
cannot_double_move(Alpha);
5151
cannot_move_from_type_with_drop();
5252
borrower_propagation();
53+
not_consumed();
5354
}
5455

5556
#[derive(Clone)]
@@ -136,3 +137,26 @@ fn borrower_propagation() {
136137
let _f = f.clone(); // ok
137138
}
138139
}
140+
141+
fn not_consumed() {
142+
let x = std::path::PathBuf::from("home");
143+
let y = x.clone().join("matthias");
144+
// join() creates a new owned PathBuf, does not take a &mut to x variable, thus the .clone() is
145+
// redundant. (It also does not consume the PathBuf)
146+
147+
println!("x: {:?}, y: {:?}", x, y);
148+
149+
let mut s = String::new();
150+
s.clone().push_str("foo"); // OK, removing this `clone()` will change the behavior.
151+
s.push_str("bar");
152+
assert_eq!(s, "bar");
153+
154+
let t = Some(s);
155+
// OK
156+
if let Some(x) = t.clone() {
157+
println!("{}", x);
158+
}
159+
if let Some(x) = t {
160+
println!("{}", x);
161+
}
162+
}

tests/ui/redundant_clone.stderr

+21-9
Original file line numberDiff line numberDiff line change
@@ -108,52 +108,64 @@ LL | let _t = tup.0.clone();
108108
| ^^^^^
109109

110110
error: redundant clone
111-
--> $DIR/redundant_clone.rs:59:22
111+
--> $DIR/redundant_clone.rs:60:22
112112
|
113113
LL | (a.clone(), a.clone())
114114
| ^^^^^^^^ help: remove this
115115
|
116116
note: this value is dropped without further use
117-
--> $DIR/redundant_clone.rs:59:21
117+
--> $DIR/redundant_clone.rs:60:21
118118
|
119119
LL | (a.clone(), a.clone())
120120
| ^
121121

122122
error: redundant clone
123-
--> $DIR/redundant_clone.rs:119:15
123+
--> $DIR/redundant_clone.rs:120:15
124124
|
125125
LL | let _s = s.clone();
126126
| ^^^^^^^^ help: remove this
127127
|
128128
note: this value is dropped without further use
129-
--> $DIR/redundant_clone.rs:119:14
129+
--> $DIR/redundant_clone.rs:120:14
130130
|
131131
LL | let _s = s.clone();
132132
| ^
133133

134134
error: redundant clone
135-
--> $DIR/redundant_clone.rs:120:15
135+
--> $DIR/redundant_clone.rs:121:15
136136
|
137137
LL | let _t = t.clone();
138138
| ^^^^^^^^ help: remove this
139139
|
140140
note: this value is dropped without further use
141-
--> $DIR/redundant_clone.rs:120:14
141+
--> $DIR/redundant_clone.rs:121:14
142142
|
143143
LL | let _t = t.clone();
144144
| ^
145145

146146
error: redundant clone
147-
--> $DIR/redundant_clone.rs:130:19
147+
--> $DIR/redundant_clone.rs:131:19
148148
|
149149
LL | let _f = f.clone();
150150
| ^^^^^^^^ help: remove this
151151
|
152152
note: this value is dropped without further use
153-
--> $DIR/redundant_clone.rs:130:18
153+
--> $DIR/redundant_clone.rs:131:18
154154
|
155155
LL | let _f = f.clone();
156156
| ^
157157

158-
error: aborting due to 13 previous errors
158+
error: redundant clone
159+
--> $DIR/redundant_clone.rs:143:14
160+
|
161+
LL | let y = x.clone().join("matthias");
162+
| ^^^^^^^^ help: remove this
163+
|
164+
note: cloned value is neither consumed nor mutated
165+
--> $DIR/redundant_clone.rs:143:13
166+
|
167+
LL | let y = x.clone().join("matthias");
168+
| ^^^^^^^^^
169+
170+
error: aborting due to 14 previous errors
159171

0 commit comments

Comments
 (0)