use if_chain::if_chain; use rustc_hir::{Local, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::subst::GenericArgKind; use rustc_session::{declare_lint_pass, declare_tool_lint}; use crate::utils::{is_must_use_func_call, is_must_use_ty, match_type, paths, span_lint_and_help}; declare_clippy_lint! { /// **What it does:** Checks for `let _ = ` /// where expr is #[must_use] /// /// **Why is this bad?** It's better to explicitly /// handle the value of a #[must_use] expr /// /// **Known problems:** None. /// /// **Example:** /// ```rust /// fn f() -> Result { /// Ok(0) /// } /// /// let _ = f(); /// // is_ok() is marked #[must_use] /// let _ = f().is_ok(); /// ``` pub LET_UNDERSCORE_MUST_USE, restriction, "non-binding let on a `#[must_use]` expression" } declare_clippy_lint! { /// **What it does:** Checks for `let _ = sync_lock` /// /// **Why is this bad?** This statement immediately drops the lock instead of /// extending it's 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. /// /// **Known problems:** None. /// /// **Example:** /// /// Bad: /// ```rust,ignore /// let _ = mutex.lock(); /// ``` /// /// Good: /// ```rust,ignore /// let _lock = mutex.lock(); /// ``` pub LET_UNDERSCORE_LOCK, correctness, "non-binding let on a synchronization lock" } declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK]); const SYNC_GUARD_PATHS: [&[&str]; 3] = [ &paths::MUTEX_GUARD, &paths::RWLOCK_READ_GUARD, &paths::RWLOCK_WRITE_GUARD, ]; impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetUnderscore { fn check_local(&mut self, cx: &LateContext<'_, '_>, local: &Local<'_>) { if in_external_macro(cx.tcx.sess, local.span) { return; } if_chain! { if let PatKind::Wild = local.pat.kind; if let Some(ref init) = local.init; then { let init_ty = cx.tables.expr_ty(init); let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() { GenericArgKind::Type(inner_ty) => { SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path)) }, GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, }); if contains_sync_guard { span_lint_and_help( cx, LET_UNDERSCORE_LOCK, local.span, "non-binding let on a synchronization lock", None, "consider using an underscore-prefixed named \ binding or dropping explicitly with `std::mem::drop`" ) } else if is_must_use_ty(cx, cx.tables.expr_ty(init)) { span_lint_and_help( cx, LET_UNDERSCORE_MUST_USE, local.span, "non-binding let on an expression with `#[must_use]` type", None, "consider explicitly using expression value" ) } else if is_must_use_func_call(cx, init) { span_lint_and_help( cx, LET_UNDERSCORE_MUST_USE, local.span, "non-binding let on a result of a `#[must_use]` function", None, "consider explicitly using function result" ) } } } } }