Skip to content

Commit db5bd9f

Browse files
committed
Improve clone_on_copy
Lint on `_.clone().method()` when method takes self by value Set applicability correctly Correct suggestion when the cloned value is a macro call. e.g. `m!(x).clone()` Don't lint when not using the `Clone` trait
1 parent 8e56a2b commit db5bd9f

File tree

5 files changed

+156
-77
lines changed

5 files changed

+156
-77
lines changed
Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,38 @@
1-
use clippy_utils::diagnostics::span_lint_and_then;
1+
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
2+
use clippy_utils::get_parent_node;
3+
use clippy_utils::source::snippet_with_context;
24
use clippy_utils::sugg;
35
use clippy_utils::ty::is_copy;
46
use rustc_errors::Applicability;
5-
use rustc_hir as hir;
7+
use rustc_hir::{BindingAnnotation, Expr, ExprKind, MatchSource, Node, PatKind};
68
use rustc_lint::LateContext;
7-
use rustc_middle::ty;
9+
use rustc_middle::ty::{self, adjustment::Adjust};
810
use rustc_span::symbol::{sym, Symbol};
911
use std::iter;
1012

1113
use super::CLONE_DOUBLE_REF;
1214
use super::CLONE_ON_COPY;
1315

1416
/// Checks for the `CLONE_ON_COPY` lint.
15-
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol, args: &[hir::Expr<'_>]) {
16-
if !(args.len() == 1 && method_name == sym::clone) {
17+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, args: &[Expr<'_>]) {
18+
let arg = match args {
19+
[arg] if method_name == sym::clone => arg,
20+
_ => return,
21+
};
22+
if cx
23+
.typeck_results()
24+
.type_dependent_def_id(expr.hir_id)
25+
.and_then(|id| cx.tcx.trait_of_item(id))
26+
.zip(cx.tcx.lang_items().clone_trait())
27+
.map_or(true, |(x, y)| x != y)
28+
{
1729
return;
1830
}
19-
let arg = &args[0];
20-
let arg_ty = cx.typeck_results().expr_ty_adjusted(&args[0]);
31+
let arg_adjustments = cx.typeck_results().expr_adjustments(arg);
32+
let arg_ty = arg_adjustments
33+
.last()
34+
.map_or_else(|| cx.typeck_results().expr_ty(arg), |a| a.target);
35+
2136
let ty = cx.typeck_results().expr_ty(expr);
2237
if let ty::Ref(_, inner, _) = arg_ty.kind() {
2338
if let ty::Ref(_, innermost, _) = inner.kind() {
@@ -61,57 +76,57 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Sym
6176
}
6277

6378
if is_copy(cx, ty) {
64-
let snip;
65-
if let Some(snippet) = sugg::Sugg::hir_opt(cx, arg) {
66-
let parent = cx.tcx.hir().get_parent_node(expr.hir_id);
67-
match &cx.tcx.hir().get(parent) {
68-
hir::Node::Expr(parent) => match parent.kind {
69-
// &*x is a nop, &x.clone() is not
70-
hir::ExprKind::AddrOf(..) => return,
71-
// (*x).func() is useless, x.clone().func() can work in case func borrows mutably
72-
hir::ExprKind::MethodCall(_, _, parent_args, _) if expr.hir_id == parent_args[0].hir_id => {
73-
return;
74-
},
75-
76-
_ => {},
77-
},
78-
hir::Node::Stmt(stmt) => {
79-
if let hir::StmtKind::Local(ref loc) = stmt.kind {
80-
if let hir::PatKind::Ref(..) = loc.pat.kind {
81-
// let ref y = *x borrows x, let ref y = x.clone() does not
82-
return;
83-
}
84-
}
85-
},
86-
_ => {},
79+
let parent_is_suffix_expr = match get_parent_node(cx.tcx, expr.hir_id) {
80+
Some(Node::Expr(parent)) => match parent.kind {
81+
// &*x is a nop, &x.clone() is not
82+
ExprKind::AddrOf(..) => return,
83+
// (*x).func() is useless, x.clone().func() can work in case func borrows self
84+
ExprKind::MethodCall(_, _, [self_arg, ..], _)
85+
if expr.hir_id == self_arg.hir_id && ty != cx.typeck_results().expr_ty_adjusted(expr) =>
86+
{
87+
return;
88+
}
89+
ExprKind::MethodCall(_, _, [self_arg, ..], _) if expr.hir_id == self_arg.hir_id => true,
90+
ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
91+
| ExprKind::Field(..)
92+
| ExprKind::Index(..) => true,
93+
_ => false,
94+
},
95+
// local binding capturing a reference
96+
Some(Node::Local(l))
97+
if matches!(
98+
l.pat.kind,
99+
PatKind::Binding(BindingAnnotation::Ref | BindingAnnotation::RefMut, ..)
100+
) =>
101+
{
102+
return;
87103
}
104+
_ => false,
105+
};
88106

89-
// x.clone() might have dereferenced x, possibly through Deref impls
90-
if cx.typeck_results().expr_ty(arg) == ty {
91-
snip = Some(("try removing the `clone` call", format!("{}", snippet)));
92-
} else {
93-
let deref_count = cx
94-
.typeck_results()
95-
.expr_adjustments(arg)
96-
.iter()
97-
.filter(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_)))
98-
.count();
99-
let derefs: String = iter::repeat('*').take(deref_count).collect();
100-
snip = Some(("try dereferencing it", format!("{}{}", derefs, snippet)));
101-
}
107+
let mut app = Applicability::MachineApplicable;
108+
let snip = snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut app).0;
109+
110+
let deref_count = arg_adjustments
111+
.iter()
112+
.take_while(|adj| matches!(adj.kind, Adjust::Deref(_)))
113+
.count();
114+
let (help, sugg) = if deref_count == 0 {
115+
("try removing the `clone` call", snip.into())
116+
} else if parent_is_suffix_expr {
117+
("try dereferencing it", format!("({}{})", "*".repeat(deref_count), snip))
102118
} else {
103-
snip = None;
104-
}
105-
span_lint_and_then(
119+
("try dereferencing it", format!("{}{}", "*".repeat(deref_count), snip))
120+
};
121+
122+
span_lint_and_sugg(
106123
cx,
107124
CLONE_ON_COPY,
108125
expr.span,
109126
&format!("using `clone` on type `{}` which implements the `Copy` trait", ty),
110-
|diag| {
111-
if let Some((text, snip)) = snip {
112-
diag.span_suggestion(expr.span, text, snip, Applicability::MachineApplicable);
113-
}
114-
},
127+
help,
128+
sugg,
129+
app,
115130
);
116131
}
117132
}

tests/ui/clone_on_copy.fixed

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
clippy::deref_addrof,
77
clippy::no_effect,
88
clippy::unnecessary_operation,
9-
clippy::vec_init_then_push
9+
clippy::vec_init_then_push,
10+
clippy::toplevel_ref_arg
1011
)]
1112

