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