| use clippy_utils::diagnostics::span_lint_and_then; | |
| use clippy_utils::ty::{implements_trait, is_must_use_ty}; | |
| use clippy_utils::{is_from_proc_macro, is_must_use_func_call, paths}; | |
| use rustc_hir::{LetStmt, LocalSource, PatKind}; | |
| use rustc_lint::{LateContext, LateLintPass}; | |
| use rustc_middle::ty::{GenericArgKind, IsSuggestable}; | |
| use rustc_session::declare_lint_pass; | |
| use rustc_span::{BytePos, Span}; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for `let _ = <expr>` where the resulting type of expr implements `Future` | |
| /// | |
| /// ### Why is this bad? | |
| /// Futures must be polled for work to be done. The original intention was most likely to await the future | |
| /// and ignore the resulting value. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// async fn foo() -> Result<(), ()> { | |
| /// Ok(()) | |
| /// } | |
| /// let _ = foo(); | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```no_run | |
| /// # async fn context() { | |
| /// async fn foo() -> Result<(), ()> { | |
| /// Ok(()) | |
| /// } | |
| /// let _ = foo().await; | |
| /// # } | |
| /// ``` | |
| #[clippy::version = "1.67.0"] | |
| pub LET_UNDERSCORE_FUTURE, | |
| suspicious, | |
| "non-binding `let` on a future" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for `let _ = sync_lock`. This supports `mutex` and `rwlock` in | |
| /// `parking_lot`. For `std` locks see the `rustc` lint | |
| /// [`let_underscore_lock`](https://doc.rust-lang.org/nightly/rustc/lints/listing/deny-by-default.html#let-underscore-lock) | |
| /// | |
| /// ### Why is this bad? | |
| /// This statement immediately drops the lock instead of | |
| /// extending its lifetime to the end of the scope, which is often not intended. | |
| /// To extend lock lifetime to the end of the scope, use an underscore-prefixed | |
| /// name instead (i.e. _lock). If you want to explicitly drop the lock, | |
| /// `std::mem::drop` conveys your intention better and is less error-prone. | |
| /// | |
| /// ### Example | |
| /// ```rust,ignore | |
| /// let _ = mutex.lock(); | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```rust,ignore | |
| /// let _lock = mutex.lock(); | |
| /// ``` | |
| #[clippy::version = "1.43.0"] | |
| pub LET_UNDERSCORE_LOCK, | |
| correctness, | |
| "non-binding `let` on a synchronization lock" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for `let _ = <expr>` where expr is `#[must_use]` | |
| /// | |
| /// ### Why restrict this? | |
| /// To ensure that all `#[must_use]` types are used rather than ignored. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// fn f() -> Result<u32, u32> { | |
| /// Ok(0) | |
| /// } | |
| /// | |
| /// let _ = f(); | |
| /// // is_ok() is marked #[must_use] | |
| /// let _ = f().is_ok(); | |
| /// ``` | |
| #[clippy::version = "1.42.0"] | |
| pub LET_UNDERSCORE_MUST_USE, | |
| restriction, | |
| "non-binding `let` on a `#[must_use]` expression" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for `let _ = <expr>` without a type annotation, and suggests to either provide one, | |
| /// or remove the `let` keyword altogether. | |
| /// | |
| /// ### Why restrict this? | |
| /// The `let _ = <expr>` expression ignores the value of `<expr>`, but will continue to do so even | |
| /// if the type were to change, thus potentially introducing subtle bugs. By supplying a type | |
| /// annotation, one will be forced to re-visit the decision to ignore the value in such cases. | |
| /// | |
| /// ### Known problems | |
| /// The `_ = <expr>` is not properly supported by some tools (e.g. IntelliJ) and may seem odd | |
| /// to many developers. This lint also partially overlaps with the other `let_underscore_*` | |
| /// lints. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// fn foo() -> Result<u32, ()> { | |
| /// Ok(123) | |
| /// } | |
| /// let _ = foo(); | |
| /// ``` | |
| /// Use instead: | |
| /// ```no_run | |
| /// fn foo() -> Result<u32, ()> { | |
| /// Ok(123) | |
| /// } | |
| /// // Either provide a type annotation: | |
| /// let _: Result<u32, ()> = foo(); | |
| /// // …or drop the let keyword: | |
| /// _ = foo(); | |
| /// ``` | |
| #[clippy::version = "1.69.0"] | |
| pub LET_UNDERSCORE_UNTYPED, | |
| restriction, | |
| "non-binding `let` without a type annotation" | |
| } | |
| declare_lint_pass!(LetUnderscore => [ | |
| LET_UNDERSCORE_FUTURE, | |
| LET_UNDERSCORE_LOCK, | |
| LET_UNDERSCORE_MUST_USE, | |
| LET_UNDERSCORE_UNTYPED, | |
| ]); | |
| impl<'tcx> LateLintPass<'tcx> for LetUnderscore { | |
| fn check_local(&mut self, cx: &LateContext<'tcx>, local: &LetStmt<'tcx>) { | |
| if matches!(local.source, LocalSource::Normal) | |
| && let PatKind::Wild = local.pat.kind | |
| && let Some(init) = local.init | |
| && !local.span.in_external_macro(cx.tcx.sess.source_map()) | |
| { | |
| let init_ty = cx.typeck_results().expr_ty(init); | |
| let contains_sync_guard = init_ty.walk().any(|inner| match inner.kind() { | |
| GenericArgKind::Type(inner_ty) => inner_ty | |
| .ty_adt_def() | |
| .is_some_and(|adt| paths::PARKING_LOT_GUARDS.iter().any(|path| path.matches(cx, adt.did()))), | |
| GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, | |
| }); | |
| if contains_sync_guard { | |
| #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] | |
| span_lint_and_then( | |
| cx, | |
| LET_UNDERSCORE_LOCK, | |
| local.span, | |
| "non-binding `let` on a synchronization lock", | |
| |diag| { | |
| diag.help( | |
| "consider using an underscore-prefixed named \ | |
| binding or dropping explicitly with `std::mem::drop`", | |
| ); | |
| }, | |
| ); | |
| } else if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() | |
| && implements_trait(cx, cx.typeck_results().expr_ty(init), future_trait_def_id, &[]) | |
| { | |
| #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] | |
| span_lint_and_then( | |
| cx, | |
| LET_UNDERSCORE_FUTURE, | |
| local.span, | |
| "non-binding `let` on a future", | |
| |diag| { | |
| diag.help("consider awaiting the future or dropping explicitly with `std::mem::drop`"); | |
| }, | |
| ); | |
| } else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) { | |
| #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] | |
| span_lint_and_then( | |
| cx, | |
| LET_UNDERSCORE_MUST_USE, | |
| local.span, | |
| "non-binding `let` on an expression with `#[must_use]` type", | |
| |diag| { | |
| diag.help("consider explicitly using expression value"); | |
| }, | |
| ); | |
| } else if is_must_use_func_call(cx, init) { | |
| #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] | |
| span_lint_and_then( | |
| cx, | |
| LET_UNDERSCORE_MUST_USE, | |
| local.span, | |
| "non-binding `let` on a result of a `#[must_use]` function", | |
| |diag| { | |
| diag.help("consider explicitly using function result"); | |
| }, | |
| ); | |
| } | |
| if local.pat.default_binding_modes && local.ty.is_none() { | |
| // When `default_binding_modes` is true, the `let` keyword is present. | |
| // Ignore unnameable types | |
| if let Some(init) = local.init | |
| && !cx.typeck_results().expr_ty(init).is_suggestable(cx.tcx, true) | |
| { | |
| return; | |
| } | |
| // Ignore if it is from a procedural macro... | |
| if is_from_proc_macro(cx, init) { | |
| return; | |
| } | |
| span_lint_and_then( | |
| cx, | |
| LET_UNDERSCORE_UNTYPED, | |
| local.span, | |
| "non-binding `let` without a type annotation", | |
| |diag| { | |
| diag.span_help( | |
| Span::new( | |
| local.pat.span.hi(), | |
| local.pat.span.hi() + BytePos(1), | |
| local.pat.span.ctxt(), | |
| local.pat.span.parent(), | |
| ), | |
| "consider adding a type annotation", | |
| ); | |
| }, | |
| ); | |
| } | |
| } | |
| } | |
| } |