]> git.lizzy.rs Git - rust.git/commitdiff
Add `let_underscore_drop` lint.
authorAaron Kofsky <aaronko@umich.edu>
Sun, 29 May 2022 18:35:00 +0000 (14:35 -0400)
committerAaron Kofsky <aaronko@umich.edu>
Sun, 29 May 2022 20:20:40 +0000 (16:20 -0400)
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 [new file with mode: 0644]
compiler/rustc_lint/src/lib.rs
src/test/ui/let_underscore_drop.rs [new file with mode: 0644]
src/test/ui/let_underscore_drop.stderr [new file with mode: 0644]

diff --git a/compiler/rustc_lint/src/let_underscore.rs b/compiler/rustc_lint/src/let_underscore.rs
new file mode 100644 (file)
index 0000000..4424217
--- /dev/null
@@ -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();
+                })
+            }
+        }
+    }
+}
index 0a0f292fe7a4d2f02a512fb9318f3fac2c1854d7..55396b36dbc2e7d8e4ac950e30f475f8fce0e020 100644 (file)
@@ -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 (file)
index 0000000..c1c5207
--- /dev/null
@@ -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 (file)
index 0000000..40ed1ab
--- /dev/null
@@ -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
+