]> git.lizzy.rs Git - rust.git/blob - src/booleans.rs
better simplification
[rust.git] / src / booleans.rs
1 use rustc::lint::*;
2 use rustc_front::hir::*;
3 use rustc_front::intravisit::*;
4 use syntax::ast::LitKind;
5 use utils::{span_lint_and_then, in_macro, snippet_opt};
6
7 /// **What it does:** This lint checks for boolean expressions that can be written more concisely
8 ///
9 /// **Why is this bad?** Readability of boolean expressions suffers from unnecesessary duplication
10 ///
11 /// **Known problems:** None
12 ///
13 /// **Example:** `if a && b || a` should be `if a`
14 declare_lint! {
15     pub NONMINIMAL_BOOL, Warn,
16     "checks for boolean expressions that can be written more concisely"
17 }
18
19 #[derive(Copy,Clone)]
20 pub struct NonminimalBool;
21
22 impl LintPass for NonminimalBool {
23     fn get_lints(&self) -> LintArray {
24         lint_array!(NONMINIMAL_BOOL)
25     }
26 }
27
28 impl LateLintPass for NonminimalBool {
29     fn check_crate(&mut self, cx: &LateContext, krate: &Crate) {
30         krate.visit_all_items(&mut NonminimalBoolVisitor(cx))
31     }
32 }
33
34 struct NonminimalBoolVisitor<'a, 'tcx: 'a>(&'a LateContext<'a, 'tcx>);
35
36 use quine_mc_cluskey::Bool;
37 struct Hir2Qmm<'tcx>(Vec<&'tcx Expr>);
38
39 impl<'tcx> Hir2Qmm<'tcx> {
40     fn extract(&mut self, op: BinOp_, a: &[&'tcx Expr], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
41         for a in a {
42             if let ExprBinary(binop, ref lhs, ref rhs) = a.node {
43                 if binop.node == op {
44                     v = self.extract(op, &[lhs, rhs], v)?;
45                     continue;
46                 }
47             }
48             v.push(self.run(a)?);
49         }
50         Ok(v)
51     }
52
53     fn run(&mut self, e: &'tcx Expr) -> Result<Bool, String> {
54         match e.node {
55             ExprUnary(UnNot, ref inner) => return Ok(Bool::Not(box self.run(inner)?)),
56             ExprBinary(binop, ref lhs, ref rhs) => {
57                 match binop.node {
58                     BiOr => return Ok(Bool::Or(self.extract(BiOr, &[lhs, rhs], Vec::new())?)),
59                     BiAnd => return Ok(Bool::And(self.extract(BiAnd, &[lhs, rhs], Vec::new())?)),
60                     _ => {},
61                 }
62             },
63             ExprLit(ref lit) => {
64                 match lit.node {
65                     LitKind::Bool(true) => return Ok(Bool::True),
66                     LitKind::Bool(false) => return Ok(Bool::False),
67                     _ => {},
68                 }
69             },
70             _ => {},
71         }
72         let n = self.0.len();
73         self.0.push(e);
74         if n < 32 {
75             #[allow(cast_possible_truncation)]
76             Ok(Bool::Term(n as u8))
77         } else {
78             Err("too many literals".to_owned())
79         }
80     }
81 }
82
83 fn suggest(cx: &LateContext, suggestion: &Bool, terminals: &[&Expr]) -> String {
84     fn recurse(cx: &LateContext, suggestion: &Bool, terminals: &[&Expr], mut s: String) -> String {
85         use quine_mc_cluskey::Bool::*;
86         match *suggestion {
87             True => {
88                 s.extend("true".chars());
89                 s
90             },
91             False => {
92                 s.extend("false".chars());
93                 s
94             },
95             Not(ref inner) => {
96                 s.push('!');
97                 recurse(cx, inner, terminals, s)
98             },
99             And(ref v) => {
100                 s = recurse(cx, &v[0], terminals, s);
101                 for inner in &v[1..] {
102                     s.extend(" && ".chars());
103                     s = recurse(cx, inner, terminals, s);
104                 }
105                 s
106             },
107             Or(ref v) => {
108                 s = recurse(cx, &v[0], terminals, s);
109                 for inner in &v[1..] {
110                     s.extend(" || ".chars());
111                     s = recurse(cx, inner, terminals, s);
112                 }
113                 s
114             },
115             Term(n) => {
116                 s.extend(snippet_opt(cx, terminals[n as usize].span).expect("don't try to improve booleans created by macros").chars());
117                 s
118             }
119         }
120     }
121     recurse(cx, suggestion, terminals, String::new())
122 }
123
124 impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
125     fn bool_expr(&self, e: &Expr) {
126         let mut h2q = Hir2Qmm(Vec::new());
127         if let Ok(expr) = h2q.run(e) {
128             let simplified = expr.simplify();
129             if !simplified.iter().any(|s| *s == expr) {
130                 span_lint_and_then(self.0, NONMINIMAL_BOOL, e.span, "this boolean expression can be simplified", |db| {
131                     for suggestion in &simplified {
132                         db.span_suggestion(e.span, "try", suggest(self.0, suggestion, &h2q.0));
133                     }
134                 });
135             }
136         }
137     }
138 }
139
140 impl<'a, 'v, 'tcx> Visitor<'v> for NonminimalBoolVisitor<'a, 'tcx> {
141     fn visit_expr(&mut self, e: &'v Expr) {
142         if in_macro(self.0, e.span) { return }
143         match e.node {
144             ExprBinary(binop, _, _) if binop.node == BiOr || binop.node == BiAnd => self.bool_expr(e),
145             ExprUnary(UnNot, ref inner) => {
146                 if self.0.tcx.node_types()[&inner.id].is_bool() {
147                     self.bool_expr(e);
148                 } else {
149                     walk_expr(self, e);
150                 }
151             },
152             _ => walk_expr(self, e),
153         }
154     }
155 }