1 //! calculate cognitive complexity and warn about overly complex functions
3 use clippy_utils
::diagnostics
::span_lint_and_help
;
4 use clippy_utils
::source
::snippet_opt
;
5 use clippy_utils
::ty
::is_type_diagnostic_item
;
6 use clippy_utils
::visitors
::for_each_expr
;
7 use clippy_utils
::LimitStack
;
8 use core
::ops
::ControlFlow
;
9 use rustc_ast
::ast
::Attribute
;
10 use rustc_hir
::intravisit
::FnKind
;
11 use rustc_hir
::{Body, ExprKind, FnDecl, HirId}
;
12 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
13 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
14 use rustc_span
::source_map
::Span
;
15 use rustc_span
::{sym, BytePos}
;
17 declare_clippy_lint
! {
19 /// Checks for methods with high cognitive complexity.
21 /// ### Why is this bad?
22 /// Methods of high cognitive complexity tend to be hard to
23 /// both read and maintain. Also LLVM will tend to optimize small methods better.
25 /// ### Known problems
26 /// Sometimes it's hard to find a way to reduce the
30 /// You'll see it when you get the warning.
31 #[clippy::version = "1.35.0"]
32 pub COGNITIVE_COMPLEXITY
,
34 "functions that should be split up into multiple functions"
37 pub struct CognitiveComplexity
{
41 impl CognitiveComplexity
{
43 pub fn new(limit
: u64) -> Self {
45 limit
: LimitStack
::new(limit
),
50 impl_lint_pass
!(CognitiveComplexity
=> [COGNITIVE_COMPLEXITY
]);
52 impl CognitiveComplexity
{
53 #[expect(clippy::cast_possible_truncation)]
56 cx
: &LateContext
<'tcx
>,
58 decl
: &'tcx FnDecl
<'_
>,
62 if body_span
.from_expansion() {
66 let expr
= body
.value
;
69 let mut returns
= 0u64;
70 let _
: Option
<!> = for_each_expr(expr
, |e
| {
72 ExprKind
::If(_
, _
, _
) => {
75 ExprKind
::Match(_
, arms
, _
) => {
79 cc
+= arms
.iter().filter(|arm
| arm
.guard
.is_some()).count() as u64;
81 ExprKind
::Ret(_
) => returns
+= 1,
84 ControlFlow
::Continue(())
87 let ret_ty
= cx
.typeck_results().node_type(expr
.hir_id
);
88 let ret_adjust
= if is_type_diagnostic_item(cx
, ret_ty
, sym
::Result
) {
91 #[expect(clippy::integer_division)]
95 // prevent degenerate cases where unreachable code contains `return` statements
100 if cc
> self.limit
.limit() {
101 let fn_span
= match kind
{
102 FnKind
::ItemFn(ident
, _
, _
) | FnKind
::Method(ident
, _
) => ident
.span
,
104 let header_span
= body_span
.with_hi(decl
.output
.span().lo());
105 let pos
= snippet_opt(cx
, header_span
).and_then(|snip
| {
106 let low_offset
= snip
.find('
|'
)?
;
107 let high_offset
= 1 + snip
.get(low_offset
+ 1..)?
.find('
|'
)?
;
108 let low
= header_span
.lo() + BytePos(low_offset
as u32);
109 let high
= low
+ BytePos(high_offset
as u32 + 1);
114 if let Some((low
, high
)) = pos
{
115 Span
::new(low
, high
, header_span
.ctxt(), header_span
.parent())
124 COGNITIVE_COMPLEXITY
,
127 "the function has a cognitive complexity of ({cc}/{})",
131 "you could split it up into multiple smaller functions",
137 impl<'tcx
> LateLintPass
<'tcx
> for CognitiveComplexity
{
140 cx
: &LateContext
<'tcx
>,
142 decl
: &'tcx FnDecl
<'_
>,
143 body
: &'tcx Body
<'_
>,
147 let def_id
= cx
.tcx
.hir().local_def_id(hir_id
);
148 if !cx
.tcx
.has_attr(def_id
.to_def_id(), sym
::test
) {
149 self.check(cx
, kind
, decl
, body
, span
);
153 fn enter_lint_attrs(&mut self, cx
: &LateContext
<'tcx
>, attrs
: &'tcx
[Attribute
]) {
154 self.limit
.push_attrs(cx
.sess(), attrs
, "cognitive_complexity");
156 fn exit_lint_attrs(&mut self, cx
: &LateContext
<'tcx
>, attrs
: &'tcx
[Attribute
]) {
157 self.limit
.pop_attrs(cx
.sess(), attrs
, "cognitive_complexity");