about summary refs log tree commit diff
path: root/clippy_lints/src/methods
diff options
context:
space:
mode:
authorPhilipp Krones <hello@philkrones.com>2024-01-11 17:27:03 +0100
committerPhilipp Krones <hello@philkrones.com>2024-01-11 17:27:03 +0100
commitaa220c7ee7a51ab39bccd03ee90fa1c9365a589a (patch)
tree03b106a539ec79c34715e864ff35e8dc9b43b502 /clippy_lints/src/methods
parentbeeaee97859f34c7a618565e6ff8539b52ac3912 (diff)
downloadrust-aa220c7ee7a51ab39bccd03ee90fa1c9365a589a.tar.gz
rust-aa220c7ee7a51ab39bccd03ee90fa1c9365a589a.zip
Merge commit '26ac6aab023393c94edf42f38f6ad31196009643'
Diffstat (limited to 'clippy_lints/src/methods')
-rw-r--r--clippy_lints/src/methods/filter_map.rs3
-rw-r--r--clippy_lints/src/methods/iter_filter.rs206
-rw-r--r--clippy_lints/src/methods/manual_is_variant_and.rs59
-rw-r--r--clippy_lints/src/methods/map_clone.rs142
-rw-r--r--clippy_lints/src/methods/mod.rs115
-rw-r--r--clippy_lints/src/methods/option_as_ref_cloned.rs24
-rw-r--r--clippy_lints/src/methods/option_map_unwrap_or.rs2
-rw-r--r--clippy_lints/src/methods/path_ends_with_ext.rs2
-rw-r--r--clippy_lints/src/methods/redundant_as_str.rs6
-rw-r--r--clippy_lints/src/methods/str_split.rs38
-rw-r--r--clippy_lints/src/methods/unnecessary_to_owned.rs24
-rw-r--r--clippy_lints/src/methods/useless_asref.rs123
-rw-r--r--clippy_lints/src/methods/utils.rs1
13 files changed, 641 insertions, 104 deletions
diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs
index 7cfd3d346b6..e489899c19e 100644
--- a/clippy_lints/src/methods/filter_map.rs
+++ b/clippy_lints/src/methods/filter_map.rs
@@ -26,13 +26,14 @@ fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) ->
         hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
             let body = cx.tcx.hir().body(body);
             let closure_expr = peel_blocks(body.value);
