]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::span_lint; |
f20569fa XL |
2 | use rustc_ast::ast; |
3 | use rustc_hir as hir; | |
4 | use rustc_lint::{self, LateContext, LateLintPass, LintContext}; | |
5 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
6 | use rustc_span::source_map::Span; | |
7 | use rustc_span::sym; | |
8 | ||
9 | declare_clippy_lint! { | |
94222f64 | 10 | /// ### What it does |
3c0e092e | 11 | /// It lints if an exported function, method, trait method with default impl, |
f20569fa XL |
12 | /// or trait method impl is not `#[inline]`. |
13 | /// | |
94222f64 XL |
14 | /// ### Why is this bad? |
15 | /// In general, it is not. Functions can be inlined across | |
f20569fa XL |
16 | /// crates when that's profitable as long as any form of LTO is used. When LTO is disabled, |
17 | /// functions that are not `#[inline]` cannot be inlined across crates. Certain types of crates | |
18 | /// might intend for most of the methods in their public API to be able to be inlined across | |
19 | /// crates even when LTO is disabled. For these types of crates, enabling this lint might make | |
20 | /// sense. It allows the crate to require all exported methods to be `#[inline]` by default, and | |
21 | /// then opt out for specific methods where this might not make sense. | |
22 | /// | |
94222f64 | 23 | /// ### Example |
f20569fa XL |
24 | /// ```rust |
25 | /// pub fn foo() {} // missing #[inline] | |
26 | /// fn ok() {} // ok | |
27 | /// #[inline] pub fn bar() {} // ok | |
28 | /// #[inline(always)] pub fn baz() {} // ok | |
29 | /// | |
30 | /// pub trait Bar { | |
31 | /// fn bar(); // ok | |
32 | /// fn def_bar() {} // missing #[inline] | |
33 | /// } | |
34 | /// | |
35 | /// struct Baz; | |
36 | /// impl Baz { | |
37 | /// fn private() {} // ok | |
38 | /// } | |
39 | /// | |
40 | /// impl Bar for Baz { | |
41 | /// fn bar() {} // ok - Baz is not exported | |
42 | /// } | |
43 | /// | |
44 | /// pub struct PubBaz; | |
45 | /// impl PubBaz { | |
46 | /// fn private() {} // ok | |
04454e1e | 47 | /// pub fn not_private() {} // missing #[inline] |
f20569fa XL |
48 | /// } |
49 | /// | |
50 | /// impl Bar for PubBaz { | |
51 | /// fn bar() {} // missing #[inline] | |
52 | /// fn def_bar() {} // missing #[inline] | |
53 | /// } | |
54 | /// ``` | |
a2a8927a | 55 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
56 | pub MISSING_INLINE_IN_PUBLIC_ITEMS, |
57 | restriction, | |
58 | "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)" | |
59 | } | |
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.has_name(sym::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_or_proc_macro(cx: &LateContext<'_>) -> bool { | |
74 | use rustc_session::config::CrateType; | |
75 | ||
76 | cx.tcx | |
77 | .sess | |
78 | .crate_types() | |
79 | .iter() | |
80 | .any(|t: &CrateType| matches!(t, CrateType::Executable | CrateType::ProcMacro)) | |
81 | } | |
82 | ||
83 | declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]); | |
84 | ||
85 | impl<'tcx> LateLintPass<'tcx> for MissingInline { | |
86 | fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { | |
87 | if rustc_middle::lint::in_external_macro(cx.sess(), it.span) || is_executable_or_proc_macro(cx) { | |
88 | return; | |
89 | } | |
90 | ||
94222f64 | 91 | if !cx.access_levels.is_exported(it.def_id) { |
f20569fa XL |
92 | return; |
93 | } | |
94 | match it.kind { | |
95 | hir::ItemKind::Fn(..) => { | |
96 | let desc = "a function"; | |
97 | let attrs = cx.tcx.hir().attrs(it.hir_id()); | |
98 | check_missing_inline_attrs(cx, attrs, it.span, desc); | |
99 | }, | |
04454e1e | 100 | hir::ItemKind::Trait(ref _is_auto, ref _unsafe, _generics, _bounds, trait_items) => { |
f20569fa XL |
101 | // note: we need to check if the trait is exported so we can't use |
102 | // `LateLintPass::check_trait_item` here. | |
103 | for tit in trait_items { | |
104 | let tit_ = cx.tcx.hir().trait_item(tit.id); | |
105 | match tit_.kind { | |
106 | hir::TraitItemKind::Const(..) | hir::TraitItemKind::Type(..) => {}, | |
107 | hir::TraitItemKind::Fn(..) => { | |
108 | if tit.defaultness.has_value() { | |
109 | // trait method with default body needs inline in case | |
110 | // an impl is not provided | |
111 | let desc = "a default trait method"; | |
112 | let item = cx.tcx.hir().trait_item(tit.id); | |
113 | let attrs = cx.tcx.hir().attrs(item.hir_id()); | |
114 | check_missing_inline_attrs(cx, attrs, item.span, desc); | |
115 | } | |
116 | }, | |
117 | } | |
118 | } | |
119 | }, | |
120 | hir::ItemKind::Const(..) | |
121 | | hir::ItemKind::Enum(..) | |
94222f64 | 122 | | hir::ItemKind::Macro(..) |
f20569fa XL |
123 | | hir::ItemKind::Mod(..) |
124 | | hir::ItemKind::Static(..) | |
125 | | hir::ItemKind::Struct(..) | |
126 | | hir::ItemKind::TraitAlias(..) | |
127 | | hir::ItemKind::GlobalAsm(..) | |
128 | | hir::ItemKind::TyAlias(..) | |
129 | | hir::ItemKind::Union(..) | |
130 | | hir::ItemKind::OpaqueTy(..) | |
131 | | hir::ItemKind::ExternCrate(..) | |
132 | | hir::ItemKind::ForeignMod { .. } | |
133 | | hir::ItemKind::Impl { .. } | |
134 | | hir::ItemKind::Use(..) => {}, | |
135 | }; | |
136 | } | |
137 | ||
138 | fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { | |
139 | use rustc_middle::ty::{ImplContainer, TraitContainer}; | |
140 | if rustc_middle::lint::in_external_macro(cx.sess(), impl_item.span) || is_executable_or_proc_macro(cx) { | |
141 | return; | |
142 | } | |
143 | ||
144 | // If the item being implemented is not exported, then we don't need #[inline] | |
94222f64 | 145 | if !cx.access_levels.is_exported(impl_item.def_id) { |
f20569fa XL |
146 | return; |
147 | } | |
148 | ||
149 | let desc = match impl_item.kind { | |
150 | hir::ImplItemKind::Fn(..) => "a method", | |
151 | hir::ImplItemKind::Const(..) | hir::ImplItemKind::TyAlias(_) => return, | |
152 | }; | |
153 | ||
154 | let trait_def_id = match cx.tcx.associated_item(impl_item.def_id).container { | |
155 | TraitContainer(cid) => Some(cid), | |
156 | ImplContainer(cid) => cx.tcx.impl_trait_ref(cid).map(|t| t.def_id), | |
157 | }; | |
158 | ||
159 | if let Some(trait_def_id) = trait_def_id { | |
94222f64 | 160 | if trait_def_id.is_local() && !cx.access_levels.is_exported(impl_item.def_id) { |
f20569fa XL |
161 | // If a trait is being implemented for an item, and the |
162 | // trait is not exported, we don't need #[inline] | |
163 | return; | |
164 | } | |
165 | } | |
166 | ||
167 | let attrs = cx.tcx.hir().attrs(impl_item.hir_id()); | |
168 | check_missing_inline_attrs(cx, attrs, impl_item.span, desc); | |
169 | } | |
170 | } |