use crate::{ lints::{NonBindingLet, NonBindingLetSub}, LateContext, LateLintPass, LintContext, }; use rustc_errors::MultiSpan; use rustc_hir as hir; use rustc_middle::ty; use rustc_span::{sym, Symbol}; declare_lint! { /// The `let_underscore_drop` lint checks for statements which don't bind /// an expression which has a non-trivial Drop implementation to anything, /// causing the expression to be dropped immediately instead of at end of /// scope. /// /// ### Example /// /// ```rust /// struct SomeStruct; /// impl Drop for SomeStruct { /// fn drop(&mut self) { /// println!("Dropping SomeStruct"); /// } /// } /// /// fn main() { /// #[warn(let_underscore_drop)] /// // SomeStruct is dropped immediately instead of at end of scope, /// // so "Dropping SomeStruct" is printed before "end of main". /// // The order of prints would be reversed if SomeStruct was bound to /// // a name (such as "_foo"). /// let _ = SomeStruct; /// println!("end of main"); /// } /// ``` /// /// {{produces}} /// /// ### Explanation /// /// Statements which assign an expression to an underscore causes the /// expression to immediately drop instead of extending the expression's /// lifetime to the end of the scope. This is usually unintended, /// especially for types like `MutexGuard`, which are typically used to /// lock a mutex for the duration of an entire scope. /// /// If you want to extend the expression's lifetime to the end of the scope, /// assign an underscore-prefixed name (such as `_foo`) to the expression. /// If you do actually want to drop the expression immediately, then /// calling `std::mem::drop` on the expression is clearer and helps convey /// intent. pub LET_UNDERSCORE_DROP, Allow, "non-binding let on a type that implements `Drop`" } declare_lint! { /// The `let_underscore_lock` lint checks for statements which don't bind /// a mutex to anything, causing the lock to be released immediately instead /// of at end of scope, which is typically incorrect. /// /// ### Example /// ```rust,compile_fail /// use std::sync::{Arc, Mutex}; /// use std::thread; /// let data = Arc::new(Mutex::new(0)); /// /// thread::spawn(move || { /// // The lock is immediately released instead of at the end of the /// // scope, which is probably not intended. /// let _ = data.lock().unwrap(); /// println!("doing some work"); /// let mut lock = data.lock().unwrap(); /// *lock += 1; /// }); /// ``` /// /// {{produces}} /// /// ### Explanation /// /// Statements which assign an expression to an underscore causes the /// expression to immediately drop instead of extending the expression's /// lifetime to the end of the scope. This is usually unintended, /// especially for types like `MutexGuard`, which are typically used to /// lock a mutex for the duration of an entire scope. /// /// If you want to extend the expression's lifetime to the end of the scope, /// assign an underscore-prefixed name (such as `_foo`) to the expression. /// If you do actually want to drop the expression immediately, then /// calling `std::mem::drop` on the expression is clearer and helps convey /// intent. pub LET_UNDERSCORE_LOCK, Deny, "non-binding let on a synchronization lock" } declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK]); const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [ rustc_span::sym::MutexGuard, rustc_span::sym::RwLockReadGuard, rustc_span::sym::RwLockWriteGuard, ]; impl<'tcx> LateLintPass<'tcx> for LetUnderscore { fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) { if matches!(local.source, rustc_hir::LocalSource::AsyncFn) { return; } let mut top_level = true; // We recursively walk through all patterns, so that we can catch cases where the lock is nested in a pattern. // For the basic `let_underscore_drop` lint, we only look at the top level, since there are many legitimate reasons // to bind a sub-pattern to an `_`, if we're only interested in the rest. // But with locks, we prefer having the chance of "false positives" over missing cases, since the effects can be // quite catastrophic. local.pat.walk_always(|pat| { let is_top_level = top_level; top_level = false; if !matches!(pat.kind, hir::PatKind::Wild) { return; } let ty = cx.typeck_results().pat_ty(pat); // If the type has a trivial Drop implementation, then it doesn't // matter that we drop the value immediately. if !ty.needs_drop(cx.tcx, cx.param_env) { return; } // Lint for patterns like `mutex.lock()`, which returns `Result` as well. let potential_lock_type = match ty.kind() { ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => { args.type_at(0) } _ => ty, }; let is_sync_lock = match potential_lock_type.kind() { ty::Adt(adt, _) => SYNC_GUARD_SYMBOLS .iter() .any(|guard_symbol| cx.tcx.is_diagnostic_item(*guard_symbol, adt.did())), _ => false, }; let can_use_init = is_top_level.then_some(local.init).flatten(); let sub = NonBindingLetSub { suggestion: pat.span, // We can't suggest `drop()` when we're on the top level. drop_fn_start_end: can_use_init .map(|init| (local.span.until(init.span), init.span.shrink_to_hi())), is_assign_desugar: matches!(local.source, rustc_hir::LocalSource::AssignDesugar(_)), }; if is_sync_lock { let mut span = MultiSpan::from_span(pat.span); span.push_span_label( pat.span, "this lock is not assigned to a binding and is immediately dropped".to_string(), ); cx.emit_spanned_lint(LET_UNDERSCORE_LOCK, span, NonBindingLet::SyncLock { sub }); // Only emit let_underscore_drop for top-level `_` patterns. } else if can_use_init.is_some() { cx.emit_spanned_lint( LET_UNDERSCORE_DROP, local.span, NonBindingLet::DropType { sub }, ); } }); } }