about summary refs log tree commit diff
path: root/clippy_lints/src/methods
diff options
context:
space:
mode:
Diffstat (limited to 'clippy_lints/src/methods')
-rw-r--r--clippy_lints/src/methods/collapsible_str_replace.rs4
-rw-r--r--clippy_lints/src/methods/mod.rs62
-rw-r--r--clippy_lints/src/methods/needless_character_iteration.rs124
-rw-r--r--clippy_lints/src/methods/option_map_unwrap_or.rs2
-rw-r--r--clippy_lints/src/methods/path_buf_push_overwrite.rs2
-rw-r--r--clippy_lints/src/methods/search_is_some.rs12
-rw-r--r--clippy_lints/src/methods/single_char_insert_string.rs47
-rw-r--r--clippy_lints/src/methods/single_char_pattern.rs64
-rw-r--r--clippy_lints/src/methods/single_char_push_string.rs46
-rw-r--r--clippy_lints/src/methods/str_splitn.rs4
-rw-r--r--clippy_lints/src/methods/unnecessary_filter_map.rs4
-rw-r--r--clippy_lints/src/methods/unnecessary_iter_cloned.rs4
-rw-r--r--clippy_lints/src/methods/unnecessary_result_map_or_else.rs19
-rw-r--r--clippy_lints/src/methods/unnecessary_sort_by.rs3
-rw-r--r--clippy_lints/src/methods/utils.rs63
15 files changed, 267 insertions, 193 deletions
diff --git a/clippy_lints/src/methods/collapsible_str_replace.rs b/clippy_lints/src/methods/collapsible_str_replace.rs
index 5409ede6008..1fab6c0e499 100644
--- a/clippy_lints/src/methods/collapsible_str_replace.rs
+++ b/clippy_lints/src/methods/collapsible_str_replace.rs
@@ -1,6 +1,6 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet;
-use clippy_utils::visitors::for_each_expr;
+use clippy_utils::visitors::for_each_expr_without_closures;
 use clippy_utils::{eq_expr_value, get_parent_expr};
 use core::ops::ControlFlow;
 use rustc_errors::Applicability;
@@ -46,7 +46,7 @@ fn collect_replace_calls<'tcx>(
     let mut methods = VecDeque::new();
     let mut from_args = VecDeque::new();
 
