]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/cognitive_complexity.rs
Use lint pass macros
[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::{in_macro, 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: 'a>(&mut self, cx: &'a LateContext<'a, 'tcx>, body: &'tcx Body, span: Span) {
45         if in_macro(span) {
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             returns / 2
78         };
79
80         if cc + divergence < match_arms + short_circuits {
81             report_cc_bug(
82                 cx,
83                 cc,
84                 match_arms,
85                 divergence,
86                 short_circuits,
87                 ret_adjust,
88                 span,
89                 body.id().hir_id,
90             );
91         } else {
92             let mut rust_cc = cc + divergence - match_arms - short_circuits;
93             // prevent degenerate cases where unreachable code contains `return` statements
94             if rust_cc >= ret_adjust {
95                 rust_cc -= ret_adjust;
96             }
97             if rust_cc > self.limit.limit() {
98                 span_help_and_lint(
99                     cx,
100                     COGNITIVE_COMPLEXITY,
101                     span,
102                     &format!("the function has a cognitive complexity of {}", rust_cc),
103                     "you could split it up into multiple smaller functions",
104                 );
105             }
106         }
107     }
108 }
109
110 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CognitiveComplexity {
111     fn check_fn(
112         &mut self,
113         cx: &LateContext<'a, 'tcx>,
114         _: intravisit::FnKind<'tcx>,
115         _: &'tcx FnDecl,
116         body: &'tcx Body,
117         span: Span,
118         hir_id: HirId,
119     ) {
120         let def_id = cx.tcx.hir().local_def_id_from_hir_id(hir_id);
121         if !cx.tcx.has_attr(def_id, "test") {
122             self.check(cx, body, span);
123         }
124     }
125
126     fn enter_lint_attrs(&mut self, cx: &LateContext<'a, 'tcx>, attrs: &'tcx [Attribute]) {
127         self.limit.push_attrs(cx.sess(), attrs, "cognitive_complexity");
128     }
129     fn exit_lint_attrs(&mut self, cx: &LateContext<'a, 'tcx>, attrs: &'tcx [Attribute]) {
130         self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity");
131     }
132 }
133
134 struct CCHelper<'a, 'tcx: 'a> {
135     match_arms: u64,
136     divergence: u64,
137     returns: u64,
138     short_circuits: u64, // && and ||
139     cx: &'a LateContext<'a, 'tcx>,
140 }
141
142 impl<'a, 'tcx> Visitor<'tcx> for CCHelper<'a, 'tcx> {
143     fn visit_expr(&mut self, e: &'tcx Expr) {
144         match e.node {
145             ExprKind::Match(_, ref arms, _) => {
146                 walk_expr(self, e);
147                 let arms_n: u64 = arms.iter().map(|arm| arm.pats.len() as u64).sum();
148                 if arms_n > 1 {
149                     self.match_arms += arms_n - 2;
150                 }
151             },
152             ExprKind::Call(ref callee, _) => {
153                 walk_expr(self, e);
154                 let ty = self.cx.tables.node_type(callee.hir_id);
155                 match ty.sty {
156                     ty::FnDef(..) | ty::FnPtr(_) => {
157                         let sig = ty.fn_sig(self.cx.tcx);
158                         if sig.skip_binder().output().sty == ty::Never {
159                             self.divergence += 1;
160                         }
161                     },
162                     _ => (),
163                 }
164             },
165             ExprKind::Closure(.., _) => (),
166             ExprKind::Binary(op, _, _) => {
167                 walk_expr(self, e);
168                 match op.node {
169                     BinOpKind::And | BinOpKind::Or => self.short_circuits += 1,
170                     _ => (),
171                 }
172             },
173             ExprKind::Ret(_) => self.returns += 1,
174             _ => walk_expr(self, e),
175         }
176     }
177     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
178         NestedVisitorMap::None
179     }
180 }
181
182 #[cfg(feature = "debugging")]
183 #[allow(clippy::too_many_arguments)]
184 fn report_cc_bug(
185     _: &LateContext<'_, '_>,
186     cc: u64,
187     narms: u64,
188     div: u64,
189     shorts: u64,
190     returns: u64,
191     span: Span,
192     _: HirId,
193 ) {
194     span_bug!(
195         span,
196         "Clippy encountered a bug calculating cognitive complexity: cc = {}, arms = {}, \
197          div = {}, shorts = {}, returns = {}. Please file a bug report.",
198         cc,
199         narms,
200         div,
201         shorts,
202         returns
203     );
204 }
205 #[cfg(not(feature = "debugging"))]
206 #[allow(clippy::too_many_arguments)]
207 fn report_cc_bug(
208     cx: &LateContext<'_, '_>,
209     cc: u64,
210     narms: u64,
211     div: u64,
212     shorts: u64,
213     returns: u64,
214     span: Span,
215     id: HirId,
216 ) {
217     if !is_allowed(cx, COGNITIVE_COMPLEXITY, id) {
218         cx.sess().span_note_without_error(
219             span,
220             &format!(
221                 "Clippy encountered a bug calculating cognitive complexity \
222                  (hide this message with `#[allow(cognitive_complexity)]`): \
223                  cc = {}, arms = {}, div = {}, shorts = {}, returns = {}. \
224                  Please file a bug report.",
225                 cc, narms, div, shorts, returns
226             ),
227         );
228     }
229 }