Skip to main content

rustc_lint/
let_underscore.rs

1use rustc_errors::MultiSpan;
2use rustc_hir as hir;
3use rustc_lint_defs::{declare_lint, declare_lint_pass};
4use rustc_middle::ty;
5use rustc_span::{Symbol, sym};
6
7use crate::diagnostics::{NonBindingLet, NonBindingLetSub};
8use crate::{LateContext, LateLintPass, LintContext};
9
10#[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",
            ..
        };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.
52    pub LET_UNDERSCORE_DROP,
53    Allow,
54    "non-binding let on a type that has a destructor"
55}
56
57#[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",
            ..
        };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.
93    pub LET_UNDERSCORE_LOCK,
94    Deny,
95    "non-binding let on a synchronization lock"
96}
97
98#[doc =
r" The `let_underscore_drop_guard` lint checks for statements which don't bind"]
#[doc =
r" a `DropGuard` to anything, causing its closure to run immediately instead"]
#[doc = r" of at end of scope, which is typically incorrect."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = "```rust,compile_fail"]
#[doc = r" use std::mem::DropGuard;"]
#[doc = r""]
#[doc =
r" // The closure runs immediately instead of at the end of the scope,"]
#[doc = r" // which is probably not intended."]
#[doc = r" let _ = DropGuard::new((), |_| {"]
#[doc = r#"     println!("cleaning up");"#]
#[doc = r" });"]
#[doc = "```"]
#[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 `DropGuard`, which are typically used to"]
#[doc = r" ensure running some cleanup code at the end of a 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_GUARD: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "LET_UNDERSCORE_DROP_GUARD",
            default_level: ::rustc_lint_defs::Deny,
            desc: "non-binding let on a `DropGuard`",
            ..
        };declare_lint! {
99    /// The `let_underscore_drop_guard` lint checks for statements which don't bind
100    /// a `DropGuard` to anything, causing its closure to run immediately instead
101    /// of at end of scope, which is typically incorrect.
102    ///
103    /// ### Example
104    #[cfg_attr(bootstrap, doc = "```rust")]
105    #[cfg_attr(not(bootstrap), doc = "```rust,compile_fail")]
106    /// use std::mem::DropGuard;
107    ///
108    /// // The closure runs immediately instead of at the end of the scope,
109    /// // which is probably not intended.
110    /// let _ = DropGuard::new((), |_| {
111    ///     println!("cleaning up");
112    /// });
113    #[cfg_attr(bootstrap, doc = "```")]
114    #[cfg_attr(not(bootstrap), doc = "```")]
115    ///
116    /// {{produces}}
117    ///
118    /// ### Explanation
119    ///
120    /// Statements which assign an expression to an underscore causes the
121    /// expression to immediately drop instead of extending the expression's
122    /// lifetime to the end of the scope. This is usually unintended,
123    /// especially for types like `DropGuard`, which are typically used to
124    /// ensure running some cleanup code at the end of a scope.
125    ///
126    /// If you want to extend the expression's lifetime to the end of the scope,
127    /// assign an underscore-prefixed name (such as `_foo`) to the expression.
128    /// If you do actually want to drop the expression immediately, then
129    /// calling `std::mem::drop` on the expression is clearer and helps convey
130    /// intent.
131    pub LET_UNDERSCORE_DROP_GUARD,
132    Deny,
133    "non-binding let on a `DropGuard`"
134}
135
136pub 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) -> Self { *self }
}
impl ::rustc_lint_defs::LintPass for LetUnderscore {
    fn name(&self) -> &'static str { "LetUnderscore" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK,
                        LET_UNDERSCORE_DROP_GUARD]))
    }
}
impl LetUnderscore {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK,
                        LET_UNDERSCORE_DROP_GUARD]))
    }
}declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_DROP_GUARD]);
137
138const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
139    rustc_span::sym::MutexGuard,
140    rustc_span::sym::RwLockReadGuard,
141    rustc_span::sym::RwLockWriteGuard,
142];
143
144impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
145    fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::LetStmt<'_>) {
146        if #[allow(non_exhaustive_omitted_patterns)] match local.source {
    rustc_hir::LocalSource::AsyncFn => true,
    _ => false,
}matches!(local.source, rustc_hir::LocalSource::AsyncFn) {
147            return;
148        }
149
150        let mut top_level = true;
151
152        // We recursively walk through all patterns, so that we can catch cases where the lock is
153        // nested in a pattern. For the basic `let_underscore_drop` lint, we only look at the top
154        // level, since there are many legitimate reasons to bind a sub-pattern to an `_`, if we're
155        // only interested in the rest. But with locks, we prefer having the chance of "false
156        // positives" over missing cases, since the effects can be quite catastrophic.
157        local.pat.walk_always(|pat| {
158            let is_top_level = top_level;
159            top_level = false;
160
161            if !#[allow(non_exhaustive_omitted_patterns)] match pat.kind {
    hir::PatKind::Wild => true,
    _ => false,
}matches!(pat.kind, hir::PatKind::Wild) {
162                return;
163            }
164
165            let ty = cx.typeck_results().pat_ty(pat);
166
167            // If the type has a trivial Drop implementation, then it doesn't
168            // matter that we drop the value immediately.
169            if !ty.needs_drop(cx.tcx, cx.typing_env()) {
170                return;
171            }
172            // Lint for patterns like `mutex.lock()`, which returns `Result<MutexGuard, _>` as well.
173            let potential_lock_type = match ty.kind() {
174                ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => {
175                    args.type_at(0)
176                }
177                _ => ty,
178            };
179            let is_sync_lock = match potential_lock_type.kind() {
180                ty::Adt(adt, _) => SYNC_GUARD_SYMBOLS
181                    .iter()
182                    .any(|guard_symbol| cx.tcx.is_diagnostic_item(*guard_symbol, adt.did())),
183                _ => false,
184            };
185            let is_drop_guard = #[allow(non_exhaustive_omitted_patterns)] match ty.kind() {
    ty::Adt(adt, _) if cx.tcx.is_diagnostic_item(sym::DropGuard, adt.did()) =>
        true,
    _ => false,
}matches!(ty.kind(), ty::Adt(adt, _) if cx.tcx.is_diagnostic_item(sym::DropGuard, adt.did()));
186
187            let can_use_init = is_top_level.then_some(local.init).flatten();
188
189            let sub = NonBindingLetSub {
190                suggestion: pat.span,
191                // We can't suggest `drop()` when we're on the top level.
192                drop_fn_start_end: can_use_init
193                    .map(|init| (local.span.until(init.span), init.span.shrink_to_hi())),
194                is_assign_desugar: #[allow(non_exhaustive_omitted_patterns)] match local.source {
    rustc_hir::LocalSource::AssignDesugar => true,
    _ => false,
}matches!(local.source, rustc_hir::LocalSource::AssignDesugar),
195            };
196            if is_sync_lock {
197                let span = MultiSpan::from_span(pat.span);
198                cx.emit_span_lint(
199                    LET_UNDERSCORE_LOCK,
200                    span,
201                    NonBindingLet::SyncLock { sub, pat: pat.span },
202                );
203            } else if is_drop_guard {
204                let span = MultiSpan::from_span(pat.span);
205                cx.emit_span_lint(
206                    LET_UNDERSCORE_DROP_GUARD,
207                    span,
208                    NonBindingLet::DropGuard { sub, pat: pat.span },
209                );
210            // Only emit let_underscore_drop for top-level `_` patterns.
211            } else if can_use_init.is_some() {
212                cx.emit_span_lint(LET_UNDERSCORE_DROP, local.span, NonBindingLet::DropType { sub });
213            }
214        });
215    }
216}