-    let _: Option<()> = for_each_expr(expr, |e| {
+    let _: Option<()> = for_each_expr_without_closures(expr, |e| {
         if let Some(("replace", _, [from, to], _, _)) = method_call(e) {
             if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() {
                 methods.push_front(e);
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 75a86c0c834..6200716afbe 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -66,6 +66,7 @@ mod map_flatten;
 mod map_identity;
 mod map_unwrap_or;
 mod mut_mutex_lock;
+mod needless_character_iteration;
 mod needless_collect;
 mod needless_option_as_deref;
 mod needless_option_take;
@@ -93,7 +94,6 @@ mod seek_from_current;
 mod seek_to_start_instead_of_rewind;
 mod single_char_add_str;
 mod single_char_insert_string;
-mod single_char_pattern;
 mod single_char_push_string;
 mod skip_while_next;
 mod stable_sort_primitive;
@@ -1142,38 +1142,6 @@ declare_clippy_lint! {
 
 declare_clippy_lint! {
     /// ### What it does
-    /// Checks for string methods that receive a single-character
-    /// `str` as an argument, e.g., `_.split("x")`.
-    ///
-    /// ### Why is this bad?
-    /// While this can make a perf difference on some systems,
-    /// benchmarks have proven inconclusive. But at least using a
-    /// char literal makes it clear that we are looking at a single
-    /// character.
-    ///
-    /// ### Known problems
-    /// Does not catch multi-byte unicode characters. This is by
-    /// design, on many machines, splitting by a non-ascii char is
-    /// actually slower. Please do your own measurements instead of
-    /// relying solely on the results of this lint.
-    ///
-    /// ### Example
-    /// ```rust,ignore
-    /// _.split("x");
-    /// ```
-    ///
-    /// Use instead:
-    /// ```rust,ignore
-    /// _.split('x');
-    /// ```
-    #[clippy::version = "pre 1.29.0"]
-    pub SINGLE_CHAR_PATTERN,
-    pedantic,
-    "using a single-character str where a char could be used, e.g., `_.split(\"x\")`"
-}
-
-declare_clippy_lint! {
-    /// ### What it does
     /// Checks for calling `.step_by(0)` on iterators which panics.
     ///
     /// ### Why is this bad?
@@ -4084,12 +4052,33 @@ declare_clippy_lint! {
     /// ```no_run
     /// println!("the string is empty");
     /// ```
-    #[clippy::version = "1.78.0"]
+    #[clippy::version = "1.79.0"]
     pub CONST_IS_EMPTY,
     suspicious,
     "is_empty() called on strings known at compile time"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks if an iterator is used to check if a string is ascii.
+    ///
+    /// ### Why is this bad?
+    /// The `str` type already implements the `is_ascii` method.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// "foo".chars().all(|c| c.is_ascii());
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// "foo".is_ascii();
+    /// ```
+    #[clippy::version = "1.80.0"]
+    pub NEEDLESS_CHARACTER_ITERATION,
+    suspicious,
+    "is_ascii() called on a char iterator"
+}
+
 pub struct Methods {
     avoid_breaking_exported_api: bool,
     msrv: Msrv,
@@ -4147,7 +4136,6 @@ impl_lint_pass!(Methods => [
     FLAT_MAP_OPTION,
     INEFFICIENT_TO_STRING,
     NEW_RET_NO_SELF,
-    SINGLE_CHAR_PATTERN,
     SINGLE_CHAR_ADD_STR,
     SEARCH_IS_SOME,
     FILTER_NEXT,
@@ -4255,6 +4243,7 @@ impl_lint_pass!(Methods => [
     UNNECESSARY_RESULT_MAP_OR_ELSE,
     MANUAL_C_STR_LITERALS,
     UNNECESSARY_GET_THEN_CHECK,
+    NEEDLESS_CHARACTER_ITERATION,
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
@@ -4301,7 +4290,6 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
                 inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args);
                 single_char_add_str::check(cx, expr, receiver, args);
                 into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, receiver);
-                single_char_pattern::check(cx, expr, method_call.ident.name, receiver, args);
                 unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, &self.msrv);
             },
             ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
@@ -4462,6 +4450,7 @@ 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,
@@ -4482,6 +4471,7 @@ impl Methods {
                 },
                 ("any", [arg]) => {
                     unused_enumerate_index::check(cx, expr, recv, arg);
+                    needless_character_iteration::check(cx, expr, recv, arg, false);
                     match method_call(recv) {
                         Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
                             cx,
diff --git a/clippy_lints/src/methods/needless_character_iteration.rs b/clippy_lints/src/methods/needless_character_iteration.rs
new file mode 100644
index 00000000000..e3d78207715
--- /dev/null
+++ b/clippy_lints/src/methods/needless_character_iteration.rs
@@ -0,0 +1,124 @@
+use rustc_errors::Applicability;
+use rustc_hir::{Closure, Expr, ExprKind, HirId, StmtKind, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::Span;
+
+use super::utils::get_last_chain_binding_hir_id;
+use super::NEEDLESS_CHARACTER_ITERATION;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::{match_def_path, path_to_local_id, peel_blocks};
+
+fn peels_expr_ref<'a, 'tcx>(mut expr: &'a Expr<'tcx>) -> &'a Expr<'tcx> {
+    while let ExprKind::AddrOf(_, _, e) = expr.kind {
+        expr = e;
+    }
+    expr
+}
+
+fn handle_expr(
+    cx: &LateContext<'_>,
+    expr: &Expr<'_>,
+    first_param: HirId,
+    span: Span,
+    before_chars: Span,
+    revert: bool,
+    is_all: bool,
+) {
+    match expr.kind {
+        ExprKind::MethodCall(method, receiver, [], _) => {
+            // If we have `!is_ascii`, then only `.any()` should warn. And if the condition is
+            // `is_ascii`, then only `.all()` should warn.
+            if revert != is_all
+                && method.ident.name.as_str() == "is_ascii"
+                && path_to_local_id(receiver, first_param)
+                && let char_arg_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs()
+                && *char_arg_ty.kind() == ty::Char
+                && let Some(snippet) = snippet_opt(cx, before_chars)
+            {
+                span_lint_and_sugg(
+                    cx,
+                    NEEDLESS_CHARACTER_ITERATION,
+                    span,
+                    "checking if a string is ascii using iterators",
+                    "try",
+                    format!("{}{snippet}.is_ascii()", if revert { "!" } else { "" }),
+                    Applicability::MachineApplicable,
+                );
+            }
+        },
+        ExprKind::Block(block, _) => {
+            if block.stmts.iter().any(|stmt| !matches!(stmt.kind, StmtKind::Let(_))) {
+                // If there is something else than let bindings, then better not emit the lint.
+                return;
+            }
+            if let Some(block_expr) = block.expr
+                // First we ensure that this is a "binding chain" (each statement is a binding
+                // of the previous one) and that it is a binding of the closure argument.
+                && let Some(last_chain_binding_id) =
+                    get_last_chain_binding_hir_id(first_param, block.stmts)
+            {
+                handle_expr(
+                    cx,
+                    block_expr,
+                    last_chain_binding_id,
+                    span,
+                    before_chars,
+                    revert,
+                    is_all,
+                );
+            }
+        },
+        ExprKind::Unary(UnOp::Not, expr) => handle_expr(cx, expr, first_param, span, before_chars, !revert, is_all),
+        ExprKind::Call(fn_path, [arg]) => {
+            // If we have `!is_ascii`, then only `.any()` should warn. And if the condition is
+            // `is_ascii`, then only `.all()` should warn.
+            if revert != is_all
+                && let ExprKind::Path(path) = fn_path.kind
+                && let Some(fn_def_id) = cx.qpath_res(&path, fn_path.hir_id).opt_def_id()
+                && match_def_path(cx, fn_def_id, &["core", "char", "methods", "<impl char>", "is_ascii"])
+                && path_to_local_id(peels_expr_ref(arg), first_param)
+                && let Some(snippet) = snippet_opt(cx, before_chars)
+            {
+                span_lint_and_sugg(
+                    cx,
+                    NEEDLESS_CHARACTER_ITERATION,
+                    span,
+                    "checking if a string is ascii using iterators",
+                    "try",
+                    format!("{}{snippet}.is_ascii()", if revert { "!" } else { "" }),
+                    Applicability::MachineApplicable,
+                );
+            }
+        },
+        _ => {},
+    }
+}
+
+pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>, is_all: bool) {
+    if let ExprKind::Closure(&Closure { body, .. }) = closure_arg.kind
+        && let body = cx.tcx.hir().body(body)
+        && let Some(first_param) = body.params.first()
+        && let ExprKind::MethodCall(method, mut recv, [], _) = recv.kind
+        && method.ident.name.as_str() == "chars"
+        && let str_ty = cx.typeck_results().expr_ty_adjusted(recv).peel_refs()
+        && *str_ty.kind() == ty::Str
+    {
+        let expr_start = recv.span;
+        while let ExprKind::MethodCall(_, new_recv, _, _) = recv.kind {
+            recv = new_recv;
+        }
+        let body_expr = peel_blocks(body.value);
+
+        handle_expr(
+            cx,
+            body_expr,
+            first_param.pat.hir_id,
+            recv.span.with_hi(call_expr.span.hi()),
+            recv.span.with_hi(expr_start.hi()),
+            false,
+            is_all,
+        );
+    }
+}
diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs
index ca331f3e756..3d326bc99f9 100644
--- a/clippy_lints/src/methods/option_map_unwrap_or.rs
+++ b/clippy_lints/src/methods/option_map_unwrap_or.rs
@@ -60,7 +60,7 @@ pub(super) fn check<'tcx>(
 
             let map = cx.tcx.hir();
             let body = map.body_owned_by(map.enclosing_body_owner(expr.hir_id));
-            reference_visitor.visit_body(&body);
+            reference_visitor.visit_body(body);
 
             if reference_visitor.found_reference {
                 return;
diff --git a/clippy_lints/src/methods/path_buf_push_overwrite.rs b/clippy_lints/src/methods/path_buf_push_overwrite.rs
index 04a27cc98f3..2d3007e50b8 100644
--- a/clippy_lints/src/methods/path_buf_push_overwrite.rs
+++ b/clippy_lints/src/methods/path_buf_push_overwrite.rs
@@ -27,7 +27,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
             lit.span,
             "calling `push` with '/' or '\\' (file system root) will overwrite the previous path definition",
             "try",
-            format!("\"{}\"", pushed_path_lit.trim_start_matches(|c| c == '/' || c == '\\')),
+            format!("\"{}\"", pushed_path_lit.trim_start_matches(['/', '\\'])),
             Applicability::MachineApplicable,
         );
     }
diff --git a/clippy_lints/src/methods/search_is_some.rs b/clippy_lints/src/methods/search_is_some.rs
index f5f1e94bbf4..3b2dd285b8c 100644
--- a/clippy_lints/src/methods/search_is_some.rs
+++ b/clippy_lints/src/methods/search_is_some.rs
@@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
 use clippy_utils::source::{snippet, snippet_with_applicability};
 use clippy_utils::sugg::deref_closure_args;
 use clippy_utils::ty::is_type_lang_item;
-use clippy_utils::{get_parent_expr, is_trait_method, strip_pat_refs};
+use clippy_utils::{is_receiver_of_method_call, is_trait_method, strip_pat_refs};
 use hir::ExprKind;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
@@ -156,13 +156,3 @@ pub(super) fn check<'tcx>(
         }
     }
 }
-
-fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
-    if let Some(parent_expr) = get_parent_expr(cx, expr)
-        && let ExprKind::MethodCall(_, receiver, ..) = parent_expr.kind
-        && receiver.hir_id == expr.hir_id
-    {
-        return true;
-    }
-    false
-}
diff --git a/clippy_lints/src/methods/single_char_insert_string.rs b/clippy_lints/src/methods/single_char_insert_string.rs
index 20ec2b74d81..e2f76ac114c 100644
--- a/clippy_lints/src/methods/single_char_insert_string.rs
+++ b/clippy_lints/src/methods/single_char_insert_string.rs
@@ -1,8 +1,8 @@
-use super::utils::get_hint_if_single_char_arg;
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal};
+use rustc_ast::BorrowKind;
 use rustc_errors::Applicability;
-use rustc_hir as hir;
+use rustc_hir::{self as hir, ExprKind};
 use rustc_lint::LateContext;
 
 use super::SINGLE_CHAR_ADD_STR;
@@ -10,7 +10,7 @@ use super::SINGLE_CHAR_ADD_STR;
 /// lint for length-1 `str`s as argument for `insert_str`
 pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
     let mut applicability = Applicability::MachineApplicable;
-    if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[1], &mut applicability, false) {
+    if let Some(extension_string) = str_literal_to_char_literal(cx, &args[1], &mut applicability, false) {
         let base_string_snippet =
             snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability);
         let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability);
@@ -25,4 +25,43 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::
             applicability,
         );
     }
+
+    if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind
+        && let ExprKind::MethodCall(path_segment, method_arg, _, _) = &arg.kind
+        && path_segment.ident.name == rustc_span::sym::to_string
+        && (is_ref_char(cx, method_arg) || is_char(cx, method_arg))
+    {
+        let base_string_snippet =
+            snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability);
+        let extension_string =
+            snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability);
+        let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability);
+        let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" };
+
+        let sugg = format!("{base_string_snippet}.insert({pos_arg}, {deref_string}{extension_string})");
+        span_lint_and_sugg(
+            cx,
+            SINGLE_CHAR_ADD_STR,
+            expr.span,
+            "calling `insert_str()` using a single-character converted to string",
+            "consider using `insert` without `to_string()`",
+            sugg,
+            applicability,
+        );
+    }
+}
+
+fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
+    if cx.typeck_results().expr_ty(expr).is_ref()
+        && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind()
+        && ty.is_char()
+    {
+        return true;
+    }
+
+    false
+}
+
+fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
+    cx.typeck_results().expr_ty(expr).is_char()
 }
