about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-05-01 13:54:24 +0000
committerbors <bors@rust-lang.org>2024-05-01 13:54:24 +0000
commit852a64f8759c23b3ecf2ab975d3f652cffbc27cf (patch)
tree82800dcbcc59fa4b67bc5375e2cd08a104fe180a
parenta7f66baaf15c6aa4c74333d312a522c1a76735e6 (diff)
parentcbdc36aa091d4d99a4848155a74a770d3793219b (diff)
downloadrust-852a64f8759c23b3ecf2ab975d3f652cffbc27cf.tar.gz
rust-852a64f8759c23b3ecf2ab975d3f652cffbc27cf.zip
Auto merge of #11988 - J-ZhengLi:issue11324, r=Alexendoo
fix suggestion error for [`manual_is_ascii_check`] with missing type

fixes: #11324
fixes: #11776

changelog: improve [`manual_is_ascii_check`] to suggest labeling type in closure, fix FP with type generics, and improve linting on ref expressions.
-rw-r--r--clippy_lints/src/manual_is_ascii_check.rs86
-rw-r--r--tests/ui/manual_is_ascii_check.fixed27
-rw-r--r--tests/ui/manual_is_ascii_check.rs27
-rw-r--r--tests/ui/manual_is_ascii_check.stderr42
4 files changed, 151 insertions, 31 deletions
diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs
index ed7eabd9b99..6f6ba1852a6 100644
--- a/clippy_lints/src/manual_is_ascii_check.rs
+++ b/clippy_lints/src/manual_is_ascii_check.rs
@@ -1,13 +1,14 @@
 use clippy_config::msrvs::{self, Msrv};
-use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::macros::matching_root_macro_call;
 use clippy_utils::sugg::Sugg;
-use clippy_utils::{higher, in_constant};
+use clippy_utils::{higher, in_constant, path_to_local, peel_ref_operators};
 use rustc_ast::ast::RangeLimits;
 use rustc_ast::LitKind::{Byte, Char};
 use rustc_errors::Applicability;
-use rustc_hir::{BorrowKind, Expr, ExprKind, PatKind, RangeEnd};
+use rustc_hir::{Expr, ExprKind, Node, Param, PatKind, RangeEnd};
 use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
 use rustc_session::impl_lint_pass;
 use rustc_span::{sym, Span};
 
@@ -99,7 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
         if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) {
             if let ExprKind::Match(recv, [arm, ..], _) = expr.kind {
                 let range = check_pat(&arm.pat.kind);
-                check_is_ascii(cx, macro_call.span, recv, &range);
+                check_is_ascii(cx, macro_call.span, recv, &range, None);
             }
         } else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind
             && path.ident.name == sym!(contains)
@@ -108,42 +109,67 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
                 end: Some(end),
                 limits: RangeLimits::Closed,
             }) = higher::Range::hir(receiver)
+            && !matches!(cx.typeck_results().expr_ty(arg).peel_refs().kind(), ty::Param(_))
         {
+            let arg = peel_ref_operators(cx, arg);
+            let ty_sugg = get_ty_sugg(cx, arg, start);
             let range = check_range(start, end);
-            if let ExprKind::AddrOf(BorrowKind::Ref, _, e) = arg.kind {
-                check_is_ascii(cx, expr.span, e, &range);
-            } else {
-                check_is_ascii(cx, expr.span, arg, &range);
-            }
+            check_is_ascii(cx, expr.span, arg, &range, ty_sugg);
         }
     }
 
     extract_msrv_attr!(LateContext);
 }
 
-fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &CharRange) {
-    if let Some(sugg) = match range {
-        CharRange::UpperChar => Some("is_ascii_uppercase"),
-        CharRange::LowerChar => Some("is_ascii_lowercase"),
-        CharRange::FullChar => Some("is_ascii_alphabetic"),
-        CharRange::Digit => Some("is_ascii_digit"),
-        CharRange::HexDigit => Some("is_ascii_hexdigit"),
-        CharRange::Otherwise | CharRange::LowerHexLetter | CharRange::UpperHexLetter => None,
-    } {
-        let default_snip = "..";
-        let mut app = Applicability::MachineApplicable;
-        let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_par();
+fn get_ty_sugg(cx: &LateContext<'_>, arg: &Expr<'_>, bound_expr: &Expr<'_>) -> Option<(Span, &'static str)> {
+    if let ExprKind::Lit(lit) = bound_expr.kind
+        && let local_hid = path_to_local(arg)?
+        && let Node::Param(Param { ty_span, span, .. }) = cx.tcx.parent_hir_node(local_hid)
+        // `ty_span` and `span` are the same for inferred type, thus a type suggestion must be given
+        && ty_span == span
+    {
+        let ty_str = match lit.node {
+            Char(_) => "char",
+            Byte(_) => "u8",
+            _ => return None,
+        };
+        return Some((*ty_span, ty_str));
+    }
+    None
+}
 
-        span_lint_and_sugg(
-            cx,
-            MANUAL_IS_ASCII_CHECK,
-            span,
-            "manual check for common ascii range",
-            "try",
-            format!("{recv}.{sugg}()"),
-            app,
-        );
+fn check_is_ascii(
+    cx: &LateContext<'_>,
+    span: Span,
+    recv: &Expr<'_>,
+    range: &CharRange,
+    ty_sugg: Option<(Span, &'_ str)>,
+) {
+    let sugg = match range {
+        CharRange::UpperChar => "is_ascii_uppercase",
+        CharRange::LowerChar => "is_ascii_lowercase",
+        CharRange::FullChar => "is_ascii_alphabetic",
+        CharRange::Digit => "is_ascii_digit",
+        CharRange::HexDigit => "is_ascii_hexdigit",
+        CharRange::Otherwise | CharRange::LowerHexLetter | CharRange::UpperHexLetter => return,
+    };
+    let default_snip = "..";
+    let mut app = Applicability::MachineApplicable;
+    let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_par();
+    let mut suggestion = vec![(span, format!("{recv}.{sugg}()"))];
+    if let Some((ty_span, ty_str)) = ty_sugg {
+        suggestion.push((ty_span, format!("{recv}: {ty_str}")));
     }
+
+    span_lint_and_then(
+        cx,
+        MANUAL_IS_ASCII_CHECK,
+        span,
+        "manual check for common ascii range",
+        |diag| {
+            diag.multipart_suggestion("try", suggestion, app);
+        },
+    );
 }
 
 fn check_pat(pat_kind: &PatKind<'_>) -> CharRange {
diff --git a/tests/ui/manual_is_ascii_check.fixed b/tests/ui/manual_is_ascii_check.fixed
index 9c4bd335ad8..a72caa3a37e 100644
--- a/tests/ui/manual_is_ascii_check.fixed
+++ b/tests/ui/manual_is_ascii_check.fixed
@@ -55,3 +55,30 @@ fn msrv_1_47() {
     const FOO: bool = 'x'.is_ascii_digit();
     const BAR: bool = 'x'.is_ascii_hexdigit();
 }
+
+#[allow(clippy::deref_addrof, clippy::needless_borrow)]
+fn with_refs() {
+    let cool_letter = &&'g';
+    cool_letter.is_ascii_digit();
+    cool_letter.is_ascii_lowercase();
+}
+
+fn generics() {
+    fn a<U>(u: &U) -> bool
+    where
+        char: PartialOrd<U>,
+        U: PartialOrd<char> + ?Sized,
+    {
+        ('A'..='Z').contains(u)
+    }
+
+    fn take_while<Item, F>(cond: F)
+    where
+        Item: Sized,
+        F: Fn(Item) -> bool,
+    {
+    }
+    take_while(|c: char| c.is_ascii_uppercase());
+    take_while(|c: u8| c.is_ascii_uppercase());
+    take_while(|c: char| c.is_ascii_uppercase());
+}
diff --git a/tests/ui/manual_is_ascii_check.rs b/tests/ui/manual_is_ascii_check.rs
index 785943cd24d..bb6e2a317da 100644
--- a/tests/ui/manual_is_ascii_check.rs
+++ b/tests/ui/manual_is_ascii_check.rs
@@ -55,3 +55,30 @@ fn msrv_1_47() {
     const FOO: bool = matches!('x', '0'..='9');
     const BAR: bool = matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F');
 }
+
+#[allow(clippy::deref_addrof, clippy::needless_borrow)]
+fn with_refs() {
+    let cool_letter = &&'g';
+    ('0'..='9').contains(&&cool_letter);
+    ('a'..='z').contains(*cool_letter);
+}
+
+fn generics() {
+    fn a<U>(u: &U) -> bool
+    where
+        char: PartialOrd<U>,
+        U: PartialOrd<char> + ?Sized,
+    {
+        ('A'..='Z').contains(u)
+    }
+
+    fn take_while<Item, F>(cond: F)
+    where
+        Item: Sized,
+        F: Fn(Item) -> bool,
+    {
+    }
+    take_while(|c| ('A'..='Z').contains(&c));
+    take_while(|c| (b'A'..=b'Z').contains(&c));
+    take_while(|c: char| ('A'..='Z').contains(&c));
+}
diff --git a/tests/ui/manual_is_ascii_check.stderr b/tests/ui/manual_is_ascii_check.stderr
index 3632077ec80..a93ccace28a 100644
--- a/tests/ui/manual_is_ascii_check.stderr
+++ b/tests/ui/manual_is_ascii_check.stderr
@@ -133,5 +133,45 @@ error: manual check for common ascii range
 LL |     const BAR: bool = matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F');
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_hexdigit()`
 
-error: aborting due to 22 previous errors
+error: manual check for common ascii range
+  --> tests/ui/manual_is_ascii_check.rs:62:5
+   |
+LL |     ('0'..='9').contains(&&cool_letter);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cool_letter.is_ascii_digit()`
+
+error: manual check for common ascii range
+  --> tests/ui/manual_is_ascii_check.rs:63:5
+   |
+LL |     ('a'..='z').contains(*cool_letter);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cool_letter.is_ascii_lowercase()`
+
+error: manual check for common ascii range
+  --> tests/ui/manual_is_ascii_check.rs:81:20
+   |
+LL |     take_while(|c| ('A'..='Z').contains(&c));
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: try
+   |
+LL |     take_while(|c: char| c.is_ascii_uppercase());
+   |                 ~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~
+
+error: manual check for common ascii range
+  --> tests/ui/manual_is_ascii_check.rs:82:20
+   |
+LL |     take_while(|c| (b'A'..=b'Z').contains(&c));
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: try
+   |
+LL |     take_while(|c: u8| c.is_ascii_uppercase());
+   |                 ~~~~~  ~~~~~~~~~~~~~~~~~~~~~~
+
+error: manual check for common ascii range
+  --> tests/ui/manual_is_ascii_check.rs:83:26
+   |
+LL |     take_while(|c: char| ('A'..='Z').contains(&c));
+   |                          ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `c.is_ascii_uppercase()`
+
+error: aborting due to 27 previous errors