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