Skip to content

Commit f9dde4b

Browse files
committed
uplift clippy::clone_double_ref to rustc
1 parent d558796 commit f9dde4b

File tree

6 files changed

+173
-0
lines changed

6 files changed

+173
-0
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ lint_array_into_iter =
55
.use_explicit_into_iter_suggestion =
66
or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value
77
8+
lint_clone_double_ref =
9+
using `clone` on a double-reference, which copies the reference of type `{$ty}` instead of cloning the inner type
10+
11+
lint_clone_double_ref_try_deref = try dereferencing it
12+
13+
lint_clone_double_ref_sugg_explicit = or try being explicit if you are sure, that you want to clone a reference
14+
815
lint_enum_intrinsics_mem_discriminant =
916
the return value of `mem::discriminant` is unspecified when called with a non-enum type
1017
.note = the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `{$ty_param}`, which is not an enum.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use crate::lints::{CloneDoubleRefExplicit, CloneDoubleRefTryDeref};
2+
use crate::{lints, LateLintPass, LintContext};
3+
4+
use rustc_hir as hir;
5+
use rustc_hir::ExprKind;
6+
use rustc_middle::ty;
7+
use rustc_middle::ty::adjustment::Adjust;
8+
use rustc_span::sym;
9+
10+
declare_lint! {
11+
/// The `clone_double_ref` lint checks for usage of `.clone()` on an `&&T`,
12+
/// which copies the inner `&T`, instead of cloning the underlying `T` and
13+
/// can be confusing.
14+
pub CLONE_DOUBLE_REF,
15+
Warn,
16+
"using `clone` on `&&T`"
17+
}
18+
19+
declare_lint_pass!(CloneDoubleRef => [CLONE_DOUBLE_REF]);
20+
21+
impl<'tcx> LateLintPass<'tcx> for CloneDoubleRef {
22+
fn check_expr(&mut self, cx: &crate::LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) {
23+
let ExprKind::MethodCall(path, receiver, args, _) = &e.kind else { return; };
24+
25+
if path.ident.name != sym::clone || !args.is_empty() {
26+
return;
27+
}
28+
29+
let typeck_results = cx.typeck_results();
30+
31+
if typeck_results
32+
.type_dependent_def_id(e.hir_id)
33+
.and_then(|id| cx.tcx.trait_of_item(id))
34+
.zip(cx.tcx.lang_items().clone_trait())
35+
.map_or(true, |(x, y)| x != y)
36+
{
37+
return;
38+
}
39+
40+
let arg_adjustments = cx.typeck_results().expr_adjustments(receiver);
41+
42+
// https://github.com/rust-lang/rust-clippy/issues/9272
43+
if arg_adjustments.iter().any(|adj| matches!(adj.kind, Adjust::Deref(Some(_)))) {
44+
return;
45+
}
46+
47+
if !receiver.span.eq_ctxt(e.span) {
48+
return;
49+
}
50+
51+
let arg_ty = arg_adjustments
52+
.last()
53+
.map_or_else(|| cx.typeck_results().expr_ty(receiver), |a| a.target);
54+
55+
let ty = cx.typeck_results().expr_ty(e);
56+
57+
if let ty::Ref(_, inner, _) = arg_ty.kind()
58+
&& let ty::Ref(_, innermost, _) = inner.kind() {
59+
let mut inner_ty = innermost;
60+
let mut n = 1;
61+
while let ty::Ref(_, inner, _) = inner_ty.kind() {
62+
inner_ty = inner;
63+
n += 1;
64+
}
65+
let refs = "&".repeat(n);
66+
let derefs = "*".repeat(n);
67+
let start = e.span.with_hi(receiver.span.lo());
68+
let end = e.span.with_lo(receiver.span.hi());
69+
cx.emit_spanned_lint(CLONE_DOUBLE_REF, e.span, lints::CloneDoubleRef {
70+
ty,
71+
try_deref: CloneDoubleRefTryDeref {
72+
start,
73+
end,
74+
refs: refs.clone(),
75+
derefs,
76+
},
77+
explicit: CloneDoubleRefExplicit {
78+
start,
79+
end,
80+
refs,
81+
ty: *inner_ty,
82+
}
83+
});
84+
}
85+
}
86+
}

compiler/rustc_lint/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ extern crate tracing;
5050

