]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/cognitive_complexity.rs
Auto merge of #4469 - vityafx:master, r=flip1995
[rust.git] / clippy_lints / src / cognitive_complexity.rs
1 //! calculate cognitive complexity and warn about overly complex functions
2
3 use rustc::cfg::CFG;
4 use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
5 use rustc::hir::*;
6 use rustc::lint::{LateContext, LateLintPass, LintArray, LintContext, LintPass};
7 use rustc::ty;
8 use rustc::{declare_tool_lint, impl_lint_pass};
9 use syntax::ast::Attribute;
10 use syntax::source_map::Span;
11
12 use crate::utils::{is_allowed, match_type, paths, span_help_and_lint, LimitStack};
13
14 declare_clippy_lint! {
15     /// **What it does:** Checks for methods with high cognitive complexity.
16     ///
17     /// **Why is this bad?** Methods of high cognitive complexity tend to be hard to
18     /// both read and maintain. Also LLVM will tend to optimize small methods better.
19     ///
20     /// **Known problems:** Sometimes it's hard to find a way to reduce the
21     /// complexity.
22     ///
23     /// **Example:** No. You'll see it when you get the warning.
24     pub COGNITIVE_COMPLEXITY,
25     complexity,
26     "functions that should be split up into multiple functions"
27 }
28
29 pub struct CognitiveComplexity {
30     limit: LimitStack,
31 }
32
33 impl CognitiveComplexity {
34     pub fn new(limit: u64) -> Self {
35         Self {
36             limit: LimitStack::new(limit),
37         }
38     }
39 }
40
41 impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
42
43 impl CognitiveComplexity {
44     fn check<'a, 'tcx>(&mut self, cx: &'a LateContext<'a, 'tcx>, body: &'tcx Body, span: Span) {
45         if span.from_expansion() {
46             return;
47         }
48
49         let cfg = CFG::new(cx.tcx, body);
50         let expr = &body.value;
51         let n = cfg.graph.len_nodes() as u64;
52         let e = cfg.graph.len_edges() as u64;
53         if e + 2 < n {
54             // the function has unreachable code, other lints should catch this
55             return;
56         }
57         let cc = e + 2 - n;
58         let mut helper = CCHelper {
59             match_arms: 0,
60             divergence: 0,
61             short_circuits: 0,
62             returns: 0,
63             cx,
64         };
65         helper.visit_expr(expr);
66         let CCHelper {
67             match_arms,
68             divergence,
69             short_circuits,
70             returns,
71             ..
72         } = helper;
73         let ret_ty = cx.tables.node_type(expr.hir_id);
74         let ret_adjust = if match_type(cx, ret_ty, &paths::RESULT) {
75             returns
76         } else {
77             #[allow(clippy::integer_division)]
78             (returns / 2)
79         };
80
81         if cc + divergence < match_arms + short_circuits {
82             report_cc_bug(
83                 cx,
84                 cc,
85                 match_arms,
86                 divergence,
87                 short_circuits,
88                 ret_adjust,
89                 span,
90                 body.id().hir_id,
91             );
92         } else {
93             let mut rust_cc = cc + divergence - match_arms - short_circuits;
94             // prevent degenerate cases where unreachable code contains `return` statements
95             if rust_cc >= ret_adjust {
96                 rust_cc -= ret_adjust;
97             }
98             if rust_cc > self.limit.limit() {
99                 span_help_and_lint(
100                     cx,
101                     COGNITIVE_COMPLEXITY,
102                     span,
103                     &format!(
104                         "the function has a cognitive complexity of ({}/{})",
105                         rust_cc,
106                         self.limit.limit()
107                     ),
108                     "you could split it up into multiple smaller functions",
109                 );
110             }
111         }
112     }
113 }
114
115 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CognitiveComplexity {
116     fn check_fn(
117         &mut self,
118         cx: &LateContext<'a, 'tcx>,
119         _: intravisit::FnKind<'tcx>,
120         _: &'tcx FnDecl,
121         body: &'tcx Body,
122         span: Span,
123         hir_id: HirId,
124     ) {
125         let def_id = cx.tcx.hir().local_def_id(hir_id);
126         if !cx.tcx.has_attr(def_id, sym!(test)) {
127             self.check(cx, body, span);
128         }
129     }
130
131     fn enter_lint_attrs(&mut self, cx: &LateContext<'a, 'tcx>, attrs: &'tcx [Attribute]) {
132         self.limit.push_attrs(cx.sess(), attrs, "cognitive_complexity");
133     }
134     fn exit_lint_attrs(&mut self, cx: &LateContext<'a, 'tcx>, attrs: &'tcx [Attribute]) {
135         self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity");
136     }
137 }
138
139 struct CCHelper<'a, 'tcx> {
140     match_arms: u64,
141     divergence: u64,
142     returns: u64,
143     short_circuits: u64, // && and ||
144     cx: &'a LateContext<'a, 'tcx>,
145 }
146
147 impl<'a, 'tcx> Visitor<'tcx> for CCHelper<'a, 'tcx> {
148     fn visit_expr(&mut self, e: &'tcx Expr) {
149         match e.node {
150             ExprKind::Match(_, ref arms, _) => {
151                 walk_expr(self, e);
152                 let arms_n: u64 = arms.iter().map(|arm| arm.pats.len() as u64).sum();
153                 if arms_n > 1 {
154                     self.match_arms += arms_n - 2;
155                 }
156             },
157             ExprKind::Call(ref callee, _) => {
158                 walk_expr(self, e);
159                 let ty = self.cx.tables.node_type(callee.hir_id);
160                 match ty.sty {
161                     ty::FnDef(..) | ty::FnPtr(_) => {
162                         let sig = ty.fn_sig(self.cx.tcx);
163                         if sig.skip_binder().output().sty == ty::Never {
164                             self.divergence += 1;
165                         }
166                     },
167                     _ => (),
168                 }
169             },
170             ExprKind::Closure(.., _) => (),
171             ExprKind::Binary(op, _, _) => {
172                 walk_expr(self, e);
173                 match op.node {
174                     BinOpKind::And | BinOpKind::Or => self.short_circuits += 1,
175                     _ => (),
176                 }
177             },
178             ExprKind::Ret(_) => self.returns += 1,
179             _ => walk_expr(self, e),
180         }
181     }
182     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
183         NestedVisitorMap::None
184     }
185 }
186
187 #[cfg(feature = "debugging")]
188 #[allow(clippy::too_many_arguments)]
189 fn report_cc_bug(
190     _: &LateContext<'_, '_>,
191     cc: u64,
192     narms: u64,
193     div: u64,
194     shorts: u64,
195     returns: u64,
196     span: Span,
197     _: HirId,
198 ) {
199     span_bug!(
200         span,
201         "Clippy encountered a bug calculating cognitive complexity: cc = {}, arms = {}, \
202          div = {}, shorts = {}, returns = {}. Please file a bug report.",
203         cc,
204         narms,
205         div,
206         shorts,
207         returns
208     );
209 }
210 #[cfg(not(feature = "debugging"))]
211 #[allow(clippy::too_many_arguments)]
212 fn report_cc_bug(
213     cx: &LateContext<'_, '_>,
214     cc: u64,
215     narms: u64,
216     div: u64,
217     shorts: u64,
218     returns: u64,
219     span: Span,
220     id: HirId,
221 ) {
222     if !is_allowed(cx, COGNITIVE_COMPLEXITY, id) {
223         cx.sess().span_note_without_error(
224             span,
225             &format!(
226                 "Clippy encountered a bug calculating cognitive complexity \
227                  (hide this message with `#[allow(cognitive_complexity)]`): \
228                  cc = {}, arms = {}, div = {}, shorts = {}, returns = {}. \
229                  Please file a bug report.",
230                 cc, narms, div, shorts, returns
231             ),
232         );
233     }
234 }