11use clippy_utils:: diagnostics:: span_lint_and_sugg;
2- // use clippy_utils::source::snippet_with_context ;
2+ use clippy_utils:: get_parent_expr ;
33use clippy_utils:: visitors:: for_each_expr;
44use core:: ops:: ControlFlow ;
55use if_chain:: if_chain;
66use rustc_ast:: ast:: LitKind ;
7+ use rustc_data_structures:: fx:: FxHashSet ;
78use rustc_errors:: Applicability ;
89use rustc_hir as hir;
910use rustc_hir:: * ;
1011use rustc_lint:: LateContext ;
1112use rustc_middle:: ty;
1213use rustc_span:: source_map:: Spanned ;
13- use std:: unreachable;
14- // use rustc_span::Span;
1514
1615use super :: method_call;
1716use super :: COLLAPSIBLE_STR_REPLACE ;
@@ -25,28 +24,46 @@ pub(super) fn check<'tcx>(
2524) {
2625 match ( name, args) {
2726 ( "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`
2928 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+
3132 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 ;
3637 if matches!( array_ty. kind( ) , ty:: Char ) ;
3738 then {
3839 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+ }
3957 }
4058 }
4159
4260 match method_call ( recv) {
4361 // 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+ }
5067 } ,
5168 _ => { } ,
5269 }
@@ -55,100 +72,246 @@ pub(super) fn check<'tcx>(
5572 }
5673}
5774
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.
7877fn check_replace_call_with_char_slice < ' tcx > (
7978 cx : & LateContext < ' tcx > ,
8079 from_arg : & ' tcx hir:: Expr < ' tcx > ,
8180 to_arg : & ' tcx hir:: Expr < ' tcx > ,
8281) {
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+
8585 // Go through the `from_arg` to collect all char literals
8686 let _: Option < ( ) > = for_each_expr ( from_arg, |e| {
8787 if let ExprKind :: Lit ( Spanned {
88- node : LitKind :: Char ( val) ,
89- ..
88+ node : LitKind :: Char ( _) , ..
9089 } ) = e. kind
9190 {
92- char_list . push ( val ) ;
91+ chars . push ( get_replace_call_char_arg_repr ( e ) . unwrap ( ) ) ;
9392 ControlFlow :: Continue ( ( ) )
9493 } else if let ExprKind :: Path ( ..) = e. kind {
9594 // 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 ;
9796 ControlFlow :: BREAK
9897 } else {
9998 ControlFlow :: Continue ( ( ) )
10099 }
101100 } ) ;
102101
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 ( ( ) )
118203 } ,
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+ ..
127285 } ,
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 ,
145295 }
146296}
147297
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
154317}
0 commit comments