about summary refs log tree commit diff
path: root/clippy_lints/src/methods
diff options
context:
space:
mode:
authorPhilipp Krones <hello@philkrones.com>2024-11-07 17:09:56 +0100
committerPhilipp Krones <hello@philkrones.com>2024-11-07 17:22:32 +0100
commitb816d4ee4fc2ea265d862727b12ec9672223fa80 (patch)
tree3797bdaf06f030cca550f050d04b96dcd5ddadf7 /clippy_lints/src/methods
parent4847c40c8b40cc2a1155204b934f7a3c29178782 (diff)
parentab560d88e926462ae9e3705c71a8b7ae3a08ef5c (diff)
downloadrust-b816d4ee4fc2ea265d862727b12ec9672223fa80.tar.gz
rust-b816d4ee4fc2ea265d862727b12ec9672223fa80.zip
Merge remote-tracking branch 'upstream/master' into rustup
Diffstat (limited to 'clippy_lints/src/methods')
-rw-r--r--clippy_lints/src/methods/filter_map.rs8
-rw-r--r--clippy_lints/src/methods/is_empty.rs13
-rw-r--r--clippy_lints/src/methods/iter_out_of_bounds.rs5
-rw-r--r--clippy_lints/src/methods/map_all_any_identity.rs43
-rw-r--r--clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs134
-rw-r--r--clippy_lints/src/methods/mod.rs134
-rw-r--r--clippy_lints/src/methods/needless_as_bytes.rs28
-rw-r--r--clippy_lints/src/methods/needless_collect.rs2
-rw-r--r--clippy_lints/src/methods/read_line_without_trim.rs4
-rw-r--r--clippy_lints/src/methods/unnecessary_filter_map.rs33
-rw-r--r--clippy_lints/src/methods/unnecessary_sort_by.rs3
11 files changed, 376 insertions, 31 deletions
diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs
index 06482a743dd..c1653b65e98 100644
--- a/clippy_lints/src/methods/filter_map.rs
+++ b/clippy_lints/src/methods/filter_map.rs
@@ -21,8 +21,8 @@ fn is_method(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol) -> bool
         ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name,
         ExprKind::Path(QPath::Resolved(_, segments)) => segments.segments.last().unwrap().ident.name == method_name,
         ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name,
