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
+ closure_params : 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
+ closure_params : 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
+ collect_params_with_inferred_ty ( cx, * body, fn_decl, & mut self . closure_params ) ;
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,61 @@ 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 . closure_params , start) ;
132
+ let range = check_range ( start, end) ;
133
+ check_is_ascii ( cx, expr. span , peel_ref_operators ( cx, arg) , & range, ty_sugg) ;
121
134
}
122
135
}
123
136
137
+ fn check_mod ( & mut self , _: & LateContext < ' tcx > , _: & ' tcx rustc_hir:: Mod < ' tcx > , _: HirId ) {
138
+ self . closure_params = FxHashMap :: default ( ) ;
139
+ }
140
+
124
141
extract_msrv_attr ! ( LateContext ) ;
125
142
}
126
143
127
- fn check_is_ascii ( cx : & LateContext < ' _ > , span : Span , recv : & Expr < ' _ > , range : & CharRange ) {
144
+ fn get_ty_sugg ( arg : & Expr < ' _ > , map : & FxHashMap < HirId , Span > , range_expr : & Expr < ' _ > ) -> Option < ( Span , & ' static str ) > {
145
+ let ExprKind :: Lit ( lit) = range_expr. kind else {
146
+ return None ;
147
+ } ;
148
+ let sugg_ty_span = path_to_local ( arg) . and_then ( |id| map. get ( & id) ) . copied ( ) ?;
149
+ let sugg_ty_str = match lit. node {
150
+ LitKind :: Char ( _) => "char" ,
151
+ LitKind :: Byte ( _) => "u8" ,
152
+ _ => return None ,
153
+ } ;
154
+
155
+ Some ( ( sugg_ty_span, sugg_ty_str) )
156
+ }
157
+
158
+ /// Collect closure params' `HirId` and `Span` pairs into a map,
159
+ /// if they have implicit (inferred) `ty`.
160
+ fn collect_params_with_inferred_ty (
161
+ cx : & LateContext < ' _ > ,
162
+ body_id : BodyId ,
163
+ fn_decl : & FnDecl < ' _ > ,
164
+ map : & mut FxHashMap < HirId , Span > ,
165
+ ) {
166
+ let params = cx. tcx . hir ( ) . body ( body_id) . params . iter ( ) ;
167
+ let fn_decl_input_tys = fn_decl. inputs . iter ( ) ;
168
+ for ( id, span) in params. zip ( fn_decl_input_tys) . filter_map ( |( param, ty) | {
169
+ if let TyKind :: Infer = ty. kind {
170
+ Some ( ( param. pat . hir_id , param. span ) )
171
+ } else {
172
+ None
173
+ }
174
+ } ) {
175
+ map. insert ( id, span) ;
176
+ }
177
+ }
178
+
179
+ fn check_is_ascii ( cx : & LateContext < ' _ > , span : Span , recv : & Expr < ' _ > , range : & CharRange , ty_sugg : Option < ( Span , & str ) > ) {
128
180
if let Some ( sugg) = match range {
129
181
CharRange :: UpperChar => Some ( "is_ascii_uppercase" ) ,
130
182
CharRange :: LowerChar => Some ( "is_ascii_lowercase" ) ,
@@ -137,14 +189,22 @@ fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &Cha
137
189
let mut app = Applicability :: MachineApplicable ;
138
190
let recv = Sugg :: hir_with_context ( cx, recv, span. ctxt ( ) , default_snip, & mut app) . maybe_par ( ) ;
139
191
140
- span_lint_and_sugg (
192
+ span_lint_and_then (
141
193
cx,
142
194
MANUAL_IS_ASCII_CHECK ,
143
195
span,
144
196
"manual check for common ascii range" ,
145
- "try" ,
146
- format ! ( "{recv}.{sugg}()" ) ,
147
- app,
197
+ |diag| {
198
+ diag. span_suggestion ( span, "try" , format ! ( "{recv}.{sugg}()" ) , app) ;
199
+ if let Some ( ( ty_span, ty_str) ) = ty_sugg {
200
+ diag. span_suggestion (
201
+ ty_span,
202
+ "also make sure to label the correct type" ,
203
+ format ! ( "{recv}: {ty_str}" ) ,
204
+ app,
205
+ ) ;
206
+ }
207
+ } ,
148
208
) ;
149
209
}
150
210
}
0 commit comments