Skip to content

let_unit_with_type_underscore: make early-pass #15458

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
16 changes: 8 additions & 8 deletions clippy_lints/src/let_with_type_underscore.rs
Original file line number Diff line number Diff line change
@@ -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! {
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,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)));
Expand Down
130 changes: 128 additions & 2 deletions clippy_utils/src/check_proc_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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)]
Expand Down Expand Up @@ -400,6 +403,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(""))
},
Expand All @@ -408,6 +412,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"
)]
Comment on lines +487 to +490
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allow shouldn't be needed anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how so?..

Copy link
Contributor

@Jarcho Jarcho Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought there was a PR that blocked the lint if there was a comment.

edit: I thought you had copied that from somewhere else in the file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I rebased onto latest master, and it still lints.. but now that I've already done the rebase, I guess I might just leave it in

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))
}
Expand Down Expand Up @@ -442,6 +567,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>;
Expand Down