summary refs log tree commit diff
path: root/compiler/rustc_lint/src/traits.rs
blob: d4f79036e5a180f92d67d1529b8709041fd73061 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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()
                });
            }
        }
    }
}