diff --git a/compiler/rustc_lint/src/diagnostics.rs b/compiler/rustc_lint/src/diagnostics.rs index 652fbabb174cd..2b1a8132e65e8 100644 --- a/compiler/rustc_lint/src/diagnostics.rs +++ b/compiler/rustc_lint/src/diagnostics.rs @@ -1348,6 +1348,13 @@ pub(crate) enum NonBindingLet { #[subdiagnostic] sub: NonBindingLetSub, }, + #[diag("non-binding let on a `DropGuard`")] + DropGuard { + #[label("this `DropGuard` is not assigned to a binding and is immediately dropped")] + pat: Span, + #[subdiagnostic] + sub: NonBindingLetSub, + }, #[diag("non-binding let on a type that has a destructor")] DropType { #[subdiagnostic] diff --git a/compiler/rustc_lint/src/let_underscore.rs b/compiler/rustc_lint/src/let_underscore.rs index 9d154be832995..07c2bf6f0ca13 100644 --- a/compiler/rustc_lint/src/let_underscore.rs +++ b/compiler/rustc_lint/src/let_underscore.rs @@ -95,7 +95,45 @@ declare_lint! { "non-binding let on a synchronization lock" } -declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK]); +declare_lint! { + /// The `let_underscore_drop_guard` lint checks for statements which don't bind + /// a `DropGuard` to anything, causing its closure to run immediately instead + /// of at end of scope, which is typically incorrect. + /// + /// ### Example + #[cfg_attr(bootstrap, doc = "```rust")] + #[cfg_attr(not(bootstrap), doc = "```rust,compile_fail")] + /// use std::mem::DropGuard; + /// + /// // The closure runs immediately instead of at the end of the scope, + /// // which is probably not intended. + /// let _ = DropGuard::new((), |_| { + /// println!("cleaning up"); + /// }); + #[cfg_attr(bootstrap, doc = "```")] + #[cfg_attr(not(bootstrap), doc = "```")] + /// + /// {{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 `DropGuard`, which are typically used to + /// ensure running some cleanup code at the end of a 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_GUARD, + Deny, + "non-binding let on a `DropGuard`" +} + +declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_DROP_GUARD]); const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [ rustc_span::sym::MutexGuard, @@ -144,6 +182,7 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore { .any(|guard_symbol| cx.tcx.is_diagnostic_item(*guard_symbol, adt.did())), _ => false, }; + let is_drop_guard = matches!(ty.kind(), ty::Adt(adt, _) if cx.tcx.is_diagnostic_item(sym::DropGuard, adt.did())); let can_use_init = is_top_level.then_some(local.init).flatten(); @@ -161,6 +200,13 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore { span, NonBindingLet::SyncLock { sub, pat: pat.span }, ); + } else if is_drop_guard { + let span = MultiSpan::from_span(pat.span); + cx.emit_span_lint( + LET_UNDERSCORE_DROP_GUARD, + span, + NonBindingLet::DropGuard { sub, pat: pat.span }, + ); // Only emit let_underscore_drop for top-level `_` patterns. } else if can_use_init.is_some() { cx.emit_span_lint(LET_UNDERSCORE_DROP, local.span, NonBindingLet::DropType { sub }); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 6f078fef4892b..8e477a7a5e057 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -208,6 +208,7 @@ symbols! { Deref, DispatchFromDyn, Display, + DropGuard, Dual, DualOnly, Dualv, diff --git a/library/core/src/mem/drop_guard.rs b/library/core/src/mem/drop_guard.rs index d997ab680e354..f7e610c68ddc9 100644 --- a/library/core/src/mem/drop_guard.rs +++ b/library/core/src/mem/drop_guard.rs @@ -30,6 +30,7 @@ use crate::ops::{Deref, DerefMut}; #[stable(feature = "drop_guard", since = "1.100.0")] #[doc(alias = "ScopeGuard")] #[doc(alias = "defer")] +#[rustc_diagnostic_item = "DropGuard"] pub struct DropGuard<#[rustc_assert_variance(covariant)] T, #[rustc_assert_variance(covariant)] F> where F: FnOnce(T), diff --git a/tests/ui/lint/let_underscore/let_underscore_drop_guard.rs b/tests/ui/lint/let_underscore/let_underscore_drop_guard.rs new file mode 100644 index 0000000000000..b1a82438acdfc --- /dev/null +++ b/tests/ui/lint/let_underscore/let_underscore_drop_guard.rs @@ -0,0 +1,23 @@ +use std::mem::DropGuard; + +struct Struct { + a: T, +} + +fn main() { + let _ = DropGuard::new((), |_| {}); + //~^ERROR non-binding let on a `DropGuard` + _ = DropGuard::new((), |_| {}); + //~^ERROR non-binding let on a `DropGuard` + + let (_, _) = (DropGuard::new((), |_| {}), 1); + //~^ERROR non-binding let on a `DropGuard` + (_, _) = (DropGuard::new((), |_| {}), 1); + //~^ERROR non-binding let on a `DropGuard` + + let (_a, Struct { a: _ }) = (0, Struct { a: DropGuard::new((), |_| {}) }); + //~^ERROR non-binding let on a `DropGuard` + let _b; + (_b, Struct { a: _ }) = (0, Struct { a: DropGuard::new((), |_| {}) }); + //~^ERROR non-binding let on a `DropGuard` +} diff --git a/tests/ui/lint/let_underscore/let_underscore_drop_guard.stderr b/tests/ui/lint/let_underscore/let_underscore_drop_guard.stderr new file mode 100644 index 0000000000000..c4c58fa6b4b95 --- /dev/null +++ b/tests/ui/lint/let_underscore/let_underscore_drop_guard.stderr @@ -0,0 +1,86 @@ +error: non-binding let on a `DropGuard` + --> $DIR/let_underscore_drop_guard.rs:8:9 + | +LL | let _ = DropGuard::new((), |_| {}); + | ^ this `DropGuard` is not assigned to a binding and is immediately dropped + | + = note: `#[deny(let_underscore_drop_guard)]` on by default +help: consider binding to an unused variable to avoid immediately dropping the value + | +LL | let _unused = DropGuard::new((), |_| {}); + | ++++++ +help: consider immediately dropping the value + | +LL - let _ = DropGuard::new((), |_| {}); +LL + drop(DropGuard::new((), |_| {})); + | + +error: non-binding let on a `DropGuard` + --> $DIR/let_underscore_drop_guard.rs:10:5 + | +LL | _ = DropGuard::new((), |_| {}); + | ^ this `DropGuard` is not assigned to a binding and is immediately dropped + | +help: consider binding to an unused variable to avoid immediately dropping the value + | +LL - _ = DropGuard::new((), |_| {}); +LL + let _unused = DropGuard::new((), |_| {}); + | +help: consider immediately dropping the value + | +LL - _ = DropGuard::new((), |_| {}); +LL + drop(DropGuard::new((), |_| {})); + | + +error: non-binding let on a `DropGuard` + --> $DIR/let_underscore_drop_guard.rs:13:10 + | +LL | let (_, _) = (DropGuard::new((), |_| {}), 1); + | ^ this `DropGuard` is not assigned to a binding and is immediately dropped + | + = help: consider immediately dropping the value using `drop(..)` after the `let` statement +help: consider binding to an unused variable to avoid immediately dropping the value + | +LL | let (_unused, _) = (DropGuard::new((), |_| {}), 1); + | ++++++ + +error: non-binding let on a `DropGuard` + --> $DIR/let_underscore_drop_guard.rs:15:6 + | +LL | (_, _) = (DropGuard::new((), |_| {}), 1); + | ^ this `DropGuard` is not assigned to a binding and is immediately dropped + | +help: consider binding to an unused variable to avoid immediately dropping the value + --> $DIR/let_underscore_drop_guard.rs:15:6 + | +LL | (_, _) = (DropGuard::new((), |_| {}), 1); + | ^ + = help: consider immediately dropping the value using `drop(..)` after the `let` statement + +error: non-binding let on a `DropGuard` + --> $DIR/let_underscore_drop_guard.rs:18:26 + | +LL | let (_a, Struct { a: _ }) = (0, Struct { a: DropGuard::new((), |_| {}) }); + | ^ this `DropGuard` is not assigned to a binding and is immediately dropped + | + = help: consider immediately dropping the value using `drop(..)` after the `let` statement +help: consider binding to an unused variable to avoid immediately dropping the value + | +LL | let (_a, Struct { a: _unused }) = (0, Struct { a: DropGuard::new((), |_| {}) }); + | ++++++ + +error: non-binding let on a `DropGuard` + --> $DIR/let_underscore_drop_guard.rs:21:22 + | +LL | (_b, Struct { a: _ }) = (0, Struct { a: DropGuard::new((), |_| {}) }); + | ^ this `DropGuard` is not assigned to a binding and is immediately dropped + | +help: consider binding to an unused variable to avoid immediately dropping the value + --> $DIR/let_underscore_drop_guard.rs:21:22 + | +LL | (_b, Struct { a: _ }) = (0, Struct { a: DropGuard::new((), |_| {}) }); + | ^ + = help: consider immediately dropping the value using `drop(..)` after the `let` statement + +error: aborting due to 6 previous errors +