]> git.lizzy.rs Git - rust.git/commitdiff
Uplift drop-bounds lint from clippy
authorMichael Howell <michael@notriddle.com>
Wed, 19 Aug 2020 10:05:44 +0000 (03:05 -0700)
committerMichael Howell <michael@notriddle.com>
Thu, 1 Oct 2020 19:06:33 +0000 (12:06 -0700)
compiler/rustc_lint/src/lib.rs
compiler/rustc_lint/src/traits.rs [new file with mode: 0644]
library/core/src/mem/mod.rs
src/test/ui/drop-bounds/drop-bounds-impl-drop.rs [new file with mode: 0644]
src/test/ui/drop-bounds/drop-bounds.rs [new file with mode: 0644]
src/test/ui/drop-bounds/drop-bounds.stderr [new file with mode: 0644]

index 33caedfc198260f846c51c3f6fbabe54d69ee932..49e80f9d8a531741b5d8e1c19cd219a66ee4a080 100644 (file)
@@ -53,6 +53,7 @@
 mod nonstandard_style;
 mod passes;
 mod redundant_semicolon;
+mod traits;
 mod types;
 mod unused;
 
@@ -75,6 +76,7 @@
 use non_ascii_idents::*;
 use nonstandard_style::*;
 use redundant_semicolon::*;
+use traits::*;
 use types::*;
 use unused::*;
 
@@ -157,6 +159,7 @@ macro_rules! late_lint_passes {
                 MissingDebugImplementations: MissingDebugImplementations::default(),
                 ArrayIntoIter: ArrayIntoIter,
                 ClashingExternDeclarations: ClashingExternDeclarations::new(),
+                DropTraitConstraints: DropTraitConstraints,
             ]
         );
     };
