11use std:: cmp:: Ordering ;
22use std:: ops:: ControlFlow ;
33
4- use clippy_utils:: diagnostics:: span_lint_and_sugg;
4+ use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then } ;
55use clippy_utils:: macros:: matching_root_macro_call;
66use clippy_utils:: path_to_local_id;
7- use clippy_utils:: source:: str_literal_to_char_literal;
7+ use clippy_utils:: source:: { snippet_opt , str_literal_to_char_literal} ;
88use clippy_utils:: visitors:: { for_each_expr, Descend } ;
9+ use itertools:: Itertools ;
910use rustc_ast:: { BinOpKind , LitKind } ;
1011use rustc_errors:: Applicability ;
1112use rustc_hir:: { Expr , ExprKind , PatKind , QPath } ;
1213use rustc_lint:: { LateContext , LateLintPass } ;
1314use rustc_middle:: ty;
1415use rustc_session:: declare_lint_pass;
15- use rustc_span:: sym;
16+ use rustc_span:: { sym, Span } ;
1617
1718declare_clippy_lint ! {
1819 /// ### What it does
@@ -110,17 +111,17 @@ fn check_single_char_pattern_lint(cx: &LateContext<'_>, arg: &Expr<'_>) {
110111 }
111112}
112113
113- fn get_char_value ( expr : & Expr < ' _ > ) -> Option < String > {
114+ fn get_char_span < ' tcx > ( cx : & ' _ LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < Span > {
115+ if !cx. typeck_results ( ) . expr_ty_adjusted ( expr) . is_char ( ) || expr. span . from_expansion ( ) {
116+ return None ;
117+ }
114118 match expr. kind {
115- ExprKind :: Lit ( lit) => match lit. node {
116- LitKind :: Char ( c) => Some ( format ! ( "'{}'" , c. escape_default( ) ) ) ,
117- _ => None ,
118- } ,
119+ ExprKind :: Lit ( lit) if let LitKind :: Char ( _) = lit. node => Some ( lit. span ) ,
119120 ExprKind :: Path ( QPath :: Resolved ( _, path) ) => {
120121 if path. segments . len ( ) == 1 {
121122 let segment = & path. segments [ 0 ] ;
122123 if segment. args . is_none ( ) {
123- Some ( segment. ident . name . to_string ( ) )
124+ Some ( segment. ident . span )
124125 } else {
125126 None
126127 }
@@ -138,44 +139,39 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<
138139 && let body = cx. tcx . hir ( ) . body ( closure. body )
139140 && let PatKind :: Binding ( _, binding, ..) = body. params [ 0 ] . pat . kind
140141 {
141- let mut set_chars : Vec < String > = Vec :: new ( ) ;
142+ let mut set_char_spans : Vec < Span > = Vec :: new ( ) ;
142143
143144 // We want to retrieve all the comparisons done.
144145 // They are ordered in a nested way and so we need to traverse the AST to collect them all.
145146 if for_each_expr ( body. value , |sub_expr| -> ControlFlow < ( ) , Descend > {
146147 match sub_expr. kind {
147148 ExprKind :: Binary ( op, left, right) if op. node == BinOpKind :: Eq => {
148149 if path_to_local_id ( left, binding)
149- && cx. typeck_results ( ) . expr_ty_adjusted ( right) . kind ( ) == & ty:: Char
150- && let Some ( c) = get_char_value ( right)
150+ && let Some ( span) = get_char_span ( cx, right)
151151 {
152- set_chars . push ( c ) ;
152+ set_char_spans . push ( span ) ;
153153 ControlFlow :: Continue ( Descend :: No )
154154 } else if path_to_local_id ( right, binding)
155- && cx. typeck_results ( ) . expr_ty_adjusted ( left) . kind ( ) == & ty:: Char
156- && let Some ( c) = get_char_value ( left)
155+ && let Some ( span) = get_char_span ( cx, left)
157156 {
158- set_chars . push ( c ) ;
157+ set_char_spans . push ( span ) ;
159158 ControlFlow :: Continue ( Descend :: No )
160159 } else {
161160 ControlFlow :: Break ( ( ) )
162161 }
163162 } ,
164163 ExprKind :: Binary ( op, _, _) if op. node == BinOpKind :: Or => ControlFlow :: Continue ( Descend :: Yes ) ,
165164 ExprKind :: Match ( match_value, [ arm, _] , _) => {
166- if matching_root_macro_call ( cx, sub_expr. span , sym:: matches_macro) . is_none ( ) {
167- return ControlFlow :: Break ( ( ) ) ;
168- }
169- if arm. guard . is_some ( ) {
170- return ControlFlow :: Break ( ( ) ) ;
171- }
172- if !path_to_local_id ( match_value, binding) {
165+ if matching_root_macro_call ( cx, sub_expr. span , sym:: matches_macro) . is_none ( )
166+ || arm. guard . is_some ( )
167+ || !path_to_local_id ( match_value, binding)
168+ {
173169 return ControlFlow :: Break ( ( ) ) ;
174170 }
175171 if arm. pat . walk_short ( |pat| match pat. kind {
176172 PatKind :: Lit ( expr) if let ExprKind :: Lit ( lit) = expr. kind => {
177- if let LitKind :: Char ( c ) = lit. node {
178- set_chars . push ( format ! ( "'{}'" , c . escape_default ( ) ) ) ;
173+ if let LitKind :: Char ( _ ) = lit. node {
174+ set_char_spans . push ( lit . span ) ;
179175 }
180176 true
181177 } ,
@@ -194,27 +190,37 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<
194190 {
195191 return ;
196192 }
197- match set_chars. len ( ) . cmp ( & 1 ) {
198- Ordering :: Equal => span_lint_and_sugg (
199- cx,
200- MANUAL_PATTERN_CHAR_COMPARISON ,
201- method_arg. span ,
202- "this manual char comparison can be written more succinctly" ,
203- "consider using a `char`" ,
204- set_chars[ 0 ] . clone ( ) ,
205- applicability,
206- ) ,
207- Ordering :: Greater => span_lint_and_sugg (
208- cx,
209- MANUAL_PATTERN_CHAR_COMPARISON ,
210- method_arg. span ,
211- "this manual char comparison can be written more succinctly" ,
212- "consider using an array of `char`" ,
213- format ! ( "[{}]" , set_chars. join( ", " ) ) ,
214- applicability,
215- ) ,
216- Ordering :: Less => { } ,
217- }
193+ span_lint_and_then (
194+ cx,
195+ MANUAL_PATTERN_CHAR_COMPARISON ,
196+ method_arg. span ,
197+ "this manual char comparison can be written more succinctly" ,
198+ |diag| match set_char_spans. len ( ) . cmp ( & 1 ) {
199+ Ordering :: Equal => {
200+ diag. span_suggestion (
201+ method_arg. span ,
202+ "consider using a `char`" ,
203+ snippet_opt ( cx, set_char_spans[ 0 ] ) . unwrap ( ) ,
204+ applicability,
205+ ) ;
206+ } ,
207+ Ordering :: Greater => {
208+ diag. span_suggestion (
209+ method_arg. span ,
210+ "consider using an array of `char`" ,
211+ format ! (
212+ "[{}]" ,
213+ set_char_spans
214+ . into_iter( )
215+ . map( |span| snippet_opt( cx, span) . unwrap( ) )
216+ . join( ", " )
217+ ) ,
218+ applicability,
219+ ) ;
220+ } ,
221+ Ordering :: Less => { } ,
222+ } ,
223+ )
218224 }
219225}
220226
@@ -228,9 +234,8 @@ impl<'tcx> LateLintPass<'tcx> for StringPatterns {
228234 && let Some ( & ( _, pos) ) = PATTERN_METHODS
229235 . iter ( )
230236 . find ( |( array_method_name, _) | * array_method_name == method_name)
231- && args . len ( ) > pos
237+ && let Some ( arg ) = args . get ( pos)
232238 {
233- let arg = & args[ pos] ;
234239 check_single_char_pattern_lint ( cx, arg) ;
235240
236241 check_manual_pattern_char_comparison ( cx, arg) ;
0 commit comments