snix_eval/compiler/
optimiser.rs

1//! Helper functions for extending the compiler with more linter-like
2//! functionality while compiling (i.e. smarter warnings).
3
4use super::*;
5
6use ast::Expr;
7
8/// Optimise the given expression where possible.
9pub(super) fn optimise_expr(c: &mut Compiler, slot: LocalIdx, expr: ast::Expr) -> ast::Expr {
10    match expr {
11        Expr::BinOp(_) => optimise_bin_op(c, slot, expr),
12        _ => expr,
13    }
14}
15
16enum LitBool {
17    Expr(Expr),
18    True(Expr),
19    False(Expr),
20}
21
22/// Is this a literal boolean, or something else?
23fn is_lit_bool(expr: ast::Expr) -> LitBool {
24    if let ast::Expr::Ident(ident) = &expr {
25        match ident.ident_token().unwrap().text() {
26            "true" => LitBool::True(expr),
27            "false" => LitBool::False(expr),
28            _ => LitBool::Expr(expr),
29        }
30    } else {
31        LitBool::Expr(expr)
32    }
33}
34
35/// Detect useless binary operations (i.e. useless bool comparisons).
36fn optimise_bin_op(c: &mut Compiler, slot: LocalIdx, expr: ast::Expr) -> ast::Expr {
37    use ast::BinOpKind;
38
39    // bail out of this check if the user has overridden either `true`
40    // or `false` identifiers. Note that they will have received a
41    // separate warning about this for shadowing the global(s).
42    if c.is_user_defined("true") || c.is_user_defined("false") {
43        return expr;
44    }
45
46    if let Expr::BinOp(op) = &expr {
47        let lhs = is_lit_bool(op.lhs().unwrap());
48        let rhs = is_lit_bool(op.rhs().unwrap());
49
50        match (op.operator().unwrap(), lhs, rhs) {
51            // useless `false` arm in `||` expression
52            (BinOpKind::Or, LitBool::False(f), LitBool::Expr(other))
53            | (BinOpKind::Or, LitBool::Expr(other), LitBool::False(f)) => {
54                c.emit_warning(
55                    &f,
56                    WarningKind::UselessBoolOperation(
57                        "this `false` has no effect on the result of the comparison",
58                    ),
59                );
60
61                return other;
62            }
63
64            // useless `true` arm in `&&` expression
65            (BinOpKind::And, LitBool::True(t), LitBool::Expr(other))
66            | (BinOpKind::And, LitBool::Expr(other), LitBool::True(t)) => {
67                c.emit_warning(
68                    &t,
69                    WarningKind::UselessBoolOperation(
70                        "this `true` has no effect on the result of the comparison",
71                    ),
72                );
73
74                return other;
75            }
76
77            // useless `||` expression (one arm is `true`), return
78            // `true` directly (and warn about dead code on the right)
79            (BinOpKind::Or, LitBool::True(t), LitBool::Expr(other)) => {
80                c.emit_warning(
81                    op,
82                    WarningKind::UselessBoolOperation("this expression is always true"),
83                );
84
85                c.compile_dead_code(slot, other);
86
87                return t;
88            }
89
90            (BinOpKind::Or, _, LitBool::True(t)) | (BinOpKind::Or, LitBool::True(t), _) => {
91                c.emit_warning(
92                    op,
93                    WarningKind::UselessBoolOperation("this expression is always true"),
94                );
95
96                return t;
97            }
98
99            // useless `&&` expression (one arm is `false), same as above
100            (BinOpKind::And, LitBool::False(f), LitBool::Expr(other)) => {
101                c.emit_warning(
102                    op,
103                    WarningKind::UselessBoolOperation("this expression is always false"),
104                );
105
106                c.compile_dead_code(slot, other);
107
108                return f;
109            }
110
111            (BinOpKind::And, _, LitBool::False(f)) | (BinOpKind::Or, LitBool::False(f), _) => {
112                c.emit_warning(
113                    op,
114                    WarningKind::UselessBoolOperation("this expression is always false"),
115                );
116
117                return f;
118            }
119
120            _ => { /* nothing to optimise */ }
121        }
122    }
123
124    expr
125}