Skip to content

Commit 7353bd0

Browse files
committed
Auto merge of #12256 - y21:single_call_fn_rm_visitor, r=llogiq
Merge `single_call_fn` post-crate visitor into lint pass The `single_call_fn` lint worked by first collecting a list of function definitions in the lint pass, then populating the list of uses for each function in a second visitor after the crate is checked. Doing another pass through the crate shouldn't be needed, and we should be able to do it in the same lint pass, by looking for path references to functions only and then processing them post-crate. Other changes: - `FxHashMap` -> `FxIndexMap` so that we emit warnings in a consistent order, as we see them (making the diff a bit confusing to look at, because warnings were moved around) - no longer storing a `Vec<Span>` per function: an enum representing "seen once" or "seen more than once" should be enough (only the first element is used later) - "used here" help is now a note I also noticed that it lints on trait methods with a default implementation, but not on regular trait methods without a body (because that's what `check_fn` does). I'm not sure if that's useful though, maybe we shouldn't lint trait methods at all? It's not like you can avoid it sometimes (but then again it's a restriction lint). Either way, I left the behavior where it was before so that there are no functional changes made in this PR and it's purely a refactor. I can change it though changelog: none
2 parents 10c9903 + 9a56153 commit 7353bd0

File tree

4 files changed

+141
-85
lines changed

4 files changed

+141
-85
lines changed

clippy_lints/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
10681068
store.register_late_pass(move |_| {
10691069
Box::new(single_call_fn::SingleCallFn {
10701070
avoid_breaking_exported_api,
1071-
def_id_to_usage: rustc_data_structures::fx::FxHashMap::default(),
1071+
def_id_to_usage: rustc_data_structures::fx::FxIndexMap::default(),
10721072
})
10731073
});
10741074
store.register_early_pass(move || {

clippy_lints/src/single_call_fn.rs

+70-60
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use clippy_utils::diagnostics::span_lint_hir_and_then;
22
use clippy_utils::{is_from_proc_macro, is_in_test_function};
3-
use rustc_data_structures::fx::FxHashMap;
3+
use rustc_data_structures::fx::{FxIndexMap, IndexEntry};
4+
use rustc_hir::def::DefKind;
45
use rustc_hir::def_id::LocalDefId;
5-
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
6-
use rustc_hir::{Body, Expr, ExprKind, FnDecl};
6+
use rustc_hir::{Expr, ExprKind, HirId, Node};
77
use rustc_lint::{LateContext, LateLintPass, LintContext};
8-
use rustc_middle::hir::nested_filter::OnlyBodies;
98
use rustc_middle::lint::in_external_macro;
109
use rustc_session::impl_lint_pass;
1110
use rustc_span::Span;
@@ -54,82 +53,93 @@ declare_clippy_lint! {
5453
}
5554
impl_lint_pass!(SingleCallFn => [SINGLE_CALL_FN]);
5655

56+
#[derive(Debug, Clone)]
57+
pub enum CallState {
58+
Once { call_site: Span },
59+
Multiple,
60+
}
61+
5762
#[derive(Clone)]
5863
pub struct SingleCallFn {
5964
pub avoid_breaking_exported_api: bool,
60-
pub def_id_to_usage: FxHashMap<LocalDefId, (Span, Vec<Span>)>,
65+
pub def_id_to_usage: FxIndexMap<LocalDefId, CallState>,
66+
}
67+
68+
impl SingleCallFn {
69+
fn is_function_allowed(
70+
&self,
71+
cx: &LateContext<'_>,
72+
fn_def_id: LocalDefId,
73+
fn_hir_id: HirId,
74+
fn_span: Span,
75+
) -> bool {
76+
(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(fn_def_id))
77+
|| in_external_macro(cx.sess(), fn_span)
78+
|| cx
79+
.tcx
80+
.hir()
81+
.maybe_body_owned_by(fn_def_id)
82+
.map(|body| cx.tcx.hir().body(body))
83+
.map_or(true, |body| is_in_test_function(cx.tcx, body.value.hir_id))
84+
|| match cx.tcx.hir_node(fn_hir_id) {
85+
Node::Item(item) => is_from_proc_macro(cx, item),
86+
Node::ImplItem(item) => is_from_proc_macro(cx, item),
87+
Node::TraitItem(item) => is_from_proc_macro(cx, item),
88+
_ => true,
89+
}
90+
}
91+
}
92+
93+
/// Whether a called function is a kind of item that the lint cares about.
94+
/// For example, calling an `extern "C" { fn fun(); }` only once is totally fine and does not
95+
/// to be considered.
96+
fn is_valid_item_kind(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
97+
matches!(
98+
cx.tcx.hir_node(cx.tcx.local_def_id_to_hir_id(def_id)),
99+
Node::Item(_) | Node::ImplItem(_) | Node::TraitItem(_)
100+
)
61101
}
62102

63103
impl<'tcx> LateLintPass<'tcx> for SingleCallFn {
64-
fn check_fn(
65-
&mut self,
66-
cx: &LateContext<'tcx>,
67-
kind: FnKind<'tcx>,
68-
_: &'tcx FnDecl<'_>,
69-
body: &'tcx Body<'_>,
70-
span: Span,
71-
def_id: LocalDefId,
72-
) {
73-
if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id)
74-
|| in_external_macro(cx.sess(), span)
75-
|| is_in_test_function(cx.tcx, body.value.hir_id)
76-
|| is_from_proc_macro(cx, &(&kind, body, cx.tcx.local_def_id_to_hir_id(def_id), span))
104+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) {
105+
if let ExprKind::Path(qpath) = expr.kind
106+
&& let res = cx.qpath_res(&qpath, expr.hir_id)
107+
&& let Some(call_def_id) = res.opt_def_id()
108+
&& let Some(def_id) = call_def_id.as_local()
109+
&& let DefKind::Fn | DefKind::AssocFn = cx.tcx.def_kind(def_id)
110+
&& is_valid_item_kind(cx, def_id)
77111
{
78-
return;
112+
match self.def_id_to_usage.entry(def_id) {
113+
IndexEntry::Occupied(mut entry) => {
114+
if let CallState::Once { .. } = entry.get() {
115+
entry.insert(CallState::Multiple);
116+
}
117+
},
118+
IndexEntry::Vacant(entry) => {
119+
entry.insert(CallState::Once { call_site: expr.span });
120+
},
121+
}
79122
}
80-
81-
self.def_id_to_usage.insert(def_id, (span, vec![]));
82123
}
83124

84125
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
85-
let mut v = FnUsageVisitor {
86-
cx,
87-
def_id_to_usage: &mut self.def_id_to_usage,
88-
};
89-
cx.tcx.hir().visit_all_item_likes_in_crate(&mut v);
90-
91126
for (&def_id, usage) in &self.def_id_to_usage {
92-
let single_call_fn_span = usage.0;
93-
if let [caller_span] = *usage.1 {
127+
if let CallState::Once { call_site } = *usage
128+
&& let fn_hir_id = cx.tcx.local_def_id_to_hir_id(def_id)
129+
&& let fn_span = cx.tcx.hir().span_with_body(fn_hir_id)
130+
&& !self.is_function_allowed(cx, def_id, fn_hir_id, fn_span)
131+
{
94132
span_lint_hir_and_then(
95133
cx,
96134
SINGLE_CALL_FN,
97-
cx.tcx.local_def_id_to_hir_id(def_id),
98-
single_call_fn_span,
135+
fn_hir_id,
136+
fn_span,
99137
"this function is only used once",
100138
|diag| {
101-
diag.span_help(caller_span, "used here");
139+
diag.span_note(call_site, "used here");
102140
},
103141
);
104142
}
105143
}
106144
}
107145
}
108-
109-
struct FnUsageVisitor<'a, 'tcx> {
110-
cx: &'a LateContext<'tcx>,
111-
def_id_to_usage: &'a mut FxHashMap<LocalDefId, (Span, Vec<Span>)>,
112-
}
113-
114-
impl<'a, 'tcx> Visitor<'tcx> for FnUsageVisitor<'a, 'tcx> {
115-
type NestedFilter = OnlyBodies;
116-
117-
fn nested_visit_map(&mut self) -> Self::Map {
118-
self.cx.tcx.hir()
119-
}
120-
121-
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
122-
let Self { cx, .. } = *self;
123-
124-
if let ExprKind::Path(qpath) = expr.kind
125-
&& let res = cx.qpath_res(&qpath, expr.hir_id)
126-
&& let Some(call_def_id) = res.opt_def_id()
127-
&& let Some(def_id) = call_def_id.as_local()
128-
&& let Some(usage) = self.def_id_to_usage.get_mut(&def_id)
129-
{
130-
usage.1.push(expr.span);
131-
}
132-
133-
walk_expr(self, expr);
134-
}
135-
}

