From 821b32bd406e9c29b2e9ca2a647d30021cff653d Mon Sep 17 00:00:00 2001 From: Aaron Kofsky Date: Sun, 29 May 2022 14:35:00 -0400 Subject: [PATCH] Add `let_underscore_drop` lint. This lint checks for statements similar to `let _ = foo`, where `foo` is a type that implements `Drop`. These types of let statements cause the expression in them to be dropped immediately, instead of at the end of the scope. Such behavior can be surprizing, especially if you are relying on the value to be dropped at the end of the scope. Instead, the binding should be an underscore prefixed name (like `_unused`) or the value should explicitly be passed to `std::mem::drop()` if the value really should be dropped immediately. --- compiler/rustc_lint/src/let_underscore.rs | 66 +++++++++++++++++++++++ compiler/rustc_lint/src/lib.rs | 5 ++ src/test/ui/let_underscore_drop.rs | 13 +++++ src/test/ui/let_underscore_drop.stderr | 12 +++++ 4 files changed, 96 insertions(+) create mode 100644 compiler/rustc_lint/src/let_underscore.rs create mode 100644 src/test/ui/let_underscore_drop.rs create mode 100644 src/test/ui/let_underscore_drop.stderr diff --git a/compiler/rustc_lint/src/let_underscore.rs b/compiler/rustc_lint/src/let_underscore.rs new file mode 100644 index 00000000000..44242173c00 --- /dev/null +++ b/compiler/rustc_lint/src/let_underscore.rs @@ -0,0 +1,66 @@ +use crate::{LateContext, LateLintPass, LintContext}; +use rustc_hir as hir; + +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() { + /// // SomeStuct 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"); + /// } + /// ``` + /// ### 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, + Warn, + "non-binding let on a type that implements `Drop`" +} + +declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP]); + +impl<'tcx> LateLintPass<'tcx> for LetUnderscore { + fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) { + if !matches!(local.pat.kind, hir::PatKind::Wild) { + return; + } + if let Some(init) = local.init { + let init_ty = cx.typeck_results().expr_ty(init); + let needs_drop = init_ty.needs_drop(cx.tcx, cx.param_env); + if needs_drop { + cx.struct_span_lint(LET_UNDERSCORE_DROP, local.span, |lint| { + lint.build("non-binding let on a type that implements `Drop`") + .help("consider binding to an unused variable") + .help("consider explicitly droping with `std::mem::drop`") + .emit(); + }) + } + } + } +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 0a0f292fe7a..55396b36dbc 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -54,6 +54,7 @@ pub mod hidden_unicode_codepoints; mod internal; mod late; +mod let_underscore; mod levels; mod methods; mod non_ascii_idents; @@ -85,6 +86,7 @@ use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums; use hidden_unicode_codepoints::*; use internal::*; +use let_underscore::*; use methods::*; use non_ascii_idents::*; use non_fmt_panic::NonPanicFmt; @@ -199,6 +201,7 @@ macro_rules! late_lint_mod_passes { VariantSizeDifferences: VariantSizeDifferences, BoxPointers: BoxPointers, PathStatements: PathStatements, + LetUnderscore: LetUnderscore, // Depends on referenced function signatures in expressions UnusedResults: UnusedResults, NonUpperCaseGlobals: NonUpperCaseGlobals, @@ -314,6 +317,8 @@ macro_rules! register_passes { REDUNDANT_SEMICOLONS ); + add_lint_group!("let_underscore", LET_UNDERSCORE_DROP); + add_lint_group!( "rust_2018_idioms", BARE_TRAIT_OBJECTS, diff --git a/src/test/ui/let_underscore_drop.rs b/src/test/ui/let_underscore_drop.rs new file mode 100644 index 00000000000..c1c5207d0fe --- /dev/null +++ b/src/test/ui/let_underscore_drop.rs @@ -0,0 +1,13 @@ +// run-pass + +struct NontrivialDrop; + +impl Drop for NontrivialDrop { + fn drop(&mut self) { + println!("Dropping!"); + } +} + +fn main() { + let _ = NontrivialDrop; //~WARNING non-binding let on a type that implements `Drop` +} diff --git a/src/test/ui/let_underscore_drop.stderr b/src/test/ui/let_underscore_drop.stderr new file mode 100644 index 00000000000..40ed1abd8dc --- /dev/null +++ b/src/test/ui/let_underscore_drop.stderr @@ -0,0 +1,12 @@ +warning: non-binding let on a type that implements `Drop` + --> $DIR/let_underscore_drop.rs:12:5 + | +LL | let _ = NontrivialDrop; + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(let_underscore_drop)]` on by default + = help: consider binding to an unused variable + = help: consider explicitly droping with `std::mem::drop` + +warning: 1 warning emitted +