summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs132
1 files changed, 132 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs b/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs
new file mode 100644
index 000000000..0b38a0720
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs
@@ -0,0 +1,132 @@
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::get_parent_node;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::sugg;
+use clippy_utils::ty::is_copy;
+use rustc_errors::Applicability;
+use rustc_hir::{BindingAnnotation, Expr, ExprKind, MatchSource, Node, PatKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, adjustment::Adjust};
+use rustc_span::symbol::{sym, Symbol};
+
+use super::CLONE_DOUBLE_REF;
+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, args: &[Expr<'_>]) {
+ let arg = match args {
+ [arg] if method_name == sym::clone => arg,
+ _ => 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())
+ .map_or(true, |(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() {
+ if let ty::Ref(_, innermost, _) = inner.kind() {
+ span_lint_and_then(
+ cx,
+ CLONE_DOUBLE_REF,
+ expr.span,
+ &format!(
+ "using `clone` on a double-reference; \
+ this will copy the reference of type `{}` instead of cloning the inner type",
+ ty
+ ),
+ |diag| {
+ if let Some(snip) = sugg::Sugg::hir_opt(cx, arg) {
+ let mut ty = innermost;
+ let mut n = 0;
+ while let ty::Ref(_, inner, _) = ty.kind() {
+ ty = inner;
+ n += 1;
+ }
+ let refs = "&".repeat(n + 1);
+ let derefs = "*".repeat(n);
+ let explicit = format!("<{}{}>::clone({})", refs, ty, snip);
+ diag.span_suggestion(
+ expr.span,
+ "try dereferencing it",
+ format!("{}({}{}).clone()", refs, derefs, snip.deref()),
+ Applicability::MaybeIncorrect,
+ );
+ diag.span_suggestion(
+ expr.span,
+ "or try being explicit if you are sure, that you want to clone a reference",
+ explicit,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ },
+ );
+ return; // don't report clone_on_copy
+ }
+ }
+
+ if is_copy(cx, ty) {
+ let parent_is_suffix_expr = match get_parent_node(cx.tcx, expr.hir_id) {
+ Some(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;
+ },
+ 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
+ Some(Node::Local(l))
+ if matches!(
+ l.pat.kind,
+ PatKind::Binding(BindingAnnotation::Ref | BindingAnnotation::RefMut, ..)
+ ) =>
+ {
+ 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!("({}{})", "*".repeat(deref_count), snip))
+ } else {
+ ("try dereferencing it", format!("{}{}", "*".repeat(deref_count), snip))
+ };
+
+ span_lint_and_sugg(
+ cx,
+ CLONE_ON_COPY,
+ expr.span,
+ &format!("using `clone` on type `{}` which implements the `Copy` trait", ty),
+ help,
+ sugg,
+ app,
+ );
+ }
+}