Skip to content

Commit 577869e

Browse files
committed
Add direct-recursion Lint
1 parent f2922e7 commit 577869e

File tree

6 files changed

+125
-0
lines changed

6 files changed

+125
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5721,6 +5721,7 @@ Released 2018-09-13
57215721
[`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord
57225722
[`derive_partial_eq_without_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq
57235723
[`derived_hash_with_manual_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derived_hash_with_manual_eq
5724+
[`direct_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#direct_recursion
57245725
[`disallowed_macros`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros
57255726
[`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method
57265727
[`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
107107
crate::derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ_INFO,
108108
crate::derive::EXPL_IMPL_CLONE_ON_COPY_INFO,
109109
crate::derive::UNSAFE_DERIVE_DESERIALIZE_INFO,
110+
crate::direct_recursion::DIRECT_RECURSION_INFO,
110111
crate::disallowed_macros::DISALLOWED_MACROS_INFO,
111112
crate::disallowed_methods::DISALLOWED_METHODS_INFO,
112113
crate::disallowed_names::DISALLOWED_NAMES_INFO,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use clippy_utils::diagnostics::span_lint;
2+
use rustc_hir::def::{DefKind, Res};
3+
use rustc_hir::{Expr, ExprKind, QPath};
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_session::declare_lint_pass;
6+
7+
declare_clippy_lint! {
8+
/// ### What it does
9+
///
10+
/// ### Why restrict this?
11+
///
12+
/// ### Example
13+
/// ```no_run
14+
/// // example code where clippy issues a warning
15+
/// ```
16+
/// Use instead:
17+
/// ```no_run
18+
/// // example code which does not raise clippy warning
19+
/// ```
20+
#[clippy::version = "1.89.0"]
21+
pub DIRECT_RECURSION,
22+
restriction,
23+
"default lint description"
24+
}
25+
declare_lint_pass!(DirectRecursion => [DIRECT_RECURSION]);
26+
27+
impl<'tcx> LateLintPass<'tcx> for DirectRecursion {
28+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
29+
if let ExprKind::Call(call_expr, _) = &expr.kind
30+
&& let body_def_id = cx.tcx.hir_enclosing_body_owner(call_expr.hir_id)
31+
&& let ExprKind::Path(c_expr_path) = call_expr.kind
32+
&& let QPath::Resolved(_lhs, path) = c_expr_path
33+
&& let Res::Def(DefKind::Fn, fn_path_id) = path.res
34+
&& fn_path_id == body_def_id.into()
35+
{
36+
span_lint(
37+
cx,
38+
DIRECT_RECURSION,
39+
expr.span,
40+
"this function contains a call to itself",
41+
);
42+
}
43+
}
44+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ mod default_union_representation;
114114
mod dereference;
115115
mod derivable_impls;
116116
mod derive;
117+
mod direct_recursion;
117118
mod disallowed_macros;
118119
mod disallowed_methods;
119120
mod disallowed_names;
@@ -951,5 +952,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
951952
store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
952953
store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom));
953954
store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny));
955+
store.register_late_pass(|_| Box::new(direct_recursion::DirectRecursion));
954956
// add lints here, do not remove this comment, it's used in `new_lint`
955957
}

tests/ui/direct_recursion.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#![deny(clippy::direct_recursion)]
2+
3+
// Basic Cases //
4+
5+
#[allow(unconditional_recursion)]
6+
fn i_call_myself_always() {
7+
i_call_myself_always();
8+
//~^ direct_recursion
9+
}
10+
11+
fn i_call_myself_conditionally(do_i: bool) {
12+
if do_i {
13+
i_call_myself_conditionally(false);
14+
//~^ direct_recursion
15+
}
16+
}
17+
18+
// Basic Counterexamples //
19+
20+
fn i_get_called_by_others() {}
21+
22+
fn i_call_something_else() {
23+
i_get_called_by_others();
24+
}
25+
26+
// Elaborate Cases //
27+
28+
// Here we check that we're allowed to bless specific recursive calls.
29+
// A fine-grained control of where to allow recursion is desirable.
30+
// This is a test of such a feature.
31+
fn i_call_myself_in_a_bounded_way(bound: u8) {
32+
if bound > 0 {
33+
// "Author has audited this function and determined that its recursive call is fine."
34+
#[allow(clippy::direct_recursion)]
35+
i_call_myself_in_a_bounded_way(bound - 1);
36+
}
37+
}
38+
39+
// Here we check that blessing a specific recursive call doesn't
40+
// let other recursive calls go through.
41+
fn i_have_one_blessing_but_two_calls(bound: u8) {
42+
if bound > 25 {
43+
// "Author has audited this function and determined that its recursive call is fine."
44+
#[allow(clippy::direct_recursion)]
45+
i_have_one_blessing_but_two_calls(bound - 1);
46+
} else if bound > 0 {
47+
// "WIP: we still need to audit this part of the function"
48+
i_have_one_blessing_but_two_calls(bound - 2)
49+
//~^ direct_recursion
50+
}
51+
}

tests/ui/direct_recursion.stderr

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: this function contains a call to itself
2+
--> tests/ui/direct_recursion.rs:7:5
3+
|
4+
LL | i_call_myself_always();
5+
| ^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> tests/ui/direct_recursion.rs:1:9
9+
|
10+
LL | #![deny(clippy::direct_recursion)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: this function contains a call to itself
14+
--> tests/ui/direct_recursion.rs:13:9
15+
|
16+
LL | i_call_myself_conditionally(false);
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18+
19+
error: this function contains a call to itself
20+
--> tests/ui/direct_recursion.rs:48:9
21+
|
22+
LL | i_have_one_blessing_but_two_calls(bound - 2)
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
24+
25+
error: aborting due to 3 previous errors
26+

0 commit comments

Comments
 (0)