-            let arg_id = body.params[0].pat.hir_id;
             match closure_expr.kind {
                 hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, receiver, ..) => {
                     if ident.name == method_name
                         && let hir::ExprKind::Path(path) = &receiver.kind
                         && let Res::Local(ref local) = cx.qpath_res(path, receiver.hir_id)
+                        && !body.params.is_empty()
                     {
+                        let arg_id = body.params[0].pat.hir_id;
                         return arg_id == *local;
                     }
                     false
diff --git a/clippy_lints/src/methods/iter_filter.rs b/clippy_lints/src/methods/iter_filter.rs
index ade8e3155fa..9f84321ced4 100644
--- a/clippy_lints/src/methods/iter_filter.rs
+++ b/clippy_lints/src/methods/iter_filter.rs
@@ -1,87 +1,197 @@
+use clippy_utils::ty::get_iterator_item_ty;
+use hir::ExprKind;
 use rustc_lint::{LateContext, LintContext};
 
 use super::{ITER_FILTER_IS_OK, ITER_FILTER_IS_SOME};
 
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::{indent_of, reindent_multiline};
-use clippy_utils::{is_trait_method, peel_blocks, span_contains_comment};
+use clippy_utils::{get_parent_expr, is_trait_method, peel_blocks, span_contains_comment};
 use rustc_errors::Applicability;
 use rustc_hir as hir;
-use rustc_hir::def::Res;
 use rustc_hir::QPath;
-use rustc_span::symbol::{sym, Symbol};
+use rustc_span::symbol::{sym, Ident, Symbol};
 use rustc_span::Span;
 use std::borrow::Cow;
 
-fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool {
-    match &expr.kind {
-        hir::ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name,
-        hir::ExprKind::Path(QPath::Resolved(_, segments)) => {
-            segments.segments.last().unwrap().ident.name == method_name
+///
+/// Returns true if the expression is a method call to `method_name`
+/// e.g. `a.method_name()` or `Option::method_name`.
+///
+/// The type-checker verifies for us that the method accepts the right kind of items
+/// (e.g. `Option::is_some` accepts `Option<_>`), so we don't need to check that.
+///
+/// How to capture each case:
+///
+/// `.filter(|a| { std::option::Option::is_some(a) })`
+///          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a closure, getting unwrapped and
+/// recursively checked.
+/// `std::option::Option::is_some(a)`
+///  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a call. It unwraps to a path with
+/// `QPath::TypeRelative`. Since this is a type relative path, we need to check the method name, the
+/// type, and that the parameter of the closure is passed in the call. This part is the dual of
+/// `receiver.method_name()` below.
+///
+/// `filter(std::option::Option::is_some);`
+///          ^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a type relative path, like above, we check the
+/// type and the method name.
+///
+/// `filter(|a| a.is_some());`
+///         ^^^^^^^^^^^^^^^ <- this is a method call inside a closure,
+/// we check that the parameter of the closure is the receiver of the method call and don't allow
+/// any other parameters.
+fn is_method(
+    cx: &LateContext<'_>,
+    expr: &hir::Expr<'_>,
+    type_symbol: Symbol,
+    method_name: Symbol,
+    params: &[&hir::Pat<'_>],
+) -> bool {
+    fn pat_is_recv(ident: Ident, param: &hir::Pat<'_>) -> bool {
+        match param.kind {
+            hir::PatKind::Binding(_, _, other, _) => ident == other,
+            hir::PatKind::Ref(pat, _) => pat_is_recv(ident, pat),
+            _ => false,
+        }
+    }
+    match expr.kind {
+        hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, recv, ..) => {
+            // compare the identifier of the receiver to the parameter
+            // we are in a filter => closure has a single parameter and a single, non-block
+            // expression, this means that the parameter shadows all outside variables with
+            // the same name => avoid FPs. If the parameter is not the receiver, then this hits
+            // outside variables => avoid FP
+            if ident.name == method_name
+                && let ExprKind::Path(QPath::Resolved(None, path)) = recv.kind
+                && let &[seg] = path.segments
+                && params.iter().any(|p| pat_is_recv(seg.ident, p))
+            {
+                return true;
+            }
+            false
+        },
+        // This is used to check for complete paths via `|a| std::option::Option::is_some(a)`
+        // this then unwraps to a path with `QPath::TypeRelative`
+        // we pass the params as they've been passed to the current call through the closure
+        hir::ExprKind::Call(expr, [param]) => {
+            // this will hit the `QPath::TypeRelative` case and check that the method name is correct
+            if is_method(cx, expr, type_symbol, method_name, params)
+                // we then check that this is indeed passing the parameter of the closure
+                && let ExprKind::Path(QPath::Resolved(None, path)) = param.kind
+                && let &[seg] = path.segments
+                && params.iter().any(|p| pat_is_recv(seg.ident, p))
+            {
+                return true;
+            }
+            false
+        },
+        hir::ExprKind::Path(QPath::TypeRelative(ty, mname)) => {
+            let ty = cx.typeck_results().node_type(ty.hir_id);
+            if let Some(did) = cx.tcx.get_diagnostic_item(type_symbol)
+                && ty.ty_adt_def() == cx.tcx.type_of(did).skip_binder().ty_adt_def()
+            {
+                return mname.ident.name == method_name;
+            }
+            false
         },
-        hir::ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name,
         hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
             let body = cx.tcx.hir().body(body);
             let closure_expr = peel_blocks(body.value);
-            let arg_id = body.params[0].pat.hir_id;
-            match closure_expr.kind {
-                hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, receiver, ..) => {
-                    if ident.name == method_name
-                        && let hir::ExprKind::Path(path) = &receiver.kind
-                        && let Res::Local(ref local) = cx.qpath_res(path, receiver.hir_id)
-                    {
-                        return arg_id == *local;
-                    }
-                    false
-                },
-                _ => false,
-            }
+            let params = body.params.iter().map(|param| param.pat).collect::<Vec<_>>();
+            is_method(cx, closure_expr, type_symbol, method_name, params.as_slice())
         },
         _ => false,
     }
 }
 
 fn parent_is_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
-    if let hir::Node::Expr(parent_expr) = cx.tcx.hir().get_parent(expr.hir_id) {
-        is_method(cx, parent_expr, rustc_span::sym::map)
-    } else {
-        false
+    if let Some(expr) = get_parent_expr(cx, expr)
+        && is_trait_method(cx, expr, sym::Iterator)
+        && let hir::ExprKind::MethodCall(path, _, _, _) = expr.kind
+        && path.ident.name == rustc_span::sym::map
+    {
+        return true;
     }
+    false
 }
 
-#[allow(clippy::too_many_arguments)]
-pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_arg: &hir::Expr<'_>, filter_span: Span) {
-    let is_iterator = is_trait_method(cx, expr, sym::Iterator);
-    let parent_is_not_map = !parent_is_map(cx, expr);
+enum FilterType {
+    IsSome,
+    IsOk,
+}
 
-    if is_iterator
-        && parent_is_not_map
-        && is_method(cx, filter_arg, sym!(is_some))
-        && !span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi()))
+/// Returns the `FilterType` of the expression if it is a filter over an Iter<Option> or
+/// Iter<Result> with the parent expression not being a map, and not having a comment in the span of
+/// the filter. If it is not a filter over an Iter<Option> or Iter<Result> then it returns None
+///
+/// How this is done:
+/// 1. we know that this is invoked in a method call with `filter` as the method name via `mod.rs`
+/// 2. we check that we are in a trait method. Therefore we are in an
+/// `(x as Iterator).filter({filter_arg})` method call.
+/// 3. we check that the parent expression is not a map. This is because we don't want to lint
+///    twice, and we already have a specialized lint for that.
+/// 4. we check that the span of the filter does not contain a comment.
+/// 5. we get the type of the `Item` in the `Iterator`, and compare against the type of Option and
+///   Result.
+/// 6. we finally check the contents of the filter argument to see if it is a call to `is_some` or
+///   `is_ok`.
+/// 7. if all of the above are true, then we return the `FilterType`
+fn expression_type(
+    cx: &LateContext<'_>,
+    expr: &hir::Expr<'_>,
+    filter_arg: &hir::Expr<'_>,
+    filter_span: Span,
+) -> Option<FilterType> {
+    if !is_trait_method(cx, expr, sym::Iterator)
+        || parent_is_map(cx, expr)
+        || span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi()))
     {
-        span_lint_and_sugg(
+        return None;
+    }
+    if let hir::ExprKind::MethodCall(_, receiver, _, _) = expr.kind
+        && let receiver_ty = cx.typeck_results().expr_ty(receiver)
+        && let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty)
+    {
+        if let Some(opt_defid) = cx.tcx.get_diagnostic_item(sym::Option)
+            && let opt_ty = cx.tcx.type_of(opt_defid).skip_binder()
+            && iter_item_ty.ty_adt_def() == opt_ty.ty_adt_def()
+            && is_method(cx, filter_arg, sym::Option, sym!(is_some), &[])
+        {
+            return Some(FilterType::IsSome);
+        }
+
+        if let Some(opt_defid) = cx.tcx.get_diagnostic_item(sym::Result)
+            && let opt_ty = cx.tcx.type_of(opt_defid).skip_binder()
+            && iter_item_ty.ty_adt_def() == opt_ty.ty_adt_def()
+            && is_method(cx, filter_arg, sym::Result, sym!(is_ok), &[])
+        {
+            return Some(FilterType::IsOk);
+        }
+    }
+    None
+}
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_arg: &hir::Expr<'_>, filter_span: Span) {
+    // we are in a filter inside an iterator
+    match expression_type(cx, expr, filter_arg, filter_span) {
+        None => (),
+        Some(FilterType::IsOk) => span_lint_and_sugg(
             cx,
-            ITER_FILTER_IS_SOME,
+            ITER_FILTER_IS_OK,
             filter_span.with_hi(expr.span.hi()),
-            "`filter` for `is_some` on iterator over `Option`",
+            "`filter` for `is_ok` on iterator over `Result`s",
             "consider using `flatten` instead",
             reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(),
             Applicability::HasPlaceholders,
-        );
-    }
-    if is_iterator
-        && parent_is_not_map
-        && is_method(cx, filter_arg, sym!(is_ok))
-        && !span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi()))
-    {
-        span_lint_and_sugg(
+        ),
+        Some(FilterType::IsSome) => span_lint_and_sugg(
             cx,
-            ITER_FILTER_IS_OK,
+            ITER_FILTER_IS_SOME,
             filter_span.with_hi(expr.span.hi()),
-            "`filter` for `is_ok` on iterator over `Result`s",
+            "`filter` for `is_some` on iterator over `Option`",
             "consider using `flatten` instead",
             reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(),
             Applicability::HasPlaceholders,
-        );
+        ),
     }
 }
