]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/utils/mod.rs
Add a new lint for comparison chains
[rust.git] / clippy_lints / src / utils / mod.rs
index 1aff61e3186075c0aaf0080403e8c37e9e31a13e..49d9de35e182160155f06edf5bae6864c7142e2c 100644 (file)
@@ -47,6 +47,7 @@
 use syntax::source_map::{Span, DUMMY_SP};
 use syntax::symbol::{kw, Symbol};
 
+use crate::consts::{constant, Constant};
 use crate::reexport::*;
 
 /// Returns `true` if the two spans come from differing expansions (i.e., one is
@@ -130,6 +131,14 @@ pub fn match_type(cx: &LateContext<'_, '_>, ty: Ty<'_>, path: &[&str]) -> bool {
     }
 }
 
+/// Checks if the type is equal to a diagnostic item
+pub fn is_type_diagnostic_item(cx: &LateContext<'_, '_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
+    match ty.sty {
+        ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did),
+        _ => false,
+    }
+}
+
 /// Checks if the method call given in `expr` belongs to the given trait.
 pub fn match_trait_method(cx: &LateContext<'_, '_>, expr: &Expr, path: &[&str]) -> bool {
     let def_id = cx.tables.type_dependent_def_id(expr.hir_id).unwrap();
@@ -264,6 +273,19 @@ pub fn path_to_res(cx: &LateContext<'_, '_>, path: &[&str]) -> Option<(def::Res)
     }
 }
 
+pub fn qpath_res(cx: &LateContext<'_, '_>, qpath: &hir::QPath, id: hir::HirId) -> Res {
+    match qpath {
+        hir::QPath::Resolved(_, path) => path.res,
+        hir::QPath::TypeRelative(..) => {
+            if cx.tcx.has_typeck_tables(id.owner_def_id()) {
+                cx.tcx.typeck_tables_of(id.owner_def_id()).qpath_res(qpath, id)
+            } else {
+                Res::Err
+            }
+        },
+    }
+}
+
 /// Convenience function to get the `DefId` of a trait by path.
 /// It could be a trait or trait alias.
 pub fn get_trait_def_id(cx: &LateContext<'_, '_>, path: &[&str]) -> Option<DefId> {
@@ -342,26 +364,28 @@ pub fn resolve_node(cx: &LateContext<'_, '_>, qpath: &QPath, id: HirId) -> Res {
 }
 
 /// Returns the method names and argument list of nested method call expressions that make up
-/// `expr`.
-pub fn method_calls(expr: &Expr, max_depth: usize) -> (Vec<Symbol>, Vec<&[Expr]>) {
+/// `expr`. method/span lists are sorted with the most recent call first.
+pub fn method_calls(expr: &Expr, max_depth: usize) -> (Vec<Symbol>, Vec<&[Expr]>, Vec<Span>) {
     let mut method_names = Vec::with_capacity(max_depth);
     let mut arg_lists = Vec::with_capacity(max_depth);
+    let mut spans = Vec::with_capacity(max_depth);
 
     let mut current = expr;
     for _ in 0..max_depth {
-        if let ExprKind::MethodCall(path, _, args) = &current.node {
+        if let ExprKind::MethodCall(path, span, args) = &current.node {
             if args.iter().any(|e| e.span.from_expansion()) {
                 break;
             }
             method_names.push(path.ident.name);
             arg_lists.push(&**args);
+            spans.push(*span);
             current = &args[0];
         } else {
             break;
         }
     }
 
-    (method_names, arg_lists)
+    (method_names, arg_lists, spans)
 }
 
 /// Matches an `Expr` against a chain of methods, and return the matched `Expr`s.
@@ -396,10 +420,9 @@ pub fn method_chain_args<'a>(expr: &'a Expr, methods: &[&str]) -> Option<Vec<&'a
 
 /// Returns `true` if the provided `def_id` is an entrypoint to a program.
 pub fn is_entrypoint_fn(cx: &LateContext<'_, '_>, def_id: DefId) -> bool {
-    if let Some((entry_fn_def_id, _)) = cx.tcx.entry_fn(LOCAL_CRATE) {
-        return def_id == entry_fn_def_id;
-    }
-    false
+    cx.tcx
+        .entry_fn(LOCAL_CRATE)
+        .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id)
 }
 
 /// Gets the name of the item the expression is in, if available.
@@ -660,6 +683,24 @@ fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) {
     inner(ty, 0)
 }
 
+/// Checks whether the given expression is a constant integer of the given value.
+/// unlike `is_integer_literal`, this version does const folding
+pub fn is_integer_const(cx: &LateContext<'_, '_>, e: &Expr, value: u128) -> bool {
+    if is_integer_literal(e, value) {
+        return true;
+    }
+    let map = cx.tcx.hir();
+    let parent_item = map.get_parent_item(e.hir_id);
+    if let Some((Constant::Int(v), _)) = map
+        .maybe_body_owned_by(parent_item)
+        .and_then(|body_id| constant(cx, cx.tcx.body_tables(body_id), e))
+    {
+        value == v
+    } else {
+        false
+    }
+}
+
 /// Checks whether the given expression is a constant literal of the given value.
 pub fn is_integer_literal(expr: &Expr, value: u128) -> bool {
     // FIXME: use constant folding
@@ -1124,7 +1165,50 @@ fn test_without_block_comments_lines_without_block_comments() {
 }
 
 pub fn match_def_path<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, did: DefId, syms: &[&str]) -> bool {
-    // HACK: find a way to use symbols from clippy or just go fully to diagnostic items
-    let syms: Vec<_> = syms.iter().map(|sym| Symbol::intern(sym)).collect();
-    cx.match_def_path(did, &syms)
+    let path = cx.get_def_path(did);
+    path.len() == syms.len() && path.into_iter().zip(syms.iter()).all(|(a, &b)| a.as_str() == b)
+}
+
+/// Returns the list of condition expressions and the list of blocks in a
+/// sequence of `if/else`.
+/// E.g., this returns `([a, b], [c, d, e])` for the expression
+/// `if a { c } else if b { d } else { e }`.
+pub fn if_sequence(mut expr: &Expr) -> (SmallVec<[&Expr; 1]>, SmallVec<[&Block; 1]>) {
+    let mut conds = SmallVec::new();
+    let mut blocks: SmallVec<[&Block; 1]> = SmallVec::new();
+
+    while let Some((ref cond, ref then_expr, ref else_expr)) = higher::if_block(&expr) {
+        conds.push(&**cond);
+        if let ExprKind::Block(ref block, _) = then_expr.node {
+            blocks.push(block);
+        } else {
+            panic!("ExprKind::If node is not an ExprKind::Block");
+        }
+
+        if let Some(ref else_expr) = *else_expr {
+            expr = else_expr;
+        } else {
+            break;
+        }
+    }
+
+    // final `else {..}`
+    if !blocks.is_empty() {
+        if let ExprKind::Block(ref block, _) = expr.node {
+            blocks.push(&**block);
+        }
+    }
+
+    (conds, blocks)
+}
+
+pub fn parent_node_is_if_expr<'a, 'b>(expr: &Expr, cx: &LateContext<'a, 'b>) -> bool {
+    let parent_id = cx.tcx.hir().get_parent_node(expr.hir_id);
+    let parent_node = cx.tcx.hir().get(parent_id);
+
+    match parent_node {
+        rustc::hir::Node::Expr(e) => higher::if_block(&e).is_some(),
+        rustc::hir::Node::Arm(e) => higher::if_block(&e.body).is_some(),
+        _ => false,
+    }
 }