tests/ui/single_call_fn.rs

+22
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,25 @@ mod issue12182 {
8484
fn l() {
8585
k();
8686
}
87+
88+
trait Trait {
89+
fn default() {}
90+
fn foo(&self);
91+
}
92+
extern "C" {
93+
// test some kind of foreign item
94+
fn rand() -> std::ffi::c_int;
95+
}
96+
fn m<T: Trait>(v: T) {
97+
const NOT_A_FUNCTION: i32 = 1;
98+
let _ = NOT_A_FUNCTION;
99+
100+
struct S;
101+
impl S {
102+
fn foo() {}
103+
}
104+
T::default();
105+
S::foo();
106+
v.foo();
107+
unsafe { rand() };
108+
}

tests/ui/single_call_fn.stderr

+48-24
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
error: this function is only used once
2+
--> tests/ui/single_call_fn.rs:13:1
3+
|
4+
LL | fn i() {}
5+
| ^^^^^^^^^
6+
|
7+
note: used here
8+
--> tests/ui/single_call_fn.rs:18:13
9+
|
10+
LL | let a = i;
11+
| ^
12+
= note: `-D clippy::single-call-fn` implied by `-D warnings`
13+
= help: to override `-D warnings` add `#[allow(clippy::single_call_fn)]`
14+
15+
error: this function is only used once
16+
--> tests/ui/single_call_fn.rs:14:1
17+
|
18+
LL | fn j() {}
19+
| ^^^^^^^^^
20+
|
21+
note: used here
22+
--> tests/ui/single_call_fn.rs:25:9
23+
|
24+
LL | j();
25+
| ^
26+
127
error: this function is only used once
228
--> tests/ui/single_call_fn.rs:34:1
329
|
@@ -8,49 +34,47 @@ LL | | println!("function...");
834
LL | | }
935
| |_^
1036
|
11-
help: used here
37+
note: used here
1238
--> tests/ui/single_call_fn.rs:41:5
1339
|
1440
LL | c();
1541
| ^
16-
= note: `-D clippy::single-call-fn` implied by `-D warnings`
17-
= help: to override `-D warnings` add `#[allow(clippy::single_call_fn)]`
18-
19-
error: this function is only used once
20-
--> tests/ui/single_call_fn.rs:13:1
21-
|
22-
LL | fn i() {}
23-
| ^^^^^^^^^
24-
|
25-
help: used here
26-
--> tests/ui/single_call_fn.rs:18:13
27-
|
28-
LL | let a = i;
29-
| ^
3042

