Skip to content

Commit 06b444b

Browse files
committed
Auto merge of rust-lang#10951 - Centri3:single_call_fn, r=giraffate
new lint [`single_call_fn`] Closes rust-lang#10861 changelog: New lint [`single_call_fn`]
2 parents 8c8ff5f + 2cd4a91 commit 06b444b

File tree

8 files changed

+274
-1
lines changed

8 files changed

+274
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5158,6 +5158,7 @@ Released 2018-09-13
51585158
[`significant_drop_in_scrutinee`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_in_scrutinee
51595159
[`significant_drop_tightening`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_tightening
51605160
[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names
5161+
[`single_call_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_call_fn
51615162
[`single_char_add_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_add_str
51625163
[`single_char_lifetime_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_lifetime_names
51635164
[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern

book/src/lint_configuration.md

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat
9494
* [`linkedlist`](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist)
9595
* [`rc_mutex`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex)
9696
* [`unnecessary_box_returns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns)
97+
* [`single_call_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#single_call_fn)
9798

9899

99100
## `msrv`

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
570570
crate::shadow::SHADOW_SAME_INFO,
571571
crate::shadow::SHADOW_UNRELATED_INFO,
572572
crate::significant_drop_tightening::SIGNIFICANT_DROP_TIGHTENING_INFO,
573+
crate::single_call_fn::SINGLE_CALL_FN_INFO,
573574
crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO,
574575
crate::single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS_INFO,
575576
crate::single_range_in_vec_init::SINGLE_RANGE_IN_VEC_INIT_INFO,

clippy_lints/src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ mod semicolon_if_nothing_returned;
287287
mod serde_api;
288288
mod shadow;
289289
mod significant_drop_tightening;
290+
mod single_call_fn;
290291
mod single_char_lifetime_names;
291292
mod single_component_path_imports;
292293
mod single_range_in_vec_init;
@@ -1055,6 +1056,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10551056
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold)));
10561057
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
10571058
store.register_late_pass(|_| Box::new(incorrect_impls::IncorrectImpls));
1059+
store.register_late_pass(move |_| {
1060+
Box::new(single_call_fn::SingleCallFn {
1061+
avoid_breaking_exported_api,
1062+
def_id_to_usage: rustc_data_structures::fx::FxHashMap::default(),
1063+
})
1064+
});
10581065
// add lints here, do not remove this comment, it's used in `new_lint`
10591066
}
10601067

clippy_lints/src/single_call_fn.rs

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use clippy_utils::diagnostics::span_lint_and_help;
2+
use clippy_utils::{is_from_proc_macro, is_in_test_function};
3+
use rustc_data_structures::fx::FxHashMap;
4+
use rustc_hir::def_id::LocalDefId;
5+
use rustc_hir::intravisit::{walk_expr, Visitor};
6+
use rustc_hir::{intravisit::FnKind, Body, Expr, ExprKind, FnDecl};
7+
use rustc_lint::{LateContext, LateLintPass, LintContext};
8+
use rustc_middle::hir::nested_filter::OnlyBodies;
9+
use rustc_middle::lint::in_external_macro;
10+
use rustc_session::{declare_tool_lint, impl_lint_pass};
11+
use rustc_span::Span;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
/// Checks for functions that are only used once. Does not lint tests.
16+
///
17+
/// ### Why is this bad?
18+
/// It's usually not, splitting a function into multiple parts often improves readability and in
19+
/// the case of generics, can prevent the compiler from duplicating the function dozens of
20+
/// time; instead, only duplicating a thunk. But this can prevent segmentation across a
21+
/// codebase, where many small functions are used only once.
22+
///
23+
/// Note: If this lint is used, prepare to allow this a lot.
24+
///
25+
/// ### Example
26+
/// ```rust
27+
/// pub fn a<T>(t: &T)
28+
/// where
29+
/// T: AsRef<str>,
30+
/// {
31+
/// a_inner(t.as_ref())
32+
/// }
33+
///
34+
/// fn a_inner(t: &str) {
35+
/// /* snip */
36+
/// }
37+
///
38+
/// ```
39+
/// Use instead:
40+
/// ```rust
41+
/// pub fn a<T>(t: &T)
42+
/// where
43+
/// T: AsRef<str>,
44+
/// {
45+
/// let t = t.as_ref();
46+
/// /* snip */
47+
/// }
48+
///
49+
/// ```
50+
#[clippy::version = "1.72.0"]
51+
pub SINGLE_CALL_FN,
52+
restriction,
53+
"checks for functions that are only used once"
54+
}
55+
impl_lint_pass!(SingleCallFn => [SINGLE_CALL_FN]);
56+
57+
#[derive(Clone)]
58+
pub struct SingleCallFn {
59+
pub avoid_breaking_exported_api: bool,
60+
pub def_id_to_usage: FxHashMap<LocalDefId, (Span, Vec<Span>)>,
61+
}
62+
63+
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_from_proc_macro(cx, &(&kind, body, cx.tcx.local_def_id_to_hir_id(def_id), span))
76+
|| is_in_test_function(cx.tcx, body.value.hir_id)
77+
{
78+
return;
79+
}
80+
81+
self.def_id_to_usage.insert(def_id, (span, vec![]));
82+
}
83+
84+
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+
91+
for usage in self.def_id_to_usage.values() {
92+
let single_call_fn_span = usage.0;
93+
if let [caller_span] = *usage.1 {
94+
span_lint_and_help(
95+
cx,
96+
SINGLE_CALL_FN,
97+
single_call_fn_span,
98+
"this function is only used once",
99+
Some(caller_span),
100+
"used here",
101+
);
102+
}
103+
}
104+
}
105+
}
106+
107+
struct FnUsageVisitor<'a, 'tcx> {
108+
cx: &'a LateContext<'tcx>,
109+
def_id_to_usage: &'a mut FxHashMap<LocalDefId, (Span, Vec<Span>)>,
110+
}
111+
112+
impl<'a, 'tcx> Visitor<'tcx> for FnUsageVisitor<'a, 'tcx> {
113+
type NestedFilter = OnlyBodies;
114+
115+
fn nested_visit_map(&mut self) -> Self::Map {
116+
self.cx.tcx.hir()
117+
}
118+
119+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
120+
let Self { cx, .. } = *self;
121+
122+
if let ExprKind::Path(qpath) = expr.kind
123+
&& let res = cx.qpath_res(&qpath, expr.hir_id)
124+
&& let Some(call_def_id) = res.opt_def_id()
125+
&& let Some(def_id) = call_def_id.as_local()
126+
&& let Some(usage) = self.def_id_to_usage.get_mut(&def_id)
127+
{
128+
usage.1.push(expr.span);
129+
}
130+
131+
walk_expr(self, expr);
132+
}
133+
}

