1use clippy_config::Conf;
2use clippy_utils::diagnostics::span_lint_and_help;
3use clippy_utils::source::{IntoSpan, SpanRangeExt};
4use clippy_utils::ty::is_type_diagnostic_item;
5use clippy_utils::visitors::for_each_expr_without_closures;
6use clippy_utils::{LimitStack, get_async_fn_body, is_async_fn};
7use core::ops::ControlFlow;
8use rustc_hir::intravisit::FnKind;
9use rustc_hir::{Attribute, Body, Expr, ExprKind, FnDecl};
10use rustc_lint::{LateContext, LateLintPass, LintContext};
11use rustc_session::impl_lint_pass;
12use rustc_span::def_id::LocalDefId;
13use rustc_span::{Span, sym};
14
15declare_clippy_lint! {
16 /// ### What it does
17 /// Checks for methods with high cognitive complexity.
18 ///
19 /// ### Why is this bad?
20 /// Methods of high cognitive complexity tend to be hard to
21 /// both read and maintain. Also LLVM will tend to optimize small methods better.
22 ///
23 /// ### Known problems
24 /// Sometimes it's hard to find a way to reduce the
25 /// complexity.
26 ///
27 /// ### Example
28 /// You'll see it when you get the warning.
29 #[clippy::version = "1.35.0"]
30 pub COGNITIVE_COMPLEXITY,
31 nursery,
32 "functions that should be split up into multiple functions",
33 @eval_always = true
34}
35
36pub struct CognitiveComplexity {
37 limit: LimitStack,
38}
39
40impl CognitiveComplexity {
41 pub fn new(conf: &'static Conf) -> Self {
42 Self {
43 limit: LimitStack::new(limit:conf.cognitive_complexity_threshold),
44 }
45 }
46}
47
48impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
49
50impl CognitiveComplexity {
51 fn check<'tcx>(
52 &mut self,
53 cx: &LateContext<'tcx>,
54 kind: FnKind<'tcx>,
55 decl: &'tcx FnDecl<'_>,
56 expr: &'tcx Expr<'_>,
57 body_span: Span,
58 ) {
59 if body_span.from_expansion() {
60 return;
61 }
62
63 let mut cc = 1u64;
64 let mut returns = 0u64;
65 let _: Option<!> = for_each_expr_without_closures(expr, |e| {
66 match e.kind {
67 ExprKind::If(_, _, _) => {
68 cc += 1;
69 },
70 ExprKind::Match(_, arms, _) => {
71 if arms.len() > 1 {
72 cc += 1;
73 }
74 cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
75 },
76 ExprKind::Ret(_) => returns += 1,
77 _ => {},
78 }
79 ControlFlow::Continue(())
80 });
81
82 let ret_ty = cx.typeck_results().node_type(expr.hir_id);
83 let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym::Result) {
84 returns
85 } else {
86 #[expect(clippy::integer_division)]
87 (returns / 2)
88 };
89
90 // prevent degenerate cases where unreachable code contains `return` statements
91 if cc >= ret_adjust {
92 cc -= ret_adjust;
93 }
94
95 if cc > self.limit.limit() {
96 let fn_span = match kind {
97 FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
98 FnKind::Closure => {
99 let header_span = body_span.with_hi(decl.output.span().lo());
100 #[expect(clippy::range_plus_one)]
101 if let Some(range) = header_span.map_range(cx, |src, range| {
102 let mut idxs = src.get(range.clone())?.match_indices('|');
103 Some(range.start + idxs.next()?.0..range.start + idxs.next()?.0 + 1)
104 }) {
105 range.with_ctxt(header_span.ctxt())
106 } else {
107 return;
108 }
109 },
110 };
111
112 span_lint_and_help(
113 cx,
114 COGNITIVE_COMPLEXITY,
115 fn_span,
116 format!(
117 "the function has a cognitive complexity of ({cc}/{})",
118 self.limit.limit()
119 ),
120 None,
121 "you could split it up into multiple smaller functions",
122 );
123 }
124 }
125}
126
127impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity {
128 fn check_fn(
129 &mut self,
130 cx: &LateContext<'tcx>,
131 kind: FnKind<'tcx>,
132 decl: &'tcx FnDecl<'_>,
133 body: &'tcx Body<'_>,
134 span: Span,
135 def_id: LocalDefId,
136 ) {
137 if !cx.tcx.has_attr(def_id, sym::test) {
138 let expr = if is_async_fn(kind) {
139 match get_async_fn_body(cx.tcx, body) {
140 Some(b) => b,
141 None => {
142 return;
143 },
144 }
145 } else {
146 body.value
147 };
148
149 self.check(cx, kind, decl, expr, span);
150 }
151 }
152
153 fn check_attributes(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
154 self.limit.push_attrs(cx.sess(), attrs, "cognitive_complexity");
155 }
156 fn check_attributes_post(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
157 self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity");
158 }
159}
160