use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::{is_from_proc_macro, is_in_test_function}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::{Body, Expr, ExprKind, FnDecl}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::nested_filter::OnlyBodies; use rustc_middle::lint::in_external_macro; use rustc_session::impl_lint_pass; use rustc_span::Span; declare_clippy_lint! { /// ### What it does /// Checks for functions that are only used once. Does not lint tests. /// /// ### Why is this bad? /// It's usually not, splitting a function into multiple parts often improves readability and in /// the case of generics, can prevent the compiler from duplicating the function dozens of /// time; instead, only duplicating a thunk. But this can prevent segmentation across a /// codebase, where many small functions are used only once. /// /// Note: If this lint is used, prepare to allow this a lot. /// /// ### Example /// ```no_run /// pub fn a(t: &T) /// where /// T: AsRef, /// { /// a_inner(t.as_ref()) /// } /// /// fn a_inner(t: &str) { /// /* snip */ /// } /// /// ``` /// Use instead: /// ```no_run /// pub fn a(t: &T) /// where /// T: AsRef, /// { /// let t = t.as_ref(); /// /* snip */ /// } /// /// ``` #[clippy::version = "1.72.0"] pub SINGLE_CALL_FN, restriction, "checks for functions that are only used once" } impl_lint_pass!(SingleCallFn => [SINGLE_CALL_FN]); #[derive(Clone)] pub struct SingleCallFn { pub avoid_breaking_exported_api: bool, pub def_id_to_usage: FxHashMap)>, } impl<'tcx> LateLintPass<'tcx> for SingleCallFn { fn check_fn( &mut self, cx: &LateContext<'tcx>, kind: FnKind<'tcx>, _: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, span: Span, def_id: LocalDefId, ) { if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) || in_external_macro(cx.sess(), span) || is_in_test_function(cx.tcx, body.value.hir_id) || is_from_proc_macro(cx, &(&kind, body, cx.tcx.local_def_id_to_hir_id(def_id), span)) { return; } self.def_id_to_usage.insert(def_id, (span, vec![])); } fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { let mut v = FnUsageVisitor { cx, def_id_to_usage: &mut self.def_id_to_usage, }; cx.tcx.hir().visit_all_item_likes_in_crate(&mut v); for usage in self.def_id_to_usage.values() { let single_call_fn_span = usage.0; if let [caller_span] = *usage.1 { span_lint_and_help( cx, SINGLE_CALL_FN, single_call_fn_span, "this function is only used once", Some(caller_span), "used here", ); } } } } struct FnUsageVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, def_id_to_usage: &'a mut FxHashMap)>, } impl<'a, 'tcx> Visitor<'tcx> for FnUsageVisitor<'a, 'tcx> { type NestedFilter = OnlyBodies; fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let Self { cx, .. } = *self; if let ExprKind::Path(qpath) = expr.kind && let res = cx.qpath_res(&qpath, expr.hir_id) && let Some(call_def_id) = res.opt_def_id() && let Some(def_id) = call_def_id.as_local() && let Some(usage) = self.def_id_to_usage.get_mut(&def_id) { usage.1.push(expr.span); } walk_expr(self, expr); } }