Skip to content

Commit ada0b2b

Browse files
committed
Auto merge of #3518 - sinkuu:redundant_clone_tw, r=phansch
Lint redundant clone of fields Makes `redundant_clone` warn on unnecessary `foo.field.clone()` sometimes (it can detect an unnecessary clone only if the base of projection, `foo` in this case, is not used at all after that). This is enough for cases like `returns_tuple().0.clone()`.
2 parents 1b93f8d + e7d1808 commit ada0b2b

File tree

6 files changed

+132
-39
lines changed

6 files changed

+132
-39
lines changed

clippy_lints/src/attrs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ fn check_clippy_lint_names(cx: &LateContext<'_, '_>, items: &[NestedMetaItem]) {
332332
&format!("unknown clippy lint: clippy::{}", name),
333333
|db| {
334334
if name.as_str().chars().any(|c| c.is_uppercase()) {
335-
let name_lower = name.as_str().to_lowercase().to_string();
335+
let name_lower = name.as_str().to_lowercase();
336336
match lint_store.check_lint_name(
337337
&name_lower,
338338
Some(tool_name.as_str())

clippy_lints/src/no_effect.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ fn has_no_effect(cx: &LateContext<'_, '_>, expr: &Expr) -> bool {
5757
}
5858
match expr.node {
5959
ExprKind::Lit(..) | ExprKind::Closure(.., _) => true,
60-
ExprKind::Path(..) => !has_drop(cx, expr),
60+
ExprKind::Path(..) => !has_drop(cx, cx.tables.expr_ty(expr)),
6161
ExprKind::Index(ref a, ref b) | ExprKind::Binary(_, ref a, ref b) => {
6262
has_no_effect(cx, a) && has_no_effect(cx, b)
6363
},
@@ -70,7 +70,7 @@ fn has_no_effect(cx: &LateContext<'_, '_>, expr: &Expr) -> bool {
7070
| ExprKind::AddrOf(_, ref inner)
7171
| ExprKind::Box(ref inner) => has_no_effect(cx, inner),
7272
ExprKind::Struct(_, ref fields, ref base) => {
73-
!has_drop(cx, expr)
73+
!has_drop(cx, cx.tables.expr_ty(expr))
7474
&& fields.iter().all(|field| has_no_effect(cx, &field.expr))
7575
&& match *base {
7676
Some(ref base) => has_no_effect(cx, base),
@@ -82,7 +82,7 @@ fn has_no_effect(cx: &LateContext<'_, '_>, expr: &Expr) -> bool {
8282
let def = cx.tables.qpath_def(qpath, callee.hir_id);
8383
match def {
8484
Def::Struct(..) | Def::Variant(..) | Def::StructCtor(..) | Def::VariantCtor(..) => {
85-
!has_drop(cx, expr) && args.iter().all(|arg| has_no_effect(cx, arg))
85+
!has_drop(cx, cx.tables.expr_ty(expr)) && args.iter().all(|arg| has_no_effect(cx, arg))
8686
},
8787
_ => false,
8888
}
@@ -161,7 +161,7 @@ fn reduce_expression<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr) -> Option<Vec
161161
| ExprKind::AddrOf(_, ref inner)
162162
| ExprKind::Box(ref inner) => reduce_expression(cx, inner).or_else(|| Some(vec![inner])),
163163
ExprKind::Struct(_, ref fields, ref base) => {
164-
if has_drop(cx, expr) {
164+
if has_drop(cx, cx.tables.expr_ty(expr)) {
165165
None
166166
} else {
167167
Some(fields.iter().map(|f| &f.expr).chain(base).map(Deref::deref).collect())
@@ -172,7 +172,7 @@ fn reduce_expression<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr) -> Option<Vec
172172
let def = cx.tables.qpath_def(qpath, callee.hir_id);
173173
match def {
174174
Def::Struct(..) | Def::Variant(..) | Def::StructCtor(..) | Def::VariantCtor(..)
175-
if !has_drop(cx, expr) =>
175+
if !has_drop(cx, cx.tables.expr_ty(expr)) =>
176176
{
177177
Some(args.iter().collect())
178178
},

clippy_lints/src/redundant_clone.rs

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::rustc::hir::{def_id, Body, FnDecl};
1212
use crate::rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
1313
use crate::rustc::mir::{
1414
self, traversal,
15-
visit::{MutatingUseContext, NonUseContext, PlaceContext, Visitor},
15+
visit::{MutatingUseContext, PlaceContext, Visitor},
1616
TerminatorKind,
1717
};
1818
use crate::rustc::ty;
@@ -23,10 +23,11 @@ use crate::syntax::{
2323
source_map::{BytePos, Span},
2424
};
2525
use crate::utils::{
26-
in_macro, is_copy, match_def_path, match_type, paths, snippet_opt, span_lint_node, span_lint_node_and_then,
27-
walk_ptrs_ty_depth,
26+
has_drop, in_macro, is_copy, match_def_path, match_type, paths, snippet_opt, span_lint_node,
27+
span_lint_node_and_then, walk_ptrs_ty_depth,
2828
};
2929
use if_chain::if_chain;
30+
use matches::matches;
3031
use std::convert::TryFrom;
3132

3233
macro_rules! unwrap_or_continue {
@@ -126,7 +127,17 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantClone {
126127
// _1 in MIR `{ _2 = &_1; clone(move _2); }` or `{ _2 = _1; to_path_buf(_2); } (from_deref)
127128
// In case of `from_deref`, `arg` is already a reference since it is `deref`ed in the previous
128129
// block.
129-
let cloned = unwrap_or_continue!(find_stmt_assigns_to(arg, from_borrow, bbdata.statements.iter().rev()));
130+
let (cloned, cannot_move_out) = unwrap_or_continue!(find_stmt_assigns_to(
131+
cx,
132+
mir,
133+
arg,
134+
from_borrow,
135+
bbdata.statements.iter()
136+
));
137+
138+
if from_borrow && cannot_move_out {
139+
continue;
140+
}
130141

131142
// _1 in MIR `{ _2 = &_1; _3 = deref(move _2); } -> { _4 = _3; to_path_buf(move _4); }`
132143
let referent = if from_deref {
@@ -150,7 +161,17 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantClone {
150161
}
151162
};
152163

153-
unwrap_or_continue!(find_stmt_assigns_to(pred_arg, true, mir[ps[0]].statements.iter().rev()))
164+
let (local, cannot_move_out) = unwrap_or_continue!(find_stmt_assigns_to(
165+
cx,
166+
mir,
167+
pred_arg,
168+
true,
169+
mir[ps[0]].statements.iter()
170+
));
171+
if cannot_move_out {
172+
continue;
173+
}
174+
local
154175
} else {
155176
cloned
156177
};
@@ -227,27 +248,69 @@ fn is_call_with_ref_arg<'tcx>(
227248
}
228249
}
229250

230-
/// Finds the first `to = (&)from`, and returns `Some(from)`.
251+
type CannotMoveOut = bool;
252+
253+
/// Finds the first `to = (&)from`, and returns
254+
/// ``Some((from, [`true` if `from` cannot be moved out]))``.
231255
fn find_stmt_assigns_to<'a, 'tcx: 'a>(
256+
cx: &LateContext<'_, 'tcx>,
257+
mir: &mir::Mir<'tcx>,
232258
to: mir::Local,
233259
by_ref: bool,
234-
mut stmts: impl Iterator<Item = &'a mir::Statement<'tcx>>,
235-
) -> Option<mir::Local> {
236-
stmts.find_map(|stmt| {
237-
if let mir::StatementKind::Assign(mir::Place::Local(local), v) = &stmt.kind {
238-
if *local == to {
239-
if by_ref {
240-
if let mir::Rvalue::Ref(_, _, mir::Place::Local(r)) = **v {
241-
return Some(r);
242-
}
243-
} else if let mir::Rvalue::Use(mir::Operand::Copy(mir::Place::Local(r))) = **v {
244-
return Some(r);
260+
stmts: impl DoubleEndedIterator<Item = &'a mir::Statement<'tcx>>,
261+
) -> Option<(mir::Local, CannotMoveOut)> {
262+
stmts
263+
.rev()
264+
.find_map(|stmt| {
265+
if let mir::StatementKind::Assign(mir::Place::Local(local), v) = &stmt.kind {
266+
if *local == to {
267+
return Some(v);
245268
}
246269
}
247-
}
248270

249-
None
250-
})
271+
None
272+
})
273+
.and_then(|v| {
274+
if by_ref {
275+
if let mir::Rvalue::Ref(_, _, ref place) = **v {
276+
return base_local_and_movability(cx, mir, place);
277+
}
278+
} else if let mir::Rvalue::Use(mir::Operand::Copy(ref place)) = **v {
279+
return base_local_and_movability(cx, mir, place);
280+
}
281+
None
282+
})
283+
}
284+
285+
/// Extracts and returns the undermost base `Local` of given `place`. Returns `place` itself
286+
/// if it is already a `Local`.
287+
///
288+
/// Also reports whether given `place` cannot be moved out.
289+
fn base_local_and_movability<'tcx>(
290+
cx: &LateContext<'_, 'tcx>,
291+
mir: &mir::Mir<'tcx>,
292+
mut place: &mir::Place<'tcx>,
293+
) -> Option<(mir::Local, CannotMoveOut)> {
294+
use rustc::mir::Place::*;
295+
296+
// Dereference. You cannot move things out from a borrowed value.
297+
let mut deref = false;
298+
// Accessing a field of an ADT that has `Drop`. Moving the field out will cause E0509.
299+
let mut field = false;
300+
301+
loop {
302+
match place {
303+
Local(local) => return Some((*local, deref || field)),
304+
Projection(proj) => {
305+
place = &proj.base;
306+
deref = deref || matches!(proj.elem, mir::ProjectionElem::Deref);
307+
if !field && matches!(proj.elem, mir::ProjectionElem::Field(..)) {
308+
field = has_drop(cx, place.ty(&mir.local_decls, cx.tcx).to_ty(cx.tcx));
309+
}
310+
},
311+
_ => return None,
312+
}
313+
}
251314
}
252315

253316
struct LocalUseVisitor {
@@ -279,9 +342,7 @@ impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor {
279342

280343
fn visit_local(&mut self, local: &mir::Local, ctx: PlaceContext<'tcx>, _: mir::Location) {
281344
match ctx {
282-
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(NonUseContext::StorageDead) => {
283-
return;
284-
},
345+
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_) => return,
285346
_ => {},
286347
}
287348

clippy_lints/src/utils/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,8 @@ pub fn implements_trait<'a, 'tcx>(
266266
}
267267

268268
/// Check whether this type implements Drop.
269-
pub fn has_drop(cx: &LateContext<'_, '_>, expr: &Expr) -> bool {
270-
let struct_ty = cx.tables.expr_ty(expr);
271-
match struct_ty.ty_adt_def() {
269+
pub fn has_drop<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>) -> bool {
270+
match ty.ty_adt_def() {
272271
Some(def) => def.has_dtor(cx.tcx),
273272
_ => false,
274273
}

tests/ui/redundant_clone.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,35 @@ fn main() {
3434

3535
// Check that lint level works
3636
#[allow(clippy::redundant_clone)] let _ = String::new().to_string();
37+
38+
let tup = (String::from("foo"),);
39+
let _ = tup.0.clone();
40+
41+
let tup_ref = &(String::from("foo"),);
42+
let _s = tup_ref.0.clone(); // this `.clone()` cannot be removed
3743
}
3844

3945
#[derive(Clone)]
4046
struct Alpha;
41-
fn double(a: Alpha) -> (Alpha, Alpha) {
42-
if true {
47+
fn with_branch(a: Alpha, b: bool) -> (Alpha, Alpha) {
48+
if b {
4349
(a.clone(), a.clone())
4450
} else {
4551
(Alpha, a)
4652
}
4753
}
54+
55+
struct TypeWithDrop {
56+
x: String,
57+
}
58+
59+
impl Drop for TypeWithDrop {
60+
fn drop(&mut self) {}
61+
}
62+
63+
fn cannot_move_from_type_with_drop() -> String {
64+
let s = TypeWithDrop {
65+
x: String::new()
66+
};
67+
s.x.clone() // removing this `clone()` summons E0509
68+
}

tests/ui/redundant_clone.stderr

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,28 @@ note: this value is dropped without further use
9696
| ^^^^^^^^^^^^^^^
9797

9898
error: redundant clone
99-
--> $DIR/redundant_clone.rs:43:22
99+
--> $DIR/redundant_clone.rs:39:18
100100
|
101-
43 | (a.clone(), a.clone())
101+
39 | let _ = tup.0.clone();
102+
| ^^^^^^^^ help: remove this
103+
|
104+
note: this value is dropped without further use
105+
--> $DIR/redundant_clone.rs:39:13
106+
|
107+
39 | let _ = tup.0.clone();
108+
| ^^^^^
109+
110+
error: redundant clone
111+
--> $DIR/redundant_clone.rs:49:22
112+
|
113+
49 | (a.clone(), a.clone())
102114
| ^^^^^^^^ help: remove this
103115
|
104116
note: this value is dropped without further use
105-
--> $DIR/redundant_clone.rs:43:21
117+
--> $DIR/redundant_clone.rs:49:21
106118
|
107-
43 | (a.clone(), a.clone())
119+
49 | (a.clone(), a.clone())
108120
| ^
109121

110-
error: aborting due to 9 previous errors
122+
error: aborting due to 10 previous errors
111123

0 commit comments

Comments
 (0)