about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNahua Kang <kangnahua@gmail.com>2022-08-14 18:33:55 +0200
committerNahua Kang <kangnahua@gmail.com>2022-08-20 12:09:09 +0200
commitb070b4045f348f9222008270a435bda17b048c15 (patch)
tree1bfb1c288f1fd91bb29fc6628dd842a7c2934174
parentfb30b64f6379eb22880d70bfebce66ceccc75269 (diff)
downloadrust-b070b4045f348f9222008270a435bda17b048c15.tar.gz
rust-b070b4045f348f9222008270a435bda17b048c15.zip
Simplify lint logic and address code review comments
-rw-r--r--clippy_lints/src/methods/collapsible_str_replace.rs301
-rw-r--r--clippy_lints/src/methods/mod.rs14
-rw-r--r--tests/ui/collapsible_str_replace.fixed70
-rw-r--r--tests/ui/collapsible_str_replace.rs68
-rw-r--r--tests/ui/collapsible_str_replace.stderr78
5 files changed, 201 insertions, 330 deletions
diff --git a/clippy_lints/src/methods/collapsible_str_replace.rs b/clippy_lints/src/methods/collapsible_str_replace.rs
index 4c6288e798c..561033be5b6 100644
--- a/clippy_lints/src/methods/collapsible_str_replace.rs
+++ b/clippy_lints/src/methods/collapsible_str_replace.rs
@@ -1,19 +1,12 @@
-// run-rustfix
-
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::get_parent_expr;
+use clippy_utils::source::snippet;
 use clippy_utils::visitors::for_each_expr;
+use clippy_utils::{eq_expr_value, get_parent_expr};
 use core::ops::ControlFlow;
-use if_chain::if_chain;
-use rustc_ast::ast::LitKind;
-use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
-use rustc_hir::{ExprKind, Path, QPath};
 use rustc_lint::LateContext;
-use rustc_middle::ty;
-use rustc_span::source_map::Spanned;
-use rustc_span::Span;
+use std::collections::VecDeque;
 
 use super::method_call;
 use super::COLLAPSIBLE_STR_REPLACE;
@@ -21,255 +14,83 @@ use super::COLLAPSIBLE_STR_REPLACE;
 pub(super) fn check<'tcx>(
     cx: &LateContext<'tcx>,
     expr: &'tcx hir::Expr<'tcx>,