clippy_lints/src/utils/conf.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ define_Conf! {
289289
/// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
290290
/// ```
291291
(arithmetic_side_effects_allowed_unary: rustc_data_structures::fx::FxHashSet<String> = <_>::default()),
292-
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS.
292+
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS, SINGLE_CALL_FN.
293293
///
294294
/// Suppress lints whenever the suggested change would cause breakage for other crates.
295295
(avoid_breaking_exported_api: bool = true),

tests/ui/single_call_fn.rs

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//@aux-build:proc_macros.rs
2+
//@compile-flags: --test
3+
#![allow(clippy::redundant_closure_call, unused)]
4+
#![warn(clippy::single_call_fn)]
5+
#![no_main]
6+
7+
#[macro_use]
8+
extern crate proc_macros;
9+
10+
// Do not lint since it's public
11+
pub fn f() {}
12+
13+
fn i() {}
14+
fn j() {}
15+
16+
fn h() {
17+
// Linted
18+
let a = i;
19+
// Do not lint closures
20+
let a = (|| {
21+
// Not linted
22+
a();
23+
// Imo, it's reasonable to lint this as the function is still only being used once. Just in
24+
// a closure.
25+
j();
26+
});
27+
a();
28+
}
29+
30+
fn g() {
31+
f();
32+
}
33+
34+
fn c() {
35+
println!("really");
36+
println!("long");
37+
println!("function...");
38+
}
39+
40+
fn d() {
41+
c();
42+
}
43+
44+
fn a() {}
45+
46+
fn b() {
47+
a();
48+
49+
external! {
50+
fn lol() {
51+
lol_inner();
52+
}
53+
fn lol_inner() {}
54+
}
55+
with_span! {
56+
span
57+
fn lol2() {
58+
lol2_inner();
59+
}
60+
fn lol2_inner() {}
61+
}
62+
}
63+
64+
fn e() {
65+
b();
66+
b();
67+
}
68+
69+
#[test]
70+
fn k() {}
71+
72+
#[test]
73+
fn l() {
74+
k();
75+
}

tests/ui/single_call_fn.stderr

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
error: this function is only used once
2+
--> $DIR/single_call_fn.rs:34:1
3+
|
4+
LL | / fn c() {
5+
LL | | println!("really");
6+
LL | | println!("long");
7+
LL | | println!("function...");
8+
LL | | }
9+
| |_^
10+
|
11+
help: used here
12+
--> $DIR/single_call_fn.rs:41:5
13+
|
14+
LL | c();
15+
| ^
16+
= note: `-D clippy::single-call-fn` implied by `-D warnings`
17+
18+
error: this function is only used once
19+
--> $DIR/single_call_fn.rs:13:1
20+
|
21+
LL | fn i() {}
22+
| ^^^^^^^^^
23+
|
24+
help: used here
25+
--> $DIR/single_call_fn.rs:18:13
26+
|
27+
LL | let a = i;
28+
| ^
29+
30+
error: this function is only used once
31+
--> $DIR/single_call_fn.rs:44:1
32+
|
33+
LL | fn a() {}
34+
| ^^^^^^^^^
35+
|
36+
help: used here
37+
--> $DIR/single_call_fn.rs:47:5
38+
|
39+
LL | a();
40+
| ^
41+
42+
error: this function is only used once
43+
--> $DIR/single_call_fn.rs:14:1
44+
|
45+
LL | fn j() {}
46+
| ^^^^^^^^^
47+
|
48+
help: used here
49+
--> $DIR/single_call_fn.rs:25:9
50+
|
51+
LL | j();
52+
| ^
53+
54+
error: aborting due to 4 previous errors
55+

0 commit comments

Comments
 (0)