1
1
use std:: cmp:: Ordering ;
2
2
use std:: ops:: ControlFlow ;
3
3
4
- use clippy_utils:: diagnostics:: span_lint_and_sugg;
4
+ use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then } ;
5
5
use clippy_utils:: macros:: matching_root_macro_call;
6
6
use 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} ;
8
8
use clippy_utils:: visitors:: { for_each_expr, Descend } ;
9
+ use itertools:: Itertools ;
9
10
use rustc_ast:: { BinOpKind , LitKind } ;
10
11
use rustc_errors:: Applicability ;
11
12
use rustc_hir:: { Expr , ExprKind , PatKind , QPath } ;
12
13
use rustc_lint:: { LateContext , LateLintPass } ;
13
14
use rustc_middle:: ty;
14
15
use rustc_session:: declare_lint_pass;
15
- use rustc_span:: sym;
16
+ use rustc_span:: { sym, Span } ;
16
17
17
18
declare_clippy_lint ! {
18
19
/// ### What it does
@@ -110,17 +111,17 @@ fn check_single_char_pattern_lint(cx: &LateContext<'_>, arg: &Expr<'_>) {
110
111
}
111
112
}
112
113
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
+ }
114
118
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 ) ,
119
120
ExprKind :: Path ( QPath :: Resolved ( _, path) ) => {
120
121
if path. segments . len ( ) == 1 {
121
122
let segment = & path. segments [ 0 ] ;
122
123
if segment. args . is_none ( ) {
123
- Some ( segment. ident . name . to_string ( ) )
124
+ Some ( segment. ident . span )
124
125
} else {
125
126
None
126
127
}
@@ -138,44 +139,39 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<
138
139
&& let body = cx. tcx . hir ( ) . body ( closure. body )
139
140
&& let PatKind :: Binding ( _, binding, ..) = body. params [ 0 ] . pat . kind
140
141
{
141
- let mut set_chars : Vec < String > = Vec :: new ( ) ;
142
+ let mut set_char_spans : Vec < Span > = Vec :: new ( ) ;
142
143
143
144
// We want to retrieve all the comparisons done.
144
145
// They are ordered in a nested way and so we need to traverse the AST to collect them all.
145
146
if for_each_expr ( body. value , |sub_expr| -> ControlFlow < ( ) , Descend > {
146
147
match sub_expr. kind {
147
148
ExprKind :: Binary ( op, left, right) if op. node == BinOpKind :: Eq => {
148
149
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)
151
151
{
152
- set_chars . push ( c ) ;
152
+ set_char_spans . push ( span ) ;
153
153
ControlFlow :: Continue ( Descend :: No )
154
154
} 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)
157
156
{
158
- set_chars . push ( c ) ;
157
+ set_char_spans . push ( span ) ;
159
158
ControlFlow :: Continue ( Descend :: No )
160
159
} else {
161
160
ControlFlow :: Break ( ( ) )
162
161
}
163
162
} ,
164
163
ExprKind :: Binary ( op, _, _) if op. node == BinOpKind :: Or => ControlFlow :: Continue ( Descend :: Yes ) ,
165
164
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
+ {
173
169
return ControlFlow :: Break ( ( ) ) ;
174
170
}
175
171
if arm. pat . walk_short ( |pat| match pat. kind {
176
172
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 ) ;
179
175
}
180
176
true
181
177
} ,
@@ -194,27 +190,37 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<
194
190
{
195
191
return ;
196
192
}
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
+ )
218
224
}
219
225
}
220
226
@@ -228,9 +234,8 @@ impl<'tcx> LateLintPass<'tcx> for StringPatterns {
228
234
&& let Some ( & ( _, pos) ) = PATTERN_METHODS
229
235
. iter ( )
230
236
. find ( |( array_method_name, _) | * array_method_name == method_name)
231
- && args . len ( ) > pos
237
+ && let Some ( arg ) = args . get ( pos)
232
238
{
233
- let arg = & args[ pos] ;
234
239
check_single_char_pattern_lint ( cx, arg) ;
235
240
236
241
check_manual_pattern_char_comparison ( cx, arg) ;
0 commit comments