]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/cognitive_complexity.rs
rustup https://github.com/rust-lang/rust/pull/68944
[rust.git] / clippy_lints / src / cognitive_complexity.rs
index 88b8d8688b669e2f44423a57efe52ed42d699f1d..fcab459018848c5539438e6ead9cb67f4f6c008d 100644 (file)
@@ -1,15 +1,15 @@
 //! calculate cognitive complexity and warn about overly complex functions
 
-use rustc::cfg::CFG;
-use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
-use rustc::hir::*;
-use rustc::lint::{LateContext, LateLintPass, LintArray, LintContext, LintPass};
-use rustc::ty;
-use rustc::{declare_tool_lint, impl_lint_pass};
-use syntax::ast::Attribute;
-use syntax::source_map::Span;
+use rustc::hir::map::Map;
+use rustc_ast::ast::Attribute;
+use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor};
+use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::BytePos;
 
-use crate::utils::{is_allowed, match_type, paths, span_help_and_lint, LimitStack};
+use crate::utils::{match_type, paths, snippet_opt, span_lint_and_help, LimitStack};
 
 declare_clippy_lint! {
     /// **What it does:** Checks for methods with high cognitive complexity.
@@ -31,6 +31,7 @@ pub struct CognitiveComplexity {
 }
 
 impl CognitiveComplexity {
+    #[must_use]
     pub fn new(limit: u64) -> Self {
         Self {
             limit: LimitStack::new(limit),
@@ -41,35 +42,24 @@ pub fn new(limit: u64) -> Self {
 impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
 
 impl CognitiveComplexity {
-    fn check<'a, 'tcx>(&mut self, cx: &'a LateContext<'a, 'tcx>, body: &'tcx Body, span: Span) {
-        if span.from_expansion() {
+    #[allow(clippy::cast_possible_truncation)]
+    fn check<'a, 'tcx>(
+        &mut self,
+        cx: &'a LateContext<'a, 'tcx>,
+        kind: FnKind<'tcx>,
+        decl: &'tcx FnDecl<'_>,
+        body: &'tcx Body<'_>,
+        body_span: Span,
+    ) {
+        if body_span.from_expansion() {
             return;
         }
 
-        let cfg = CFG::new(cx.tcx, body);
         let expr = &body.value;
-        let n = cfg.graph.len_nodes() as u64;
-        let e = cfg.graph.len_edges() as u64;
-        if e + 2 < n {
-            // the function has unreachable code, other lints should catch this
-            return;
-        }
-        let cc = e + 2 - n;
-        let mut helper = CCHelper {
-            match_arms: 0,
-            divergence: 0,
-            short_circuits: 0,
-            returns: 0,
-            cx,
-        };
+
+        let mut helper = CCHelper { cc: 1, returns: 0 };
         helper.visit_expr(expr);
-        let CCHelper {
-            match_arms,
-            divergence,
-            short_circuits,
-            returns,
-            ..
-        } = helper;
+        let CCHelper { cc, returns } = helper;
         let ret_ty = cx.tables.node_type(expr.hir_id);
         let ret_adjust = if match_type(cx, ret_ty, &paths::RESULT) {
             returns
@@ -78,32 +68,45 @@ fn check<'a, 'tcx>(&mut self, cx: &'a LateContext<'a, 'tcx>, body: &'tcx Body, s
             (returns / 2)
         };
 
-        if cc + divergence < match_arms + short_circuits {
-            report_cc_bug(
+        let mut rust_cc = cc;
+        // prevent degenerate cases where unreachable code contains `return` statements
+        if rust_cc >= ret_adjust {
+            rust_cc -= ret_adjust;
+        }
+
+        if rust_cc > self.limit.limit() {
+            let fn_span = match kind {
+                FnKind::ItemFn(ident, _, _, _, _) | FnKind::Method(ident, _, _, _) => ident.span,
+                FnKind::Closure(_) => {
+                    let header_span = body_span.with_hi(decl.output.span().lo());
+                    let pos = snippet_opt(cx, header_span).and_then(|snip| {
+                        let low_offset = snip.find('|')?;
+                        let high_offset = 1 + snip.get(low_offset + 1..)?.find('|')?;
+                        let low = header_span.lo() + BytePos(low_offset as u32);
+                        let high = low + BytePos(high_offset as u32 + 1);
+
+                        Some((low, high))
+                    });
+
+                    if let Some((low, high)) = pos {
+                        Span::new(low, high, header_span.ctxt())
+                    } else {
+                        return;
+                    }
+                },
+            };
+
+            span_lint_and_help(
                 cx,
-                cc,
-                match_arms,
-                divergence,
-                short_circuits,
-                ret_adjust,
-                span,
-                body.id().hir_id,
+                COGNITIVE_COMPLEXITY,
+                fn_span,
+                &format!(
+                    "the function has a cognitive complexity of ({}/{})",
+                    rust_cc,
+                    self.limit.limit()
+                ),
+                "you could split it up into multiple smaller functions",
             );
-        } else {
-            let mut rust_cc = cc + divergence - match_arms - short_circuits;
-            // prevent degenerate cases where unreachable code contains `return` statements
-            if rust_cc >= ret_adjust {
-                rust_cc -= ret_adjust;
-            }
-            if rust_cc > self.limit.limit() {
-                span_help_and_lint(
-                    cx,
-                    COGNITIVE_COMPLEXITY,
-                    span,
-                    &format!("the function has a cognitive complexity of {}", rust_cc),
-                    "you could split it up into multiple smaller functions",
-                );
-            }
         }
     }
 }
@@ -112,15 +115,15 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CognitiveComplexity {
     fn check_fn(
         &mut self,
         cx: &LateContext<'a, 'tcx>,
-        _: intravisit::FnKind<'tcx>,
-        _: &'tcx FnDecl,
-        body: &'tcx Body,
+        kind: FnKind<'tcx>,
+        decl: &'tcx FnDecl<'_>,
+        body: &'tcx Body<'_>,
         span: Span,
         hir_id: HirId,
     ) {
         let def_id = cx.tcx.hir().local_def_id(hir_id);
         if !cx.tcx.has_attr(def_id, sym!(test)) {
-            self.check(cx, body, span);
+            self.check(cx, kind, decl, body, span);
         }
     }
 
@@ -132,99 +135,28 @@ fn exit_lint_attrs(&mut self, cx: &LateContext<'a, 'tcx>, attrs: &'tcx [Attribut
     }
 }
 
-struct CCHelper<'a, 'tcx> {
-    match_arms: u64,
-    divergence: u64,
+struct CCHelper {
+    cc: u64,
     returns: u64,
-    short_circuits: u64, // && and ||
-    cx: &'a LateContext<'a, 'tcx>,
 }
 
-impl<'a, 'tcx> Visitor<'tcx> for CCHelper<'a, 'tcx> {
-    fn visit_expr(&mut self, e: &'tcx Expr) {
-        match e.node {
+impl<'tcx> Visitor<'tcx> for CCHelper {
+    type Map = Map<'tcx>;
+
+    fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+        walk_expr(self, e);
+        match e.kind {
             ExprKind::Match(_, ref arms, _) => {
-                walk_expr(self, e);
-                let arms_n: u64 = arms.iter().map(|arm| arm.pats.len() as u64).sum();
-                if arms_n > 1 {
-                    self.match_arms += arms_n - 2;
-                }
-            },
-            ExprKind::Call(ref callee, _) => {
-                walk_expr(self, e);
-                let ty = self.cx.tables.node_type(callee.hir_id);
-                match ty.sty {
-                    ty::FnDef(..) | ty::FnPtr(_) => {
-                        let sig = ty.fn_sig(self.cx.tcx);
-                        if sig.skip_binder().output().sty == ty::Never {
-                            self.divergence += 1;
-                        }
-                    },
-                    _ => (),
-                }
-            },
-            ExprKind::Closure(.., _) => (),
-            ExprKind::Binary(op, _, _) => {
-                walk_expr(self, e);
-                match op.node {
-                    BinOpKind::And | BinOpKind::Or => self.short_circuits += 1,
-                    _ => (),
+                if arms.len() > 1 {
+                    self.cc += 1;
                 }
+                self.cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
             },
             ExprKind::Ret(_) => self.returns += 1,
-            _ => walk_expr(self, e),
+            _ => {},
         }
     }
-    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
+    fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
         NestedVisitorMap::None
     }
 }
-
-#[cfg(feature = "debugging")]
-#[allow(clippy::too_many_arguments)]
-fn report_cc_bug(
-    _: &LateContext<'_, '_>,
-    cc: u64,
-    narms: u64,
-    div: u64,
-    shorts: u64,
-    returns: u64,
-    span: Span,
-    _: HirId,
-) {
-    span_bug!(
-        span,
-        "Clippy encountered a bug calculating cognitive complexity: cc = {}, arms = {}, \
-         div = {}, shorts = {}, returns = {}. Please file a bug report.",
-        cc,
-        narms,
-        div,
-        shorts,
-        returns
-    );
-}
-#[cfg(not(feature = "debugging"))]
-#[allow(clippy::too_many_arguments)]
-fn report_cc_bug(
-    cx: &LateContext<'_, '_>,
-    cc: u64,
-    narms: u64,
-    div: u64,
-    shorts: u64,
-    returns: u64,
-    span: Span,
-    id: HirId,
-) {
-    if !is_allowed(cx, COGNITIVE_COMPLEXITY, id) {
-        cx.sess().span_note_without_error(
-            span,
-            &format!(
-                "Clippy encountered a bug calculating cognitive complexity \
-                 (hide this message with `#[allow(cognitive_complexity)]`): \
-                 cc = {}, arms = {}, div = {}, shorts = {}, returns = {}. \
-                 Please file a bug report.",
-                cc, narms, div, shorts, returns
-            ),
-        );
-    }
-}