Skip to content

Commit c989746

Browse files
committed
Remove checks on char slice; improve lint suggestion
1 parent a9bd0bd commit c989746

File tree

2 files changed

+59
-91
lines changed

2 files changed

+59
-91
lines changed

clippy_lints/src/methods/collapsible_str_replace.rs

Lines changed: 29 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use rustc_hir::*;
1111
use rustc_lint::LateContext;
1212
use rustc_middle::ty;
1313
use rustc_span::source_map::Spanned;
14+
use rustc_span::Span;
1415

1516
use super::method_call;
1617
use super::COLLAPSIBLE_STR_REPLACE;
@@ -23,31 +24,20 @@ pub(super) fn check<'tcx>(
2324
args: &'tcx [hir::Expr<'tcx>],
2425
) {
2526
match (name, args) {
26-
("replace", [from, to]) => {
27+
("replace", ..) => {
2728
// The receiver of the method call must be `str` type to lint `collapsible_str_replace`
2829
let original_recv = find_original_recv(recv);
2930
let original_recv_ty_kind = cx.typeck_results().expr_ty(original_recv).peel_refs().kind();
3031
let original_recv_is_str_kind = matches!(original_recv_ty_kind, ty::Str);
3132

32-
if_chain! {
33-
// Check for `str::replace` calls with char slice for linting
34-
if original_recv_is_str_kind;
35-
let from_ty_kind = cx.typeck_results().expr_ty(from).peel_refs().kind();
36-
if let ty::Array(array_ty, _) = from_ty_kind;
37-
if matches!(array_ty.kind(), ty::Char);
38-
then {
39-
check_replace_call_with_char_slice(cx, from, to);
40-
return;
41-
}
42-
}
43-
4433
if_chain! {
4534
if original_recv_is_str_kind;
4635
if let Some(parent) = get_parent_expr(cx, expr);
47-
if let Some((name, [..], _)) = method_call(parent);
36+
if let Some((name, ..)) = method_call(parent);
4837

4938
then {
5039
match name {
40+
// If the parent node is a `str::replace` call, we've already handled the lint, don't lint again
5141
"replace" => return,
5242
_ => {
5343
check_consecutive_replace_calls(cx, expr);
@@ -59,7 +49,7 @@ pub(super) fn check<'tcx>(
5949

6050
match method_call(recv) {
6151
// Check if there's an earlier `str::replace` call
62-
Some(("replace", [_, _, _], _)) => {
52+
Some(("replace", ..)) => {
6353
if original_recv_is_str_kind {
6454
check_consecutive_replace_calls(cx, expr);
6555
return;
@@ -72,55 +62,14 @@ pub(super) fn check<'tcx>(
7262
}
7363
}
7464

75-
/// Check a `str::replace` call that contains a char slice as `from` argument for
76-
/// `collapsible_str_replace` lint.
77-
fn check_replace_call_with_char_slice<'tcx>(
78-
cx: &LateContext<'tcx>,
79-
from_arg: &'tcx hir::Expr<'tcx>,
80-
to_arg: &'tcx hir::Expr<'tcx>,
81-
) {
82-
let mut char_slice_has_no_variables = true;
83-
let mut chars: Vec<String> = Vec::new();
84-
85-
// Go through the `from_arg` to collect all char literals
86-
let _: Option<()> = for_each_expr(from_arg, |e| {
87-
if let ExprKind::Lit(Spanned {
88-
node: LitKind::Char(_), ..
89-
}) = e.kind
90-
{
91-
chars.push(get_replace_call_char_arg_repr(e).unwrap());
92-
ControlFlow::Continue(())
93-
} else if let ExprKind::Path(..) = e.kind {
94-
// If a variable is found in the char slice, no lint for first version of this lint
95-
char_slice_has_no_variables = false;
96-
ControlFlow::BREAK
97-
} else {
98-
ControlFlow::Continue(())
99-
}
100-
});
101-
102-
if char_slice_has_no_variables {
103-
if let Some(to_arg_repr) = get_replace_call_char_arg_repr(to_arg) {
104-
let app = Applicability::MachineApplicable;
105-
span_lint_and_sugg(
106-
cx,
107-
COLLAPSIBLE_STR_REPLACE,
108-
from_arg.span,
109-
"used slice of chars in `str::replace` call",
110-
"replace with",
111-
format!("replace(|c| matches!(c, {}), {})", chars.join(" | "), to_arg_repr,),
112-
app,
113-
);
114-
}
115-
}
116-
}
117-
11865
/// Check a chain of `str::replace` calls for `collapsible_str_replace` lint.
11966
fn check_consecutive_replace_calls<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
12067
if_chain! {
12168
if let Some(from_args) = get_replace_call_from_args_if_all_char_ty(cx, expr);
12269
if let Some(to_arg) = get_replace_call_unique_to_arg_repr(expr);
12370
then {
71+
let earliest_replace_call_span = get_earliest_replace_call_span(expr);
72+
12473
if replace_call_from_args_are_only_lit_chars(&from_args) {
12574
let from_arg_reprs: Vec<String> = from_args.iter().map(|from_arg| {
12675
get_replace_call_char_arg_repr(from_arg).unwrap()
@@ -130,7 +79,7 @@ fn check_consecutive_replace_calls<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir
13079
span_lint_and_sugg(
13180
cx,
13281
COLLAPSIBLE_STR_REPLACE,
133-
expr.span,
82+
expr.span.with_lo(earliest_replace_call_span.lo()),
13483
"used consecutive `str::replace` call",
13584
"replace with",
13685
format!(
@@ -150,7 +99,7 @@ fn check_consecutive_replace_calls<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir
15099
span_lint_and_sugg(
151100
cx,
152101
COLLAPSIBLE_STR_REPLACE,
153-
expr.span,
102+
expr.span.with_lo(earliest_replace_call_span.lo()),
154103
"used consecutive `str::replace` call",
155104
"replace with",
156105
format!(
@@ -295,6 +244,26 @@ fn get_replace_call_char_arg_repr<'tcx>(arg: &'tcx hir::Expr<'tcx>) -> Option<St
295244
}
296245
}
297246

247+
fn get_earliest_replace_call_span<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Span {
248+
let mut earliest_replace_call_span = expr.span;
249+
250+
let _: Option<()> = for_each_expr(expr, |e| {
251+
if let Some((name, [_, args @ ..], span)) = method_call(e) {
252+
match (name, args) {
253+
("replace", [_, _]) => {
254+
earliest_replace_call_span = span;
255+
ControlFlow::Continue(())
256+
},
257+
_ => ControlFlow::BREAK,
258+
}
259+
} else {
260+
ControlFlow::Continue(())
261+
}
262+
});
263+
264+
earliest_replace_call_span
265+
}
266+
298267
/// Find the original receiver of a chain of `str::replace` method calls.
299268
fn find_original_recv<'tcx>(recv: &'tcx hir::Expr<'tcx>) -> &'tcx hir::Expr<'tcx> {
300269
let mut original_recv = recv;

tests/ui/collapsible_str_replace.rs

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,38 @@ fn main() {
1313
let l = "l";
1414

1515
// LINT CASES
16-
// If the first argument to a single `str::replace` call is a slice and none of the chars
17-
// are variables, recommend `collapsible_str_replace`
18-
let replacement = misspelled.replace(&['s', 'u', 'p'], "l");
16+
let replacement = misspelled.replace('s', "l").replace('u', "l");
1917
println!("{replacement}");
2018

21-
let replacement = misspelled.replace(&['s', 'u', 'p'], l);
19+
let replacement = misspelled.replace('s', l).replace('u', l);
2220
println!("{replacement}");
2321

24-
// If multiple `str::replace` calls contain slices and none of the chars are variables,
25-
// recommend `collapsible_str_replace`
26-
let replacement = misspelled.replace(&['s', 'u'], "l").replace(&['u', 'p'], "l");
22+
let replacement = misspelled.replace('s', "l").replace('u', "l").replace('p', "l");
2723
println!("{replacement}");
2824

29-
let replacement = misspelled.replace('s', "l").replace(&['u', 'p'], "l");
25+
let replacement = misspelled
26+
.replace('s', "l")
27+
.replace('u', "l")
28+
.replace('p', "l")
29+
.replace('d', "l");
3030
println!("{replacement}");
3131

32-
let replacement = misspelled.replace(&['s', 'u'], "l").replace('p', "l");
33-
println!("replacement");
32+
// FALLBACK CASES
33+
// If there are consecutive calls to `str::replace` and all or any chars are variables,
34+
// recommend the fallback `misspelled.replace(&[s, u, p], "l")`
35+
let replacement = misspelled.replace(s, "l").replace('u', "l");
36+
println!("{replacement}");
3437

35-
// If there are consecutive calls to `str::replace` and none of the chars are variables,
36-
// recommend `collapsible_str_replace`
37-
let replacement = misspelled.replace('s', "l").replace('u', "l");
38+
let replacement = misspelled.replace(s, "l").replace('u', "l").replace('p', "l");
3839
println!("{replacement}");
3940

40-
let replacement = misspelled.replace('s', "l").replace('u', "l").replace('p', "l");
41+
let replacement = misspelled.replace(s, "l").replace(u, "l").replace('p', "l");
42+
println!("{replacement}");
43+
44+
let replacement = misspelled.replace(s, "l").replace(u, "l").replace(p, "l");
4145
println!("{replacement}");
4246

4347
// NO LINT CASES
44-
// If there is a single call to `str::replace` and the first argument is a char or a variable,
45-
// do not recommend `collapsible_str_replace`
4648
let replacement = misspelled.replace('s', "l");
4749
println!("{replacement}");
4850

@@ -53,36 +55,33 @@ fn main() {
5355
let replacement = misspelled.replace('s', "l").replace('u', "p");
5456
println!("{replacement}");
5557

56-
// If the `from` argument is of kind other than a slice or a char, do not lint
5758
let replacement = misspelled.replace(&get_filter(), "l");
59+
println!("{replacement}");
5860

59-
// NO LINT TIL IMPROVEMENT
60-
// The first iteration of `collapsible_str_replace` will not create lint if the first argument to
61-
// a single `str::replace` call is a slice and one or more of its chars are variables
62-
let replacement = misspelled.replace(&['s', u, 'p'], "l");
61+
let replacement = misspelled.replace(&['s', 'u', 'p'], "l");
6362
println!("{replacement}");
6463

65-
let replacement = misspelled.replace(&[s, u, 'p'], "l");
64+
let replacement = misspelled.replace(&['s', 'u', 'p'], l);
6665
println!("{replacement}");
6766

68-
let replacement = misspelled.replace(&[s, u, p], "l");
67+
let replacement = misspelled.replace(&['s', 'u'], "l").replace(&['u', 'p'], "l");
6968
println!("{replacement}");
7069

71-
let replacement = misspelled.replace(&[s, u], "l").replace(&[u, p], "l");
70+
let replacement = misspelled.replace('s', "l").replace(&['u', 'p'], "l");
7271
println!("{replacement}");
7372

74-
// FALLBACK CASES
75-
// If there are consecutive calls to `str::replace` and all or any chars are variables,
76-
// recommend the fallback `misspelled.replace(&[s, u, p], "l")`
77-
let replacement = misspelled.replace(s, "l").replace('u', "l");
73+
let replacement = misspelled.replace(&['s', 'u'], "l").replace('p', "l");
74+
println!("replacement");
75+
76+
let replacement = misspelled.replace(&['s', u, 'p'], "l");
7877
println!("{replacement}");
7978

80-
let replacement = misspelled.replace(s, "l").replace('u', "l").replace('p', "l");
79+
let replacement = misspelled.replace(&[s, u, 'p'], "l");
8180
println!("{replacement}");
8281

83-
let replacement = misspelled.replace(s, "l").replace(u, "l").replace('p', "l");
82+
let replacement = misspelled.replace(&[s, u, p], "l");
8483
println!("{replacement}");
8584

86-
let replacement = misspelled.replace(s, "l").replace(u, "l").replace(p, "l");
85+
let replacement = misspelled.replace(&[s, u], "l").replace(&[u, p], "l");
8786
println!("{replacement}");
8887
}

0 commit comments

Comments
 (0)