1213
use std::cell::RefCell;
@@ -29,6 +30,37 @@ fn clone_on_copy() {
2930
let rc = RefCell::new(0);
3031
*rc.borrow();
3132

33+
let x = 0u32;
34+
x.rotate_left(1);
35+
36+
#[derive(Clone, Copy)]
37+
struct Foo;
38+
impl Foo {
39+
fn clone(&self) -> u32 {
40+
0
41+
}
42+
}
43+
Foo.clone(); // ok, this is not the clone trait
44+
45+
macro_rules! m {
46+
($e:expr) => {{ $e }};
47+
}
48+
m!(42);
49+
50+
struct Wrap([u32; 2]);
51+
impl core::ops::Deref for Wrap {
52+
type Target = [u32; 2];
53+
fn deref(&self) -> &[u32; 2] {
54+
&self.0
55+
}
56+
}
57+
let x = Wrap([0, 0]);
58+
(*x)[0];
59+
60+
let x = 42;
61+
let ref y = x.clone(); // ok, binds by reference
62+
let ref mut y = x.clone(); // ok, binds by reference
63+
3264
// Issue #4348
3365
let mut x = 43;
3466
let _ = &x.clone(); // ok, getting a ref

tests/ui/clone_on_copy.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
clippy::deref_addrof,
77
clippy::no_effect,
88
clippy::unnecessary_operation,
9-
clippy::vec_init_then_push
9+
clippy::vec_init_then_push,
10+
clippy::toplevel_ref_arg
1011
)]
1112

