Skip to content

Commit 40e1b0e

Browse files
committed
add lint cloned_ref_to_slice_refs
remove merge removed false positive and improved tests clarify known problems for `cloned_ref_to_slice_refs`
1 parent 781fdab commit 40e1b0e

8 files changed

+257
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5583,6 +5583,7 @@ Released 2018-09-13
55835583
[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
55845584
[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
55855585
[`cloned_instead_of_copied`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied
5586+
[`cloned_ref_to_slice_refs`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_ref_to_slice_refs
55865587
[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan
55875588
[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null
55885589
[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use clippy_config::Conf;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::msrvs::{self, Msrv};
4+
use clippy_utils::sugg::Sugg;
5+
use clippy_utils::visitors::is_const_evaluatable;
6+
use clippy_utils::{is_in_const_context, is_mutable, is_trait_method};
7+
use rustc_errors::Applicability;
8+
use rustc_hir::{Expr, ExprKind};
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_session::impl_lint_pass;
11+
use rustc_span::sym;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
///
16+
/// Checks for slice references with cloned references such as `&[f.clone()]`.
17+
///
18+
/// ### Why is this bad
19+
///
20+
/// A reference does not need to be owned in order to used as a slice.
21+
///
22+
/// ### Known problems
23+
///
24+
/// This lint does not know whether or not a clone implementation has side effects.
25+
///
26+
/// ### Example
27+
///
28+
/// ```ignore
29+
/// let data = 10;
30+
/// let data_ref = &data;
31+
/// take_slice(&[data_ref.clone()]);
32+
/// ```
33+
/// Use instead:
34+
/// ```ignore
35+
/// use std::slice;
36+
/// let data = 10;
37+
/// let data_ref = &data;
38+
/// take_slice(slice::from_ref(data_ref));
39+
/// ```
40+
#[clippy::version = "1.87.0"]
41+
pub CLONED_REF_TO_SLICE_REFS,
42+
perf,
43+
"cloning a reference for slice references"
44+
}
45+
46+
pub struct ClonedRefToSliceRefs<'a> {
47+
msrv: &'a Msrv,
48+
}
49+
impl<'a> ClonedRefToSliceRefs<'a> {
50+
pub fn new(conf: &'a Conf) -> Self {
51+
Self { msrv: &conf.msrv }
52+
}
53+
}
54+
55+
impl_lint_pass!(ClonedRefToSliceRefs<'_> => [CLONED_REF_TO_SLICE_REFS]);
56+
57+
impl<'tcx> LateLintPass<'tcx> for ClonedRefToSliceRefs<'_> {
58+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
59+
if self.msrv.meets(cx, {
60+
if is_in_const_context(cx) {
61+
msrvs::CONST_SLICE_FROM_REF
62+
} else {
63+
msrvs::SLICE_FROM_REF
64+
}
65+
})
66+
// `&[foo.clone()]` expressions
67+
&& let ExprKind::AddrOf(_, mutability, arr) = &expr.kind
68+
// mutable references would have a different meaning
69+
&& mutability.is_not()
70+
71+
// check for single item arrays
72+
&& let ExprKind::Array([item]) = &arr.kind
73+
74+
// check for clones
75+
&& let ExprKind::MethodCall(_, val, _, _) = item.kind
76+
&& is_trait_method(cx, item, sym::Clone)
77+
78+
// check for immutability or purity
79+
&& (!is_mutable(cx, val) || is_const_evaluatable(cx, val))
80+
81+
// get appropriate crate for `slice::from_ref`
82+
&& let Some(builtin_crate) = clippy_utils::std_or_core(cx)
83+
{
84+
let mut sugg = Sugg::hir(cx, val, "_");
85+
if !cx.typeck_results().expr_ty(val).is_ref() {
86+
sugg = sugg.addr();
87+
}
88+
89+
span_lint_and_sugg(
90+
cx,
91+
CLONED_REF_TO_SLICE_REFS,
92+
expr.span,
93+
format!("this call to `clone` can be replaced with `{builtin_crate}::slice::from_ref`"),
94+
"try",
95+
format!("{builtin_crate}::slice::from_ref({sugg})"),
96+
Applicability::MaybeIncorrect,
97+
);
98+
}
99+
}
100+
}

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
7575
crate::casts::ZERO_PTR_INFO,
7676
crate::cfg_not_test::CFG_NOT_TEST_INFO,
7777
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
78+
crate::cloned_ref_to_slice_refs::CLONED_REF_TO_SLICE_REFS_INFO,
7879
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
7980
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,
8081
crate::collapsible_if::COLLAPSIBLE_IF_INFO,

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ mod cargo;
9696
mod casts;
9797
mod cfg_not_test;
9898
mod checked_conversions;
99+
mod cloned_ref_to_slice_refs;
99100
mod cognitive_complexity;
100101
mod collapsible_if;
101102
mod collection_is_never_read;
@@ -943,5 +944,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
943944
store.register_late_pass(|_| Box::new(manual_option_as_slice::ManualOptionAsSlice::new(conf)));
944945
store.register_late_pass(|_| Box::new(single_option_map::SingleOptionMap));
945946
store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix));
947+
store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
946948
// add lints here, do not remove this comment, it's used in `new_lint`
947949
}

clippy_utils/src/msrvs.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ msrv_aliases! {
3838
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
3939
1,68,0 { PATH_MAIN_SEPARATOR_STR }
4040
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
41-
1,63,0 { CLONE_INTO }
41+
1,63,0 { CLONE_INTO, CONST_SLICE_FROM_REF }
4242
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }
4343
1,60,0 { ABS_DIFF }
4444
1,59,0 { THREAD_LOCAL_CONST_INIT }
@@ -68,7 +68,7 @@ msrv_aliases! {
6868
1,31,0 { OPTION_REPLACE }
6969
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
7070
1,29,0 { ITER_FLATTEN }
71-
1,28,0 { FROM_BOOL, REPEAT_WITH }
71+
1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF }
7272
1,27,0 { ITERATOR_TRY_FOLD }
7373
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
7474
1,24,0 { IS_ASCII_DIGIT }
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#![warn(clippy::cloned_ref_to_slice_refs)]
2+
3+
#[derive(Clone)]
4+
struct Data;
5+
6+
fn main() {
7+
{
8+
let data = Data;
9+
let data_ref = &data;
10+
let _ = std::slice::from_ref(data_ref); //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref`
11+
}
12+
13+
{
14+
let _ = std::slice::from_ref(&Data); //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref`
15+
}
16+
17+
{
18+
#[derive(Clone)]
19+
struct Point(i32, i32);
20+
21+
let _ = std::slice::from_ref(&Point(0, 0)); //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref`
22+
}
23+
24+
// the string was cloned with the intention to not mutate
25+
{
26+
struct BetterString(String);
27+
28+
let mut message = String::from("good");
29+
let sender = BetterString(message.clone());
30+
31+
message.push_str("bye!");
32+
33+
println!("{} {}", message, sender.0)
34+
}
35+
36+
// the string was cloned with the intention to not mutate
37+
{
38+
let mut x = String::from("Hello");
39+
let r = &[x.clone()];
40+
x.push('!');
41+
println!("r = `{}', x = `{x}'", r[0]);
42+
}
43+
44+
// mutable borrows may have the intention to clone
45+
{
46+
let data = Data;
47+
let data_ref = &data;
48+
let _ = &mut [data_ref.clone()];
49+
}
50+
51+
// `T::clone` is used to denote a clone with side effects
52+
{
53+
use std::sync::Arc;
54+
let data = Arc::new(Data);
55+
let _ = &[Arc::clone(&data)];
56+
}
57+
58+
// slices with multiple members can only be made from a singular reference
59+
{
60+
let data_1 = Data;
61+
let data_2 = Data;
62+
let _ = &[data_1.clone(), data_2.clone()];
63+
}
64+
}

tests/ui/cloned_ref_to_slice_refs.rs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#![warn(clippy::cloned_ref_to_slice_refs)]
2+
3+
#[derive(Clone)]
4+
struct Data;
5+
6+
fn main() {
7+
{
8+
let data = Data;
9+
let data_ref = &data;
10+
let _ = &[data_ref.clone()]; //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref`
11+
}
12+
13+
{
14+
let _ = &[Data.clone()]; //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref`
15+
}
16+
17+
{
18+
#[derive(Clone)]
19+
struct Point(i32, i32);
20+
21+
let _ = &[Point(0, 0).clone()]; //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref`
22+
}
23+
24+
// the string was cloned with the intention to not mutate
25+
{
26+
struct BetterString(String);
27+
28+
let mut message = String::from("good");
29+
let sender = BetterString(message.clone());
30+
31+
message.push_str("bye!");
32+
33+
println!("{} {}", message, sender.0)
34+
}
35+
36+
// the string was cloned with the intention to not mutate
37+
{
38+
let mut x = String::from("Hello");
39+
let r = &[x.clone()];
40+
x.push('!');
41+
println!("r = `{}', x = `{x}'", r[0]);
42+
}
43+
44+
// mutable borrows may have the intention to clone
45+
{
46+
let data = Data;
47+
let data_ref = &data;
48+
let _ = &mut [data_ref.clone()];
49+
}
50+
51+
// `T::clone` is used to denote a clone with side effects
52+
{
53+
use std::sync::Arc;
54+
let data = Arc::new(Data);
55+
let _ = &[Arc::clone(&data)];
56+
}
57+
58+
// slices with multiple members can only be made from a singular reference
59+
{
60+
let data_1 = Data;
61+
let data_2 = Data;
62+
let _ = &[data_1.clone(), data_2.clone()];
63+
}
64+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: this call to `clone` can be replaced with `std::slice::from_ref`
2+
--> tests/ui/cloned_ref_to_slice_refs.rs:10:17
3+
|
4+
LL | let _ = &[data_ref.clone()];
5+
| ^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(data_ref)`
6+
|
7+
= note: `-D clippy::cloned-ref-to-slice-refs` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::cloned_ref_to_slice_refs)]`
9+
10+
error: this call to `clone` can be replaced with `std::slice::from_ref`
11+
--> tests/ui/cloned_ref_to_slice_refs.rs:14:17
12+
|
13+
LL | let _ = &[Data.clone()];
14+
| ^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&Data)`
15+
16+
error: this call to `clone` can be replaced with `std::slice::from_ref`
17+
--> tests/ui/cloned_ref_to_slice_refs.rs:21:17
18+
|
19+
LL | let _ = &[Point(0, 0).clone()];
20+
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&Point(0, 0))`
21+
22+
error: aborting due to 3 previous errors
23+

0 commit comments

Comments
 (0)