11use clippy_utils:: msrvs:: { self , Msrv } ;
2- use clippy_utils:: { diagnostics:: span_lint_and_sugg, in_constant, macros:: root_macro_call, source:: snippet} ;
2+ use clippy_utils:: { diagnostics:: span_lint_and_sugg, higher, in_constant, macros:: root_macro_call, source:: snippet} ;
3+ use rustc_ast:: ast:: RangeLimits ;
34use rustc_ast:: LitKind :: { Byte , Char } ;
45use rustc_errors:: Applicability ;
5- use rustc_hir:: { Expr , ExprKind , PatKind , RangeEnd } ;
6+ use rustc_hir:: { BorrowKind , Expr , ExprKind , PatKind , RangeEnd } ;
67use rustc_lint:: { LateContext , LateLintPass } ;
78use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
8- use rustc_span:: { def_id:: DefId , sym} ;
9+ use rustc_span:: { def_id:: DefId , sym, Span } ;
910
1011declare_clippy_lint ! {
1112 /// ### What it does
@@ -23,6 +24,10 @@ declare_clippy_lint! {
2324 /// assert!(matches!(b'X', b'A'..=b'Z'));
2425 /// assert!(matches!('2', '0'..='9'));
2526 /// assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
27+ ///
28+ /// ('0'..='9').contains(&'0');
29+ /// ('a'..='z').contains(&'a');
30+ /// ('A'..='Z').contains(&'A');
2631 /// }
2732 /// ```
2833 /// Use instead:
@@ -32,6 +37,10 @@ declare_clippy_lint! {
3237 /// assert!(b'X'.is_ascii_uppercase());
3338 /// assert!('2'.is_ascii_digit());
3439 /// assert!('x'.is_ascii_alphabetic());
40+ ///
41+ /// '0'.is_ascii_digit();
42+ /// 'a'.is_ascii_lowercase();
43+ /// 'A'.is_ascii_uppercase();
3544 /// }
3645 /// ```
3746 #[ clippy:: version = "1.66.0" ]
@@ -75,47 +84,59 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
7584 return ;
7685 }
7786
78- let Some ( macro_call) = root_macro_call ( expr. span ) else { return } ;
79-
80- if is_matches_macro ( cx, macro_call. def_id ) {
87+ if let Some ( macro_call) = root_macro_call ( expr. span )
88+ && is_matches_macro ( cx, macro_call. def_id ) {
8189 if let ExprKind :: Match ( recv, [ arm, ..] , _) = expr. kind {
8290 let range = check_pat ( & arm. pat . kind ) ;
83-
84- if let Some ( sugg) = match range {
85- CharRange :: UpperChar => Some ( "is_ascii_uppercase" ) ,
86- CharRange :: LowerChar => Some ( "is_ascii_lowercase" ) ,
87- CharRange :: FullChar => Some ( "is_ascii_alphabetic" ) ,
88- CharRange :: Digit => Some ( "is_ascii_digit" ) ,
89- CharRange :: Otherwise => None ,
90- } {
91- let default_snip = ".." ;
92- // `snippet_with_applicability` may set applicability to `MaybeIncorrect` for
93- // macro span, so we check applicability manually by comparing `recv` is not default.
94- let recv = snippet ( cx, recv. span , default_snip) ;
95-
96- let applicability = if recv == default_snip {
97- Applicability :: HasPlaceholders
98- } else {
99- Applicability :: MachineApplicable
100- } ;
101-
102- span_lint_and_sugg (
103- cx,
104- MANUAL_IS_ASCII_CHECK ,
105- macro_call. span ,
106- "manual check for common ascii range" ,
107- "try" ,
108- format ! ( "{recv}.{sugg}()" ) ,
109- applicability,
110- ) ;
111- }
91+ check_is_ascii ( cx, macro_call. span , recv, & range) ;
92+ }
93+ } else if let ExprKind :: MethodCall ( path, receiver, [ arg] , ..) = expr. kind
94+ && path. ident . name == sym ! ( contains)
95+ && let Some ( higher:: Range { start : Some ( start) , end : Some ( end) , limits : RangeLimits :: Closed } )
96+ = higher:: Range :: hir ( receiver) {
97+ let range = check_range ( start, end) ;
98+ if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, e) = arg. kind {
99+ check_is_ascii ( cx, expr. span , e, & range) ;
100+ } else {
101+ check_is_ascii ( cx, expr. span , arg, & range) ;
112102 }
113103 }
114104 }
115105
116106 extract_msrv_attr ! ( LateContext ) ;
117107}
118108
109+ fn check_is_ascii ( cx : & LateContext < ' _ > , span : Span , recv : & Expr < ' _ > , range : & CharRange ) {
110+ if let Some ( sugg) = match range {
111+ CharRange :: UpperChar => Some ( "is_ascii_uppercase" ) ,
112+ CharRange :: LowerChar => Some ( "is_ascii_lowercase" ) ,
113+ CharRange :: FullChar => Some ( "is_ascii_alphabetic" ) ,
114+ CharRange :: Digit => Some ( "is_ascii_digit" ) ,
115+ CharRange :: Otherwise => None ,
116+ } {
117+ let default_snip = ".." ;
118+ // `snippet_with_applicability` may set applicability to `MaybeIncorrect` for
119+ // macro span, so we check applicability manually by comparing `recv` is not default.
120+ let recv = snippet ( cx, recv. span , default_snip) ;
121+
122+ let applicability = if recv == default_snip {
123+ Applicability :: HasPlaceholders
124+ } else {
125+ Applicability :: MachineApplicable
126+ } ;
127+
128+ span_lint_and_sugg (
129+ cx,
130+ MANUAL_IS_ASCII_CHECK ,
131+ span,
132+ "manual check for common ascii range" ,
133+ "try" ,
134+ format ! ( "{recv}.{sugg}()" ) ,
135+ applicability,
136+ ) ;
137+ }
138+ }
139+
119140fn check_pat ( pat_kind : & PatKind < ' _ > ) -> CharRange {
120141 match pat_kind {
121142 PatKind :: Or ( pats) => {
0 commit comments