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
::LimitStack
;
7 use rustc_ast
::ast
::Attribute
;
8 use rustc_hir
::intravisit
::{walk_expr, FnKind, NestedVisitorMap, Visitor}
;
9 use rustc_hir
::{Body, Expr, ExprKind, FnDecl, HirId}
;
10 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
11 use rustc_middle
::hir
::map
::Map
;
12 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
13 use rustc_span
::source_map
::Span
;
14 use rustc_span
::{sym, BytePos}
;
16 declare_clippy_lint
! {
18 /// Checks for methods with high cognitive complexity.
20 /// ### Why is this bad?
21 /// Methods of high cognitive complexity tend to be hard to
22 /// both read and maintain. Also LLVM will tend to optimize small methods better.
24 /// ### Known problems
25 /// Sometimes it's hard to find a way to reduce the
29 /// No. You'll see it when you get the warning.
30 pub COGNITIVE_COMPLEXITY
,
32 "functions that should be split up into multiple functions"
35 pub struct CognitiveComplexity
{
39 impl CognitiveComplexity
{
41 pub fn new(limit
: u64) -> Self {
43 limit
: LimitStack
::new(limit
),
48 impl_lint_pass
!(CognitiveComplexity
=> [COGNITIVE_COMPLEXITY
]);
50 impl CognitiveComplexity
{
51 #[allow(clippy::cast_possible_truncation)]
54 cx
: &LateContext
<'tcx
>,
56 decl
: &'tcx FnDecl
<'_
>,
60 if body_span
.from_expansion() {
64 let expr
= &body
.value
;
66 let mut helper
= CcHelper { cc: 1, returns: 0 }
;
67 helper
.visit_expr(expr
);
68 let CcHelper { cc, returns }
= helper
;
69 let ret_ty
= cx
.typeck_results().node_type(expr
.hir_id
);
70 let ret_adjust
= if is_type_diagnostic_item(cx
, ret_ty
, sym
::Result
) {
73 #[allow(clippy::integer_division)]
78 // prevent degenerate cases where unreachable code contains `return` statements
79 if rust_cc
>= ret_adjust
{
80 rust_cc
-= ret_adjust
;
83 if rust_cc
> self.limit
.limit() {
84 let fn_span
= match kind
{
85 FnKind
::ItemFn(ident
, _
, _
, _
) | FnKind
::Method(ident
, _
, _
) => ident
.span
,
87 let header_span
= body_span
.with_hi(decl
.output
.span().lo());
88 let pos
= snippet_opt(cx
, header_span
).and_then(|snip
| {
89 let low_offset
= snip
.find('
|'
)?
;
90 let high_offset
= 1 + snip
.get(low_offset
+ 1..)?
.find('
|'
)?
;
91 let low
= header_span
.lo() + BytePos(low_offset
as u32);
92 let high
= low
+ BytePos(high_offset
as u32 + 1);
97 if let Some((low
, high
)) = pos
{
98 Span
::new(low
, high
, header_span
.ctxt(), header_span
.parent())
107 COGNITIVE_COMPLEXITY
,
110 "the function has a cognitive complexity of ({}/{})",
115 "you could split it up into multiple smaller functions",
121 impl<'tcx
> LateLintPass
<'tcx
> for CognitiveComplexity
{
124 cx
: &LateContext
<'tcx
>,
126 decl
: &'tcx FnDecl
<'_
>,
127 body
: &'tcx Body
<'_
>,
131 let def_id
= cx
.tcx
.hir().local_def_id(hir_id
);
132 if !cx
.tcx
.has_attr(def_id
.to_def_id(), sym
::test
) {
133 self.check(cx
, kind
, decl
, body
, span
);
137 fn enter_lint_attrs(&mut self, cx
: &LateContext
<'tcx
>, attrs
: &'tcx
[Attribute
]) {
138 self.limit
.push_attrs(cx
.sess(), attrs
, "cognitive_complexity");
140 fn exit_lint_attrs(&mut self, cx
: &LateContext
<'tcx
>, attrs
: &'tcx
[Attribute
]) {
141 self.limit
.pop_attrs(cx
.sess(), attrs
, "cognitive_complexity");
150 impl<'tcx
> Visitor
<'tcx
> for CcHelper
{
151 type Map
= Map
<'tcx
>;
153 fn visit_expr(&mut self, e
: &'tcx Expr
<'_
>) {
156 ExprKind
::If(_
, _
, _
) => {
159 ExprKind
::Match(_
, arms
, _
) => {
163 self.cc
+= arms
.iter().filter(|arm
| arm
.guard
.is_some()).count() as u64;
165 ExprKind
::Ret(_
) => self.returns
+= 1,
169 fn nested_visit_map(&mut self) -> NestedVisitorMap
<Self::Map
> {
170 NestedVisitorMap
::None