-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathclone_on_copy.rs
105 lines (98 loc) · 3.73 KB
/
clone_on_copy.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_copy;
use rustc_errors::Applicability;
use rustc_hir::{BindingMode, ByRef, Expr, ExprKind, MatchSource, Node, PatKind, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::print::with_forced_trimmed_paths;
use rustc_middle::ty::{self};
use rustc_span::symbol::{Symbol, sym};
use super::CLONE_ON_COPY;
/// Checks for the `CLONE_ON_COPY` lint.
#[allow(clippy::too_many_lines)]
pub(super) fn check(
cx: &LateContext<'_>,
expr: &Expr<'_>,
method_name: Symbol,
receiver: &Expr<'_>,
args: &[Expr<'_>],
) {
let arg = if method_name == sym::clone && args.is_empty() {
receiver
} else {
return;
};
if cx
.typeck_results()
.type_dependent_def_id(expr.hir_id)
.and_then(|id| cx.tcx.trait_of_item(id))
.zip(cx.tcx.lang_items().clone_trait())
.is_none_or(|(x, y)| x != y)
{
return;
}
let arg_adjustments = cx.typeck_results().expr_adjustments(arg);
let arg_ty = arg_adjustments
.last()
.map_or_else(|| cx.typeck_results().expr_ty(arg), |a| a.target);
let ty = cx.typeck_results().expr_ty(expr);
if let ty::Ref(_, inner, _) = arg_ty.kind()
&& let ty::Ref(..) = inner.kind()
{
return; // don't report clone_on_copy
}
if is_copy(cx, ty) {
let parent_is_suffix_expr = match cx.tcx.parent_hir_node(expr.hir_id) {
Node::Expr(parent) => match parent.kind {
// &*x is a nop, &x.clone() is not
ExprKind::AddrOf(..) => return,
// (*x).func() is useless, x.clone().func() can work in case func borrows self
ExprKind::MethodCall(_, self_arg, ..)
if expr.hir_id == self_arg.hir_id && ty != cx.typeck_results().expr_ty_adjusted(expr) =>
{
return;
},
// ? is a Call, makes sure not to rec *x?, but rather (*x)?
ExprKind::Call(hir_callee, [_]) => matches!(
hir_callee.kind,
ExprKind::Path(QPath::LangItem(rustc_hir::LangItem::TryTraitBranch, ..))
),
ExprKind::MethodCall(_, self_arg, ..) if expr.hir_id == self_arg.hir_id => true,
ExprKind::Match(_, _, MatchSource::TryDesugar(_) | MatchSource::AwaitDesugar)
| ExprKind::Field(..)
| ExprKind::Index(..) => true,
_ => false,
},
// local binding capturing a reference
Node::LetStmt(l) if matches!(l.pat.kind, PatKind::Binding(BindingMode(ByRef::Yes(_), _), ..)) => {
return;
},
_ => false,
};
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut app).0;
let deref_count = arg_adjustments
.iter()
.take_while(|adj| matches!(adj.kind, Adjust::Deref(_)))
.count();
let (help, sugg) = if deref_count == 0 {
("try removing the `clone` call", snip.into())
} else if parent_is_suffix_expr {
("try dereferencing it", format!("({}{snip})", "*".repeat(deref_count)))
} else {
("try dereferencing it", format!("{}{snip}", "*".repeat(deref_count)))
};
span_lint_and_sugg(
cx,
CLONE_ON_COPY,
expr.span,
with_forced_trimmed_paths!(format!(
"using `clone` on type `{ty}` which implements the `Copy` trait"
)),
help,
sugg,
app,
);
}
}