-    name: &str,
-    recv: &'tcx hir::Expr<'tcx>,
+    from: &'tcx hir::Expr<'tcx>,
+    to: &'tcx hir::Expr<'tcx>,
 ) {
-    if name == "replace" {
-        // The receiver of the method call must be `str` type to lint `collapsible_str_replace`
-        let original_recv = find_original_recv(recv);
-        let original_recv_ty_kind = cx.typeck_results().expr_ty(original_recv).peel_refs().kind();
-        let original_recv_is_str_kind = matches!(original_recv_ty_kind, ty::Str);
-
-        if_chain! {
-            if original_recv_is_str_kind;
-            if let Some(parent) = get_parent_expr(cx, expr);
-            if let Some((name, ..)) = method_call(parent);
-            if name == "replace";
-
-            then {
-                // If the parent node is a `str::replace` call, we've already handled the lint, don't lint again
-                return;
-            }
+    let replace_methods = collect_replace_calls(cx, expr, to);
+    if replace_methods.methods.len() > 1 {
+        let from_kind = cx.typeck_results().expr_ty(from).peel_refs().kind();
+        // If the parent node's `to` argument is the same as the `to` argument
+        // of the last replace call in the current chain, don't lint as it was already linted
+        if let Some(parent) = get_parent_expr(cx, expr)
+            && let Some(("replace", [_, current_from, current_to], _)) = method_call(parent)
+            && eq_expr_value(cx, to, current_to)
+            && from_kind == cx.typeck_results().expr_ty(current_from).peel_refs().kind()
+        {
+            return;
         }
 
-        if let Some(("replace", ..)) = method_call(recv) {
-            // Check if there's an earlier `str::replace` call
-            if original_recv_is_str_kind {
-                check_consecutive_replace_calls(cx, expr);
-            }
-        }
+        check_consecutive_replace_calls(cx, expr, &replace_methods, to);
     }
 }
 
-/// Check a chain of `str::replace` calls for `collapsible_str_replace` lint.
-fn check_consecutive_replace_calls<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
-    if_chain! {
-        if let Some(from_args) = get_replace_call_from_args_if_all_char_ty(cx, expr);
-        if let Some(to_arg) = get_replace_call_unique_to_arg_repr(expr);
-        then {
-            let earliest_replace_call_span = get_earliest_replace_call_span(expr);
-
-            if replace_call_from_args_are_only_lit_chars(&from_args) {
-                let from_arg_reprs: Vec<String> = from_args.iter().map(|from_arg| {
-                    get_replace_call_char_arg_repr(from_arg).unwrap()
-                }).collect();
-                let app = Applicability::MachineApplicable;
-
-                span_lint_and_sugg(
-                    cx,
-                    COLLAPSIBLE_STR_REPLACE,
-                    expr.span.with_lo(earliest_replace_call_span.lo()),
-                    "used consecutive `str::replace` call",
-                    "replace with",
-                    format!(
-                        "replace(|c| matches!(c, {}), {})",
-                        from_arg_reprs.join(" | "),
-                        to_arg,
-                    ),
-                    app,
-                );
-            } else {
-                // Use fallback lint
-                let from_arg_reprs: Vec<String> = from_args.iter().map(|from_arg| {
-                    get_replace_call_char_arg_repr(from_arg).unwrap()
-                }).collect();
-                let app = Applicability::MachineApplicable;
-
-                span_lint_and_sugg(
-                    cx,
-                    COLLAPSIBLE_STR_REPLACE,
-                    expr.span.with_lo(earliest_replace_call_span.lo()),
-                    "used consecutive `str::replace` call",
-                    "replace with",
-                    format!(
-                        "replace(&[{}], {})",
-                        from_arg_reprs.join(" , "),
-                        to_arg,
-                    ),
-                    app,
-                );
-            }
-        }
-    }
+struct ReplaceMethods<'tcx> {
+    methods: VecDeque<&'tcx hir::Expr<'tcx>>,
+    from_args: VecDeque<&'tcx hir::Expr<'tcx>>,
 }
 
-/// Check if all the `from` arguments of a chain of consecutive calls to `str::replace`
-/// are all of `ExprKind::Lit` types. If any is not, return false.
-fn replace_call_from_args_are_only_lit_chars<'tcx>(from_args: &[&'tcx hir::Expr<'tcx>]) -> bool {
-    let mut only_lit_chars = true;
-
-    for from_arg in from_args.iter() {
-        match from_arg.kind {
-            ExprKind::Lit(..) => {},
-            _ => only_lit_chars = false,
-        }
-    }
-
-    only_lit_chars
-}
-
-/// Collect and return all of the `from` arguments of a chain of consecutive `str::replace` calls
-/// if these `from` arguments's expressions are of the `ty::Char` kind. Otherwise return `None`.
-fn get_replace_call_from_args_if_all_char_ty<'tcx>(
+fn collect_replace_calls<'tcx>(
     cx: &LateContext<'tcx>,
     expr: &'tcx hir::Expr<'tcx>,
