]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/missing_inline.rs
Auto merge of #3946 - rchaser53:issue-3920, r=flip1995
[rust.git] / clippy_lints / src / missing_inline.rs
1 use crate::utils::span_lint;
2 use rustc::hir;
3 use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
4 use rustc::{declare_tool_lint, lint_array};
5 use syntax::ast;
6 use syntax::source_map::Span;
7
8 declare_clippy_lint! {
9     /// **What it does:** it lints if an exported function, method, trait method with default impl,
10     /// or trait method impl is not `#[inline]`.
11     ///
12     /// **Why is this bad?** In general, it is not. Functions can be inlined across
13     /// crates when that's profitable as long as any form of LTO is used. When LTO is disabled,
14     /// functions that are not `#[inline]` cannot be inlined across crates. Certain types of crates
15     /// might intend for most of the methods in their public API to be able to be inlined across
16     /// crates even when LTO is disabled. For these types of crates, enabling this lint might make
17     /// sense. It allows the crate to require all exported methods to be `#[inline]` by default, and
18     /// then opt out for specific methods where this might not make sense.
19     ///
20     /// **Known problems:** None.
21     ///
22     /// **Example:**
23     /// ```rust
24     /// pub fn foo() {} // missing #[inline]
25     /// fn ok() {} // ok
26     /// #[inline] pub fn bar() {} // ok
27     /// #[inline(always)] pub fn baz() {} // ok
28     ///
29     /// pub trait Bar {
30     ///   fn bar(); // ok
31     ///   fn def_bar() {} // missing #[inline]
32     /// }
33     ///
34     /// struct Baz;
35     /// impl Baz {
36     ///    fn priv() {} // ok
37     /// }
38     ///
39     /// impl Bar for Baz {
40     ///   fn bar() {} // ok - Baz is not exported
41     /// }
42     ///
43     /// pub struct PubBaz;
44     /// impl PubBaz {
45     ///    fn priv() {} // ok
46     ///    pub not_ptriv() {} // missing #[inline]
47     /// }
48     ///
49     /// impl Bar for PubBaz {
50     ///    fn bar() {} // missing #[inline]
51     ///    fn def_bar() {} // missing #[inline]
52     /// }
53     /// ```
54     pub MISSING_INLINE_IN_PUBLIC_ITEMS,
55     restriction,
56     "detects missing #[inline] attribute for public callables (functions, trait methods, methods...)"
57 }
58
59 pub struct MissingInline;
60
61 fn check_missing_inline_attrs(cx: &LateContext<'_, '_>, attrs: &[ast::Attribute], sp: Span, desc: &'static str) {
62     let has_inline = attrs.iter().any(|a| a.check_name("inline"));
63     if !has_inline {
64         span_lint(
65             cx,
66             MISSING_INLINE_IN_PUBLIC_ITEMS,
67             sp,
68             &format!("missing `#[inline]` for {}", desc),
69         );
70     }
71 }
72
73 fn is_executable<'a, 'tcx>(cx: &LateContext<'a, 'tcx>) -> bool {
74     use rustc::session::config::CrateType;
75
76     cx.tcx.sess.crate_types.get().iter().any(|t: &CrateType| match t {
77         CrateType::Executable => true,
78         _ => false,
79     })
80 }
81
82 impl LintPass for MissingInline {
83     fn get_lints(&self) -> LintArray {
84         lint_array![MISSING_INLINE_IN_PUBLIC_ITEMS]
85     }
86
87     fn name(&self) -> &'static str {
88         "MissingInline"
89     }
90 }
91
92 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingInline {
93     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, it: &'tcx hir::Item) {
94         if is_executable(cx) {
95             return;
96         }
97
98         if !cx.access_levels.is_exported(it.hir_id) {
99             return;
100         }
101         match it.node {
102             hir::ItemKind::Fn(..) => {
103                 let desc = "a function";
104                 check_missing_inline_attrs(cx, &it.attrs, it.span, desc);
105             },
106             hir::ItemKind::Trait(ref _is_auto, ref _unsafe, ref _generics, ref _bounds, ref trait_items) => {
107                 // note: we need to check if the trait is exported so we can't use
108                 // `LateLintPass::check_trait_item` here.
109                 for tit in trait_items {
110                     let tit_ = cx.tcx.hir().trait_item(tit.id);
111                     match tit_.node {
112                         hir::TraitItemKind::Const(..) | hir::TraitItemKind::Type(..) => {},
113                         hir::TraitItemKind::Method(..) => {
114                             if tit.defaultness.has_value() {
115                                 // trait method with default body needs inline in case
116                                 // an impl is not provided
117                                 let desc = "a default trait method";
118                                 let item = cx.tcx.hir().expect_trait_item(tit.id.hir_id);
119                                 check_missing_inline_attrs(cx, &item.attrs, item.span, desc);
120                             }
121                         },
122                     }
123                 }
124             },
125             hir::ItemKind::Const(..)
126             | hir::ItemKind::Enum(..)
127             | hir::ItemKind::Mod(..)
128             | hir::ItemKind::Static(..)
129             | hir::ItemKind::Struct(..)
130             | hir::ItemKind::TraitAlias(..)
131             | hir::ItemKind::GlobalAsm(..)
132             | hir::ItemKind::Ty(..)
133             | hir::ItemKind::Union(..)
134             | hir::ItemKind::Existential(..)
135             | hir::ItemKind::ExternCrate(..)
136             | hir::ItemKind::ForeignMod(..)
137             | hir::ItemKind::Impl(..)
138             | hir::ItemKind::Use(..) => {},
139         };
140     }
141
142     fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx hir::ImplItem) {
143         use rustc::ty::{ImplContainer, TraitContainer};
144         if is_executable(cx) {
145             return;
146         }
147
148         // If the item being implemented is not exported, then we don't need #[inline]
149         if !cx.access_levels.is_exported(impl_item.hir_id) {
150             return;
151         }
152
153         let desc = match impl_item.node {
154             hir::ImplItemKind::Method(..) => "a method",
155             hir::ImplItemKind::Const(..) | hir::ImplItemKind::Type(_) | hir::ImplItemKind::Existential(_) => return,
156         };
157
158         let def_id = cx.tcx.hir().local_def_id_from_hir_id(impl_item.hir_id);
159         let trait_def_id = match cx.tcx.associated_item(def_id).container {
160             TraitContainer(cid) => Some(cid),
161             ImplContainer(cid) => cx.tcx.impl_trait_ref(cid).map(|t| t.def_id),
162         };
163
164         if let Some(trait_def_id) = trait_def_id {
165             if cx.tcx.hir().as_local_node_id(trait_def_id).is_some() && !cx.access_levels.is_exported(impl_item.hir_id)
166             {
167                 // If a trait is being implemented for an item, and the
168                 // trait is not exported, we don't need #[inline]
169                 return;
170             }
171         }
172
173         check_missing_inline_attrs(cx, &impl_item.attrs, impl_item.span, desc);
174     }
175 }