|
1 |
| -// run-rustfix |
2 |
| - |
3 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg;
|
4 |
| -use clippy_utils::get_parent_expr; |
| 2 | +use clippy_utils::source::snippet; |
5 | 3 | use clippy_utils::visitors::for_each_expr;
|
| 4 | +use clippy_utils::{eq_expr_value, get_parent_expr}; |
6 | 5 | use core::ops::ControlFlow;
|
7 |
| -use if_chain::if_chain; |
8 |
| -use rustc_ast::ast::LitKind; |
9 |
| -use rustc_data_structures::fx::FxHashSet; |
10 | 6 | use rustc_errors::Applicability;
|
11 | 7 | use rustc_hir as hir;
|
12 |
| -use rustc_hir::{ExprKind, Path, QPath}; |
13 | 8 | use rustc_lint::LateContext;
|
14 |
| -use rustc_middle::ty; |
15 |
| -use rustc_span::source_map::Spanned; |
16 |
| -use rustc_span::Span; |
| 9 | +use std::collections::VecDeque; |
17 | 10 |
|
18 | 11 | use super::method_call;
|
19 | 12 | use super::COLLAPSIBLE_STR_REPLACE;
|
20 | 13 |
|
21 | 14 | pub(super) fn check<'tcx>(
|
22 | 15 | cx: &LateContext<'tcx>,
|
23 | 16 | expr: &'tcx hir::Expr<'tcx>,
|
24 |
| - name: &str, |
25 |
| - recv: &'tcx hir::Expr<'tcx>, |
| 17 | + from: &'tcx hir::Expr<'tcx>, |
| 18 | + to: &'tcx hir::Expr<'tcx>, |
26 | 19 | ) {
|
27 |
| - if name == "replace" { |
28 |
| - // The receiver of the method call must be `str` type to lint `collapsible_str_replace` |
29 |
| - let original_recv = find_original_recv(recv); |
30 |
| - let original_recv_ty_kind = cx.typeck_results().expr_ty(original_recv).peel_refs().kind(); |
31 |
| - let original_recv_is_str_kind = matches!(original_recv_ty_kind, ty::Str); |
32 |
| - |
33 |
| - if_chain! { |
34 |
| - if original_recv_is_str_kind; |
35 |
| - if let Some(parent) = get_parent_expr(cx, expr); |
36 |
| - if let Some((name, ..)) = method_call(parent); |
37 |
| - if name == "replace"; |
38 |
| - |
39 |
| - then { |
40 |
| - // If the parent node is a `str::replace` call, we've already handled the lint, don't lint again |
41 |
| - return; |
42 |
| - } |
| 20 | + let replace_methods = collect_replace_calls(cx, expr, to); |
| 21 | + if replace_methods.methods.len() > 1 { |
| 22 | + let from_kind = cx.typeck_results().expr_ty(from).peel_refs().kind(); |
| 23 | + // If the parent node's `to` argument is the same as the `to` argument |
| 24 | + // of the last replace call in the current chain, don't lint as it was already linted |
| 25 | + if let Some(parent) = get_parent_expr(cx, expr) |
| 26 | + && let Some(("replace", [_, current_from, current_to], _)) = method_call(parent) |
| 27 | + && eq_expr_value(cx, to, current_to) |
| 28 | + && from_kind == cx.typeck_results().expr_ty(current_from).peel_refs().kind() |
| 29 | + { |
| 30 | + return; |
43 | 31 | }
|
44 | 32 |
|
45 |
| - if let Some(("replace", ..)) = method_call(recv) { |
46 |
| - // Check if there's an earlier `str::replace` call |
47 |
| - if original_recv_is_str_kind { |
48 |
| - check_consecutive_replace_calls(cx, expr); |
49 |
| - } |
50 |
| - } |
| 33 | + check_consecutive_replace_calls(cx, expr, &replace_methods, to); |
51 | 34 | }
|
52 | 35 | }
|
53 | 36 |
|
54 |
| -/// Check a chain of `str::replace` calls for `collapsible_str_replace` lint. |
55 |
| -fn check_consecutive_replace_calls<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { |
56 |
| - if_chain! { |
57 |
| - if let Some(from_args) = get_replace_call_from_args_if_all_char_ty(cx, expr); |
58 |
| - if let Some(to_arg) = get_replace_call_unique_to_arg_repr(expr); |
59 |
| - then { |
60 |
| - let earliest_replace_call_span = get_earliest_replace_call_span(expr); |
61 |
| - |
62 |
| - if replace_call_from_args_are_only_lit_chars(&from_args) { |
63 |
| - let from_arg_reprs: Vec<String> = from_args.iter().map(|from_arg| { |
64 |
| - get_replace_call_char_arg_repr(from_arg).unwrap() |
65 |
| - }).collect(); |
66 |
| - let app = Applicability::MachineApplicable; |
67 |
| - |
68 |
| - span_lint_and_sugg( |
69 |
| - cx, |
70 |
| - COLLAPSIBLE_STR_REPLACE, |
71 |
| - expr.span.with_lo(earliest_replace_call_span.lo()), |
72 |
| - "used consecutive `str::replace` call", |
73 |
| - "replace with", |
74 |
| - format!( |
75 |
| - "replace(|c| matches!(c, {}), {})", |
76 |
| - from_arg_reprs.join(" | "), |
77 |
| - to_arg, |
78 |
| - ), |
79 |
| - app, |
80 |
| - ); |
81 |
| - } else { |
82 |
| - // Use fallback lint |
83 |
| - let from_arg_reprs: Vec<String> = from_args.iter().map(|from_arg| { |
84 |
| - get_replace_call_char_arg_repr(from_arg).unwrap() |
85 |
| - }).collect(); |
86 |
| - let app = Applicability::MachineApplicable; |
87 |
| - |
88 |
| - span_lint_and_sugg( |
89 |
| - cx, |
90 |
| - COLLAPSIBLE_STR_REPLACE, |
91 |
| - expr.span.with_lo(earliest_replace_call_span.lo()), |
92 |
| - "used consecutive `str::replace` call", |
93 |
| - "replace with", |
94 |
| - format!( |
95 |
| - "replace(&[{}], {})", |
96 |
| - from_arg_reprs.join(" , "), |
97 |
| - to_arg, |
98 |
| - ), |
99 |
| - app, |
100 |
| - ); |
101 |
| - } |
102 |
| - } |
103 |
| - } |
| 37 | +struct ReplaceMethods<'tcx> { |
| 38 | + methods: VecDeque<&'tcx hir::Expr<'tcx>>, |
| 39 | + from_args: VecDeque<&'tcx hir::Expr<'tcx>>, |
104 | 40 | }
|
105 | 41 |
|
106 |
| -/// Check if all the `from` arguments of a chain of consecutive calls to `str::replace` |
107 |
| -/// are all of `ExprKind::Lit` types. If any is not, return false. |
108 |
| -fn replace_call_from_args_are_only_lit_chars<'tcx>(from_args: &[&'tcx hir::Expr<'tcx>]) -> bool { |
109 |
| - let mut only_lit_chars = true; |
110 |
| - |
111 |
| - for from_arg in from_args.iter() { |
112 |
| - match from_arg.kind { |
113 |
| - ExprKind::Lit(..) => {}, |
114 |
| - _ => only_lit_chars = false, |
115 |
| - } |
116 |
| - } |
117 |
| - |
118 |
| - only_lit_chars |
119 |
| -} |
120 |
| - |
121 |
| -/// Collect and return all of the `from` arguments of a chain of consecutive `str::replace` calls |
122 |
| -/// if these `from` arguments's expressions are of the `ty::Char` kind. Otherwise return `None`. |
123 |
| -fn get_replace_call_from_args_if_all_char_ty<'tcx>( |
| 42 | +fn collect_replace_calls<'tcx>( |
124 | 43 | cx: &LateContext<'tcx>,
|
125 | 44 | expr: &'tcx hir::Expr<'tcx>,
|
126 |
| -) -> Option<Vec<&'tcx hir::Expr<'tcx>>> { |
127 |
| - let mut all_from_args_are_chars = true; |
128 |
| - let mut from_args = Vec::new(); |
| 45 | + to_arg: &'tcx hir::Expr<'tcx>, |
| 46 | +) -> ReplaceMethods<'tcx> { |
| 47 | + let mut methods = VecDeque::new(); |
| 48 | + let mut from_args = VecDeque::new(); |
129 | 49 |
|
130 | 50 | let _: Option<()> = for_each_expr(expr, |e| {
|
131 |
| - if let Some((name, [_, args @ ..], _)) = method_call(e) { |
132 |
| - match (name, args) { |
133 |
| - ("replace", [from, _]) => { |
134 |
| - let from_ty_kind = cx.typeck_results().expr_ty(from).peel_refs().kind(); |
135 |
| - if matches!(from_ty_kind, ty::Char) { |
136 |
| - from_args.push(from); |
137 |
| - } else { |
138 |
| - all_from_args_are_chars = false; |
139 |
| - } |
140 |
| - ControlFlow::Continue(()) |
141 |
| - }, |
142 |
| - _ => ControlFlow::BREAK, |
143 |
| - } |
144 |
| - } else { |
145 |
| - ControlFlow::Continue(()) |
146 |
| - } |
147 |
| - }); |
148 |
| - |
149 |
| - if all_from_args_are_chars { |
150 |
| - return Some(from_args); |
151 |
| - } |
152 |
| - |
153 |
| - None |
154 |
| -} |
155 |
| - |
156 |
| -/// Return a unique String representation of the `to` argument used in a chain of `str::replace` |
157 |
| -/// calls if each `str::replace` call's `to` argument is identical to the other `to` arguments in |
158 |
| -/// the chain. Otherwise, return `None`. |
159 |
| -fn get_replace_call_unique_to_arg_repr<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<String> { |
160 |
| - let mut to_args = Vec::new(); |
161 |
| - |
162 |
| - let _: Option<()> = for_each_expr(expr, |e| { |
163 |
| - if let Some((name, [_, args @ ..], _)) = method_call(e) { |
164 |
| - match (name, args) { |
165 |
| - ("replace", [_, to]) => { |
166 |
| - to_args.push(to); |
167 |
| - ControlFlow::Continue(()) |
168 |
| - }, |
169 |
| - _ => ControlFlow::BREAK, |
| 51 | + if let Some(("replace", [_, from, to], _)) = method_call(e) { |
| 52 | + if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() { |
| 53 | + methods.push_front(e); |
| 54 | + from_args.push_front(from); |
| 55 | + ControlFlow::Continue(()) |
| 56 | + } else { |
| 57 | + ControlFlow::BREAK |
170 | 58 | }
|
171 | 59 | } else {
|
172 | 60 | ControlFlow::Continue(())
|
173 | 61 | }
|
174 | 62 | });
|
175 | 63 |
|
176 |
| - // let mut to_arg_repr_set = FxHashSet::default(); |
177 |
| - let mut to_arg_reprs = Vec::new(); |
178 |
| - for &to_arg in &to_args { |
179 |
| - if let Some(to_arg_repr) = get_replace_call_char_arg_repr(to_arg) { |
180 |
| - to_arg_reprs.push(to_arg_repr); |
181 |
| - } |
182 |
| - } |
183 |
| - |
184 |
| - let to_arg_repr_set = to_arg_reprs.iter().cloned().collect::<FxHashSet<_>>(); |
185 |
| - // Check if the set of `to` argument representations has more than one unique value |
186 |
| - if to_arg_repr_set.len() != 1 { |
187 |
| - return None; |
188 |
| - } |
189 |
| - |
190 |
| - // Return the single representation value |
191 |
| - to_arg_reprs.pop() |
| 64 | + ReplaceMethods { methods, from_args } |
192 | 65 | }
|
193 | 66 |
|
194 |
| -/// Get the representation of an argument of a `str::replace` call either of the literal char value |
195 |
| -/// or variable name, i.e. the resolved path segments `ident`. |
196 |
| -/// Return: |
197 |
| -/// - the str literal with double quotes, e.g. "\"l\"" |
198 |
| -/// - the char literal with single quotes, e.g. "'l'" |
199 |
| -/// - the variable as a String, e.g. "l" |
200 |
| -fn get_replace_call_char_arg_repr<'tcx>(arg: &'tcx hir::Expr<'tcx>) -> Option<String> { |
201 |
| - match arg.kind { |
202 |
| - ExprKind::Lit(Spanned { |
203 |
| - node: LitKind::Str(to_arg_val, _), |
204 |
| - .. |
205 |
| - }) => { |
206 |
| - let repr = to_arg_val.as_str(); |
207 |
| - let double_quote = "\""; |
208 |
| - Some(double_quote.to_owned() + repr + double_quote) |
209 |
| - }, |
210 |
| - ExprKind::Lit(Spanned { |
211 |
| - node: LitKind::Char(to_arg_val), |
212 |
| - .. |
213 |
| - }) => { |
214 |
| - let repr = to_arg_val.to_string(); |
215 |
| - let double_quote = "\'"; |
216 |
| - Some(double_quote.to_owned() + &repr + double_quote) |
217 |
| - }, |
218 |
| - ExprKind::Path(QPath::Resolved( |
219 |
| - _, |
220 |
| - Path { |
221 |
| - segments: path_segments, |
222 |
| - .. |
223 |
| - }, |
224 |
| - )) => { |
225 |
| - // join the path_segments values by "::" |
226 |
| - let path_segment_ident_names: Vec<&str> = path_segments |
227 |
| - .iter() |
228 |
| - .map(|path_seg| path_seg.ident.name.as_str()) |
229 |
| - .collect(); |
230 |
| - Some(path_segment_ident_names.join("::")) |
231 |
| - }, |
232 |
| - _ => None, |
| 67 | +/// Check a chain of `str::replace` calls for `collapsible_str_replace` lint. |
| 68 | +fn check_consecutive_replace_calls<'tcx>( |
| 69 | + cx: &LateContext<'tcx>, |
| 70 | + expr: &'tcx hir::Expr<'tcx>, |
| 71 | + replace_methods: &ReplaceMethods<'tcx>, |
| 72 | + to_arg: &'tcx hir::Expr<'tcx>, |
| 73 | +) { |
| 74 | + let from_args = &replace_methods.from_args; |
| 75 | + let from_arg_reprs: Vec<String> = from_args |
| 76 | + .iter() |
| 77 | + .map(|from_arg| snippet(cx, from_arg.span, "..").to_string()) |
| 78 | + .collect(); |
| 79 | + let app = Applicability::MachineApplicable; |
| 80 | + let earliest_replace_call = replace_methods.methods.front().unwrap(); |
| 81 | + if let Some((_, [..], span_lo)) = method_call(earliest_replace_call) { |
| 82 | + span_lint_and_sugg( |
| 83 | + cx, |
| 84 | + COLLAPSIBLE_STR_REPLACE, |
| 85 | + expr.span.with_lo(span_lo.lo()), |
| 86 | + "used consecutive `str::replace` call", |
| 87 | + "replace with", |
| 88 | + format!( |
| 89 | + "replace([{}], {})", |
| 90 | + from_arg_reprs.join(", "), |
| 91 | + snippet(cx, to_arg.span, ".."), |
| 92 | + ), |
| 93 | + app, |
| 94 | + ); |
233 | 95 | }
|
234 | 96 | }
|
235 |
| - |
236 |
| -fn get_earliest_replace_call_span<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Span { |
237 |
| - let mut earliest_replace_call_span = expr.span; |
238 |
| - |
239 |
| - let _: Option<()> = for_each_expr(expr, |e| { |
240 |
| - if let Some((name, [_, args @ ..], span)) = method_call(e) { |
241 |
| - match (name, args) { |
242 |
| - ("replace", [_, _]) => { |
243 |
| - earliest_replace_call_span = span; |
244 |
| - ControlFlow::Continue(()) |
245 |
| - }, |
246 |
| - _ => ControlFlow::BREAK, |
247 |
| - } |
248 |
| - } else { |
249 |
| - ControlFlow::Continue(()) |
250 |
| - } |
251 |
| - }); |
252 |
| - |
253 |
| - earliest_replace_call_span |
254 |
| -} |
255 |
| - |
256 |
| -/// Find the original receiver of a chain of `str::replace` method calls. |
257 |
| -fn find_original_recv<'tcx>(recv: &'tcx hir::Expr<'tcx>) -> &'tcx hir::Expr<'tcx> { |
258 |
| - let mut original_recv = recv; |
259 |
| - |
260 |
| - let _: Option<()> = for_each_expr(recv, |e| { |
261 |
| - if let Some((name, [prev_recv, args @ ..], _)) = method_call(e) { |
262 |
| - match (name, args) { |
263 |
| - ("replace", [_, _]) => { |
264 |
| - original_recv = prev_recv; |
265 |
| - ControlFlow::Continue(()) |
266 |
| - }, |
267 |
| - _ => ControlFlow::BREAK, |
268 |
| - } |
269 |
| - } else { |
270 |
| - ControlFlow::Continue(()) |
271 |
| - } |
272 |
| - }); |
273 |
| - |
274 |
| - original_recv |
275 |
| -} |
0 commit comments