diff --git a/clippy_lints/src/methods/single_char_pattern.rs b/clippy_lints/src/methods/single_char_pattern.rs
deleted file mode 100644
index 982a7901c45..00000000000
--- a/clippy_lints/src/methods/single_char_pattern.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use super::utils::get_hint_if_single_char_arg;
-use clippy_utils::diagnostics::span_lint_and_sugg;
-use rustc_errors::Applicability;
-use rustc_hir as hir;
-use rustc_lint::LateContext;
-use rustc_middle::ty;
-use rustc_span::symbol::Symbol;
-
-use super::SINGLE_CHAR_PATTERN;
-
-const PATTERN_METHODS: [(&str, usize); 22] = [
-    ("contains", 0),
-    ("starts_with", 0),
-    ("ends_with", 0),
-    ("find", 0),
-    ("rfind", 0),
-    ("split", 0),
-    ("split_inclusive", 0),
-    ("rsplit", 0),
-    ("split_terminator", 0),
-    ("rsplit_terminator", 0),
-    ("splitn", 1),
-    ("rsplitn", 1),
-    ("split_once", 0),
-    ("rsplit_once", 0),
-    ("matches", 0),
-    ("rmatches", 0),
-    ("match_indices", 0),
-    ("rmatch_indices", 0),
-    ("trim_start_matches", 0),
-    ("trim_end_matches", 0),
-    ("replace", 0),
-    ("replacen", 0),
-];
-
-/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
-pub(super) fn check(
-    cx: &LateContext<'_>,
-    _expr: &hir::Expr<'_>,
-    method_name: Symbol,
-    receiver: &hir::Expr<'_>,
-    args: &[hir::Expr<'_>],
-) {
-    for &(method, pos) in &PATTERN_METHODS {
-        if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(receiver).kind()
-            && ty.is_str()
-            && method_name.as_str() == method
-            && args.len() > pos
-            && let arg = &args[pos]
-            && let mut applicability = Applicability::MachineApplicable
-            && let Some(hint) = get_hint_if_single_char_arg(cx, arg, &mut applicability, true)
-        {
-            span_lint_and_sugg(
-                cx,
-                SINGLE_CHAR_PATTERN,
-                arg.span,
-                "single-character string constant used as pattern",
-                "consider using a `char`",
-                hint,
-                applicability,
-            );
-        }
-    }
-}
diff --git a/clippy_lints/src/methods/single_char_push_string.rs b/clippy_lints/src/methods/single_char_push_string.rs
index 97c13825bc1..4ae8634305d 100644
--- a/clippy_lints/src/methods/single_char_push_string.rs
+++ b/clippy_lints/src/methods/single_char_push_string.rs
@@ -1,8 +1,8 @@
-use super::utils::get_hint_if_single_char_arg;
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal};
+use rustc_ast::BorrowKind;
 use rustc_errors::Applicability;
