1use rustc_errors::MultiSpan;
2use rustc_hiras hir;
3use rustc_middle::ty;
4use rustc_session::{declare_lint, declare_lint_pass};
5use rustc_span::{Symbol, sym};
67use crate::lints::{NonBindingLet, NonBindingLetSub};
8use crate::{LateContext, LateLintPass, LintContext};
910#[doc =
r" The `let_underscore_drop` lint checks for statements which don't bind"]
#[doc =
r" an expression which has a non-trivial Drop implementation to anything,"]
#[doc =
r" causing the expression to be dropped immediately instead of at end of"]
#[doc = r" scope."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" struct SomeStruct;"]
#[doc = r" impl Drop for SomeStruct {"]
#[doc = r" fn drop(&mut self) {"]
#[doc = r#" println!("Dropping SomeStruct");"#]
#[doc = r" }"]
#[doc = r" }"]
#[doc = r""]
#[doc = r" fn main() {"]
#[doc = r" #[warn(let_underscore_drop)]"]
#[doc =
r" // SomeStruct is dropped immediately instead of at end of scope,"]
#[doc =
r#" // so "Dropping SomeStruct" is printed before "end of main"."#]
#[doc =
r" // The order of prints would be reversed if SomeStruct was bound to"]
#[doc = r#" // a name (such as "_foo")."#]
#[doc = r" let _ = SomeStruct;"]
#[doc = r#" println!("end of main");"#]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc = r" Statements which assign an expression to an underscore causes the"]
#[doc =
r" expression to immediately drop instead of extending the expression's"]
#[doc = r" lifetime to the end of the scope. This is usually unintended,"]
#[doc =
r" especially for types like `MutexGuard`, which are typically used to"]
#[doc = r" lock a mutex for the duration of an entire scope."]
#[doc = r""]
#[doc =
r" If you want to extend the expression's lifetime to the end of the scope,"]
#[doc =
r" assign an underscore-prefixed name (such as `_foo`) to the expression."]
#[doc = r" If you do actually want to drop the expression immediately, then"]
#[doc =
r" calling `std::mem::drop` on the expression is clearer and helps convey"]
#[doc = r" intent."]
pub static LET_UNDERSCORE_DROP: &::rustc_lint_defs::Lint =
&::rustc_lint_defs::Lint {
name: "LET_UNDERSCORE_DROP",
default_level: ::rustc_lint_defs::Allow,
desc: "non-binding let on a type that has a destructor",
is_externally_loaded: false,
..::rustc_lint_defs::Lint::default_fields_for_macro()
};declare_lint! {
11/// The `let_underscore_drop` lint checks for statements which don't bind
12 /// an expression which has a non-trivial Drop implementation to anything,
13 /// causing the expression to be dropped immediately instead of at end of
14 /// scope.
15 ///
16 /// ### Example
17 ///
18 /// ```rust
19 /// struct SomeStruct;
20 /// impl Drop for SomeStruct {
21 /// fn drop(&mut self) {
22 /// println!("Dropping SomeStruct");
23 /// }
24 /// }
25 ///
26 /// fn main() {
27 /// #[warn(let_underscore_drop)]
28 /// // SomeStruct is dropped immediately instead of at end of scope,
29 /// // so "Dropping SomeStruct" is printed before "end of main".
30 /// // The order of prints would be reversed if SomeStruct was bound to
31 /// // a name (such as "_foo").
32 /// let _ = SomeStruct;
33 /// println!("end of main");
34 /// }
35 /// ```
36 ///
37 /// {{produces}}
38 ///
39 /// ### Explanation
40 ///
41 /// Statements which assign an expression to an underscore causes the
42 /// expression to immediately drop instead of extending the expression's
43 /// lifetime to the end of the scope. This is usually unintended,
44 /// especially for types like `MutexGuard`, which are typically used to
45 /// lock a mutex for the duration of an entire scope.
46 ///
47 /// If you want to extend the expression's lifetime to the end of the scope,
48 /// assign an underscore-prefixed name (such as `_foo`) to the expression.
49 /// If you do actually want to drop the expression immediately, then
50 /// calling `std::mem::drop` on the expression is clearer and helps convey
51 /// intent.
52pub LET_UNDERSCORE_DROP,
53 Allow,
54"non-binding let on a type that has a destructor"
55}5657#[doc =
r" The `let_underscore_lock` lint checks for statements which don't bind"]
#[doc =
r" a mutex to anything, causing the lock to be released immediately instead"]
#[doc = r" of at end of scope, which is typically incorrect."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r" ```rust,compile_fail"]
#[doc = r" use std::sync::{Arc, Mutex};"]
#[doc = r" use std::thread;"]
#[doc = r" let data = Arc::new(Mutex::new(0));"]
#[doc = r""]
#[doc = r" thread::spawn(move || {"]
#[doc =
r" // The lock is immediately released instead of at the end of the"]
#[doc = r" // scope, which is probably not intended."]
#[doc = r" let _ = data.lock().unwrap();"]
#[doc = r#" println!("doing some work");"#]
#[doc = r" let mut lock = data.lock().unwrap();"]
#[doc = r" *lock += 1;"]
#[doc = r" });"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc = r" Statements which assign an expression to an underscore causes the"]
#[doc =
r" expression to immediately drop instead of extending the expression's"]
#[doc = r" lifetime to the end of the scope. This is usually unintended,"]
#[doc =
r" especially for types like `MutexGuard`, which are typically used to"]
#[doc = r" lock a mutex for the duration of an entire scope."]
#[doc = r""]
#[doc =
r" If you want to extend the expression's lifetime to the end of the scope,"]
#[doc =
r" assign an underscore-prefixed name (such as `_foo`) to the expression."]
#[doc = r" If you do actually want to drop the expression immediately, then"]
#[doc =
r" calling `std::mem::drop` on the expression is clearer and helps convey"]
#[doc = r" intent."]
pub static LET_UNDERSCORE_LOCK: &::rustc_lint_defs::Lint =
&::rustc_lint_defs::Lint {
name: "LET_UNDERSCORE_LOCK",
default_level: ::rustc_lint_defs::Deny,
desc: "non-binding let on a synchronization lock",
is_externally_loaded: false,
..::rustc_lint_defs::Lint::default_fields_for_macro()
};declare_lint! {
58/// The `let_underscore_lock` lint checks for statements which don't bind
59 /// a mutex to anything, causing the lock to be released immediately instead
60 /// of at end of scope, which is typically incorrect.
61 ///
62 /// ### Example
63 /// ```rust,compile_fail
64 /// use std::sync::{Arc, Mutex};
65 /// use std::thread;
66 /// let data = Arc::new(Mutex::new(0));
67 ///
68 /// thread::spawn(move || {
69 /// // The lock is immediately released instead of at the end of the
70 /// // scope, which is probably not intended.
71 /// let _ = data.lock().unwrap();
72 /// println!("doing some work");
73 /// let mut lock = data.lock().unwrap();
74 /// *lock += 1;
75 /// });
76 /// ```
77 ///
78 /// {{produces}}
79 ///
80 /// ### Explanation
81 ///
82 /// Statements which assign an expression to an underscore causes the
83 /// expression to immediately drop instead of extending the expression's
84 /// lifetime to the end of the scope. This is usually unintended,
85 /// especially for types like `MutexGuard`, which are typically used to
86 /// lock a mutex for the duration of an entire scope.
87 ///
88 /// If you want to extend the expression's lifetime to the end of the scope,
89 /// assign an underscore-prefixed name (such as `_foo`) to the expression.
90 /// If you do actually want to drop the expression immediately, then
91 /// calling `std::mem::drop` on the expression is clearer and helps convey
92 /// intent.
93pub LET_UNDERSCORE_LOCK,
94 Deny,
95"non-binding let on a synchronization lock"
96}9798pub struct LetUnderscore;
#[automatically_derived]
impl ::core::marker::Copy for LetUnderscore { }
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for LetUnderscore { }
#[automatically_derived]
impl ::core::clone::Clone for LetUnderscore {
#[inline]
fn clone(&self) -> LetUnderscore { *self }
}
impl ::rustc_lint_defs::LintPass for LetUnderscore {
fn name(&self) -> &'static str { "LetUnderscore" }
fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
<[_]>::into_vec(::alloc::boxed::box_new([LET_UNDERSCORE_DROP,
LET_UNDERSCORE_LOCK]))
}
}
impl LetUnderscore {
#[allow(unused)]
pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
<[_]>::into_vec(::alloc::boxed::box_new([LET_UNDERSCORE_DROP,
LET_UNDERSCORE_LOCK]))
}
}declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK]);
99100const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
101 rustc_span::sym::MutexGuard,
102 rustc_span::sym::RwLockReadGuard,
103 rustc_span::sym::RwLockWriteGuard,
104];
105106impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
107fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::LetStmt<'_>) {
108if #[allow(non_exhaustive_omitted_patterns)] match local.source {
rustc_hir::LocalSource::AsyncFn => true,
_ => false,
}matches!(local.source, rustc_hir::LocalSource::AsyncFn) {
109return;
110 }
111112let mut top_level = true;
113114// We recursively walk through all patterns, so that we can catch cases where the lock is
115 // nested in a pattern. For the basic `let_underscore_drop` lint, we only look at the top
116 // level, since there are many legitimate reasons to bind a sub-pattern to an `_`, if we're
117 // only interested in the rest. But with locks, we prefer having the chance of "false
118 // positives" over missing cases, since the effects can be quite catastrophic.
119local.pat.walk_always(|pat| {
120let is_top_level = top_level;
121top_level = false;
122123if !#[allow(non_exhaustive_omitted_patterns)] match pat.kind {
hir::PatKind::Wild => true,
_ => false,
}matches!(pat.kind, hir::PatKind::Wild) {
124return;
125 }
126127let ty = cx.typeck_results().pat_ty(pat);
128129// If the type has a trivial Drop implementation, then it doesn't
130 // matter that we drop the value immediately.
131if !ty.needs_drop(cx.tcx, cx.typing_env()) {
132return;
133 }
134// Lint for patterns like `mutex.lock()`, which returns `Result<MutexGuard, _>` as well.
135let potential_lock_type = match ty.kind() {
136 ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => {
137args.type_at(0)
138 }
139_ => ty,
140 };
141let is_sync_lock = match potential_lock_type.kind() {
142 ty::Adt(adt, _) => SYNC_GUARD_SYMBOLS143 .iter()
144 .any(|guard_symbol| cx.tcx.is_diagnostic_item(*guard_symbol, adt.did())),
145_ => false,
146 };
147148let can_use_init = is_top_level.then_some(local.init).flatten();
149150let sub = NonBindingLetSub {
151 suggestion: pat.span,
152// We can't suggest `drop()` when we're on the top level.
153drop_fn_start_end: can_use_init154 .map(|init| (local.span.until(init.span), init.span.shrink_to_hi())),
155 is_assign_desugar: #[allow(non_exhaustive_omitted_patterns)] match local.source {
rustc_hir::LocalSource::AssignDesugar => true,
_ => false,
}matches!(local.source, rustc_hir::LocalSource::AssignDesugar),
156 };
157if is_sync_lock {
158let span = MultiSpan::from_span(pat.span);
159cx.emit_span_lint(
160LET_UNDERSCORE_LOCK,
161span,
162 NonBindingLet::SyncLock { sub, pat: pat.span },
163 );
164// Only emit let_underscore_drop for top-level `_` patterns.
165} else if can_use_init.is_some() {
166cx.emit_span_lint(LET_UNDERSCORE_DROP, local.span, NonBindingLet::DropType { sub });
167 }
168 });
169 }
170}