-) -> Option<Vec<&'tcx hir::Expr<'tcx>>> {
-    let mut all_from_args_are_chars = true;
-    let mut from_args = Vec::new();
+    to_arg: &'tcx hir::Expr<'tcx>,
+) -> ReplaceMethods<'tcx> {
+    let mut methods = VecDeque::new();
+    let mut from_args = VecDeque::new();
 
     let _: Option<()> = for_each_expr(expr, |e| {
-        if let Some((name, [_, args @ ..], _)) = method_call(e) {
-            match (name, args) {
-                ("replace", [from, _]) => {
-                    let from_ty_kind = cx.typeck_results().expr_ty(from).peel_refs().kind();
-                    if matches!(from_ty_kind, ty::Char) {
-                        from_args.push(from);
-                    } else {
-                        all_from_args_are_chars = false;
-                    }
-                    ControlFlow::Continue(())
-                },
-                _ => ControlFlow::BREAK,
-            }
-        } else {
-            ControlFlow::Continue(())
-        }
-    });
-
-    if all_from_args_are_chars {
-        return Some(from_args);
-    }
-
-    None
-}
-
-/// Return a unique String representation of the `to` argument used in a chain of `str::replace`
-/// calls if each `str::replace` call's `to` argument is identical to the other `to` arguments in
-/// the chain. Otherwise, return `None`.
-fn get_replace_call_unique_to_arg_repr<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<String> {
-    let mut to_args = Vec::new();
-
-    let _: Option<()> = for_each_expr(expr, |e| {
-        if let Some((name, [_, args @ ..], _)) = method_call(e) {
-            match (name, args) {
-                ("replace", [_, to]) => {
-                    to_args.push(to);
-                    ControlFlow::Continue(())
-                },
-                _ => ControlFlow::BREAK,
+        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);
+                from_args.push_front(from);
+                ControlFlow::Continue(())
+            } else {
+                ControlFlow::BREAK
             }
         } else {
             ControlFlow::Continue(())
         }
     });
 
-    // let mut to_arg_repr_set = FxHashSet::default();
-    let mut to_arg_reprs = Vec::new();
-    for &to_arg in &to_args {
-        if let Some(to_arg_repr) = get_replace_call_char_arg_repr(to_arg) {
-            to_arg_reprs.push(to_arg_repr);
-        }
-    }
-
-    let to_arg_repr_set = to_arg_reprs.iter().cloned().collect::<FxHashSet<_>>();
-    // Check if the set of `to` argument representations has more than one unique value
-    if to_arg_repr_set.len() != 1 {
-        return None;
-    }
-
-    // Return the single representation value
-    to_arg_reprs.pop()
+    ReplaceMethods { methods, from_args }
 }
 
-/// Get the representation of an argument of a `str::replace` call either of the literal char value
-/// or variable name, i.e. the resolved path segments `ident`.
-/// Return:
-/// - the str literal with double quotes, e.g. "\"l\""
-/// - the char literal with single quotes, e.g. "'l'"
-/// - the variable as a String, e.g. "l"
-fn get_replace_call_char_arg_repr<'tcx>(arg: &'tcx hir::Expr<'tcx>) -> Option<String> {
-    match arg.kind {
-        ExprKind::Lit(Spanned {
-            node: LitKind::Str(to_arg_val, _),
-            ..
-        }) => {
-            let repr = to_arg_val.as_str();
-            let double_quote = "\"";
-            Some(double_quote.to_owned() + repr + double_quote)
-        },
-        ExprKind::Lit(Spanned {
-            node: LitKind::Char(to_arg_val),
-            ..
-        }) => {
-            let repr = to_arg_val.to_string();
-            let double_quote = "\'";
-            Some(double_quote.to_owned() + &repr + double_quote)
-        },
-        ExprKind::Path(QPath::Resolved(
-            _,
-            Path {
-                segments: path_segments,
-                ..
-            },
-        )) => {
-            // join the path_segments values by "::"
-            let path_segment_ident_names: Vec<&str> = path_segments
-                .iter()
-                .map(|path_seg| path_seg.ident.name.as_str())
-                .collect();
-            Some(path_segment_ident_names.join("::"))
-        },
-        _ => None,
+/// Check a chain of `str::replace` calls for `collapsible_str_replace` lint.
+fn check_consecutive_replace_calls<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx hir::Expr<'tcx>,
+    replace_methods: &ReplaceMethods<'tcx>,
+    to_arg: &'tcx hir::Expr<'tcx>,
+) {
+    let from_args = &replace_methods.from_args;
+    let from_arg_reprs: Vec<String> = from_args
+        .iter()
+        .map(|from_arg| snippet(cx, from_arg.span, "..").to_string())
+        .collect();
+    let app = Applicability::MachineApplicable;
+    let earliest_replace_call = replace_methods.methods.front().unwrap();
+    if let Some((_, [..], span_lo)) = method_call(earliest_replace_call) {
+        span_lint_and_sugg(
+            cx,
+            COLLAPSIBLE_STR_REPLACE,
+            expr.span.with_lo(span_lo.lo()),
+            "used consecutive `str::replace` call",
+            "replace with",
+            format!(
+                "replace([{}], {})",
+                from_arg_reprs.join(", "),
+                snippet(cx, to_arg.span, ".."),
+            ),
+            app,
+        );
     }
 }
