Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge single_call_fn post-crate visitor into lint pass #12256

Merged
merged 1 commit into from
Feb 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1068,7 +1068,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| {
Box::new(single_call_fn::SingleCallFn {
avoid_breaking_exported_api,
def_id_to_usage: rustc_data_structures::fx::FxHashMap::default(),
def_id_to_usage: rustc_data_structures::fx::FxIndexMap::default(),
})
});
store.register_early_pass(move || {
Expand Down
130 changes: 70 additions & 60 deletions clippy_lints/src/single_call_fn.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::{is_from_proc_macro, is_in_test_function};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::fx::{FxIndexMap, IndexEntry};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
use rustc_hir::{Body, Expr, ExprKind, FnDecl};
use rustc_hir::{Expr, ExprKind, HirId, Node};
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;
Expand Down Expand Up @@ -54,82 +53,93 @@ declare_clippy_lint! {
}
impl_lint_pass!(SingleCallFn => [SINGLE_CALL_FN]);

#[derive(Debug, Clone)]
pub enum CallState {
Once { call_site: Span },
Multiple,
}

#[derive(Clone)]
pub struct SingleCallFn {
pub avoid_breaking_exported_api: bool,
pub def_id_to_usage: FxHashMap<LocalDefId, (Span, Vec<Span>)>,
pub def_id_to_usage: FxIndexMap<LocalDefId, CallState>,
}

impl SingleCallFn {
fn is_function_allowed(
&self,
cx: &LateContext<'_>,
fn_def_id: LocalDefId,
fn_hir_id: HirId,
fn_span: Span,
) -> bool {
(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(fn_def_id))
|| in_external_macro(cx.sess(), fn_span)
|| cx
.tcx
.hir()
.maybe_body_owned_by(fn_def_id)
.map(|body| cx.tcx.hir().body(body))
.map_or(true, |body| is_in_test_function(cx.tcx, body.value.hir_id))
|| match cx.tcx.hir_node(fn_hir_id) {
Node::Item(item) => is_from_proc_macro(cx, item),
Node::ImplItem(item) => is_from_proc_macro(cx, item),
Node::TraitItem(item) => is_from_proc_macro(cx, item),
_ => true,
}
}
}

/// Whether a called function is a kind of item that the lint cares about.
/// For example, calling an `extern "C" { fn fun(); }` only once is totally fine and does not
/// to be considered.
fn is_valid_item_kind(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
matches!(
cx.tcx.hir_node(cx.tcx.local_def_id_to_hir_id(def_id)),
Node::Item(_) | Node::ImplItem(_) | Node::TraitItem(_)
)
}

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))
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) {
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 DefKind::Fn | DefKind::AssocFn = cx.tcx.def_kind(def_id)
&& is_valid_item_kind(cx, def_id)
{
return;
match self.def_id_to_usage.entry(def_id) {
IndexEntry::Occupied(mut entry) => {
if let CallState::Once { .. } = entry.get() {
entry.insert(CallState::Multiple);
}
},
IndexEntry::Vacant(entry) => {
entry.insert(CallState::Once { call_site: expr.span });
},
}
}

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 (&def_id, usage) in &self.def_id_to_usage {
let single_call_fn_span = usage.0;
if let [caller_span] = *usage.1 {
if let CallState::Once { call_site } = *usage
&& let fn_hir_id = cx.tcx.local_def_id_to_hir_id(def_id)
&& let fn_span = cx.tcx.hir().span_with_body(fn_hir_id)
&& !self.is_function_allowed(cx, def_id, fn_hir_id, fn_span)
{
span_lint_hir_and_then(
cx,
SINGLE_CALL_FN,
cx.tcx.local_def_id_to_hir_id(def_id),
single_call_fn_span,
fn_hir_id,
fn_span,
"this function is only used once",
|diag| {
diag.span_help(caller_span, "used here");
diag.span_note(call_site, "used here");
},
);
}
}
}
}

struct FnUsageVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
def_id_to_usage: &'a mut FxHashMap<LocalDefId, (Span, Vec<Span>)>,
}

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);
}
}
22 changes: 22 additions & 0 deletions tests/ui/single_call_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,25 @@ mod issue12182 {
fn l() {
k();
}

trait Trait {
fn default() {}
fn foo(&self);
}
extern "C" {
// test some kind of foreign item
fn rand() -> std::ffi::c_int;
}
fn m<T: Trait>(v: T) {
const NOT_A_FUNCTION: i32 = 1;
let _ = NOT_A_FUNCTION;

struct S;
impl S {
fn foo() {}
}
T::default();
S::foo();
v.foo();
unsafe { rand() };
}
72 changes: 48 additions & 24 deletions tests/ui/single_call_fn.stderr
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
error: this function is only used once
--> tests/ui/single_call_fn.rs:13:1
|
LL | fn i() {}
| ^^^^^^^^^
|
note: used here
--> tests/ui/single_call_fn.rs:18:13
|
LL | let a = i;
| ^
= note: `-D clippy::single-call-fn` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::single_call_fn)]`

error: this function is only used once
--> tests/ui/single_call_fn.rs:14:1
|
LL | fn j() {}
| ^^^^^^^^^
|
note: used here
--> tests/ui/single_call_fn.rs:25:9
|
LL | j();
| ^

error: this function is only used once
--> tests/ui/single_call_fn.rs:34:1
|
Expand All @@ -8,49 +34,47 @@ LL | | println!("function...");
LL | | }
| |_^
|
help: used here
note: used here
--> tests/ui/single_call_fn.rs:41:5
|
LL | c();
| ^
= note: `-D clippy::single-call-fn` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::single_call_fn)]`

error: this function is only used once
--> tests/ui/single_call_fn.rs:13:1
|
LL | fn i() {}
| ^^^^^^^^^
|
help: used here
--> tests/ui/single_call_fn.rs:18:13
|
LL | let a = i;
| ^

error: this function is only used once
--> tests/ui/single_call_fn.rs:44:1
|
LL | fn a() {}
| ^^^^^^^^^
|
help: used here
note: used here
--> tests/ui/single_call_fn.rs:47:5
|
LL | a();
| ^

error: this function is only used once
--> tests/ui/single_call_fn.rs:14:1
--> tests/ui/single_call_fn.rs:89:5
|
LL | fn j() {}
| ^^^^^^^^^
LL | fn default() {}
| ^^^^^^^^^^^^^^^
|
help: used here
--> tests/ui/single_call_fn.rs:25:9
note: used here
--> tests/ui/single_call_fn.rs:104:5
|
LL | j();
| ^
LL | T::default();
| ^^^^^^^^^^

error: this function is only used once
--> tests/ui/single_call_fn.rs:102:9
|
LL | fn foo() {}
| ^^^^^^^^^^^
|
note: used here
--> tests/ui/single_call_fn.rs:105:5
|
LL | S::foo();
| ^^^^^^

error: aborting due to 4 previous errors
error: aborting due to 6 previous errors

Loading