diff --git a/clippy_lints/src/methods/manual_is_variant_and.rs b/clippy_lints/src/methods/manual_is_variant_and.rs
new file mode 100644
index 00000000000..d29acd4622a
--- /dev/null
+++ b/clippy_lints/src/methods/manual_is_variant_and.rs
@@ -0,0 +1,59 @@
+use clippy_config::msrvs::{Msrv, OPTION_RESULT_IS_VARIANT_AND};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_lint::LateContext;
+use rustc_span::{sym, Span};
+
+use super::MANUAL_IS_VARIANT_AND;
+
+pub(super) fn check<'tcx>(
+    cx: &LateContext<'_>,
+    expr: &'tcx rustc_hir::Expr<'_>,
+    map_recv: &'tcx rustc_hir::Expr<'_>,
+    map_arg: &'tcx rustc_hir::Expr<'_>,
+    map_span: Span,
+    msrv: &Msrv,
+) {
+    // Don't lint if:
+
+    // 1. the `expr` is generated by a macro
+    if expr.span.from_expansion() {
+        return;
+    }
+
+    // 2. the caller of `map()` is neither `Option` nor `Result`
+    let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Option);
+    let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Result);
+    if !is_option && !is_result {
+        return;
+    }
+
+    // 3. the caller of `unwrap_or_default` is neither `Option<bool>` nor `Result<bool, _>`
+    if !cx.typeck_results().expr_ty(expr).is_bool() {
+        return;
+    }
+
+    // 4. msrv doesn't meet `OPTION_RESULT_IS_VARIANT_AND`
+    if !msrv.meets(OPTION_RESULT_IS_VARIANT_AND) {
+        return;
+    }
+
+    let lint_msg = if is_option {
+        "called `map(<f>).unwrap_or_default()` on an `Option` value"
+    } else {
+        "called `map(<f>).unwrap_or_default()` on a `Result` value"
+    };
+    let suggestion = if is_option { "is_some_and" } else { "is_ok_and" };
+
+    span_lint_and_sugg(
+        cx,
+        MANUAL_IS_VARIANT_AND,
+        expr.span.with_lo(map_span.lo()),
+        lint_msg,
+        "use",
+        format!("{}({})", suggestion, snippet(cx, map_arg.span, "..")),
+        Applicability::MachineApplicable,
+    );
+}
diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs
index cc6eeaa86e5..f9f636bbbf7 100644
--- a/clippy_lints/src/methods/map_clone.rs
+++ b/clippy_lints/src/methods/map_clone.rs
@@ -2,9 +2,10 @@ use clippy_config::msrvs::{self, Msrv};
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet_with_applicability;
 use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
