Skip to content

Commit 22ad679

Browse files
committed
new lint: option_as_ref_cloned
1 parent e1dbafd commit 22ad679

7 files changed

+135
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5442,6 +5442,7 @@ Released 2018-09-13
54425442
[`only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#only_used_in_recursion
54435443
[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
54445444
[`option_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some
5445+
[`option_as_ref_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_cloned
54455446
[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
54465447
[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
54475448
[`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
410410
crate::methods::NO_EFFECT_REPLACE_INFO,
411411
crate::methods::OBFUSCATED_IF_ELSE_INFO,
412412
crate::methods::OK_EXPECT_INFO,
413+
crate::methods::OPTION_AS_REF_CLONED_INFO,
413414
crate::methods::OPTION_AS_REF_DEREF_INFO,
414415
crate::methods::OPTION_FILTER_MAP_INFO,
415416
crate::methods::OPTION_MAP_OR_ERR_OK_INFO,

clippy_lints/src/methods/mod.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ mod no_effect_replace;
7171
mod obfuscated_if_else;
7272
mod ok_expect;
7373
mod open_options;
74+
mod option_as_ref_cloned;
7475
mod option_as_ref_deref;
7576
mod option_map_or_err_ok;
7677
mod option_map_or_none;
@@ -3885,6 +3886,30 @@ declare_clippy_lint! {
38853886
pub STR_SPLIT_AT_NEWLINE,
38863887
pedantic,
38873888
"splitting a trimmed string at hard-coded newlines"
3889+
3890+
declare_clippy_lint! {
3891+
/// ### What it does
3892+
/// Checks for usage of `.as_ref().cloned()` on `Option`s
3893+
///
3894+
/// ### Why is this bad?
3895+
/// This can be written more concisely by cloning the option directly.
3896+
///
3897+
/// ### Example
3898+
/// ```no_run
3899+
/// fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> {
3900+
/// bar.as_ref().cloned()
3901+
/// }
3902+
/// ```
3903+
/// Use instead:
3904+
/// ```no_run
3905+
/// fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> {
3906+
/// bar.clone()
3907+
/// }
3908+
/// ```
3909+
#[clippy::version = "1.77.0"]
3910+
pub OPTION_AS_REF_CLONED,
3911+
pedantic,
3912+
"cloning an `Option` via `as_ref().cloned()`"
38883913
}
38893914

38903915
pub struct Methods {
@@ -4043,6 +4068,7 @@ impl_lint_pass!(Methods => [
40434068
ITER_FILTER_IS_OK,
40444069
MANUAL_IS_VARIANT_AND,
40454070
STR_SPLIT_AT_NEWLINE,
4071+
OPTION_AS_REF_CLONED,
40464072
]);
40474073

40484074
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4290,7 +4316,12 @@ impl Methods {
42904316
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
42914317
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
42924318
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
4293-
("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv),
4319+
("cloned", []) => {
4320+
cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv);
4321+
if let Some(("as_ref", recv, .., as_ref_ident_span)) = method_call(recv) {
4322+
option_as_ref_cloned::check(cx, recv, as_ref_ident_span, span);
4323+
}
4324+
},
42944325
("collect", []) if is_trait_method(cx, expr, sym::Iterator) => {
42954326
needless_collect::check(cx, span, expr, recv, call_span);
42964327
match method_call(recv) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::ty::is_type_diagnostic_item;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::Expr;
5+
use rustc_lint::LateContext;
6+
use rustc_span::{sym, Span};
7+
8+
use super::OPTION_AS_REF_CLONED;
9+
10+
pub(super) fn check(cx: &LateContext<'_>, as_ref_recv: &Expr<'_>, as_ref_ident_span: Span, cloned_span: Span) {
11+
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(as_ref_recv).peel_refs(), sym::Option) {
12+
span_lint_and_sugg(
13+
cx,
14+
OPTION_AS_REF_CLONED,
15+
as_ref_ident_span.to(cloned_span),
16+
"cloning an `Option<_>` using `.as_ref().cloned()`",
17+
"this can be written more concisely by cloning the `Option<_>` directly",
18+
"clone".into(),
19+
Applicability::MachineApplicable,
20+
);
21+
}
22+
}

tests/ui/option_as_ref_cloned.fixed

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![warn(clippy::option_as_ref_cloned)]
2+
#![allow(clippy::clone_on_copy)]
3+
4+
fn main() {
5+
let x = Some(String::new());
6+
7+
let _: Option<String> = x.clone();
8+
let _: Option<String> = x.clone();
9+
10+
let y = x.as_ref();
11+
let _: Option<&String> = y.clone();
12+
13+
macro_rules! cloned_recv {
14+
() => {
15+
x.as_ref()
16+
};
17+
}
18+
19+
// Don't lint when part of the expression is from a macro
20+
let _: Option<String> = cloned_recv!().cloned();
21+
}

tests/ui/option_as_ref_cloned.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![warn(clippy::option_as_ref_cloned)]
2+
#![allow(clippy::clone_on_copy)]
3+
4+
fn main() {
5+
let x = Some(String::new());
6+
7+
let _: Option<String> = x.as_ref().cloned();
8+
let _: Option<String> = x.as_ref().cloned();
9+
10+
let y = x.as_ref();
11+
let _: Option<&String> = y.as_ref().cloned();
12+
13+
macro_rules! cloned_recv {
14+
() => {
15+
x.as_ref()
16+
};
17+
}
18+
19+
// Don't lint when part of the expression is from a macro
20+
let _: Option<String> = cloned_recv!().cloned();
21+
}

tests/ui/option_as_ref_cloned.stderr

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
error: cloning an `Option<_>` using `.as_ref().cloned()`
2+
--> $DIR/option_as_ref_cloned.rs:7:31
3+
|
4+
LL | let _: Option<String> = x.as_ref().cloned();
5+
| ^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::option-as-ref-cloned` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::option_as_ref_cloned)]`
9+
help: this can be written more concisely by cloning the `Option<_>` directly
10+
|
11+
LL | let _: Option<String> = x.clone();
12+
| ~~~~~
13+
14+
error: cloning an `Option<_>` using `.as_ref().cloned()`
15+
--> $DIR/option_as_ref_cloned.rs:8:31
16+
|
17+
LL | let _: Option<String> = x.as_ref().cloned();
18+
| ^^^^^^^^^^^^^^^
19+
|
20+
help: this can be written more concisely by cloning the `Option<_>` directly
21+
|
22+
LL | let _: Option<String> = x.clone();
23+
| ~~~~~
24+
25+
error: cloning an `Option<_>` using `.as_ref().cloned()`
26+
--> $DIR/option_as_ref_cloned.rs:11:32
27+
|
28+
LL | let _: Option<&String> = y.as_ref().cloned();
29+
| ^^^^^^^^^^^^^^^
30+
|
31+
help: this can be written more concisely by cloning the `Option<_>` directly
32+
|
33+
LL | let _: Option<&String> = y.clone();
34+
| ~~~~~
35+
36+
error: aborting due to 3 previous errors
37+

0 commit comments

Comments
 (0)