-        ExprKind::Closure(&Closure { body, .. }) => {
-            let body = cx.tcx.hir().body(body);
+        ExprKind::Closure(Closure { body, .. }) => {
+            let body = cx.tcx.hir().body(*body);
             let closure_expr = peel_blocks(body.value);
             match closure_expr.kind {
                 ExprKind::MethodCall(PathSegment { ident, .. }, receiver, ..) => {
@@ -234,12 +234,12 @@ impl<'tcx> OffendingFilterExpr<'tcx> {
             // the latter only calls `effect` once
             let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span);
 
-            if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name == sym!(is_some) {
+            if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name.as_str() == "is_some" {
                 Some(Self::IsSome {
                     receiver,
                     side_effect_expr_span,
                 })
-            } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name == sym!(is_ok) {
+            } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name.as_str() == "is_ok" {
                 Some(Self::IsOk {
                     receiver,
                     side_effect_expr_span,
diff --git a/clippy_lints/src/methods/is_empty.rs b/clippy_lints/src/methods/is_empty.rs
index cc82f6cfd63..a0c21faaa4c 100644
--- a/clippy_lints/src/methods/is_empty.rs
+++ b/clippy_lints/src/methods/is_empty.rs
@@ -1,6 +1,7 @@
 use clippy_utils::consts::ConstEvalCtxt;
 use clippy_utils::diagnostics::span_lint;
-use clippy_utils::{find_binding_init, path_to_local};
+use clippy_utils::macros::{is_assert_macro, root_macro_call};
+use clippy_utils::{find_binding_init, get_parent_expr, is_inside_always_const_context, path_to_local};
 use rustc_hir::{Expr, HirId};
 use rustc_lint::{LateContext, LintContext};
 use rustc_middle::lint::in_external_macro;
@@ -14,6 +15,16 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_
     if in_external_macro(cx.sess(), expr.span) || !receiver.span.eq_ctxt(expr.span) {
         return;
     }
+    if let Some(parent) = get_parent_expr(cx, expr) {
+        if let Some(parent) = get_parent_expr(cx, parent) {
+            if is_inside_always_const_context(cx.tcx, expr.hir_id)
+                && let Some(macro_call) = root_macro_call(parent.span)
+                && is_assert_macro(cx, macro_call.def_id)
+            {
+                return;
+            }
+        }
+    }
     let init_expr = expr_or_init(cx, receiver);
     if !receiver.span.eq_ctxt(init_expr.span) {
         return;
diff --git a/clippy_lints/src/methods/iter_out_of_bounds.rs b/clippy_lints/src/methods/iter_out_of_bounds.rs
index 9d462bd1845..9a62b719a8f 100644
--- a/clippy_lints/src/methods/iter_out_of_bounds.rs
+++ b/clippy_lints/src/methods/iter_out_of_bounds.rs
@@ -29,10 +29,7 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) ->
     if cx.tcx.is_diagnostic_item(sym::ArrayIntoIter, did) {
         // For array::IntoIter<T, const N: usize>, the length is the second generic
         // parameter.
-        substs
-            .const_at(1)
-            .try_to_target_usize(cx.tcx)
-            .map(u128::from)
+        substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from)
     } else if cx.tcx.is_diagnostic_item(sym::SliceIter, did)
         && let ExprKind::MethodCall(_, recv, ..) = iter.kind
     {
diff --git a/clippy_lints/src/methods/map_all_any_identity.rs b/clippy_lints/src/methods/map_all_any_identity.rs
new file mode 100644
index 00000000000..ac11baa2d54
--- /dev/null
+++ b/clippy_lints/src/methods/map_all_any_identity.rs
@@ -0,0 +1,43 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::SpanRangeExt;
+use clippy_utils::{is_expr_identity_function, is_trait_method};
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_span::{Span, sym};
+
+use super::MAP_ALL_ANY_IDENTITY;
+
+#[allow(clippy::too_many_arguments)]
+pub(super) fn check(
+    cx: &LateContext<'_>,
+    expr: &Expr<'_>,
+    recv: &Expr<'_>,
+    map_call_span: Span,
+    map_arg: &Expr<'_>,
+    any_call_span: Span,
+    any_arg: &Expr<'_>,
+    method: &str,
+) {
+    if is_trait_method(cx, expr, sym::Iterator)
+        && is_trait_method(cx, recv, sym::Iterator)
+        && is_expr_identity_function(cx, any_arg)
+        && let map_any_call_span = map_call_span.with_hi(any_call_span.hi())
+        && let Some(map_arg) = map_arg.span.get_source_text(cx)
+    {
+        span_lint_and_then(
+            cx,
+            MAP_ALL_ANY_IDENTITY,
+            map_any_call_span,
+            format!("usage of `.map(...).{method}(identity)`"),
+            |diag| {
+                diag.span_suggestion_verbose(
+                    map_any_call_span,
+                    format!("use `.{method}(...)` instead"),
+                    format!("{method}({map_arg})"),
+                    Applicability::MachineApplicable,
+                );
+            },
+        );
+    }
+}
diff --git a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs
new file mode 100644
index 00000000000..fc656fd78ba
--- /dev/null
+++ b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs
@@ -0,0 +1,134 @@
+use crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES;
+use clippy_config::msrvs::{self, Msrv};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{eager_or_lazy, higher, usage};
+use rustc_ast::LitKind;
+use rustc_ast::ast::RangeLimits;
+use rustc_data_structures::packed::Pu128;
+use rustc_errors::Applicability;
+use rustc_hir::{Body, Closure, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::Span;
+
+fn extract_count_with_applicability(
+    cx: &LateContext<'_>,
+    range: higher::Range<'_>,
+    applicability: &mut Applicability,
+) -> Option<String> {
+    let start = range.start?;
+    let end = range.end?;
+    // TODO: This doens't handle if either the start or end are negative literals, or if the start is
+    // not a literal. In the first case, we need to be careful about how we handle computing the
+    // count to avoid overflows. In the second, we may need to add parenthesis to make the
+    // suggestion correct.
+    if let ExprKind::Lit(lit) = start.kind
+        && let LitKind::Int(Pu128(lower_bound), _) = lit.node
+    {
+        if let ExprKind::Lit(lit) = end.kind
+            && let LitKind::Int(Pu128(upper_bound), _) = lit.node
+        {
+            // Here we can explicitly calculate the number of iterations
+            let count = if upper_bound >= lower_bound {
+                match range.limits {
+                    RangeLimits::HalfOpen => upper_bound - lower_bound,
+                    RangeLimits::Closed => (upper_bound - lower_bound).checked_add(1)?,
+                }
+            } else {
+                0
+            };
+            return Some(format!("{count}"));
+        }
+        let end_snippet = Sugg::hir_with_applicability(cx, end, "...", applicability)
+            .maybe_par()
+            .into_string();
+        if lower_bound == 0 {
+            if range.limits == RangeLimits::Closed {
+                return Some(format!("{end_snippet} + 1"));
+            }
+            return Some(end_snippet);
+        }
+        if range.limits == RangeLimits::Closed {
+            return Some(format!("{end_snippet} - {}", lower_bound - 1));
+        }
+        return Some(format!("{end_snippet} - {lower_bound}"));
+    }
+    None
+}
+
+pub(super) fn check(
+    cx: &LateContext<'_>,
+    ex: &Expr<'_>,
+    receiver: &Expr<'_>,
+    arg: &Expr<'_>,
+    msrv: &Msrv,
+    method_call_span: Span,
+) {
+    let mut applicability = Applicability::MaybeIncorrect;
+    if let Some(range) = higher::Range::hir(receiver)
+        && let ExprKind::Closure(Closure { body, .. }) = arg.kind
+        && let body_hir = cx.tcx.hir().body(*body)
+        && let Body {
+            params: [param],
+            value: body_expr,
+        } = body_hir
+        && !usage::BindingUsageFinder::are_params_used(cx, body_hir)
+        && let Some(count) = extract_count_with_applicability(cx, range, &mut applicability)
+    {
+        let method_to_use_name;
+        let new_span;
+        let use_take;
+
+        if eager_or_lazy::switch_to_eager_eval(cx, body_expr) {
+            if msrv.meets(msrvs::REPEAT_N) {
+                method_to_use_name = "repeat_n";
+                let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability);
+                new_span = (arg.span, format!("{body_snippet}, {count}"));
+                use_take = false;
+            } else {
+                method_to_use_name = "repeat";
+                let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability);
+                new_span = (arg.span, body_snippet.to_string());
+                use_take = true;
+            }
+        } else if msrv.meets(msrvs::REPEAT_WITH) {
+            method_to_use_name = "repeat_with";
+            new_span = (param.span, String::new());
+            use_take = true;
+        } else {
+            return;
+        }
+
+        // We need to provide nonempty parts to diag.multipart_suggestion so we
+        // collate all our parts here and then remove those that are empty.
+        let mut parts = vec![
+            (
+                receiver.span.to(method_call_span),
+                format!("std::iter::{method_to_use_name}"),
+            ),
+            new_span,
+        ];
+        if use_take {
+            parts.push((ex.span.shrink_to_hi(), format!(".take({count})")));
+        }
+
+        span_lint_and_then(
+            cx,
+            MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
+            ex.span,
+            "map of a closure that does not depend on its parameter over a range",
+            |diag| {
+                diag.multipart_suggestion(
+                    if use_take {
+                        format!("remove the explicit range and use `{method_to_use_name}` and `take`")
+                    } else {
+                        format!("remove the explicit range and use `{method_to_use_name}`")
+                    },
+                    parts,
+                    applicability,
+                );
+            },
+        );
+    }
+}
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 2a391870d70..b1809796355 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -60,13 +60,16 @@ mod manual_ok_or;
 mod manual_saturating_arithmetic;
 mod manual_str_repeat;
 mod manual_try_fold;
+mod map_all_any_identity;
 mod map_clone;
 mod map_collect_result_unit;
 mod map_err_ignore;
 mod map_flatten;
 mod map_identity;
 mod map_unwrap_or;
+mod map_with_unused_argument_over_ranges;
 mod mut_mutex_lock;
+mod needless_as_bytes;
 mod needless_character_iteration;
 mod needless_collect;
 mod needless_option_as_deref;
@@ -4166,6 +4169,90 @@ declare_clippy_lint! {
     "calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`"
 }
 
+declare_clippy_lint! {
+   /// ### What it does
+   /// It detects useless calls to `str::as_bytes()` before calling `len()` or `is_empty()`.
+   ///
+   /// ### Why is this bad?
+   /// The `len()` and `is_empty()` methods are also directly available on strings, and they
+   /// return identical results. In particular, `len()` on a string returns the number of
+   /// bytes.
+   ///
+   /// ### Example
+   /// ```
+   /// let len = "some string".as_bytes().len();
+   /// let b = "some string".as_bytes().is_empty();
+   /// ```
+   /// Use instead:
+   /// ```
+   /// let len = "some string".len();
+   /// let b = "some string".is_empty();
+   /// ```
+   #[clippy::version = "1.84.0"]
+   pub NEEDLESS_AS_BYTES,
+   complexity,
+   "detect useless calls to `as_bytes()`"
+}
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for usage of `.map(…)`, followed by `.all(identity)` or `.any(identity)`.
+    ///
+    /// ### Why is this bad?
+    /// The `.all(…)` or `.any(…)` methods can be called directly in place of `.map(…)`.
+    ///
+    /// ### Example
+    /// ```
+    /// # let mut v = [""];
+    /// let e1 = v.iter().map(|s| s.is_empty()).all(|a| a);
+    /// let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity);
+    /// ```
+    /// Use instead:
+    /// ```
+    /// # let mut v = [""];
+    /// let e1 = v.iter().all(|s| s.is_empty());
+    /// let e2 = v.iter().any(|s| s.is_empty());
+    /// ```
+    #[clippy::version = "1.84.0"]
+    pub MAP_ALL_ANY_IDENTITY,
+    complexity,
+    "combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call"
+}
+
+declare_clippy_lint! {
+    /// ### What it does
+    ///
+    /// Checks for `Iterator::map` over ranges without using the parameter which
+    /// could be more clearly expressed using `std::iter::repeat(...).take(...)`
+    /// or `std::iter::repeat_n`.
+    ///
+    /// ### Why is this bad?
+    ///
+    /// It expresses the intent more clearly to `take` the correct number of times
+    /// from a generating function than to apply a closure to each number in a
+    /// range only to discard them.
+    ///
+    /// ### Example
+    ///
+    /// ```no_run
+    /// let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect();
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect();
+    /// ```
+    ///
+    /// ### Known Issues
+    ///
+    /// This lint may suggest replacing a `Map<Range>` with a `Take<RepeatWith>`.
+    /// The former implements some traits that the latter does not, such as
+    /// `DoubleEndedIterator`.
+    #[clippy::version = "1.84.0"]
+    pub MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
+    restriction,
+    "map of a trivial closure (not dependent on parameter) over a range"
+}
+
 pub struct Methods {
     avoid_breaking_exported_api: bool,
     msrv: Msrv,
@@ -4327,6 +4414,9 @@ impl_lint_pass!(Methods => [
     NEEDLESS_CHARACTER_ITERATION,
     MANUAL_INSPECT,
     UNNECESSARY_MIN_OR_MAX,
+    NEEDLESS_AS_BYTES,
+    MAP_ALL_ANY_IDENTITY,
+    MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
@@ -4534,15 +4624,21 @@ impl Methods {
                 ("all", [arg]) => {
                     unused_enumerate_index::check(cx, expr, recv, arg);
                     needless_character_iteration::check(cx, expr, recv, arg, true);
-                    if let Some(("cloned", recv2, [], _, _)) = method_call(recv) {
-                        iter_overeager_cloned::check(
-                            cx,
-                            expr,
-                            recv,
-                            recv2,
-                            iter_overeager_cloned::Op::NeedlessMove(arg),
-                            false,
-                        );
+                    match method_call(recv) {
+                        Some(("cloned", recv2, [], _, _)) => {
+                            iter_overeager_cloned::check(
+                                cx,
+                                expr,
+                                recv,
+                                recv2,
+                                iter_overeager_cloned::Op::NeedlessMove(arg),
+                                false,
+                            );
+                        },
+                        Some(("map", _, [map_arg], _, map_call_span)) => {
+                            map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "all");
+                        },
+                        _ => {},
                     }
                 },
                 ("and_then", [arg]) => {
@@ -4571,6 +4667,9 @@ impl Methods {
                         {
                             string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
                         },
+                        Some(("map", _, [map_arg], _, map_call_span)) => {
+                            map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "any");
+                        },
                         _ => {},
                     }
                 },
@@ -4764,8 +4863,14 @@ impl Methods {
                     unit_hash::check(cx, expr, recv, arg);
                 },
                 ("is_empty", []) => {
-                    if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
-                        redundant_as_str::check(cx, expr, recv, as_str_span, span);
+                    match method_call(recv) {
+                        Some(("as_bytes", prev_recv, [], _, _)) => {
+                            needless_as_bytes::check(cx, "is_empty", recv, prev_recv, expr.span);
+                        },
+                        Some(("as_str", recv, [], as_str_span, _)) => {
+                            redundant_as_str::check(cx, expr, recv, as_str_span, span);
+                        },
+                        _ => {},
                     }
                     is_empty::check(cx, expr, recv);
                 },
@@ -4795,6 +4900,11 @@ impl Methods {
                         );
                     }
                 },
+                ("len", []) => {
+                    if let Some(("as_bytes", prev_recv, [], _, _)) = method_call(recv) {
+                        needless_as_bytes::check(cx, "len", recv, prev_recv, expr.span);
+                    }
+                },
                 ("lock", []) => {
                     mut_mutex_lock::check(cx, expr, recv, span);
                 },
@@ -4802,6 +4912,7 @@ impl Methods {
                     if name == "map" {
                         unused_enumerate_index::check(cx, expr, recv, m_arg);
                         map_clone::check(cx, expr, recv, m_arg, &self.msrv);
+                        map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, &self.msrv, span);
                         match method_call(recv) {
                             Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => {
                                 iter_kv_map::check(cx, map_name, expr, recv2, m_arg, &self.msrv);
@@ -5182,7 +5293,6 @@ impl ShouldImplTraitCase {
 }
 
 #[rustfmt::skip]
-#[expect(clippy::large_const_arrays, reason = "`Span` is not sync, so this can't be static")]
 const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
     ShouldImplTraitCase::new("std::ops::Add", "add",  2,  FN_HEADER,  SelfKind::Value,  OutType::Any, true),
     ShouldImplTraitCase::new("std::convert::AsMut", "as_mut",  1,  FN_HEADER,  SelfKind::RefMut,  OutType::Ref, true),
diff --git a/clippy_lints/src/methods/needless_as_bytes.rs b/clippy_lints/src/methods/needless_as_bytes.rs
new file mode 100644
index 00000000000..75e9f317230
--- /dev/null
+++ b/clippy_lints/src/methods/needless_as_bytes.rs
@@ -0,0 +1,28 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_lang_item;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, LangItem};
+use rustc_lint::LateContext;
+use rustc_span::Span;
+
+use super::NEEDLESS_AS_BYTES;
+
+pub fn check(cx: &LateContext<'_>, method: &str, recv: &Expr<'_>, prev_recv: &Expr<'_>, span: Span) {
+    if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice()
+        && let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs()
+        && (is_type_lang_item(cx, ty1, LangItem::String) || ty1.is_str())
+    {
+        let mut app = Applicability::MachineApplicable;
+        let sugg = Sugg::hir_with_context(cx, prev_recv, span.ctxt(), "..", &mut app);
+        span_lint_and_sugg(
+            cx,
+            NEEDLESS_AS_BYTES,
+            span,
+            "needless call to `as_bytes()`",
+            format!("`{method}()` can be called directly on strings"),
+            format!("{sugg}.{method}()"),
+            app,
+        );
+    }
+}
diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs
index 96a31812ca4..055107068ae 100644
--- a/clippy_lints/src/methods/needless_collect.rs
+++ b/clippy_lints/src/methods/needless_collect.rs
@@ -322,7 +322,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
         // Check function calls on our collection
         if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind {
             if args.is_empty()
-                && method_name.ident.name == sym!(collect)
+                && method_name.ident.name.as_str() == "collect"
                 && is_trait_method(self.cx, expr, sym::Iterator)
             {
                 self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv));
diff --git a/clippy_lints/src/methods/read_line_without_trim.rs b/clippy_lints/src/methods/read_line_without_trim.rs
index 65e545ed03a..db2b9d4d92f 100644
--- a/clippy_lints/src/methods/read_line_without_trim.rs
+++ b/clippy_lints/src/methods/read_line_without_trim.rs
@@ -44,7 +44,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
             if let Some(parent) = get_parent_expr(cx, expr) {
                 let data = if let ExprKind::MethodCall(segment, recv, args, span) = parent.kind {
                     if args.is_empty()
-                        && segment.ident.name == sym!(parse)
+                        && segment.ident.name.as_str() == "parse"
                         && let parse_result_ty = cx.typeck_results().expr_ty(parent)
                         && is_type_diagnostic_item(cx, parse_result_ty, sym::Result)
                         && let ty::Adt(_, substs) = parse_result_ty.kind()
@@ -58,7 +58,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
                             "calling `.parse()` on a string without trimming the trailing newline character",
                             "checking",
                         ))
-                    } else if segment.ident.name == sym!(ends_with)
+                    } else if segment.ident.name.as_str() == "ends_with"
                         && recv.span == expr.span
                         && let [arg] = args
                         && expr_is_string_literal_without_trailing_newline(arg)
diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs
index ca46da81fa0..bab439015c5 100644
--- a/clippy_lints/src/methods/unnecessary_filter_map.rs
+++ b/clippy_lints/src/methods/unnecessary_filter_map.rs
@@ -1,13 +1,15 @@
 use super::utils::clone_or_copy_needed;
-use clippy_utils::diagnostics::span_lint;
+use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::ty::is_copy;
 use clippy_utils::usage::mutated_variables;
 use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
-use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id};
+use clippy_utils::{MaybePath, is_res_lang_ctor, is_trait_method, path_res, path_to_local_id};
 use core::ops::ControlFlow;
+use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_hir::LangItem::{OptionNone, OptionSome};
 use rustc_lint::LateContext;
+use rustc_middle::query::Key;
 use rustc_middle::ty;
 use rustc_span::sym;
 
@@ -36,9 +38,25 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
                 ControlFlow::Continue(Descend::Yes)
             }
         });
-
         let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
         let sugg = if !found_filtering {
+            // Check if the closure is .filter_map(|x| Some(x))
+            if name == "filter_map"
+                && let hir::ExprKind::Call(expr, args) = body.value.kind
+                && is_res_lang_ctor(cx, path_res(cx, expr), OptionSome)
+                && arg_id.ty_def_id() == args[0].hir_id().ty_def_id()
+                && let hir::ExprKind::Path(_) = args[0].kind
+            {
+                span_lint_and_sugg(
+                    cx,
+                    UNNECESSARY_FILTER_MAP,
+                    expr.span,
+                    format!("{name} is unnecessary"),
+                    "try removing the filter_map",
+                    String::new(),
+                    Applicability::MaybeIncorrect,
+                );
+            }
             if name == "filter_map" { "map" } else { "map(..).next()" }
         } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) {
             match cx.typeck_results().expr_ty(body.value).kind() {
@@ -52,7 +70,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
         } else {
             return;
         };
-        span_lint(
+        span_lint_and_sugg(
             cx,
             if name == "filter_map" {
                 UNNECESSARY_FILTER_MAP
@@ -60,7 +78,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
                 UNNECESSARY_FIND_MAP
             },
             expr.span,
-            format!("this `.{name}` can be written more simply using `.{sugg}`"),
+            format!("this `.{name}` can be written more simply"),
+            "try instead",
+            sugg.to_string(),
+            Applicability::MaybeIncorrect,
         );
     }
 }
@@ -78,7 +99,7 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
             (true, true)
         },
         hir::ExprKind::MethodCall(segment, recv, [arg], _) => {
-            if segment.ident.name == sym!(then_some)
+            if segment.ident.name.as_str() == "then_some"
                 && cx.typeck_results().expr_ty(recv).is_bool()
                 && path_to_local_id(arg, arg_id)
             {
diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs
index 6911da69b94..a92fe9e55a4 100644
--- a/clippy_lints/src/methods/unnecessary_sort_by.rs
+++ b/clippy_lints/src/methods/unnecessary_sort_by.rs
@@ -5,7 +5,8 @@ use clippy_utils::ty::implements_trait;
 use rustc_errors::Applicability;
 use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
 use rustc_lint::LateContext;
-use rustc_middle::ty::{self, GenericArgKind};
+use rustc_middle::ty;
+use rustc_middle::ty::GenericArgKind;
 use rustc_span::sym;
 use rustc_span::symbol::Ident;
 use std::iter;