-use rustc_hir as hir;
+use rustc_hir::{self as hir, ExprKind};
 use rustc_lint::LateContext;
 
 use super::SINGLE_CHAR_ADD_STR;
@@ -10,7 +10,7 @@ use super::SINGLE_CHAR_ADD_STR;
 /// lint for length-1 `str`s as argument for `push_str`
 pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
     let mut applicability = Applicability::MachineApplicable;
-    if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[0], &mut applicability, false) {
+    if let Some(extension_string) = str_literal_to_char_literal(cx, &args[0], &mut applicability, false) {
         let base_string_snippet =
             snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability);
         let sugg = format!("{base_string_snippet}.push({extension_string})");
@@ -24,4 +24,42 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::
             applicability,
         );
     }
+
+    if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind
+        && let ExprKind::MethodCall(path_segment, method_arg, _, _) = &arg.kind
+        && path_segment.ident.name == rustc_span::sym::to_string
+        && (is_ref_char(cx, method_arg) || is_char(cx, method_arg))
+    {
+        let base_string_snippet =
+            snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability);
+        let extension_string =
+            snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability);
+        let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" };
+
+        let sugg = format!("{base_string_snippet}.push({deref_string}{extension_string})");
+        span_lint_and_sugg(
+            cx,
+            SINGLE_CHAR_ADD_STR,
+            expr.span,
+            "calling `push_str()` using a single-character converted to string",
+            "consider using `push` without `to_string()`",
+            sugg,
+            applicability,
+        );
+    }
+}
+
+fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
+    if cx.typeck_results().expr_ty(expr).is_ref()
+        && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind()
+        && ty.is_char()
+    {
+        return true;
+    }
+
+    false
+}
+
+fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
+    cx.typeck_results().expr_ty(expr).is_char()
 }
diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs
index e8c12bbeea0..4f42fb73547 100644
--- a/clippy_lints/src/methods/str_splitn.rs
+++ b/clippy_lints/src/methods/str_splitn.rs
@@ -3,7 +3,7 @@ use clippy_utils::consts::{constant, Constant};
 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
 use clippy_utils::source::snippet_with_context;
 use clippy_utils::usage::local_used_after_expr;
-use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
+use clippy_utils::visitors::{for_each_expr, Descend};
 use clippy_utils::{is_diag_item_method, match_def_path, path_to_local_id, paths};
 use core::ops::ControlFlow;
 use rustc_errors::Applicability;
@@ -209,7 +209,7 @@ fn indirect_usage<'tcx>(
     }) = stmt.kind
     {
         let mut path_to_binding = None;
-        let _: Option<!> = for_each_expr_with_closures(cx, init_expr, |e| {
+        let _: Option<!> = for_each_expr(cx, init_expr, |e| {
             if path_to_local_id(e, binding) {
                 path_to_binding = Some(e);
             }
diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs
index daf99d98614..c9b9d98dbe6 100644
--- a/clippy_lints/src/methods/unnecessary_filter_map.rs
+++ b/clippy_lints/src/methods/unnecessary_filter_map.rs
@@ -2,7 +2,7 @@ use super::utils::clone_or_copy_needed;
 use clippy_utils::diagnostics::span_lint;
 use clippy_utils::ty::is_copy;
 use clippy_utils::usage::mutated_variables;
-use clippy_utils::visitors::{for_each_expr, Descend};
+use clippy_utils::visitors::{for_each_expr_without_closures, Descend};
 use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id};
 use core::ops::ControlFlow;
 use rustc_hir as hir;
@@ -26,7 +26,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
 
         let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, body.value);
 
-        let _: Option<!> = for_each_expr(body.value, |e| {
+        let _: Option<!> = for_each_expr_without_closures(body.value, |e| {
             if let hir::ExprKind::Ret(Some(e)) = &e.kind {
                 let (found_mapping_res, found_filtering_res) = check_expression(cx, arg_id, e);
                 found_mapping |= found_mapping_res;
diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs
index d1300dd43c2..70885e46e95 100644
--- a/clippy_lints/src/methods/unnecessary_iter_cloned.rs
+++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs
@@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::higher::ForLoop;
 use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::{get_iterator_item_ty, implements_trait};
-use clippy_utils::visitors::for_each_expr;
+use clippy_utils::visitors::for_each_expr_without_closures;
 use clippy_utils::{can_mut_borrow_both, fn_def_id, get_parent_expr, path_to_local};
 use core::ops::ControlFlow;
 use rustc_errors::Applicability;
@@ -61,7 +61,7 @@ pub fn check_for_loop_iter(
         fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Expr<'_>) -> bool {
             let mut change = false;
             if let ExprKind::Block(block, ..) = body.kind {
-                for_each_expr(block, |e| {
+                for_each_expr_without_closures(block, |e| {
                     match e.kind {
                         ExprKind::Assign(assignee, _, _) | ExprKind::AssignOp(_, assignee, _) => {
                             change |= !can_mut_borrow_both(cx, caller, assignee);
diff --git a/clippy_lints/src/methods/unnecessary_result_map_or_else.rs b/clippy_lints/src/methods/unnecessary_result_map_or_else.rs
index cdfaa690d5f..c14a87c1534 100644
--- a/clippy_lints/src/methods/unnecessary_result_map_or_else.rs
+++ b/clippy_lints/src/methods/unnecessary_result_map_or_else.rs
@@ -4,10 +4,11 @@ use clippy_utils::source::snippet;
 use clippy_utils::ty::is_type_diagnostic_item;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
-use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath, Stmt, StmtKind};
+use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath};
 use rustc_lint::LateContext;
 use rustc_span::symbol::sym;
 
+use super::utils::get_last_chain_binding_hir_id;
 use super::UNNECESSARY_RESULT_MAP_OR_ELSE;
 
 fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) {
@@ -25,22 +26,6 @@ fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &E
     );
 }
 
-fn get_last_chain_binding_hir_id(mut hir_id: HirId, statements: &[Stmt<'_>]) -> Option<HirId> {
-    for stmt in statements {
-        if let StmtKind::Let(local) = stmt.kind
-            && let Some(init) = local.init
-            && let ExprKind::Path(QPath::Resolved(_, path)) = init.kind
-            && let hir::def::Res::Local(local_hir_id) = path.res
-            && local_hir_id == hir_id
-        {
-            hir_id = local.pat.hir_id;
-        } else {
-            return None;
-        }
-    }
-    Some(hir_id)
-}
-
 fn handle_qpath(
     cx: &LateContext<'_>,
     expr: &Expr<'_>,
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;
diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs
index 1a55b7160fb..0d2b0a31317 100644
--- a/clippy_lints/src/methods/utils.rs
+++ b/clippy_lints/src/methods/utils.rs
@@ -1,10 +1,7 @@
-use clippy_utils::source::snippet_with_applicability;
 use clippy_utils::ty::is_type_diagnostic_item;
 use clippy_utils::{get_parent_expr, path_to_local_id, usage};
-use rustc_ast::ast;
-use rustc_errors::Applicability;
 use rustc_hir::intravisit::{walk_expr, Visitor};
-use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat};
+use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, QPath, Stmt, StmtKind};
 use rustc_lint::LateContext;
 use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::{self, Ty};
@@ -49,48 +46,6 @@ pub(super) fn derefs_to_slice<'tcx>(
     }
 }
 
-pub(super) fn get_hint_if_single_char_arg(
-    cx: &LateContext<'_>,
-    arg: &Expr<'_>,
-    applicability: &mut Applicability,
-    ascii_only: bool,
-) -> Option<String> {
-    if let ExprKind::Lit(lit) = &arg.kind
-        && let ast::LitKind::Str(r, style) = lit.node
-        && let string = r.as_str()
-        && let len = if ascii_only {
-            string.len()
-        } else {
-            string.chars().count()
-        }
-        && len == 1
-    {
-        let snip = snippet_with_applicability(cx, arg.span, string, applicability);
-        let ch = if let ast::StrStyle::Raw(nhash) = style {
-            let nhash = nhash as usize;
-            // for raw string: r##"a"##
-            &snip[(nhash + 2)..(snip.len() - 1 - nhash)]
-        } else {
-            // for regular string: "a"
-            &snip[1..(snip.len() - 1)]
-        };
-
-        let hint = format!(
-            "'{}'",
-            match ch {
-                "'" => "\\'",
-                r"\" => "\\\\",
-                "\\\"" => "\"", // no need to escape `"` in `'"'`
-                _ => ch,
-            }
-        );
-
-        Some(hint)
-    } else {
-        None
-    }
-}
-
 /// The core logic of `check_for_loop_iter` in `unnecessary_iter_cloned.rs`, this function wraps a
 /// use of `CloneOrCopyVisitor`.
 pub(super) fn clone_or_copy_needed<'tcx>(
@@ -175,3 +130,19 @@ impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> {
             .any(|hir_id| path_to_local_id(expr, *hir_id))
     }
 }
+
+pub(super) fn get_last_chain_binding_hir_id(mut hir_id: HirId, statements: &[Stmt<'_>]) -> Option<HirId> {
+    for stmt in statements {
+        if let StmtKind::Let(local) = stmt.kind
+            && let Some(init) = local.init
+            && let ExprKind::Path(QPath::Resolved(_, path)) = init.kind
+            && let rustc_hir::def::Res::Local(local_hir_id) = path.res
+            && local_hir_id == hir_id
+        {
+            hir_id = local.pat.hir_id;
+        } else {
+            return None;
+        }
+    }
+    Some(hir_id)
+}