-use clippy_utils::{is_diag_trait_item, peel_blocks};
+use clippy_utils::{is_diag_trait_item, match_def_path, paths, peel_blocks};
 use rustc_errors::Applicability;
 use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
 use rustc_lint::LateContext;
 use rustc_middle::mir::Mutability;
 use rustc_middle::ty;
@@ -14,60 +15,110 @@ use rustc_span::{sym, Span};
 
 use super::MAP_CLONE;
 
+// If this `map` is called on an `Option` or a `Result` and the previous call is `as_ref`, we don't
+// run this lint because it would overlap with `useless_asref` which provides a better suggestion
+// in this case.
+fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_id: DefId) -> bool {
+    if is_diag_trait_item(cx, method_id, sym::Iterator) {
+        return true;
+    }
+    // We check if it's an `Option` or a `Result`.
+    if let Some(id) = cx.tcx.impl_of_method(method_id) {
+        let identity = cx.tcx.type_of(id).instantiate_identity();
+        if !is_type_diagnostic_item(cx, identity, sym::Option) && !is_type_diagnostic_item(cx, identity, sym::Result) {
+            return false;
+        }
+    } else {
+        return false;
+    }
+    // We check if the previous method call is `as_ref`.
+    if let hir::ExprKind::MethodCall(path1, receiver, _, _) = &e.kind
+        && let hir::ExprKind::MethodCall(path2, _, _, _) = &receiver.kind
+    {
+        return path2.ident.name != sym::as_ref || path1.ident.name != sym::map;
+    }
+
+    true
+}
+
 pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>, msrv: &Msrv) {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
-        && (cx.tcx.impl_of_method(method_id).map_or(false, |id| {
-            is_type_diagnostic_item(cx, cx.tcx.type_of(id).instantiate_identity(), sym::Option)
-        }) || is_diag_trait_item(cx, method_id, sym::Iterator))
-        && let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind
+        && should_run_lint(cx, e, method_id)
     {
-        let closure_body = cx.tcx.hir().body(body);
-        let closure_expr = peel_blocks(closure_body.value);
-        match closure_body.params[0].pat.kind {
-            hir::PatKind::Ref(inner, hir::Mutability::Not) => {
-                if let hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) = inner.kind {
-                    if ident_eq(name, closure_expr) {
-                        lint_explicit_closure(cx, e.span, recv.span, true, msrv);
-                    }
-                }
-            },
-            hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) => {
-                match closure_expr.kind {
-                    hir::ExprKind::Unary(hir::UnOp::Deref, inner) => {
-                        if ident_eq(name, inner) {
-                            if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() {
+        match arg.kind {
+            hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
+                let closure_body = cx.tcx.hir().body(body);
+                let closure_expr = peel_blocks(closure_body.value);
+                match closure_body.params[0].pat.kind {
+                    hir::PatKind::Ref(inner, hir::Mutability::Not) => {
+                        if let hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) = inner.kind {
+                            if ident_eq(name, closure_expr) {
                                 lint_explicit_closure(cx, e.span, recv.span, true, msrv);
                             }
                         }
                     },
-                    hir::ExprKind::MethodCall(method, obj, [], _) => {
-                        if ident_eq(name, obj) && method.ident.name == sym::clone
-                        && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
-                        && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
-                        && cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id)
-                        // no autoderefs
-                        && !cx.typeck_results().expr_adjustments(obj).iter()
-                            .any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
-                        {
-                            let obj_ty = cx.typeck_results().expr_ty(obj);
-                            if let ty::Ref(_, ty, mutability) = obj_ty.kind() {
-                                if matches!(mutability, Mutability::Not) {
-                                    let copy = is_copy(cx, *ty);
-                                    lint_explicit_closure(cx, e.span, recv.span, copy, msrv);
+                    hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) => {
+                        match closure_expr.kind {
+                            hir::ExprKind::Unary(hir::UnOp::Deref, inner) => {
+                                if ident_eq(name, inner) {
+                                    if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() {
+                                        lint_explicit_closure(cx, e.span, recv.span, true, msrv);
+                                    }
                                 }
-                            } else {
-                                lint_needless_cloning(cx, e.span, recv.span);
-                            }
+                            },
+                            hir::ExprKind::MethodCall(method, obj, [], _) => {
+                                if ident_eq(name, obj) && method.ident.name == sym::clone
+                                && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
+                                && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
+                                && cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id)
+                                // no autoderefs
+                                && !cx.typeck_results().expr_adjustments(obj).iter()
+                                    .any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
+                                {
+                                    let obj_ty = cx.typeck_results().expr_ty(obj);
+                                    if let ty::Ref(_, ty, mutability) = obj_ty.kind() {
+                                        if matches!(mutability, Mutability::Not) {
+                                            let copy = is_copy(cx, *ty);
+                                            lint_explicit_closure(cx, e.span, recv.span, copy, msrv);
+                                        }
+                                    } else {
+                                        lint_needless_cloning(cx, e.span, recv.span);
+                                    }
+                                }
+                            },
+                            hir::ExprKind::Call(call, [_]) => {
+                                if let hir::ExprKind::Path(qpath) = call.kind {
+                                    handle_path(cx, call, &qpath, e, recv);
+                                }
+                            },
+                            _ => {},
                         }
                     },
                     _ => {},
                 }
             },
+            hir::ExprKind::Path(qpath) => handle_path(cx, arg, &qpath, e, recv),
             _ => {},
         }
     }
 }
 
+fn handle_path(
+    cx: &LateContext<'_>,
+    arg: &hir::Expr<'_>,
+    qpath: &hir::QPath<'_>,
+    e: &hir::Expr<'_>,
+    recv: &hir::Expr<'_>,
+) {
+    if let Some(path_def_id) = cx.qpath_res(qpath, arg.hir_id).opt_def_id()
+        && match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD)
+    {
+        // FIXME: It would be better to infer the type to check if it's copyable or not
+        // to suggest to use `.copied()` instead of `.cloned()` where applicable.
+        lint_path(cx, e.span, recv.span);
+    }
+}
+
 fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool {
     if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind {
         path.segments.len() == 1 && path.segments[0].ident == name
@@ -88,6 +139,23 @@ fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) {
     );
 }
 
+fn lint_path(cx: &LateContext<'_>, replace: Span, root: Span) {
+    let mut applicability = Applicability::MachineApplicable;
+
+    span_lint_and_sugg(
+        cx,
+        MAP_CLONE,
+        replace,
+        "you are explicitly cloning with `.map()`",
+        "consider calling the dedicated `cloned` method",
+        format!(
+            "{}.cloned()",
+            snippet_with_applicability(cx, root, "..", &mut applicability),
+        ),
+        applicability,
+    );
+}
+
 fn lint_explicit_closure(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool, msrv: &Msrv) {
     let mut applicability = Applicability::MachineApplicable;
 
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index c1e126137df..89ea3597dc0 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -51,6 +51,7 @@ mod iter_skip_zero;
 mod iter_with_drain;
 mod iterator_step_by_zero;
 mod join_absolute_paths;
+mod manual_is_variant_and;
 mod manual_next_back;
 mod manual_ok_or;
 mod manual_saturating_arithmetic;
@@ -70,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;
@@ -93,6 +95,7 @@ mod single_char_pattern;
 mod single_char_push_string;
 mod skip_while_next;
 mod stable_sort_primitive;
+mod str_split;
 mod str_splitn;
 mod string_extend_chars;
 mod string_lit_chars_any;
@@ -2589,11 +2592,11 @@ declare_clippy_lint! {
 declare_clippy_lint! {
     /// ### What it does
     /// Checks for usage of `x.get(0)` instead of
-    /// `x.first()`.
+    /// `x.first()` or `x.front()`.
     ///
     /// ### Why is this bad?
-    /// Using `x.first()` is easier to read and has the same
-    /// result.
+    /// Using `x.first()` for `Vec`s and slices or `x.front()`
+    /// for `VecDeque`s is easier to read and has the same result.
     ///
     /// ### Example
     /// ```no_run
@@ -2609,7 +2612,7 @@ declare_clippy_lint! {
     #[clippy::version = "1.63.0"]
     pub GET_FIRST,
     style,
-    "Using `x.get(0)` when `x.first()` is simpler"
+    "Using `x.get(0)` when `x.first()` or `x.front()` is simpler"
 }
 
 declare_clippy_lint! {
@@ -3784,7 +3787,7 @@ declare_clippy_lint! {
     ///
     /// ### Why is this bad?
     /// This pattern is often followed by manual unwrapping of the `Option`. The simplification
-    /// results in more readable and succint code without the need for manual unwrapping.
+    /// results in more readable and succinct code without the need for manual unwrapping.
     ///
     /// ### Example
     /// ```no_run
@@ -3810,7 +3813,7 @@ declare_clippy_lint! {
     ///
     /// ### Why is this bad?
     /// This pattern is often followed by manual unwrapping of `Result`. The simplification
-    /// results in more readable and succint code without the need for manual unwrapping.
+    /// results in more readable and succinct code without the need for manual unwrapping.
     ///
     /// ### Example
     /// ```no_run
@@ -3829,6 +3832,87 @@ declare_clippy_lint! {
     "filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`"
 }
 
+declare_clippy_lint! {
+    /// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type.
+    ///
+    /// ### Why is this bad?
+    /// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// # let option = Some(1);
+    /// # let result: Result<usize, ()> = Ok(1);
+    /// option.map(|a| a > 10).unwrap_or_default();
+    /// result.map(|a| a > 10).unwrap_or_default();
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// # let option = Some(1);
+    /// # let result: Result<usize, ()> = Ok(1);
+    /// option.is_some_and(|a| a > 10);
+    /// result.is_ok_and(|a| a > 10);
+    /// ```
+    #[clippy::version = "1.76.0"]
+    pub MANUAL_IS_VARIANT_AND,
+    pedantic,
+    "using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`"
+}
+
+declare_clippy_lint! {
+    /// ### What it does
+    ///
+    /// Checks for usages of `str.trim().split("\n")` and `str.trim().split("\r\n")`.
+    ///
+    /// ### Why is this bad?
+    ///
+    /// Hard-coding the line endings makes the code less compatible. `str.lines` should be used instead.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// "some\ntext\nwith\nnewlines\n".trim().split('\n');
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// "some\ntext\nwith\nnewlines\n".lines();
+    /// ```
+    ///
+    /// ### Known Problems
+    ///
+    /// This lint cannot detect if the split is intentionally restricted to a single type of newline (`"\n"` or
+    /// `"\r\n"`), for example during the parsing of a specific file format in which precisely one newline type is
+    /// valid.
+    /// ```
+    #[clippy::version = "1.76.0"]
+    pub STR_SPLIT_AT_NEWLINE,
+    pedantic,
+    "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,
@@ -3983,6 +4067,9 @@ impl_lint_pass!(Methods => [
     RESULT_FILTER_MAP,
     ITER_FILTER_IS_SOME,
     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.
@@ -4230,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) {
@@ -4569,6 +4659,9 @@ impl Methods {
                 ("sort_unstable_by", [arg]) => {
                     unnecessary_sort_by::check(cx, expr, recv, arg, true);
                 },
+                ("split", [arg]) => {
+                    str_split::check(cx, expr, recv, arg);
+                },
                 ("splitn" | "rsplitn", [count_arg, pat_arg]) => {
                     if let Some(Constant::Int(count)) = constant(cx, cx.typeck_results(), count_arg) {
                         suspicious_splitn::check(cx, name, expr, recv, count);
@@ -4664,7 +4757,13 @@ impl Methods {
                     }
                     unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
                 },
-                ("unwrap_or_default" | "unwrap_unchecked" | "unwrap_err_unchecked", []) => {
+                ("unwrap_or_default", []) => {
+                    if let Some(("map", m_recv, [arg], span, _)) = method_call(recv) {
+                        manual_is_variant_and::check(cx, expr, m_recv, arg, span, &self.msrv);
+                    }
+                    unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
+                },
+                ("unwrap_unchecked" | "unwrap_err_unchecked", []) => {
                     unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
                 },
                 ("unwrap_or_else", [u_arg]) => {
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,
+        );
+    }
+}
diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs
index 47c9438c588..624597ffca9 100644
--- a/clippy_lints/src/methods/option_map_unwrap_or.rs
+++ b/clippy_lints/src/methods/option_map_unwrap_or.rs
@@ -72,7 +72,7 @@ pub(super) fn check<'tcx>(
         }
 
         // is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
-        let suggest_is_some_and = msrv.meets(msrvs::OPTION_IS_SOME_AND)
+        let suggest_is_some_and = msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND)
             && matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
             if matches!(lit.node, rustc_ast::LitKind::Bool(false)));
 
diff --git a/clippy_lints/src/methods/path_ends_with_ext.rs b/clippy_lints/src/methods/path_ends_with_ext.rs
index 094ead9f4ad..29f44ec2a4d 100644
--- a/clippy_lints/src/methods/path_ends_with_ext.rs
+++ b/clippy_lints/src/methods/path_ends_with_ext.rs
@@ -33,7 +33,7 @@ pub(super) fn check(
         && path.chars().all(char::is_alphanumeric)
     {
         let mut sugg = snippet(cx, recv.span, "..").into_owned();
-        if msrv.meets(msrvs::OPTION_IS_SOME_AND) {
+        if msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) {
             let _ = write!(sugg, r#".extension().is_some_and(|ext| ext == "{path}")"#);
         } else {
             let _ = write!(sugg, r#".extension().map_or(false, |ext| ext == "{path}")"#);
diff --git a/clippy_lints/src/methods/redundant_as_str.rs b/clippy_lints/src/methods/redundant_as_str.rs
index 2a2feedd2b4..24de1979c63 100644
--- a/clippy_lints/src/methods/redundant_as_str.rs
+++ b/clippy_lints/src/methods/redundant_as_str.rs
@@ -13,7 +13,11 @@ pub(super) fn check(
     as_str_span: Span,
     other_method_span: Span,
 ) {
-    if cx.typeck_results().expr_ty(recv).ty_adt_def().is_some_and(|adt| Some(adt.did()) == cx.tcx.lang_items().string())
+    if cx
+        .typeck_results()
+        .expr_ty(recv)
+        .ty_adt_def()
+        .is_some_and(|adt| Some(adt.did()) == cx.tcx.lang_items().string())
     {
         let mut applicability = Applicability::MachineApplicable;
         span_lint_and_sugg(
diff --git a/clippy_lints/src/methods/str_split.rs b/clippy_lints/src/methods/str_split.rs
new file mode 100644
index 00000000000..3586e11f56a
--- /dev/null
+++ b/clippy_lints/src/methods/str_split.rs
@@ -0,0 +1,38 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::visitors::is_const_evaluatable;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+
+use super::STR_SPLIT_AT_NEWLINE;
+
+pub(super) fn check<'a>(cx: &LateContext<'a>, expr: &'_ Expr<'_>, split_recv: &'a Expr<'_>, split_arg: &'_ Expr<'_>) {
+    // We're looking for `A.trim().split(B)`, where the adjusted type of `A` is `&str` (e.g. an
+    // expression returning `String`), and `B` is a `Pattern` that hard-codes a newline (either `"\n"`
+    // or `"\r\n"`). There are a lot of ways to specify a pattern, and this lint only checks the most
+    // basic ones: a `'\n'`, `"\n"`, and `"\r\n"`.
+    if let ExprKind::MethodCall(trim_method_name, trim_recv, [], _) = split_recv.kind
+        && trim_method_name.ident.as_str() == "trim"
+        && cx.typeck_results().expr_ty_adjusted(trim_recv).peel_refs().is_str()
+        && !is_const_evaluatable(cx, trim_recv)
+        && let ExprKind::Lit(split_lit) = split_arg.kind
+        && (matches!(split_lit.node, LitKind::Char('\n'))
+            || matches!(split_lit.node, LitKind::Str(sym, _) if (sym.as_str() == "\n" || sym.as_str() == "\r\n")))
+    {
+        let mut app = Applicability::MaybeIncorrect;
+        span_lint_and_sugg(
+            cx,
+            STR_SPLIT_AT_NEWLINE,
+            expr.span,
+            "using `str.trim().split()` with hard-coded newlines",
+            "use `str.lines()` instead",
+            format!(
+                "{}.lines()",
+                snippet_with_context(cx, trim_recv.span, expr.span.ctxt(), "..", &mut app).0
+            ),
+            app,
+        );
+    }
+}
diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs
index 637368e9361..64fcd9f8f45 100644
--- a/clippy_lints/src/methods/unnecessary_to_owned.rs
+++ b/clippy_lints/src/methods/unnecessary_to_owned.rs
@@ -3,13 +3,13 @@ use super::unnecessary_iter_cloned::{self, is_into_iter};
 use clippy_config::msrvs::{self, Msrv};
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet_opt;
-use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs};
+use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_lang_item, peel_mid_ty_refs};
 use clippy_utils::visitors::find_all_ret_expressions;
 use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
 use rustc_errors::Applicability;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::DefId;
-use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, Node};
+use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node};
 use rustc_hir_typeck::{FnCtxt, Inherited};
 use rustc_infer::infer::TyCtxtInferExt;
 use rustc_lint::LateContext;
@@ -246,6 +246,19 @@ fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symb
         && let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
         && let Some(arg_snippet) = snippet_opt(cx, argument_expr.span)
     {
+        // We may end-up here because of an expression like `x.to_string().split(…)` where the type of `x`
+        // implements `AsRef<str>` but does not implement `Deref<Target = str>`. In this case, we have to
+        // add `.as_ref()` to the suggestion.
+        let as_ref = if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String)
+            && let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
+            && cx.get_associated_type(cx.typeck_results().expr_ty(receiver), deref_trait_id, "Target")
+                != Some(cx.tcx.types.str_)
+        {
+            ".as_ref()"
+        } else {
+            ""
+        };
+
         // The next suggestion may be incorrect because the removal of the `to_owned`-like
         // function could cause the iterator to hold a reference to a resource that is used
         // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
@@ -255,7 +268,7 @@ fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symb
             parent.span,
             &format!("unnecessary use of `{method_name}`"),
             "use",
-            format!("{receiver_snippet}.split({arg_snippet})"),
+            format!("{receiver_snippet}{as_ref}.split({arg_snippet})"),
             Applicability::MaybeIncorrect,
         );
         return true;
@@ -445,7 +458,10 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<
                 {
                     let bound_fn_sig = cx.tcx.fn_sig(callee_def_id);
                     let fn_sig = bound_fn_sig.skip_binder();
-                    if let Some(arg_index) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == expr.hir_id)
+                    if let Some(arg_index) = recv
+                        .into_iter()
+                        .chain(call_args)
+                        .position(|arg| arg.hir_id == expr.hir_id)
                         && let param_ty = fn_sig.input(arg_index).skip_binder()
                         && let ty::Param(ParamTy { index: param_index , ..}) = *param_ty.kind()
                         // https://github.com/rust-lang/rust-clippy/issues/9504 and https://github.com/rust-lang/rust-clippy/issues/10021
diff --git a/clippy_lints/src/methods/useless_asref.rs b/clippy_lints/src/methods/useless_asref.rs
index 84ee64e88a6..66727e5a29d 100644
--- a/clippy_lints/src/methods/useless_asref.rs
+++ b/clippy_lints/src/methods/useless_asref.rs
@@ -1,19 +1,52 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet_with_applicability;
 use clippy_utils::ty::walk_ptrs_ty_depth;
-use clippy_utils::{get_parent_expr, is_trait_method};
+use clippy_utils::{get_parent_expr, is_diag_trait_item, match_def_path, paths, peel_blocks};
 use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_lint::LateContext;
-use rustc_span::sym;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_middle::ty::{Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
+use rustc_span::{sym, Span};
+
+use core::ops::ControlFlow;
 
 use super::USELESS_ASREF;
 
+/// Returns the first type inside the `Option`/`Result` type passed as argument.
+fn get_enum_ty(enum_ty: Ty<'_>) -> Option<Ty<'_>> {
+    struct ContainsTyVisitor {
+        level: usize,
+    }
+
+    impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsTyVisitor {
+        type BreakTy = Ty<'tcx>;
+
+        fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+            self.level += 1;
+            if self.level == 1 {
+                t.super_visit_with(self)
+            } else {
+                ControlFlow::Break(t)
+            }
+        }
+    }
+
+    match enum_ty.visit_with(&mut ContainsTyVisitor { level: 0 }) {
+        ControlFlow::Break(ty) => Some(ty),
+        ControlFlow::Continue(()) => None,
+    }
+}
+
 /// Checks for the `USELESS_ASREF` lint.
 pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, recvr: &hir::Expr<'_>) {
     // when we get here, we've already checked that the call name is "as_ref" or "as_mut"
     // check if the call is to the actual `AsRef` or `AsMut` trait
-    if is_trait_method(cx, expr, sym::AsRef) || is_trait_method(cx, expr, sym::AsMut) {
+    let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else {
+        return;
+    };
+
+    if is_diag_trait_item(cx, def_id, sym::AsRef) || is_diag_trait_item(cx, def_id, sym::AsMut) {
         // check if the type after `as_ref` or `as_mut` is the same as before
         let rcv_ty = cx.typeck_results().expr_ty(recvr);
         let res_ty = cx.typeck_results().expr_ty(expr);
@@ -39,5 +72,89 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str,
                 applicability,
             );
         }
+    } else if match_def_path(cx, def_id, &["core", "option", "Option", call_name])
+        || match_def_path(cx, def_id, &["core", "result", "Result", call_name])
+    {
+        let rcv_ty = cx.typeck_results().expr_ty(recvr).peel_refs();
+        let res_ty = cx.typeck_results().expr_ty(expr).peel_refs();
+
+        if let Some(rcv_ty) = get_enum_ty(rcv_ty)
+            && let Some(res_ty) = get_enum_ty(res_ty)
+            // If the only thing the `as_mut`/`as_ref` call is doing is adding references and not
+            // changing the type, then we can move forward.
+            && rcv_ty.peel_refs() == res_ty.peel_refs()
+            && let Some(parent) = get_parent_expr(cx, expr)
+            && let hir::ExprKind::MethodCall(segment, _, args, _) = parent.kind
+            && segment.ident.span != expr.span
+            // We check that the called method name is `map`.
+            && segment.ident.name == sym::map
+            // And that it only has one argument.
+            && let [arg] = args
+            && is_calling_clone(cx, arg)
+        {
+            lint_as_ref_clone(cx, expr.span.with_hi(parent.span.hi()), recvr, call_name);
+        }
+    }
+}
+
+fn check_qpath(cx: &LateContext<'_>, qpath: hir::QPath<'_>, hir_id: hir::HirId) -> bool {
+    // We check it's calling the `clone` method of the `Clone` trait.
+    if let Some(path_def_id) = cx.qpath_res(&qpath, hir_id).opt_def_id() {
+        match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD)
+    } else {
+        false
     }
 }
+
+fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
+    match arg.kind {
+        hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
+            // If it's a closure, we need to check what is called.
+            let closure_body = cx.tcx.hir().body(body);
+            let closure_expr = peel_blocks(closure_body.value);
+            match closure_expr.kind {
+                hir::ExprKind::MethodCall(method, obj, [], _) => {
+                    if method.ident.name == sym::clone
+                        && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
+                        && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
+                        // We check it's the `Clone` trait.
+                        && cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id)
+                        // no autoderefs
+                        && !cx.typeck_results().expr_adjustments(obj).iter()
+                            .any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
+                    {
+                        true
+                    } else {
+                        false
+                    }
+                },
+                hir::ExprKind::Call(call, [_]) => {
+                    if let hir::ExprKind::Path(qpath) = call.kind {
+                        check_qpath(cx, qpath, call.hir_id)
+                    } else {
+                        false
+                    }
+                },
+                _ => false,
+            }
+        },
+        hir::ExprKind::Path(qpath) => check_qpath(cx, qpath, arg.hir_id),
+        _ => false,
+    }
+}
+
+fn lint_as_ref_clone(cx: &LateContext<'_>, span: Span, recvr: &hir::Expr<'_>, call_name: &str) {
+    let mut applicability = Applicability::MachineApplicable;
+    span_lint_and_sugg(
+        cx,
+        USELESS_ASREF,
+        span,
+        &format!("this call to `{call_name}.map(...)` does nothing"),
+        "try",
+        format!(
+            "{}.clone()",
+            snippet_with_applicability(cx, recvr.span, "..", &mut applicability)
+        ),
+        applicability,
+    );
+}
diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs
index 9ad4250a141..3a0305b4d55 100644
--- a/clippy_lints/src/methods/utils.rs
+++ b/clippy_lints/src/methods/utils.rs
@@ -74,6 +74,7 @@ pub(super) fn get_hint_if_single_char_arg(
             match ch {
                 "'" => "\\'",
                 r"\" => "\\\\",
+                "\\\"" => "\"", // no need to escape `"` in `'"'`
                 _ => ch,
             }
         );