]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/missing_inline.rs
New upstream version 1.79.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / missing_inline.rs
1 use clippy_utils::diagnostics::span_lint;
2 use rustc_ast::ast;
3 use rustc_hir as hir;
4 use rustc_lint::{LateContext, LateLintPass, LintContext};
5 use rustc_session::declare_lint_pass;
6 use rustc_span::{sym, Span};
7
8 declare_clippy_lint! {
9 /// ### What it does
10 /// It lints if an exported function, method, trait method with default impl,
11 /// or trait method impl is not `#[inline]`.
12 ///
13 /// ### Why is this bad?
14 /// In general, it is not. Functions can be inlined across
15 /// crates when that's profitable as long as any form of LTO is used. When LTO is disabled,
16 /// functions that are not `#[inline]` cannot be inlined across crates. Certain types of crates
17 /// might intend for most of the methods in their public API to be able to be inlined across
18 /// crates even when LTO is disabled. For these types of crates, enabling this lint might make
19 /// sense. It allows the crate to require all exported methods to be `#[inline]` by default, and
20 /// then opt out for specific methods where this might not make sense.
21 ///
22 /// ### Example
23 /// ```no_run
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 private() {} // 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 private() {} // ok
46 /// pub fn not_private() {} // missing #[inline]
47 /// }
48 ///
49 /// impl Bar for PubBaz {
50 /// fn bar() {} // missing #[inline]
51 /// fn def_bar() {} // missing #[inline]
52 /// }
53 /// ```
54 #[clippy::version = "pre 1.29.0"]
55 pub MISSING_INLINE_IN_PUBLIC_ITEMS,
56 restriction,
57 "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)"
58 }
59
60 fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[ast::Attribute], sp: Span, desc: &'static str) {
61 let has_inline = attrs.iter().any(|a| a.has_name(sym::inline));
62 if !has_inline {
63 span_lint(
64 cx,
65 MISSING_INLINE_IN_PUBLIC_ITEMS,
66 sp,
67 format!("missing `#[inline]` for {desc}"),
68 );
69 }
70 }
71
72 fn is_executable_or_proc_macro(cx: &LateContext<'_>) -> bool {
73 use rustc_session::config::CrateType;
74
75 cx.tcx
76 .crate_types()
77 .iter()
78 .any(|t: &CrateType| matches!(t, CrateType::Executable | CrateType::ProcMacro))
79 }
80
81 declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]);
82
83 impl<'tcx> LateLintPass<'tcx> for MissingInline {
84 fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
85 if rustc_middle::lint::in_external_macro(cx.sess(), it.span) || is_executable_or_proc_macro(cx) {
86 return;
87 }
88
89 if !cx.effective_visibilities.is_exported(it.owner_id.def_id) {
90 return;
91 }
92 match it.kind {
93 hir::ItemKind::Fn(..) => {
94 let desc = "a function";
95 let attrs = cx.tcx.hir().attrs(it.hir_id());
96 check_missing_inline_attrs(cx, attrs, it.span, desc);
97 },
98 hir::ItemKind::Trait(ref _is_auto, ref _unsafe, _generics, _bounds, trait_items) => {
99 // note: we need to check if the trait is exported so we can't use
100 // `LateLintPass::check_trait_item` here.
101 for tit in trait_items {
102 let tit_ = cx.tcx.hir().trait_item(tit.id);
103 match tit_.kind {
104 hir::TraitItemKind::Const(..) | hir::TraitItemKind::Type(..) => {},
105 hir::TraitItemKind::Fn(..) => {
106 if cx.tcx.defaultness(tit.id.owner_id).has_value() {
107 // trait method with default body needs inline in case
108 // an impl is not provided
109 let desc = "a default trait method";
110 let item = cx.tcx.hir().trait_item(tit.id);
111 let attrs = cx.tcx.hir().attrs(item.hir_id());
112 check_missing_inline_attrs(cx, attrs, item.span, desc);
113 }
114 },
115 }
116 }
117 },
118 hir::ItemKind::Const(..)
119 | hir::ItemKind::Enum(..)
120 | hir::ItemKind::Macro(..)
121 | hir::ItemKind::Mod(..)
122 | hir::ItemKind::Static(..)
123 | hir::ItemKind::Struct(..)
124 | hir::ItemKind::TraitAlias(..)
125 | hir::ItemKind::GlobalAsm(..)
126 | hir::ItemKind::TyAlias(..)
127 | hir::ItemKind::Union(..)
128 | hir::ItemKind::OpaqueTy(..)
129 | hir::ItemKind::ExternCrate(..)
130 | hir::ItemKind::ForeignMod { .. }
131 | hir::ItemKind::Impl { .. }
132 | hir::ItemKind::Use(..) => {},
133 };
134 }
135
136 fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
137 use rustc_middle::ty::{ImplContainer, TraitContainer};
138 if rustc_middle::lint::in_external_macro(cx.sess(), impl_item.span) || is_executable_or_proc_macro(cx) {
139 return;
140 }
141
142 // If the item being implemented is not exported, then we don't need #[inline]
143 if !cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) {
144 return;
145 }
146
147 let desc = match impl_item.kind {
148 hir::ImplItemKind::Fn(..) => "a method",
149 hir::ImplItemKind::Const(..) | hir::ImplItemKind::Type(_) => return,
150 };
151
152 let assoc_item = cx.tcx.associated_item(impl_item.owner_id);
153 let container_id = assoc_item.container_id(cx.tcx);
154 let trait_def_id = match assoc_item.container {
155 TraitContainer => Some(container_id),
156 ImplContainer => cx.tcx.impl_trait_ref(container_id).map(|t| t.skip_binder().def_id),
157 };
158
159 if let Some(trait_def_id) = trait_def_id {
160 if trait_def_id.is_local() && !cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) {
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 }