diff --git a/compiler/rustc_lint/src/traits.rs b/compiler/rustc_lint/src/traits.rs
new file mode 100644 (file)
index 0000000..d4f7903
--- /dev/null
@@ -0,0 +1,79 @@
+use crate::LateContext;
+use crate::LateLintPass;
+use crate::LintContext;
+use rustc_hir as hir;
+use rustc_span::symbol::sym;
+
+declare_lint! {
+    /// The `drop_bounds` lint checks for generics with `std::ops::Drop` as
+    /// bounds.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// fn foo<T: Drop>() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// `Drop` bounds do not really accomplish anything. A type may have
+    /// compiler-generated drop glue without implementing the `Drop` trait
+    /// itself. The `Drop` trait also only has one method, `Drop::drop`, and
+    /// that function is by fiat not callable in user code. So there is really
+    /// no use case for using `Drop` in trait bounds.
+    ///
+    /// The most likely use case of a drop bound is to distinguish between
+    /// types that have destructors and types that don't. Combined with
+    /// specialization, a naive coder would write an implementation that
+    /// assumed a type could be trivially dropped, then write a specialization
+    /// for `T: Drop` that actually calls the destructor. Except that doing so
+    /// is not correct; String, for example, doesn't actually implement Drop,
+    /// but because String contains a Vec, assuming it can be trivially dropped
+    /// will leak memory.
+    pub DROP_BOUNDS,
+    Warn,
+    "bounds of the form `T: Drop` are useless"
+}
+
+declare_lint_pass!(
+    /// Lint for bounds of the form `T: Drop`, which usually
+    /// indicate an attempt to emulate `std::mem::needs_drop`.
+    DropTraitConstraints => [DROP_BOUNDS]
+);
+
+impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+        use rustc_middle::ty::PredicateAtom::*;
+
+        let def_id = cx.tcx.hir().local_def_id(item.hir_id);
+        let predicates = cx.tcx.explicit_predicates_of(def_id);
+        for &(predicate, span) in predicates.predicates {
+            let trait_predicate = match predicate.skip_binders() {
+                Trait(trait_predicate, _constness) => trait_predicate,
+                _ => continue,
+            };
+            let def_id = trait_predicate.trait_ref.def_id;
+            if cx.tcx.lang_items().drop_trait() == Some(def_id) {
+                // Explicitly allow `impl Drop`, a drop-guards-as-Voldemort-type pattern.
+                if trait_predicate.trait_ref.self_ty().is_impl_trait() {
+                    continue;
+                }
+                cx.struct_span_lint(DROP_BOUNDS, span, |lint| {
+                    let needs_drop = match cx.tcx.get_diagnostic_item(sym::needs_drop) {
+                        Some(needs_drop) => needs_drop,
+                        None => return,
+                    };
+                    let msg = format!(
+                        "bounds on `{}` are useless, consider instead \
+                         using `{}` to detect if a type has a destructor",
+                        predicate,
+                        cx.tcx.def_path_str(needs_drop)
+                    );
+                    lint.build(&msg).emit()
+                });
+            }
+        }
+    }
+}
index aa1b5529df22247a777697edb846a25c2f5194de..a2c7da6e6958e2c9dacd0d9453ebee368157cf85 100644 (file)
@@ -568,6 +568,7 @@ pub unsafe fn align_of_val_raw<T: ?Sized>(val: *const T) -> usize {
 #[inline]
 #[stable(feature = "needs_drop", since = "1.21.0")]
 #[rustc_const_stable(feature = "const_needs_drop", since = "1.36.0")]
+#[rustc_diagnostic_item = "needs_drop"]
 pub const fn needs_drop<T>() -> bool {
     intrinsics::needs_drop::<T>()
 }
diff --git a/src/test/ui/drop-bounds/drop-bounds-impl-drop.rs b/src/test/ui/drop-bounds/drop-bounds-impl-drop.rs
new file mode 100644 (file)
index 0000000..063efc7
--- /dev/null
@@ -0,0 +1,14 @@
+// run-pass
+#![deny(drop_bounds)]
+// As a special exemption, `impl Drop` in the return position raises no error.
+// This allows a convenient way to return an unnamed drop guard.
+fn voldemort_type() -> impl Drop {
+  struct Voldemort;
+  impl Drop for Voldemort {
+    fn drop(&mut self) {}
+  }
+  Voldemort
+}
+fn main() {
+  let _ = voldemort_type();
+}
diff --git a/src/test/ui/drop-bounds/drop-bounds.rs b/src/test/ui/drop-bounds/drop-bounds.rs
new file mode 100644 (file)
index 0000000..c735382
--- /dev/null
@@ -0,0 +1,19 @@
+#![deny(drop_bounds)]
+fn foo<T: Drop>() {} //~ ERROR
+fn bar<U>()
+where
+    U: Drop, //~ ERROR
+{
+}
+fn baz(_x: impl Drop) {} //~ ERROR
+struct Foo<T: Drop> { //~ ERROR
+  _x: T
+}
+struct Bar<U> where U: Drop { //~ ERROR
+  _x: U
+}
+trait Baz: Drop { //~ ERROR
+}
+impl<T: Drop> Baz for T { //~ ERROR
+}
+fn main() {}
diff --git a/src/test/ui/drop-bounds/drop-bounds.stderr b/src/test/ui/drop-bounds/drop-bounds.stderr
new file mode 100644 (file)
index 0000000..15ba4c9
--- /dev/null
@@ -0,0 +1,50 @@
+error: bounds on `T: Drop` are useless, consider instead using `std::mem::needs_drop` to detect if a type has a destructor
+  --> $DIR/drop-bounds.rs:2:11
+   |
+LL | fn foo<T: Drop>() {}
+   |           ^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/drop-bounds.rs:1:9
+   |
+LL | #![deny(drop_bounds)]
+   |         ^^^^^^^^^^^
+
+error: bounds on `U: Drop` are useless, consider instead using `std::mem::needs_drop` to detect if a type has a destructor
+  --> $DIR/drop-bounds.rs:5:8
+   |
+LL |     U: Drop,
+   |        ^^^^
+
+error: bounds on `impl Drop: Drop` are useless, consider instead using `std::mem::needs_drop` to detect if a type has a destructor
+  --> $DIR/drop-bounds.rs:8:17
+   |
+LL | fn baz(_x: impl Drop) {}
+   |                 ^^^^
+
+error: bounds on `T: Drop` are useless, consider instead using `std::mem::needs_drop` to detect if a type has a destructor
+  --> $DIR/drop-bounds.rs:9:15
+   |
+LL | struct Foo<T: Drop> {
+   |               ^^^^
+
+error: bounds on `U: Drop` are useless, consider instead using `std::mem::needs_drop` to detect if a type has a destructor
+  --> $DIR/drop-bounds.rs:12:24
+   |
+LL | struct Bar<U> where U: Drop {
+   |                        ^^^^
+
+error: bounds on `Self: Drop` are useless, consider instead using `std::mem::needs_drop` to detect if a type has a destructor
+  --> $DIR/drop-bounds.rs:15:12
+   |
+LL | trait Baz: Drop {
+   |            ^^^^
+
+error: bounds on `T: Drop` are useless, consider instead using `std::mem::needs_drop` to detect if a type has a destructor
+  --> $DIR/drop-bounds.rs:17:9
+   |
+LL | impl<T: Drop> Baz for T {
+   |         ^^^^
+
+error: aborting due to 7 previous errors
+