1 //! calculate cyclomatic complexity and warn about overly complex functions
7 use rustc
::hir
::intravisit
::{walk_expr, NestedVisitorMap, Visitor}
;
8 use syntax
::ast
::{Attribute, NodeId}
;
9 use syntax
::codemap
::Span
;
11 use utils
::{in_macro, is_allowed, match_type, paths, span_help_and_lint, LimitStack}
;
13 /// **What it does:** Checks for methods with high cyclomatic complexity.
15 /// **Why is this bad?** Methods of high cyclomatic complexity tend to be badly
16 /// readable. Also LLVM will usually optimize small methods better.
18 /// **Known problems:** Sometimes it's hard to find a way to reduce the
21 /// **Example:** No. You'll see it when you get the warning.
23 pub CYCLOMATIC_COMPLEXITY
,
25 "functions that should be split up into multiple functions"
28 pub struct CyclomaticComplexity
{
32 impl CyclomaticComplexity
{
33 pub fn new(limit
: u64) -> Self {
35 limit
: LimitStack
::new(limit
),
40 impl LintPass
for CyclomaticComplexity
{
41 fn get_lints(&self) -> LintArray
{
42 lint_array
!(CYCLOMATIC_COMPLEXITY
)
46 impl CyclomaticComplexity
{
47 fn check
<'a
, 'tcx
: 'a
>(&mut self, cx
: &'a LateContext
<'a
, 'tcx
>, body
: &'tcx Body
, span
: Span
) {
52 let cfg
= CFG
::new(cx
.tcx
, body
);
53 let expr
= &body
.value
;
54 let n
= cfg
.graph
.len_nodes() as u64;
55 let e
= cfg
.graph
.len_edges() as u64;
57 // the function has unreachable code, other lints should catch this
61 let mut helper
= CCHelper
{
68 helper
.visit_expr(expr
);
76 let ret_ty
= cx
.tables
.node_id_to_type(expr
.hir_id
);
77 let ret_adjust
= if match_type(cx
, ret_ty
, &paths
::RESULT
) {
83 if cc
+ divergence
< match_arms
+ short_circuits
{
95 let mut rust_cc
= cc
+ divergence
- match_arms
- short_circuits
;
96 // prevent degenerate cases where unreachable code contains `return` statements
97 if rust_cc
>= ret_adjust
{
98 rust_cc
-= ret_adjust
;
100 if rust_cc
> self.limit
.limit() {
103 CYCLOMATIC_COMPLEXITY
,
105 &format
!("the function has a cyclomatic complexity of {}", rust_cc
),
106 "you could split it up into multiple smaller functions",
113 impl<'a
, 'tcx
> LateLintPass
<'a
, 'tcx
> for CyclomaticComplexity
{
116 cx
: &LateContext
<'a
, 'tcx
>,
117 _
: intravisit
::FnKind
<'tcx
>,
123 let def_id
= cx
.tcx
.hir
.local_def_id(node_id
);
124 if !cx
.tcx
.has_attr(def_id
, "test") {
125 self.check(cx
, body
, span
);
129 fn enter_lint_attrs(&mut self, cx
: &LateContext
<'a
, 'tcx
>, attrs
: &'tcx
[Attribute
]) {
131 .push_attrs(cx
.sess(), attrs
, "cyclomatic_complexity");
133 fn exit_lint_attrs(&mut self, cx
: &LateContext
<'a
, 'tcx
>, attrs
: &'tcx
[Attribute
]) {
135 .pop_attrs(cx
.sess(), attrs
, "cyclomatic_complexity");
139 struct CCHelper
<'a
, 'tcx
: 'a
> {
143 short_circuits
: u64, // && and ||
144 cx
: &'a LateContext
<'a
, 'tcx
>,
147 impl<'a
, 'tcx
> Visitor
<'tcx
> for CCHelper
<'a
, 'tcx
> {
148 fn visit_expr(&mut self, e
: &'tcx Expr
) {
150 ExprMatch(_
, ref arms
, _
) => {
152 let arms_n
: u64 = arms
.iter().map(|arm
| arm
.pats
.len() as u64).sum();
154 self.match_arms
+= arms_n
- 2;
157 ExprCall(ref callee
, _
) => {
159 let ty
= self.cx
.tables
.node_id_to_type(callee
.hir_id
);
161 ty
::TyFnDef(..) | ty
::TyFnPtr(_
) => {
162 let sig
= ty
.fn_sig(self.cx
.tcx
);
163 if sig
.skip_binder().output().sty
== ty
::TyNever
{
164 self.divergence
+= 1;
170 ExprClosure(.., _
) => (),
171 ExprBinary(op
, _
, _
) => {
174 BiAnd
| BiOr
=> self.short_circuits
+= 1,
178 ExprRet(_
) => self.returns
+= 1,
179 _
=> walk_expr(self, e
),
182 fn nested_visit_map
<'this
>(&'this
mut self) -> NestedVisitorMap
<'this
, 'tcx
> {
183 NestedVisitorMap
::None
187 #[cfg(feature = "debugging")]
188 #[allow(too_many_arguments)]
189 fn report_cc_bug(_
: &LateContext
, cc
: u64, narms
: u64, div
: u64, shorts
: u64, returns
: u64, span
: Span
, _
: NodeId
) {
192 "Clippy encountered a bug calculating cyclomatic complexity: cc = {}, arms = {}, \
193 div = {}, shorts = {}, returns = {}. Please file a bug report.",
201 #[cfg(not(feature = "debugging"))]
202 #[allow(too_many_arguments)]
203 fn report_cc_bug(cx
: &LateContext
, cc
: u64, narms
: u64, div
: u64, shorts
: u64, returns
: u64, span
: Span
, id
: NodeId
) {
204 if !is_allowed(cx
, CYCLOMATIC_COMPLEXITY
, id
) {
205 cx
.sess().span_note_without_error(
208 "Clippy encountered a bug calculating cyclomatic complexity \
209 (hide this message with `#[allow(cyclomatic_complexity)]`): \
210 cc = {}, arms = {}, div = {}, shorts = {}, returns = {}. \
211 Please file a bug report.",