about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJason Newcomb <jsnewcomb@pm.me>2025-09-07 15:08:53 +0000
committerGitHub <noreply@github.com>2025-09-07 15:08:53 +0000
commit847ca731dff5fb7ee00621a1b04c8efb203585bc (patch)
treecd7a8b3a80327cf7bc9f179e140b9b11f1d175cd
parent2147bb93167e54407a8da6f6b4a0b2e652a11d20 (diff)
parent2459ad66a48d2d693f4f30a599c28bfc36603333 (diff)
downloadrust-847ca731dff5fb7ee00621a1b04c8efb203585bc.tar.gz
rust-847ca731dff5fb7ee00621a1b04c8efb203585bc.zip
`let_unit_with_type_underscore`: make early-pass (#15458)
This kind of supersedes
https://github.com/rust-lang/rust-clippy/pull/15386 -- by making the
lint early-pass, we get access to `TyKind::Paren`s that surround the
type ascription. And with that, we can return to the simpler calculation
of `span_to_remove`.

The biggest hurdle was `is_from_proc_macro` -- calling that function
required me to `impl WithSearchPat for rustc_ast::Ty`, i.e.
`ast_ty_search_pat`, which I based on `ty_search_pat`. Since that's a
larger change, I could extract it into a PR of its own.

changelog: none
-rw-r--r--clippy_lints/src/let_with_type_underscore.rs16
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_utils/src/check_proc_macro.rs130
3 files changed, 137 insertions, 11 deletions
diff --git a/clippy_lints/src/let_with_type_underscore.rs b/clippy_lints/src/let_with_type_underscore.rs
index 5b0f95ffc37..24a4c321bda 100644
--- a/clippy_lints/src/let_with_type_underscore.rs
+++ b/clippy_lints/src/let_with_type_underscore.rs
@@ -1,9 +1,9 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::is_from_proc_macro;
 use clippy_utils::source::{IntoSpan, SpanRangeExt};
+use rustc_ast::{Local, TyKind};
 use rustc_errors::Applicability;
-use rustc_hir::{LetStmt, TyKind};
-use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
 use rustc_session::declare_lint_pass;
 
 declare_clippy_lint! {
@@ -26,14 +26,14 @@ declare_clippy_lint! {
 }
 declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]);
 
-impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped {
-    fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) {
-        if let Some(ty) = local.ty // Ensure that it has a type defined
-            && let TyKind::Infer(()) = &ty.kind // that type is '_'
+impl EarlyLintPass for UnderscoreTyped {
+    fn check_local(&mut self, cx: &EarlyContext<'_>, local: &Local) {
+        if let Some(ty) = &local.ty // Ensure that it has a type defined
+            && let TyKind::Infer = ty.kind // that type is '_'
             && local.span.eq_ctxt(ty.span)
-            && let sm = cx.tcx.sess.source_map()
+            && let sm = cx.sess().source_map()
             && !local.span.in_external_macro(sm)
-            && !is_from_proc_macro(cx, ty)
+            && !is_from_proc_macro(cx, &**ty)
         {
             let span_to_remove = sm
                 .span_extend_to_prev_char_before(ty.span, ':', true)
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index a89cf3fdc1e..397dae58e5c 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -744,7 +744,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
     store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage));
     store.register_late_pass(|_| Box::new(needless_maybe_sized::NeedlessMaybeSized));
     store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock));
-    store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped));
+    store.register_early_pass(|| Box::new(let_with_type_underscore::UnderscoreTyped));
     store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(conf)));
     store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct));
     store.register_late_pass(move |_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(conf)));
diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs
index c4a759e919b..47f9d0591e9 100644
--- a/clippy_utils/src/check_proc_macro.rs
+++ b/clippy_utils/src/check_proc_macro.rs
@@ -13,8 +13,11 @@
 //! if the span is not from a `macro_rules` based macro.
 
 use rustc_abi::ExternAbi;
+use rustc_ast as ast;
 use rustc_ast::AttrStyle;
-use rustc_ast::ast::{AttrKind, Attribute, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy};
+use rustc_ast::ast::{
+    AttrKind, Attribute, GenericArgs, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy,
+};
 use rustc_ast::token::CommentKind;
 use rustc_hir::intravisit::FnKind;
 use rustc_hir::{
@@ -26,7 +29,7 @@ use rustc_lint::{EarlyContext, LateContext, LintContext};
 use rustc_middle::ty::TyCtxt;
 use rustc_session::Session;
 use rustc_span::symbol::{Ident, kw};
-use rustc_span::{Span, Symbol};
+use rustc_span::{Span, Symbol, sym};
 
 /// The search pattern to look for. Used by `span_matches_pat`
 #[derive(Clone)]
@@ -403,6 +406,7 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) {
         TyKind::OpaqueDef(..) => (Pat::Str("impl"), Pat::Str("")),
         TyKind::Path(qpath) => qpath_search_pat(&qpath),
         TyKind::Infer(()) => (Pat::Str("_"), Pat::Str("_")),
+        TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ty_search_pat(binder_ty.inner_ty).1),
         TyKind::TraitObject(_, tagged_ptr) if let TraitObjectSyntax::Dyn = tagged_ptr.tag() => {
             (Pat::Str("dyn"), Pat::Str(""))
         },
@@ -411,6 +415,127 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) {
     }
 }
 
