1
1
use clippy_config:: msrvs:: { self , Msrv } ;
2
- use clippy_utils:: diagnostics:: span_lint_and_sugg ;
2
+ use clippy_utils:: diagnostics:: span_lint_and_then ;
3
3
use clippy_utils:: macros:: root_macro_call;
4
4
use clippy_utils:: sugg:: Sugg ;
5
- use clippy_utils:: { higher, in_constant} ;
5
+ use clippy_utils:: { higher, in_constant, path_to_local , peel_ref_operators } ;
6
6
use rustc_ast:: ast:: RangeLimits ;
7
- use rustc_ast:: LitKind :: { Byte , Char } ;
7
+ use rustc_ast:: LitKind :: { self , Byte , Char } ;
8
+ use rustc_data_structures:: fx:: FxHashMap ;
8
9
use rustc_errors:: Applicability ;
9
- use rustc_hir:: { BorrowKind , Expr , ExprKind , PatKind , RangeEnd } ;
10
+ use rustc_hir:: { BodyId , Closure , Expr , ExprKind , FnDecl , HirId , PatKind , RangeEnd , TyKind } ;
10
11
use rustc_lint:: { LateContext , LateLintPass } ;
12
+ use rustc_middle:: ty;
11
13
use rustc_session:: impl_lint_pass;
12
14
use rustc_span:: def_id:: DefId ;
13
15
use rustc_span:: { sym, Span } ;
@@ -59,12 +61,16 @@ impl_lint_pass!(ManualIsAsciiCheck => [MANUAL_IS_ASCII_CHECK]);
59
61
60
62
pub struct ManualIsAsciiCheck {
61
63
msrv : Msrv ,
64
+ parent_closure_params_map : FxHashMap < HirId , Span > ,
62
65
}
63
66
64
67
impl ManualIsAsciiCheck {
65
68
#[ must_use]
66
69
pub fn new ( msrv : Msrv ) -> Self {
67
- Self { msrv }
70
+ Self {
71
+ msrv,
72
+ parent_closure_params_map : FxHashMap :: default ( ) ,
73
+ }
68
74
}
69
75
}
70
76
@@ -97,12 +103,16 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
97
103
return ;
98
104
}
99
105
106
+ if let ExprKind :: Closure ( Closure { fn_decl, body, .. } ) = expr. kind {
107
+ self . parent_closure_params_map = params_with_inferred_ty_map ( cx, * body, fn_decl) ;
108
+ }
109
+
100
110
if let Some ( macro_call) = root_macro_call ( expr. span )
101
111
&& is_matches_macro ( cx, macro_call. def_id )
102
112
{
103
113
if let ExprKind :: Match ( recv, [ arm, ..] , _) = expr. kind {
104
114
let range = check_pat ( & arm. pat . kind ) ;
105
- check_is_ascii ( cx, macro_call. span , recv, & range) ;
115
+ check_is_ascii ( cx, macro_call. span , recv, & range, None ) ;
106
116
}
107
117
} else if let ExprKind :: MethodCall ( path, receiver, [ arg] , ..) = expr. kind
108
118
&& path. ident . name == sym ! ( contains)
@@ -112,19 +122,58 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
112
122
limits : RangeLimits :: Closed ,
113
123
} ) = higher:: Range :: hir ( receiver)
114
124
{
115
- let range = check_range ( start, end) ;
116
- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, e) = arg. kind {
117
- check_is_ascii ( cx, expr. span , e, & range) ;
118
- } else {
119
- check_is_ascii ( cx, expr. span , arg, & range) ;
125
+ let arg_ty = cx. typeck_results ( ) . expr_ty ( arg) . peel_refs ( ) ;
126
+ if matches ! ( arg_ty. kind( ) , ty:: Param ( _) ) {
127
+ return ;
120
128
}
129
+
130
+ let arg = peel_ref_operators ( cx, arg) ;
131
+ let ty_sugg = get_ty_sugg ( arg, & self . parent_closure_params_map , start) ;
132
+ let range = check_range ( start, end) ;
133
+ check_is_ascii ( cx, expr. span , peel_ref_operators ( cx, arg) , & range, ty_sugg) ;
134
+ }
135
+ }
136
+
137
+ fn check_expr_post ( & mut self , _: & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
138
+ if let ExprKind :: Closure ( _) = expr. kind {
139
+ self . parent_closure_params_map = FxHashMap :: default ( ) ;
121
140
}
122
141
}
123
142
124
143
extract_msrv_attr ! ( LateContext ) ;
125
144
}
126
145
127
- fn check_is_ascii ( cx : & LateContext < ' _ > , span : Span , recv : & Expr < ' _ > , range : & CharRange ) {
146
+ fn get_ty_sugg ( arg : & Expr < ' _ > , map : & FxHashMap < HirId , Span > , range_expr : & Expr < ' _ > ) -> Option < ( Span , & ' static str ) > {
147
+ let ExprKind :: Lit ( lit) = range_expr. kind else {
148
+ return None ;
149
+ } ;
150
+ let sugg_ty_span = path_to_local ( arg) . and_then ( |id| map. get ( & id) ) . copied ( ) ?;
151
+ let sugg_ty_str = match lit. node {
152
+ LitKind :: Char ( _) => "char" ,
153
+ LitKind :: Byte ( _) => "u8" ,
154
+ _ => return None ,
155
+ } ;
156
+
157
+ Some ( ( sugg_ty_span, sugg_ty_str) )
158
+ }
159
+
160
+ fn params_with_inferred_ty_map ( cx : & LateContext < ' _ > , body_id : BodyId , fn_decl : & FnDecl < ' _ > ) -> FxHashMap < HirId , Span > {
161
+ let params = cx. tcx . hir ( ) . body ( body_id) . params . iter ( ) ;
162
+ let fn_decl_input_tys = fn_decl. inputs . iter ( ) ;
163
+ // Could just collect params with `span` == `ty_span` but this should be safer
164
+ params
165
+ . zip ( fn_decl_input_tys)
166
+ . filter_map ( |( param, ty) | {
167
+ if let TyKind :: Infer = ty. kind {
168
+ Some ( ( param. pat . hir_id , param. span ) )
169
+ } else {
170
+ None
171
+ }
172
+ } )
173
+ . collect ( )
174
+ }
175
+
176
+ fn check_is_ascii ( cx : & LateContext < ' _ > , span : Span , recv : & Expr < ' _ > , range : & CharRange , ty_sugg : Option < ( Span , & str ) > ) {
128
177
if let Some ( sugg) = match range {
129
178
CharRange :: UpperChar => Some ( "is_ascii_uppercase" ) ,
130
179
CharRange :: LowerChar => Some ( "is_ascii_lowercase" ) ,
@@ -137,14 +186,22 @@ fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &Cha
137
186
let mut app = Applicability :: MachineApplicable ;
138
187
let recv = Sugg :: hir_with_context ( cx, recv, span. ctxt ( ) , default_snip, & mut app) . maybe_par ( ) ;
139
188
140
- span_lint_and_sugg (
189
+ span_lint_and_then (
141
190
cx,
142
191
MANUAL_IS_ASCII_CHECK ,
143
192
span,
144
193
"manual check for common ascii range" ,
145
- "try" ,
146
- format ! ( "{recv}.{sugg}()" ) ,
147
- app,
194
+ |diag| {
195
+ diag. span_suggestion ( span, "try" , format ! ( "{recv}.{sugg}()" ) , app) ;
196
+ if let Some ( ( ty_span, ty_str) ) = ty_sugg {
197
+ diag. span_suggestion (
198
+ ty_span,
199
+ "also make sure to label the correct type" ,
200
+ format ! ( "{recv}: {ty_str}" ) ,
201
+ app,
202
+ ) ;
203
+ }
204
+ } ,
148
205
) ;
149
206
}
150
207
}
0 commit comments