5151
mod array_into_iter;
5252
pub mod builtin;
53+
mod clone_double_ref;
5354
mod context;
5455
mod deref_into_dyn_supertrait;
5556
mod early;
@@ -95,6 +96,7 @@ use rustc_span::Span;
9596

9697
use array_into_iter::ArrayIntoIter;
9798
use builtin::*;
99+
use clone_double_ref::CloneDoubleRef;
98100
use deref_into_dyn_supertrait::*;
99101
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
100102
use for_loops_over_fallibles::*;
@@ -199,6 +201,7 @@ late_lint_methods!(
199201
[
200202
BuiltinCombinedModuleLateLintPass,
201203
[
204+
CloneDoubleRef: CloneDoubleRef,
202205
ForLoopsOverFallibles: ForLoopsOverFallibles,
203206
DerefIntoDynSupertrait: DerefIntoDynSupertrait,
204207
HardwiredLints: HardwiredLints,

compiler/rustc_lint/src/lints.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,38 @@ pub struct BuiltinUnexpectedCliConfigValue {
564564
pub value: Symbol,
565565
}
566566

567+
#[derive(LintDiagnostic)]
568+
#[diag(lint_clone_double_ref)]
569+
pub struct CloneDoubleRef<'a> {
570+
pub ty: Ty<'a>,
571+
#[subdiagnostic]
572+
pub try_deref: CloneDoubleRefTryDeref,
573+
#[subdiagnostic]
574+
pub explicit: CloneDoubleRefExplicit<'a>,
575+
}
576+
577+
#[derive(Subdiagnostic)]
578+
#[multipart_suggestion(lint_clone_double_ref_sugg_explicit, applicability = "maybe-incorrect")]
579+
pub struct CloneDoubleRefExplicit<'a> {
580+
#[suggestion_part(code = "<{refs}{ty}>::clone(")]
581+
pub start: Span,
582+
#[suggestion_part(code = ")")]
583+
pub end: Span,
584+
pub refs: String,
585+
pub ty: Ty<'a>,
586+
}
587+
588+
#[derive(Subdiagnostic)]
589+
#[multipart_suggestion(lint_clone_double_ref_try_deref, applicability = "maybe-incorrect")]
590+
pub struct CloneDoubleRefTryDeref {
591+
#[suggestion_part(code = "{refs}({derefs}")]
592+
pub start: Span,
593+
#[suggestion_part(code = ").clone()")]
594+
pub end: Span,
595+
pub refs: String,
596+
pub derefs: String,
597+
}
598+
567599
// deref_into_dyn_supertrait.rs
568600
#[derive(LintDiagnostic)]
569601
#[diag(lint_supertrait_as_deref_target)]

tests/ui/lint/clone-double-ref.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#![feature(once_cell)]
2+
#![deny(clone_double_ref)]
3+
4+
pub fn clone_on_double_ref() {
5+
let x = vec![1];
6+
let y = &&x;
7+
let z: &Vec<_> = y.clone();
8+
//~^ ERROR using `clone` on a double-reference, which copies the reference of type `Vec<i32>`
9+
10+
println!("{:p} {:p}", *y, z);
11+
}
12+
13+
use std::sync::LazyLock;
14+
15+
pub static STRS: LazyLock<&str> = LazyLock::new(|| "First");
16+
17+
// https://github.com/rust-lang/rust-clippy/issues/9272
18+
fn rust_clippy_issue_9272() {
19+
let str = STRS.clone();
20+
println!("{str}")
21+
}
22+
23+
fn main() {}

tests/ui/lint/clone-double-ref.stderr

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: using `clone` on a double-reference, which copies the reference of type `Vec<i32>` instead of cloning the inner type
2+
--> $DIR/clone-double-ref.rs:7:22
3+
|
4+
LL | let z: &Vec<_> = y.clone();
5+
| ^^^^^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/clone-double-ref.rs:2:9
9+
|
10+
LL | #![deny(clone_double_ref)]
11+
| ^^^^^^^^^^^^^^^^
12+
help: try dereferencing it
13+
|
14+
LL | let z: &Vec<_> = &(*y).clone();
15+
| +++ ~~~~~~~~~
16+
help: or try being explicit if you are sure, that you want to clone a reference
17+
|
18+
LL | let z: &Vec<_> = <&Vec<i32>>::clone(y);
19+
| +++++++++++++++++++ ~
20+
21+
error: aborting due to previous error
22+

0 commit comments

Comments
 (0)