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
! {
17 /// **What it does:** Checks for methods with high cognitive complexity.
19 /// **Why is this bad?** Methods of high cognitive complexity tend to be hard to
20 /// both read and maintain. Also LLVM will tend to optimize small methods better.
22 /// **Known problems:** Sometimes it's hard to find a way to reduce the
25 /// **Example:** No. You'll see it when you get the warning.
26 pub COGNITIVE_COMPLEXITY
,
28 "functions that should be split up into multiple functions"
31 pub struct CognitiveComplexity
{
35 impl CognitiveComplexity
{
37 pub fn new(limit
: u64) -> Self {
39 limit
: LimitStack
::new(limit
),
44 impl_lint_pass
!(CognitiveComplexity
=> [COGNITIVE_COMPLEXITY
]);
46 impl CognitiveComplexity
{
47 #[allow(clippy::cast_possible_truncation)]
50 cx
: &LateContext
<'tcx
>,
52 decl
: &'tcx FnDecl
<'_
>,
56 if body_span
.from_expansion() {
60 let expr
= &body
.value
;
62 let mut helper
= CcHelper { cc: 1, returns: 0 }
;
63 helper
.visit_expr(expr
);
64 let CcHelper { cc, returns }
= helper
;
65 let ret_ty
= cx
.typeck_results().node_type(expr
.hir_id
);
66 let ret_adjust
= if is_type_diagnostic_item(cx
, ret_ty
, sym
::result_type
) {
69 #[allow(clippy::integer_division)]
74 // prevent degenerate cases where unreachable code contains `return` statements
75 if rust_cc
>= ret_adjust
{
76 rust_cc
-= ret_adjust
;
79 if rust_cc
> self.limit
.limit() {
80 let fn_span
= match kind
{
81 FnKind
::ItemFn(ident
, _
, _
, _
) | FnKind
::Method(ident
, _
, _
) => ident
.span
,
83 let header_span
= body_span
.with_hi(decl
.output
.span().lo());
84 let pos
= snippet_opt(cx
, header_span
).and_then(|snip
| {
85 let low_offset
= snip
.find('
|'
)?
;
86 let high_offset
= 1 + snip
.get(low_offset
+ 1..)?
.find('
|'
)?
;
87 let low
= header_span
.lo() + BytePos(low_offset
as u32);
88 let high
= low
+ BytePos(high_offset
as u32 + 1);
93 if let Some((low
, high
)) = pos
{
94 Span
::new(low
, high
, header_span
.ctxt())
103 COGNITIVE_COMPLEXITY
,
106 "the function has a cognitive complexity of ({}/{})",
111 "you could split it up into multiple smaller functions",
117 impl<'tcx
> LateLintPass
<'tcx
> for CognitiveComplexity
{
120 cx
: &LateContext
<'tcx
>,
122 decl
: &'tcx FnDecl
<'_
>,
123 body
: &'tcx Body
<'_
>,
127 let def_id
= cx
.tcx
.hir().local_def_id(hir_id
);
128 if !cx
.tcx
.has_attr(def_id
.to_def_id(), sym
::test
) {
129 self.check(cx
, kind
, decl
, body
, span
);
133 fn enter_lint_attrs(&mut self, cx
: &LateContext
<'tcx
>, attrs
: &'tcx
[Attribute
]) {
134 self.limit
.push_attrs(cx
.sess(), attrs
, "cognitive_complexity");
136 fn exit_lint_attrs(&mut self, cx
: &LateContext
<'tcx
>, attrs
: &'tcx
[Attribute
]) {
137 self.limit
.pop_attrs(cx
.sess(), attrs
, "cognitive_complexity");
146 impl<'tcx
> Visitor
<'tcx
> for CcHelper
{
147 type Map
= Map
<'tcx
>;
149 fn visit_expr(&mut self, e
: &'tcx Expr
<'_
>) {
152 ExprKind
::If(_
, _
, _
) => {
155 ExprKind
::Match(_
, arms
, _
) => {
159 self.cc
+= arms
.iter().filter(|arm
| arm
.guard
.is_some()).count() as u64;
161 ExprKind
::Ret(_
) => self.returns
+= 1,
165 fn nested_visit_map(&mut self) -> NestedVisitorMap
<Self::Map
> {
166 NestedVisitorMap
::None