| use clippy_utils::diagnostics::{span_lint, span_lint_hir}; | |
| use rustc_hir::def_id::DefId; | |
| use rustc_hir::{self as hir, Attribute, find_attr}; | |
| use rustc_lint::{LateContext, LateLintPass, LintContext}; | |
| use rustc_middle::ty::AssocContainer; | |
| use rustc_session::config::CrateType; | |
| use rustc_session::declare_lint_pass; | |
| use rustc_span::Span; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// It lints if an exported function, method, trait method with default impl, | |
| /// or trait method impl is not `#[inline]`. | |
| /// | |
| /// ### Why restrict this? | |
| /// When a function is not marked `#[inline]`, it is not | |
| /// [a “small” candidate for automatic inlining][small], and LTO is not in use, then it is not | |
| /// possible for the function to be inlined into the code of any crate other than the one in | |
| /// which it is defined. Depending on the role of the function and the relationship of the crates, | |
| /// this could significantly reduce performance. | |
| /// | |
| /// Certain types of crates might intend for most of the methods in their public API to be able | |
| /// to be inlined across crates even when LTO is disabled. | |
| /// This lint allows those crates to require all exported methods to be `#[inline]` by default, and | |
| /// then opt out for specific methods where this might not make sense. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// pub fn foo() {} // missing #[inline] | |
| /// fn ok() {} // ok | |
| /// #[inline] pub fn bar() {} // ok | |
| /// #[inline(always)] pub fn baz() {} // ok | |
| /// | |
| /// pub trait Bar { | |
| /// fn bar(); // ok | |
| /// fn def_bar() {} // missing #[inline] | |
| /// } | |
| /// | |
| /// struct Baz; | |
| /// impl Baz { | |
| /// fn private() {} // ok | |
| /// } | |
| /// | |
| /// impl Bar for Baz { | |
| /// fn bar() {} // ok - Baz is not exported | |
| /// } | |
| /// | |
| /// pub struct PubBaz; | |
| /// impl PubBaz { | |
| /// fn private() {} // ok | |
| /// pub fn not_private() {} // missing #[inline] | |
| /// } | |
| /// | |
| /// impl Bar for PubBaz { | |
| /// fn bar() {} // missing #[inline] | |
| /// fn def_bar() {} // missing #[inline] | |
| /// } | |
| /// ``` | |
| /// | |
| /// [small]: https://github.com/rust-lang/rust/pull/116505 | |
| #[clippy::version = "pre 1.29.0"] | |
| pub MISSING_INLINE_IN_PUBLIC_ITEMS, | |
| restriction, | |
| "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)" | |
| } | |
| declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]); | |
| fn check_missing_inline_attrs( | |
| cx: &LateContext<'_>, | |
| attrs: &[Attribute], | |
| sp: Span, | |
| desc: &'static str, | |
| hir_id: Option<hir::HirId>, | |
| ) { | |
| if !find_attr!(attrs, Inline(..)) { | |
| let msg = format!("missing `#[inline]` for {desc}"); | |
| if let Some(hir_id) = hir_id { | |
| span_lint_hir(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, hir_id, sp, msg); | |
| } else { | |
| span_lint(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, sp, msg); | |
| } | |
| } | |
| } | |
| impl<'tcx> LateLintPass<'tcx> for MissingInline { | |
| fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { | |
| if it.span.in_external_macro(cx.sess().source_map()) { | |
| return; | |
| } | |
| if cx | |
| .tcx | |
| .crate_types() | |
| .iter() | |
| .any(|t: &CrateType| matches!(t, CrateType::ProcMacro)) | |
| { | |
| return; | |
| } | |
| if !cx.effective_visibilities.is_exported(it.owner_id.def_id) { | |
| return; | |
| } | |
| match it.kind { | |
| hir::ItemKind::Fn { .. } => { | |
| if fn_is_externally_exported(cx, it.owner_id.to_def_id()) { | |
| return; | |
| } | |
| let desc = "a function"; | |
| let attrs = cx.tcx.hir_attrs(it.hir_id()); | |
| check_missing_inline_attrs(cx, attrs, it.span, desc, None); | |
| }, | |
| hir::ItemKind::Trait(.., trait_items) => { | |
| // note: we need to check if the trait is exported so we can't use | |
| // `LateLintPass::check_trait_item` here. | |
| for &tit in trait_items { | |
| let tit_ = cx.tcx.hir_trait_item(tit); | |
| match tit_.kind { | |
| hir::TraitItemKind::Const(..) | hir::TraitItemKind::Type(..) => {}, | |
| hir::TraitItemKind::Fn(..) => { | |
| if cx.tcx.defaultness(tit.owner_id).has_value() { | |
| // trait method with default body needs inline in case | |
| // an impl is not provided | |
| let desc = "a default trait method"; | |
| let item = cx.tcx.hir_trait_item(tit); | |
| let attrs = cx.tcx.hir_attrs(item.hir_id()); | |
| check_missing_inline_attrs(cx, attrs, item.span, desc, Some(tit.hir_id())); | |
| } | |
| }, | |
| } | |
| } | |
| }, | |
| hir::ItemKind::Const(..) | |
| | hir::ItemKind::Enum(..) | |
| | hir::ItemKind::Macro(..) | |
| | hir::ItemKind::Mod(..) | |
| | hir::ItemKind::Static(..) | |
| | hir::ItemKind::Struct(..) | |
| | hir::ItemKind::TraitAlias(..) | |
| | hir::ItemKind::GlobalAsm { .. } | |
| | hir::ItemKind::TyAlias(..) | |
| | hir::ItemKind::Union(..) | |
| | hir::ItemKind::ExternCrate(..) | |
| | hir::ItemKind::ForeignMod { .. } | |
| | hir::ItemKind::Impl { .. } | |
| | hir::ItemKind::Use(..) => {}, | |
| } | |
| } | |
| fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { | |
| if impl_item.span.in_external_macro(cx.sess().source_map()) | |
| || cx | |
| .tcx | |
| .crate_types() | |
| .iter() | |
| .any(|t: &CrateType| matches!(t, CrateType::ProcMacro)) | |
| { | |
| return; | |
| } | |
| // If the item being implemented is not exported, then we don't need #[inline] | |
| if !cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) { | |
| return; | |
| } | |
| let desc = match impl_item.kind { | |
| hir::ImplItemKind::Fn(..) => "a method", | |
| hir::ImplItemKind::Const(..) | hir::ImplItemKind::Type(_) => return, | |
| }; | |
| let assoc_item = cx.tcx.associated_item(impl_item.owner_id); | |
| let container_id = assoc_item.container_id(cx.tcx); | |
| let trait_def_id = match assoc_item.container { | |
| AssocContainer::Trait => Some(container_id), | |
| AssocContainer::TraitImpl(_) => Some(cx.tcx.impl_trait_id(container_id)), | |
| AssocContainer::InherentImpl => None, | |
| }; | |
| if let Some(trait_def_id) = trait_def_id | |
| && trait_def_id.is_local() | |
| && !cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) | |
| { | |
| // If a trait is being implemented for an item, and the | |
| // trait is not exported, we don't need #[inline] | |
| return; | |
| } | |
| let attrs = cx.tcx.hir_attrs(impl_item.hir_id()); | |
| check_missing_inline_attrs(cx, attrs, impl_item.span, desc, None); | |
| } | |
| } | |
| /// Checks if this function is externally exported, where #[inline] wouldn't have the desired effect | |
| /// and a rustc warning would be triggered, see #15301 | |
| fn fn_is_externally_exported(cx: &LateContext<'_>, def_id: DefId) -> bool { | |
| let attrs = cx.tcx.codegen_fn_attrs(def_id); | |
| attrs.contains_extern_indicator() | |
| } |