3143
error: this function is only used once
3244
--> tests/ui/single_call_fn.rs:44:1
3345
|
3446
LL | fn a() {}
3547
| ^^^^^^^^^
3648
|
37-
help: used here
49+
note: used here
3850
--> tests/ui/single_call_fn.rs:47:5
3951
|
4052
LL | a();
4153
| ^
4254

4355
error: this function is only used once
44-
--> tests/ui/single_call_fn.rs:14:1
56+
--> tests/ui/single_call_fn.rs:89:5
4557
|
46-
LL | fn j() {}
47-
| ^^^^^^^^^
58+
LL | fn default() {}
59+
| ^^^^^^^^^^^^^^^
4860
|
49-
help: used here
50-
--> tests/ui/single_call_fn.rs:25:9
61+
note: used here
62+
--> tests/ui/single_call_fn.rs:104:5
5163
|
52-
LL | j();
53-
| ^
64+
LL | T::default();
65+
| ^^^^^^^^^^
66+
67+
error: this function is only used once
68+
--> tests/ui/single_call_fn.rs:102:9
69+
|
70+
LL | fn foo() {}
71+
| ^^^^^^^^^^^
72+
|
73+
note: used here
74+
--> tests/ui/single_call_fn.rs:105:5
75+
|
76+
LL | S::foo();
77+
| ^^^^^^
5478

55-
error: aborting due to 4 previous errors
79+
error: aborting due to 6 previous errors
5680

0 commit comments

Comments
 (0)