|
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