11use clippy_config:: msrvs:: { self , Msrv } ;
2- use clippy_utils:: diagnostics:: span_lint_and_sugg ;
2+ use clippy_utils:: diagnostics:: span_lint_and_then ;
33use clippy_utils:: macros:: matching_root_macro_call;
44use clippy_utils:: sugg:: Sugg ;
5- use clippy_utils:: { higher, in_constant} ;
5+ use clippy_utils:: { higher, in_constant, path_to_local , peel_ref_operators } ;
66use rustc_ast:: ast:: RangeLimits ;
77use rustc_ast:: LitKind :: { Byte , Char } ;
88use rustc_errors:: Applicability ;
9- use rustc_hir:: { BorrowKind , Expr , ExprKind , PatKind , RangeEnd } ;
9+ use rustc_hir:: { Expr , ExprKind , Node , Param , PatKind , RangeEnd } ;
1010use rustc_lint:: { LateContext , LateLintPass } ;
11+ use rustc_middle:: ty;
1112use rustc_session:: impl_lint_pass;
1213use rustc_span:: { sym, Span } ;
1314
@@ -99,7 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
99100 if let Some ( macro_call) = matching_root_macro_call ( cx, expr. span , sym:: matches_macro) {
100101 if let ExprKind :: Match ( recv, [ arm, ..] , _) = expr. kind {
101102 let range = check_pat ( & arm. pat . kind ) ;
102- check_is_ascii ( cx, macro_call. span , recv, & range) ;
103+ check_is_ascii ( cx, macro_call. span , recv, & range, None ) ;
103104 }
104105 } else if let ExprKind :: MethodCall ( path, receiver, [ arg] , ..) = expr. kind
105106 && path. ident . name == sym ! ( contains)
@@ -108,42 +109,67 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
108109 end : Some ( end) ,
109110 limits : RangeLimits :: Closed ,
110111 } ) = higher:: Range :: hir ( receiver)
112+ && !matches ! ( cx. typeck_results( ) . expr_ty( arg) . peel_refs( ) . kind( ) , ty:: Param ( _) )
111113 {
114+ let arg = peel_ref_operators ( cx, arg) ;
115+ let ty_sugg = get_ty_sugg ( cx, arg, start) ;
112116 let range = check_range ( start, end) ;
113- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, e) = arg. kind {
114- check_is_ascii ( cx, expr. span , e, & range) ;
115- } else {
116- check_is_ascii ( cx, expr. span , arg, & range) ;
117- }
117+ check_is_ascii ( cx, expr. span , arg, & range, ty_sugg) ;
118118 }
119119 }
120120
121121 extract_msrv_attr ! ( LateContext ) ;
122122}
123123
124- fn check_is_ascii ( cx : & LateContext < ' _ > , span : Span , recv : & Expr < ' _ > , range : & CharRange ) {
125- if let Some ( sugg) = match range {
126- CharRange :: UpperChar => Some ( "is_ascii_uppercase" ) ,
127- CharRange :: LowerChar => Some ( "is_ascii_lowercase" ) ,
128- CharRange :: FullChar => Some ( "is_ascii_alphabetic" ) ,
129- CharRange :: Digit => Some ( "is_ascii_digit" ) ,
130- CharRange :: HexDigit => Some ( "is_ascii_hexdigit" ) ,
131- CharRange :: Otherwise | CharRange :: LowerHexLetter | CharRange :: UpperHexLetter => None ,
132- } {
133- let default_snip = ".." ;
134- let mut app = Applicability :: MachineApplicable ;
135- let recv = Sugg :: hir_with_context ( cx, recv, span. ctxt ( ) , default_snip, & mut app) . maybe_par ( ) ;
124+ fn get_ty_sugg ( cx : & LateContext < ' _ > , arg : & Expr < ' _ > , bound_expr : & Expr < ' _ > ) -> Option < ( Span , & ' static str ) > {
125+ if let ExprKind :: Lit ( lit) = bound_expr. kind
126+ && let local_hid = path_to_local ( arg) ?
127+ && let Node :: Param ( Param { ty_span, span, .. } ) = cx. tcx . parent_hir_node ( local_hid)
128+ // `ty_span` and `span` are the same for inferred type, thus a type suggestion must be given
129+ && ty_span == span
130+ {
131+ let ty_str = match lit. node {
132+ Char ( _) => "char" ,
133+ Byte ( _) => "u8" ,
134+ _ => return None ,
135+ } ;
136+ return Some ( ( * ty_span, ty_str) ) ;
137+ }
138+ None
139+ }
136140
137- span_lint_and_sugg (
138- cx,
139- MANUAL_IS_ASCII_CHECK ,
140- span,
141- "manual check for common ascii range" ,
142- "try" ,
143- format ! ( "{recv}.{sugg}()" ) ,
144- app,
145- ) ;
141+ fn check_is_ascii (
142+ cx : & LateContext < ' _ > ,
143+ span : Span ,
144+ recv : & Expr < ' _ > ,
145+ range : & CharRange ,
146+ ty_sugg : Option < ( Span , & ' _ str ) > ,
147+ ) {
148+ let sugg = match range {
149+ CharRange :: UpperChar => "is_ascii_uppercase" ,
150+ CharRange :: LowerChar => "is_ascii_lowercase" ,
151+ CharRange :: FullChar => "is_ascii_alphabetic" ,
152+ CharRange :: Digit => "is_ascii_digit" ,
153+ CharRange :: HexDigit => "is_ascii_hexdigit" ,
154+ CharRange :: Otherwise | CharRange :: LowerHexLetter | CharRange :: UpperHexLetter => return ,
155+ } ;
156+ let default_snip = ".." ;
157+ let mut app = Applicability :: MachineApplicable ;
158+ let recv = Sugg :: hir_with_context ( cx, recv, span. ctxt ( ) , default_snip, & mut app) . maybe_par ( ) ;
159+ let mut suggestion = vec ! [ ( span, format!( "{recv}.{sugg}()" ) ) ] ;
160+ if let Some ( ( ty_span, ty_str) ) = ty_sugg {
161+ suggestion. push ( ( ty_span, format ! ( "{recv}: {ty_str}" ) ) ) ;
146162 }
163+
164+ span_lint_and_then (
165+ cx,
166+ MANUAL_IS_ASCII_CHECK ,
167+ span,
168+ "manual check for common ascii range" ,
169+ |diag| {
170+ diag. multipart_suggestion ( "try" , suggestion, app) ;
171+ } ,
172+ ) ;
147173}
148174
149175fn check_pat ( pat_kind : & PatKind < ' _ > ) -> CharRange {
0 commit comments