Skip to content

Commit dc2f975

Browse files
committed
Auto merge of #12051 - y21:option_as_ref_cloned, r=dswij
new lint: `option_as_ref_cloned` Closes #12009 Adds a new lint that looks for `.as_ref().cloned()` on `Option`s. That's the same as just `.clone()`-ing the option directly. changelog: new lint: [`option_as_ref_cloned`]
2 parents 9eb2b22 + 692ec68 commit dc2f975

7 files changed

+136
-1
lines changed

CHANGELOG.md

+1
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

+1
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

+31-1
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;
@@ -3887,6 +3888,31 @@ declare_clippy_lint! {
38873888
"splitting a trimmed string at hard-coded newlines"
38883889
}
38893890

3891+
declare_clippy_lint! {
3892+
/// ### What it does
3893+
/// Checks for usage of `.as_ref().cloned()` and `.as_mut().cloned()` on `Option`s
3894+
///
3895+
/// ### Why is this bad?
3896+
/// This can be written more concisely by cloning the `Option` directly.
3897+
///
3898+
/// ### Example
3899+
/// ```no_run
3900+
/// fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> {
3901+
/// bar.as_ref().cloned()
3902+
/// }
3903+
/// ```
3904+
/// Use instead:
3905+
/// ```no_run
3906+
/// fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> {
3907+
/// bar.clone()
3908+
/// }
3909+
/// ```
3910+
#[clippy::version = "1.77.0"]
3911+
pub OPTION_AS_REF_CLONED,
3912+
pedantic,
3913+
"cloning an `Option` via `as_ref().cloned()`"
3914+
}
3915+
38903916
pub struct Methods {
38913917
avoid_breaking_exported_api: bool,
38923918
msrv: Msrv,
@@ -4043,6 +4069,7 @@ impl_lint_pass!(Methods => [
40434069
ITER_FILTER_IS_OK,
40444070
MANUAL_IS_VARIANT_AND,
40454071
STR_SPLIT_AT_NEWLINE,
4072+
OPTION_AS_REF_CLONED,
40464073
]);
40474074

40484075
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4290,7 +4317,10 @@ impl Methods {
42904317
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
42914318
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
42924319
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
4293-
("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv),
4320+
("cloned", []) => {
4321+
cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv);
4322+
option_as_ref_cloned::check(cx, recv, span);
4323+
},
42944324
("collect", []) if is_trait_method(cx, expr, sym::Iterator) => {
42954325
needless_collect::check(cx, span, expr, recv, call_span);
42964326
match method_call(recv) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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::{method_call, OPTION_AS_REF_CLONED};
9+
10+
pub(super) fn check(cx: &LateContext<'_>, cloned_recv: &Expr<'_>, cloned_ident_span: Span) {
11+
if let Some((method @ ("as_ref" | "as_mut"), as_ref_recv, [], as_ref_ident_span, _)) = method_call(cloned_recv)
12+
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(as_ref_recv).peel_refs(), sym::Option)
13+
{
14+
span_lint_and_sugg(
15+
cx,
16+
OPTION_AS_REF_CLONED,
17+
as_ref_ident_span.to(cloned_ident_span),
18+
&format!("cloning an `Option<_>` using `.{method}().cloned()`"),
19+
"this can be written more concisely by cloning the `Option<_>` directly",
20+
"clone".into(),
21+
Applicability::MachineApplicable,
22+
);
23+
}
24+
}

tests/ui/option_as_ref_cloned.fixed

+21
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 mut 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

+21
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 mut x = Some(String::new());
6+
7+
let _: Option<String> = x.as_ref().cloned();
8+
let _: Option<String> = x.as_mut().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

+37
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_mut().cloned()`
15+
--> $DIR/option_as_ref_cloned.rs:8:31
16+
|
17+
LL | let _: Option<String> = x.as_mut().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)