-
-fn get_earliest_replace_call_span<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Span {
-    let mut earliest_replace_call_span = expr.span;
-
-    let _: Option<()> = for_each_expr(expr, |e| {
-        if let Some((name, [_, args @ ..], span)) = method_call(e) {
-            match (name, args) {
-                ("replace", [_, _]) => {
-                    earliest_replace_call_span = span;
-                    ControlFlow::Continue(())
-                },
-                _ => ControlFlow::BREAK,
-            }
-        } else {
-            ControlFlow::Continue(())
-        }
-    });
-
-    earliest_replace_call_span
-}
-
-/// Find the original receiver of a chain of `str::replace` method calls.
-fn find_original_recv<'tcx>(recv: &'tcx hir::Expr<'tcx>) -> &'tcx hir::Expr<'tcx> {
-    let mut original_recv = recv;
-
-    let _: Option<()> = for_each_expr(recv, |e| {
-        if let Some((name, [prev_recv, args @ ..], _)) = method_call(e) {
-            match (name, args) {
-                ("replace", [_, _]) => {
-                    original_recv = prev_recv;
-                    ControlFlow::Continue(())
-                },
-                _ => ControlFlow::BREAK,
-            }
-        } else {
-            ControlFlow::Continue(())
-        }
-    });
-
-    original_recv
-}
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 8b2fa8e9457..49d4900295f 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -156,7 +156,7 @@ declare_clippy_lint! {
     /// ```
     /// Use instead:
     /// ```rust
-    /// let hello = "hesuo worpd".replace(|c| matches!(c, 's' | 'u' | 'p'), "l");
+    /// let hello = "hesuo worpd".replace(&['s', 'u', 'p'], "l");
     /// ```
     #[clippy::version = "1.64.0"]
     pub COLLAPSIBLE_STR_REPLACE,
@@ -3507,6 +3507,14 @@ impl Methods {
                 ("repeat", [arg]) => {
                     repeat_once::check(cx, expr, recv, arg);
                 },
+                (name @ ("replace" | "replacen"), [arg1, arg2] | [arg1, arg2, _]) => {
+                    no_effect_replace::check(cx, expr, arg1, arg2);
+
+                    // Check for repeated `str::replace` calls to perform `collapsible_str_replace` lint
+                    if name == "replace" && let Some(("replace", ..)) = method_call(recv) {
+                        collapsible_str_replace::check(cx, expr, arg1, arg2);
+                    }
+                },
                 ("resize", [count_arg, default_arg]) => {
                     vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span);
                 },
@@ -3519,10 +3527,6 @@ impl Methods {
                 ("sort_unstable_by", [arg]) => {
                     unnecessary_sort_by::check(cx, expr, recv, arg, true);
                 },
-                ("replace" | "replacen", [arg1, arg2] | [arg1, arg2, _]) => {
-                    no_effect_replace::check(cx, expr, arg1, arg2);
-                    collapsible_str_replace::check(cx, expr, name, recv);
-                },
                 ("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);
diff --git a/tests/ui/collapsible_str_replace.fixed b/tests/ui/collapsible_str_replace.fixed
index 0bf857d9837..49fc9a9629e 100644
--- a/tests/ui/collapsible_str_replace.fixed
+++ b/tests/ui/collapsible_str_replace.fixed
@@ -2,64 +2,72 @@
 
 #![warn(clippy::collapsible_str_replace)]
 
-fn get_filter() -> &'static str {
-    "u"
+fn get_filter() -> char {
+    'u'
 }
 
 fn main() {
-    let misspelled = "hesuo worpd";
-
+    let d = 'd';
     let p = 'p';
     let s = 's';
     let u = 'u';
     let l = "l";
 
+    let mut iter = ["l", "z"].iter();
+
     // LINT CASES
-    let _ = misspelled.replace(|c| matches!(c, 'u' | 's'), "l");
+    let _ = "hesuo worpd".replace(['s', 'u'], "l");
 
-    let _ = misspelled.replace(|c| matches!(c, 'u' | 's'), l);
+    let _ = "hesuo worpd".replace(['s', 'u'], l);
 
-    let _ = misspelled.replace(|c| matches!(c, 'p' | 'u' | 's'), "l");
+    let _ = "hesuo worpd".replace(['s', 'u', 'p'], "l");
 
-    let _ = misspelled
-        .replace(|c| matches!(c, 'd' | 'p' | 'u' | 's'), "l");
+    let _ = "hesuo worpd"
+        .replace(['s', 'u', 'p', 'd'], "l");
 
-    // FALLBACK CASES
-    // If there are consecutive calls to `str::replace` and all or any chars are variables,
-    // recommend the fallback `misspelled.replace(&[s, u, p], "l")`
-    let _ = misspelled.replace(&['u' , s], "l");
+    let _ = "hesuo world".replace([s, 'u'], "l");
 
-    let _ = misspelled.replace(&['p' , 'u' , s], "l");
+    let _ = "hesuo worpd".replace([s, 'u', 'p'], "l");
 
-    let _ = misspelled.replace(&['p' , u , s], "l");
+    let _ = "hesuo worpd".replace([s, u, 'p'], "l");
 
-    let _ = misspelled.replace(&[p , u , s], "l");
+    let _ = "hesuo worpd".replace([s, u, p], "l");
 
-    // NO LINT CASES
-    let _ = misspelled.replace('s', "l");
+    let _ = "hesuo worlp".replace(['s', 'u'], "l").replace('p', "d");
+
+    let _ = "hesuo worpd".replace('s', "x").replace(['u', 'p'], "l");
 
-    let _ = misspelled.replace(s, "l");
+    // Note: Future iterations could lint `replace(|c| matches!(c, "su" | 'd' | 'p'), "l")`
+    let _ = "hesudo worpd".replace("su", "l").replace(['d', 'p'], "l");
 
-    // If the consecutive `str::replace` calls have different `to` arguments, do not lint
-    let _ = misspelled.replace('s', "l").replace('u', "p");
+    let _ = "hesudo worpd".replace([d, 'p'], "l").replace("su", "l");
+
+    let _ = "hesuo world".replace([get_filter(), 's'], "l");
+
+    // NO LINT CASES
+    let _ = "hesuo world".replace('s', "l").replace('u', "p");
 
-    let _ = misspelled.replace(&get_filter(), "l");
+    let _ = "hesuo worpd".replace('s', "l").replace('p', l);
 
-    let _ = misspelled.replace(&['s', 'u', 'p'], "l");
+    let _ = "hesudo worpd".replace('d', "l").replace("su", "l").replace('p', "l");
 
-    let _ = misspelled.replace(&['s', 'u', 'p'], l);
+    // Note: Future iterations of `collapsible_str_replace` might lint this and combine to `[s, u, p]`
+    let _ = "hesuo worpd".replace([s, u], "l").replace([u, p], "l");
 
-    let _ = misspelled.replace(&['s', 'u'], "l").replace(&['u', 'p'], "l");
+    let _ = "hesuo worpd".replace(['s', 'u'], "l").replace(['u', 'p'], "l");
 
-    let _ = misspelled.replace('s', "l").replace(&['u', 'p'], "l");
+    let _ = "hesuo worpd".replace('s', "l").replace(['u', 'p'], "l");
 
-    let _ = misspelled.replace(&['s', 'u'], "l").replace('p', "l");
+    let _ = "hesuo worpd".replace(['s', 'u', 'p'], "l").replace('r', "l");
 
-    let _ = misspelled.replace(&['s', u, 'p'], "l");
+    let _ = "hesuo worpd".replace(['s', 'u', 'p'], l).replace('r', l);
 
-    let _ = misspelled.replace(&[s, u, 'p'], "l");
+    let _ = "hesuo worpd".replace(['s', u, 'p'], "l").replace('r', "l");
 
-    let _ = misspelled.replace(&[s, u, p], "l");
+    let _ = "hesuo worpd".replace([s, u], "l").replace(p, "l");
 
-    let _ = misspelled.replace(&[s, u], "l").replace(&[u, p], "l");
+    // Regression test
+    let _ = "hesuo worpd"
+        .replace('u', iter.next().unwrap())
+        .replace('s', iter.next().unwrap());
 }
diff --git a/tests/ui/collapsible_str_replace.rs b/tests/ui/collapsible_str_replace.rs
index 45d9fd87e5e..e3e25c4146f 100644
--- a/tests/ui/collapsible_str_replace.rs
+++ b/tests/ui/collapsible_str_replace.rs
@@ -2,67 +2,75 @@
 
 #![warn(clippy::collapsible_str_replace)]
 
-fn get_filter() -> &'static str {
-    "u"
+fn get_filter() -> char {
+    'u'
 }
 
 fn main() {
-    let misspelled = "hesuo worpd";
-
+    let d = 'd';
     let p = 'p';
     let s = 's';
     let u = 'u';
     let l = "l";
 
+    let mut iter = ["l", "z"].iter();
+
     // LINT CASES
-    let _ = misspelled.replace('s', "l").replace('u', "l");
+    let _ = "hesuo worpd".replace('s', "l").replace('u', "l");
 
-    let _ = misspelled.replace('s', l).replace('u', l);
+    let _ = "hesuo worpd".replace('s', l).replace('u', l);
 
-    let _ = misspelled.replace('s', "l").replace('u', "l").replace('p', "l");
+    let _ = "hesuo worpd".replace('s', "l").replace('u', "l").replace('p', "l");
 
-    let _ = misspelled
+    let _ = "hesuo worpd"
         .replace('s', "l")
         .replace('u', "l")
         .replace('p', "l")
         .replace('d', "l");
 
-    // FALLBACK CASES
-    // If there are consecutive calls to `str::replace` and all or any chars are variables,
-    // recommend the fallback `misspelled.replace(&[s, u, p], "l")`
-    let _ = misspelled.replace(s, "l").replace('u', "l");
+    let _ = "hesuo world".replace(s, "l").replace('u', "l");
 
-    let _ = misspelled.replace(s, "l").replace('u', "l").replace('p', "l");
+    let _ = "hesuo worpd".replace(s, "l").replace('u', "l").replace('p', "l");
 
-    let _ = misspelled.replace(s, "l").replace(u, "l").replace('p', "l");
+    let _ = "hesuo worpd".replace(s, "l").replace(u, "l").replace('p', "l");
 
-    let _ = misspelled.replace(s, "l").replace(u, "l").replace(p, "l");
+    let _ = "hesuo worpd".replace(s, "l").replace(u, "l").replace(p, "l");
 
-    // NO LINT CASES
-    let _ = misspelled.replace('s', "l");
+    let _ = "hesuo worlp".replace('s', "l").replace('u', "l").replace('p', "d");
+
+    let _ = "hesuo worpd".replace('s', "x").replace('u', "l").replace('p', "l");
 
-    let _ = misspelled.replace(s, "l");
+    // Note: Future iterations could lint `replace(|c| matches!(c, "su" | 'd' | 'p'), "l")`
+    let _ = "hesudo worpd".replace("su", "l").replace('d', "l").replace('p', "l");
 
-    // If the consecutive `str::replace` calls have different `to` arguments, do not lint
-    let _ = misspelled.replace('s', "l").replace('u', "p");
+    let _ = "hesudo worpd".replace(d, "l").replace('p', "l").replace("su", "l");
+
+    let _ = "hesuo world".replace(get_filter(), "l").replace('s', "l");
+
+    // NO LINT CASES
+    let _ = "hesuo world".replace('s', "l").replace('u', "p");
 
-    let _ = misspelled.replace(&get_filter(), "l");
+    let _ = "hesuo worpd".replace('s', "l").replace('p', l);
 
-    let _ = misspelled.replace(&['s', 'u', 'p'], "l");
+    let _ = "hesudo worpd".replace('d', "l").replace("su", "l").replace('p', "l");
 
-    let _ = misspelled.replace(&['s', 'u', 'p'], l);
+    // Note: Future iterations of `collapsible_str_replace` might lint this and combine to `[s, u, p]`
+    let _ = "hesuo worpd".replace([s, u], "l").replace([u, p], "l");
 
-    let _ = misspelled.replace(&['s', 'u'], "l").replace(&['u', 'p'], "l");
+    let _ = "hesuo worpd".replace(['s', 'u'], "l").replace(['u', 'p'], "l");
 
-    let _ = misspelled.replace('s', "l").replace(&['u', 'p'], "l");
+    let _ = "hesuo worpd".replace('s', "l").replace(['u', 'p'], "l");
 
-    let _ = misspelled.replace(&['s', 'u'], "l").replace('p', "l");
+    let _ = "hesuo worpd".replace(['s', 'u', 'p'], "l").replace('r', "l");
 
-    let _ = misspelled.replace(&['s', u, 'p'], "l");
+    let _ = "hesuo worpd".replace(['s', 'u', 'p'], l).replace('r', l);
 
-    let _ = misspelled.replace(&[s, u, 'p'], "l");
+    let _ = "hesuo worpd".replace(['s', u, 'p'], "l").replace('r', "l");
 
-    let _ = misspelled.replace(&[s, u, p], "l");
+    let _ = "hesuo worpd".replace([s, u], "l").replace(p, "l");
 
-    let _ = misspelled.replace(&[s, u], "l").replace(&[u, p], "l");
+    // Regression test
+    let _ = "hesuo worpd"
+        .replace('u', iter.next().unwrap())
+        .replace('s', iter.next().unwrap());
 }
diff --git a/tests/ui/collapsible_str_replace.stderr b/tests/ui/collapsible_str_replace.stderr
index 372fe1da448..8e3daf3b898 100644
--- a/tests/ui/collapsible_str_replace.stderr
+++ b/tests/ui/collapsible_str_replace.stderr
@@ -1,56 +1,86 @@
 error: used consecutive `str::replace` call
-  --> $DIR/collapsible_str_replace.rs:18:24
+  --> $DIR/collapsible_str_replace.rs:19:27
    |
-LL |     let _ = misspelled.replace('s', "l").replace('u', "l");
-   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(|c| matches!(c, 'u' | 's'), "l")`
+LL |     let _ = "hesuo worpd".replace('s', "l").replace('u', "l");
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['s', 'u'], "l")`
    |
    = note: `-D clippy::collapsible-str-replace` implied by `-D warnings`
 
 error: used consecutive `str::replace` call
-  --> $DIR/collapsible_str_replace.rs:20:24
+  --> $DIR/collapsible_str_replace.rs:21:27
    |
-LL |     let _ = misspelled.replace('s', l).replace('u', l);
-   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(|c| matches!(c, 'u' | 's'), l)`
+LL |     let _ = "hesuo worpd".replace('s', l).replace('u', l);
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['s', 'u'], l)`
 
 error: used consecutive `str::replace` call
-  --> $DIR/collapsible_str_replace.rs:22:24
+  --> $DIR/collapsible_str_replace.rs:23:27
    |
-LL |     let _ = misspelled.replace('s', "l").replace('u', "l").replace('p', "l");
-   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(|c| matches!(c, 'p' | 'u' | 's'), "l")`
+LL |     let _ = "hesuo worpd".replace('s', "l").replace('u', "l").replace('p', "l");
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['s', 'u', 'p'], "l")`
 
 error: used consecutive `str::replace` call
-  --> $DIR/collapsible_str_replace.rs:25:10
+  --> $DIR/collapsible_str_replace.rs:26:10
    |
 LL |           .replace('s', "l")
    |  __________^
 LL | |         .replace('u', "l")
 LL | |         .replace('p', "l")
 LL | |         .replace('d', "l");
-   | |__________________________^ help: replace with: `replace(|c| matches!(c, 'd' | 'p' | 'u' | 's'), "l")`
+   | |__________________________^ help: replace with: `replace(['s', 'u', 'p', 'd'], "l")`
 
 error: used consecutive `str::replace` call
-  --> $DIR/collapsible_str_replace.rs:33:24
+  --> $DIR/collapsible_str_replace.rs:31:27
    |
-LL |     let _ = misspelled.replace(s, "l").replace('u', "l");
-   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(&['u' , s], "l")`
+LL |     let _ = "hesuo world".replace(s, "l").replace('u', "l");
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([s, 'u'], "l")`
 
 error: used consecutive `str::replace` call
-  --> $DIR/collapsible_str_replace.rs:35:24
+  --> $DIR/collapsible_str_replace.rs:33:27
    |
-LL |     let _ = misspelled.replace(s, "l").replace('u', "l").replace('p', "l");
-   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(&['p' , 'u' , s], "l")`
+LL |     let _ = "hesuo worpd".replace(s, "l").replace('u', "l").replace('p', "l");
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([s, 'u', 'p'], "l")`
 
 error: used consecutive `str::replace` call
-  --> $DIR/collapsible_str_replace.rs:37:24
+  --> $DIR/collapsible_str_replace.rs:35:27
    |
-LL |     let _ = misspelled.replace(s, "l").replace(u, "l").replace('p', "l");
-   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(&['p' , u , s], "l")`
+LL |     let _ = "hesuo worpd".replace(s, "l").replace(u, "l").replace('p', "l");
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([s, u, 'p'], "l")`
 
 error: used consecutive `str::replace` call
-  --> $DIR/collapsible_str_replace.rs:39:24
+  --> $DIR/collapsible_str_replace.rs:37:27
    |
-LL |     let _ = misspelled.replace(s, "l").replace(u, "l").replace(p, "l");
-   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(&[p , u , s], "l")`
+LL |     let _ = "hesuo worpd".replace(s, "l").replace(u, "l").replace(p, "l");
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([s, u, p], "l")`
 
-error: aborting due to 8 previous errors
+error: used consecutive `str::replace` call
+  --> $DIR/collapsible_str_replace.rs:39:27
+   |
+LL |     let _ = "hesuo worlp".replace('s', "l").replace('u', "l").replace('p', "d");
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['s', 'u'], "l")`
+
+error: used consecutive `str::replace` call
+  --> $DIR/collapsible_str_replace.rs:41:45
+   |
+LL |     let _ = "hesuo worpd".replace('s', "x").replace('u', "l").replace('p', "l");
+   |                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['u', 'p'], "l")`
+
+error: used consecutive `str::replace` call
+  --> $DIR/collapsible_str_replace.rs:44:47
+   |
+LL |     let _ = "hesudo worpd".replace("su", "l").replace('d', "l").replace('p', "l");
+   |                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['d', 'p'], "l")`
+
+error: used consecutive `str::replace` call
+  --> $DIR/collapsible_str_replace.rs:46:28
+   |
+LL |     let _ = "hesudo worpd".replace(d, "l").replace('p', "l").replace("su", "l");
+   |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([d, 'p'], "l")`
+
+error: used consecutive `str::replace` call
+  --> $DIR/collapsible_str_replace.rs:48:27
+   |
+LL |     let _ = "hesuo world".replace(get_filter(), "l").replace('s', "l");
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([get_filter(), 's'], "l")`
+
+error: aborting due to 13 previous errors