1213
use std::cell::RefCell;
@@ -29,6 +30,37 @@ fn clone_on_copy() {
2930
let rc = RefCell::new(0);
3031
rc.borrow().clone();
3132

33+
let x = 0u32;
34+
x.clone().rotate_left(1);
35+
36+
#[derive(Clone, Copy)]
37+
struct Foo;
38+
impl Foo {
39+
fn clone(&self) -> u32 {
40+
0
41+
}
42+
}
43+
Foo.clone(); // ok, this is not the clone trait
44+
45+
macro_rules! m {
46+
($e:expr) => {{ $e }};
47+
}
48+
m!(42).clone();
49+
50+
struct Wrap([u32; 2]);
51+
impl core::ops::Deref for Wrap {
52+
type Target = [u32; 2];
53+
fn deref(&self) -> &[u32; 2] {
54+
&self.0
55+
}
56+
}
57+
let x = Wrap([0, 0]);
58+
x.clone()[0];
59+
60+
let x = 42;
61+
let ref y = x.clone(); // ok, binds by reference
62+
let ref mut y = x.clone(); // ok, binds by reference
63+
3264
// Issue #4348
3365
let mut x = 43;
3466
let _ = &x.clone(); // ok, getting a ref

tests/ui/clone_on_copy.stderr

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,52 @@
11
error: using `clone` on type `i32` which implements the `Copy` trait
2-
--> $DIR/clone_on_copy.rs:23:5
2+
--> $DIR/clone_on_copy.rs:24:5
33
|
44
LL | 42.clone();
55
| ^^^^^^^^^^ help: try removing the `clone` call: `42`
66
|
77
= note: `-D clippy::clone-on-copy` implied by `-D warnings`
88

99
error: using `clone` on type `i32` which implements the `Copy` trait
10-
--> $DIR/clone_on_copy.rs:27:5
10+
--> $DIR/clone_on_copy.rs:28:5
1111
|
1212
LL | (&42).clone();
1313
| ^^^^^^^^^^^^^ help: try dereferencing it: `*(&42)`
1414

1515
error: using `clone` on type `i32` which implements the `Copy` trait
16-
--> $DIR/clone_on_copy.rs:30:5
16+
--> $DIR/clone_on_copy.rs:31:5
1717
|
1818
LL | rc.borrow().clone();
1919
| ^^^^^^^^^^^^^^^^^^^ help: try dereferencing it: `*rc.borrow()`
2020

21+
error: using `clone` on type `u32` which implements the `Copy` trait
22+
--> $DIR/clone_on_copy.rs:34:5
23+
|
24+
LL | x.clone().rotate_left(1);
25+
| ^^^^^^^^^ help: try removing the `clone` call: `x`
26+
27+
error: using `clone` on type `i32` which implements the `Copy` trait
28+
--> $DIR/clone_on_copy.rs:48:5
29+
|
30+
LL | m!(42).clone();
31+
| ^^^^^^^^^^^^^^ help: try removing the `clone` call: `m!(42)`
32+
33+
error: using `clone` on type `[u32; 2]` which implements the `Copy` trait
34+
--> $DIR/clone_on_copy.rs:58:5
35+
|
36+
LL | x.clone()[0];
37+
| ^^^^^^^^^ help: try dereferencing it: `(*x)`
38+
2139
error: using `clone` on type `char` which implements the `Copy` trait
22-
--> $DIR/clone_on_copy.rs:36:14
40+
--> $DIR/clone_on_copy.rs:68:14
2341
|
2442
LL | is_ascii('z'.clone());
2543
| ^^^^^^^^^^^ help: try removing the `clone` call: `'z'`
2644

2745
error: using `clone` on type `i32` which implements the `Copy` trait
28-
--> $DIR/clone_on_copy.rs:40:14
46+
--> $DIR/clone_on_copy.rs:72:14
2947
|
3048
LL | vec.push(42.clone());
3149
| ^^^^^^^^^^ help: try removing the `clone` call: `42`
3250

33-
error: aborting due to 5 previous errors
51+
error: aborting due to 8 previous errors
3452

tests/ui/clone_on_copy_mut.rs

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)