about summary refs log tree commit diff
path: root/clippy_lints/src
diff options
context:
space:
mode:
authory21 <30553356+y21@users.noreply.github.com>2023-12-30 03:15:38 +0100
committery21 <30553356+y21@users.noreply.github.com>2024-01-03 19:40:47 +0100
commit5960107415caf34a84fc8bb2f2fd303b7e358041 (patch)
treef84b15dbc2271c8f2fe7ec56d2ac6c110644c070 /clippy_lints/src
parent0153ca95ae588f0423f919e199370b6ff02b02c1 (diff)
downloadrust-5960107415caf34a84fc8bb2f2fd303b7e358041.tar.gz
rust-5960107415caf34a84fc8bb2f2fd303b7e358041.zip
new lint: `option_as_ref_cloned`
Diffstat (limited to 'clippy_lints/src')
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/methods/mod.rs32
-rw-r--r--clippy_lints/src/methods/option_as_ref_cloned.rs24
3 files changed, 56 insertions, 1 deletions
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index f6c9ffea9fc..20230106d53 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -410,6 +410,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::methods::NO_EFFECT_REPLACE_INFO,
     crate::methods::OBFUSCATED_IF_ELSE_INFO,
     crate::methods::OK_EXPECT_INFO,
+    crate::methods::OPTION_AS_REF_CLONED_INFO,
     crate::methods::OPTION_AS_REF_DEREF_INFO,
     crate::methods::OPTION_FILTER_MAP_INFO,
     crate::methods::OPTION_MAP_OR_ERR_OK_INFO,
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index fbca641cfa3..cf6f6ad1a0f 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -71,6 +71,7 @@ mod no_effect_replace;
 mod obfuscated_if_else;
 mod ok_expect;
 mod open_options;
+mod option_as_ref_cloned;
 mod option_as_ref_deref;
 mod option_map_or_err_ok;
 mod option_map_or_none;
@@ -3887,6 +3888,31 @@ declare_clippy_lint! {
     "splitting a trimmed string at hard-coded newlines"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for usage of `.as_ref().cloned()` and `.as_mut().cloned()` on `Option`s
+    ///
+    /// ### Why is this bad?
+    /// This can be written more concisely by cloning the `Option` directly.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> {
+    ///     bar.as_ref().cloned()
+    /// }
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> {
+    ///     bar.clone()
+    /// }
+    /// ```
+    #[clippy::version = "1.77.0"]
+    pub OPTION_AS_REF_CLONED,
+    pedantic,
+    "cloning an `Option` via `as_ref().cloned()`"
+}
+
 pub struct Methods {
     avoid_breaking_exported_api: bool,
     msrv: Msrv,
@@ -4043,6 +4069,7 @@ impl_lint_pass!(Methods => [
     ITER_FILTER_IS_OK,
     MANUAL_IS_VARIANT_AND,
     STR_SPLIT_AT_NEWLINE,
+    OPTION_AS_REF_CLONED,
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
@@ -4290,7 +4317,10 @@ impl Methods {
                 ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
                 ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
                 ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
-                ("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv),
+                ("cloned", []) => {
+                    cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv);
+                    option_as_ref_cloned::check(cx, recv, span);
+                },
                 ("collect", []) if is_trait_method(cx, expr, sym::Iterator) => {
                     needless_collect::check(cx, span, expr, recv, call_span);
                     match method_call(recv) {
diff --git a/clippy_lints/src/methods/option_as_ref_cloned.rs b/clippy_lints/src/methods/option_as_ref_cloned.rs
new file mode 100644
index 00000000000..d7fec360fa2
--- /dev/null
+++ b/clippy_lints/src/methods/option_as_ref_cloned.rs
@@ -0,0 +1,24 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_span::{sym, Span};
+
+use super::{method_call, OPTION_AS_REF_CLONED};
+
+pub(super) fn check(cx: &LateContext<'_>, cloned_recv: &Expr<'_>, cloned_ident_span: Span) {
+    if let Some((method @ ("as_ref" | "as_mut"), as_ref_recv, [], as_ref_ident_span, _)) = method_call(cloned_recv)
+        && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(as_ref_recv).peel_refs(), sym::Option)
+    {
+        span_lint_and_sugg(
+            cx,
+            OPTION_AS_REF_CLONED,
+            as_ref_ident_span.to(cloned_ident_span),
+            &format!("cloning an `Option<_>` using `.{method}().cloned()`"),
+            "this can be written more concisely by cloning the `Option<_>` directly",
+            "clone".into(),
+            Applicability::MachineApplicable,
+        );
+    }
+}