+fn ast_ty_search_pat(ty: &ast::Ty) -> (Pat, Pat) {
+    use ast::{Extern, FnRetTy, MutTy, Safety, TraitObjectSyntax, TyKind};
+
+    match &ty.kind {
+        TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")),
+        TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ast_ty_search_pat(ty).1),
+        TyKind::Ref(_, MutTy { ty, .. }) | TyKind::PinnedRef(_, MutTy { ty, .. }) => {
+            (Pat::Str("&"), ast_ty_search_pat(ty).1)
+        },
+        TyKind::FnPtr(fn_ptr) => (
+            if let Safety::Unsafe(_) = fn_ptr.safety {
+                Pat::Str("unsafe")
+            } else if let Extern::Explicit(strlit, _) = fn_ptr.ext
+                && strlit.symbol == sym::rust
+            {
+                Pat::MultiStr(&["fn", "extern"])
+            } else {
+                Pat::Str("extern")
+            },
+            match &fn_ptr.decl.output {
+                FnRetTy::Default(_) => {
+                    if let [.., param] = &*fn_ptr.decl.inputs {
+                        ast_ty_search_pat(&param.ty).1
+                    } else {
+                        Pat::Str("(")
+                    }
+                },
+                FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1,
+            },
+        ),
+        TyKind::Never => (Pat::Str("!"), Pat::Str("!")),
+        // Parenthesis are trimmed from the text before the search patterns are matched.
+        // See: `span_matches_pat`
+        TyKind::Tup(tup) => match &**tup {
+            [] => (Pat::Str(")"), Pat::Str("(")),
+            [ty] => ast_ty_search_pat(ty),
+            [head, .., tail] => (ast_ty_search_pat(head).0, ast_ty_search_pat(tail).1),
+        },
+        TyKind::ImplTrait(..) => (Pat::Str("impl"), Pat::Str("")),
+        TyKind::Path(qself_path, path) => {
+            let start = if qself_path.is_some() {
+                Pat::Str("<")
+            } else if let Some(first) = path.segments.first() {
+                ident_search_pat(first.ident).0
+            } else {
+                // this shouldn't be possible, but sure
+                Pat::Str("")
+            };
+            let end = if let Some(last) = path.segments.last() {
+                match last.args.as_deref() {
+                    // last `>` in `std::foo::Bar<T>`
+                    Some(GenericArgs::AngleBracketed(_)) => Pat::Str(">"),
+                    Some(GenericArgs::Parenthesized(par_args)) => match &par_args.output {
+                        FnRetTy::Default(_) => {
+                            if let Some(last) = par_args.inputs.last() {
+                                // `B` in `(A, B)` -- `)` gets stripped
+                                ast_ty_search_pat(last).1
+                            } else {
+                                // `(` in `()` -- `)` gets stripped
+                                Pat::Str("(")
+                            }
+                        },
+                        // `C` in `(A, B) -> C`
+                        FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1,
+                    },
+                    // last `..` in `(..)` -- `)` gets stripped
+                    Some(GenericArgs::ParenthesizedElided(_)) => Pat::Str(".."),
+                    // `bar` in `std::foo::bar`
+                    None => ident_search_pat(last.ident).1,
+                }
+            } else {
+                // this shouldn't be possible, but sure
+                #[allow(
+                    clippy::collapsible_else_if,
+                    reason = "we want to keep these cases together, since they are both impossible"
+                )]
+                if qself_path.is_some() {
+                    // last `>` in `<Vec as IntoIterator>`
+                    Pat::Str(">")
+                } else {
+                    Pat::Str("")
+                }
+            };
+            (start, end)
+        },
+        TyKind::Infer => (Pat::Str("_"), Pat::Str("_")),
+        TyKind::Paren(ty) => ast_ty_search_pat(ty),
+        TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ast_ty_search_pat(&binder_ty.inner_ty).1),
+        TyKind::TraitObject(_, trait_obj_syntax) => {
+            if let TraitObjectSyntax::Dyn = trait_obj_syntax {
+                (Pat::Str("dyn"), Pat::Str(""))
+            } else {
+                // NOTE: `TraitObject` is incomplete. It will always return true then.
+                (Pat::Str(""), Pat::Str(""))
+            }
+        },
+        TyKind::MacCall(mac_call) => {
+            let start = if let Some(first) = mac_call.path.segments.first() {
+                ident_search_pat(first.ident).0
+            } else {
+                Pat::Str("")
+            };
+            (start, Pat::Str(""))
+        },
+
+        // implicit, so has no contents to match against
+        TyKind::ImplicitSelf
+
+        // experimental
+        |TyKind::Pat(..)
+
+        // unused
+        | TyKind::CVarArgs
+        | TyKind::Typeof(_)
+
+        // placeholder
+        | TyKind::Dummy
+        | TyKind::Err(_) => (Pat::Str(""), Pat::Str("")),
+    }
+}
+
 fn ident_search_pat(ident: Ident) -> (Pat, Pat) {
     (Pat::Sym(ident.name), Pat::Sym(ident.name))
 }
@@ -445,6 +570,7 @@ impl_with_search_pat!((_cx: LateContext<'tcx>, self: Lit) => lit_search_pat(&sel
 impl_with_search_pat!((_cx: LateContext<'tcx>, self: Path<'_>) => path_search_pat(self));
 
 impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: Attribute) => attr_search_pat(self));
+impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: ast::Ty) => ast_ty_search_pat(self));
 
 impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
     type Context = LateContext<'cx>;