]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/comparison_chain.rs
Add 'src/tools/clippy/' from commit 'd2708873ef711ec8ab45df1e984ecf24a96cd369'
[rust.git] / src / tools / clippy / clippy_lints / src / comparison_chain.rs
1 use crate::utils::{
2     get_trait_def_id, if_sequence, implements_trait, parent_node_is_if_expr, paths, span_lint_and_help, SpanlessEq,
3 };
4 use rustc_hir::{BinOpKind, Expr, ExprKind};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_session::{declare_lint_pass, declare_tool_lint};
7
8 declare_clippy_lint! {
9     /// **What it does:** Checks comparison chains written with `if` that can be
10     /// rewritten with `match` and `cmp`.
11     ///
12     /// **Why is this bad?** `if` is not guaranteed to be exhaustive and conditionals can get
13     /// repetitive
14     ///
15     /// **Known problems:** None.
16     ///
17     /// **Example:**
18     /// ```rust,ignore
19     /// # fn a() {}
20     /// # fn b() {}
21     /// # fn c() {}
22     /// fn f(x: u8, y: u8) {
23     ///     if x > y {
24     ///         a()
25     ///     } else if x < y {
26     ///         b()
27     ///     } else {
28     ///         c()
29     ///     }
30     /// }
31     /// ```
32     ///
33     /// Could be written:
34     ///
35     /// ```rust,ignore
36     /// use std::cmp::Ordering;
37     /// # fn a() {}
38     /// # fn b() {}
39     /// # fn c() {}
40     /// fn f(x: u8, y: u8) {
41     ///      match x.cmp(&y) {
42     ///          Ordering::Greater => a(),
43     ///          Ordering::Less => b(),
44     ///          Ordering::Equal => c()
45     ///      }
46     /// }
47     /// ```
48     pub COMPARISON_CHAIN,
49     style,
50     "`if`s that can be rewritten with `match` and `cmp`"
51 }
52
53 declare_lint_pass!(ComparisonChain => [COMPARISON_CHAIN]);
54
55 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ComparisonChain {
56     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
57         if expr.span.from_expansion() {
58             return;
59         }
60
61         // We only care about the top-most `if` in the chain
62         if parent_node_is_if_expr(expr, cx) {
63             return;
64         }
65
66         // Check that there exists at least one explicit else condition
67         let (conds, _) = if_sequence(expr);
68         if conds.len() < 2 {
69             return;
70         }
71
72         for cond in conds.windows(2) {
73             if let (
74                 &ExprKind::Binary(ref kind1, ref lhs1, ref rhs1),
75                 &ExprKind::Binary(ref kind2, ref lhs2, ref rhs2),
76             ) = (&cond[0].kind, &cond[1].kind)
77             {
78                 if !kind_is_cmp(kind1.node) || !kind_is_cmp(kind2.node) {
79                     return;
80                 }
81
82                 // Check that both sets of operands are equal
83                 let mut spanless_eq = SpanlessEq::new(cx);
84                 if (!spanless_eq.eq_expr(lhs1, lhs2) || !spanless_eq.eq_expr(rhs1, rhs2))
85                     && (!spanless_eq.eq_expr(lhs1, rhs2) || !spanless_eq.eq_expr(rhs1, lhs2))
86                 {
87                     return;
88                 }
89
90                 // Check that the type being compared implements `core::cmp::Ord`
91                 let ty = cx.tables.expr_ty(lhs1);
92                 let is_ord = get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[]));
93
94                 if !is_ord {
95                     return;
96                 }
97             } else {
98                 // We only care about comparison chains
99                 return;
100             }
101         }
102         span_lint_and_help(
103             cx,
104             COMPARISON_CHAIN,
105             expr.span,
106             "`if` chain can be rewritten with `match`",
107             None,
108             "Consider rewriting the `if` chain to use `cmp` and `match`.",
109         )
110     }
111 }
112
113 fn kind_is_cmp(kind: BinOpKind) -> bool {
114     match kind {
115         BinOpKind::Lt | BinOpKind::Gt | BinOpKind::Eq => true,
116         _ => false,
117     }
118 }