Skip to main content

forge_lint/sol/info/
boolean_equal.rs

1use super::BooleanEqual;
2use crate::{
3    linter::{EarlyLintPass, LintContext, Suggestion},
4    sol::{Severity, SolLint},
5};
6use solar::{
7    ast::{BinOp, BinOpKind, Expr, ExprKind, LitKind},
8    interface::diagnostics::Applicability,
9};
10
11declare_forge_lint!(
12    BOOLEAN_EQUAL,
13    Severity::Info,
14    "boolean-equal",
15    "boolean comparisons to constants should be simplified"
16);
17
18impl<'ast> EarlyLintPass<'ast> for BooleanEqual {
19    fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) {
20        if let ExprKind::Binary(
21            left,
22            op @ BinOp { kind: BinOpKind::Eq | BinOpKind::Ne, .. },
23            right,
24        ) = &expr.kind
25        {
26            match bool_comparison_suggestion(ctx, left, op.kind, right) {
27                BoolComparison::WithSuggestion(simplified) => {
28                    ctx.emit_with_suggestion(
29                        &BOOLEAN_EQUAL,
30                        expr.span,
31                        Suggestion::fix(simplified, Applicability::MachineApplicable)
32                            .with_desc("consider simplifying to"),
33                    );
34                }
35                BoolComparison::WithoutSuggestion => ctx.emit(&BOOLEAN_EQUAL, expr.span),
36                BoolComparison::None => {}
37            }
38        }
39    }
40}
41
42enum BoolComparison {
43    WithSuggestion(String),
44    WithoutSuggestion,
45    None,
46}
47
48fn bool_comparison_suggestion(
49    ctx: &LintContext,
50    left: &Expr<'_>,
51    op: BinOpKind,
52    right: &Expr<'_>,
53) -> BoolComparison {
54    let left_bool = bool_literal(left);
55    let right_bool = bool_literal(right);
56
57    match (left_bool, right_bool) {
58        (Some(value), None) => simplify_expr(ctx, right, op, value),
59        (None, Some(value)) => simplify_expr(ctx, left, op, value),
60        (Some(_), Some(_)) => BoolComparison::WithoutSuggestion,
61        (None, None) => BoolComparison::None,
62    }
63}
64
65fn bool_literal(expr: &Expr<'_>) -> Option<bool> {
66    let expr = expr.peel_parens();
67    if let ExprKind::Lit(lit, _) = &expr.kind
68        && let LitKind::Bool(value) = lit.kind
69    {
70        Some(value)
71    } else {
72        None
73    }
74}
75
76fn simplify_expr(
77    ctx: &LintContext,
78    expr: &Expr<'_>,
79    op: BinOpKind,
80    constant: bool,
81) -> BoolComparison {
82    let Some(snippet) = ctx.span_to_snippet(expr.span) else {
83        return BoolComparison::WithoutSuggestion;
84    };
85
86    let simplified = match (op, constant) {
87        (BinOpKind::Eq, true) | (BinOpKind::Ne, false) => snippet,
88        (BinOpKind::Eq, false) | (BinOpKind::Ne, true) if can_negate_without_parens(expr) => {
89            format!("!{snippet}")
90        }
91        (BinOpKind::Eq, false) | (BinOpKind::Ne, true) => format!("!({snippet})"),
92        _ => return BoolComparison::None,
93    };
94
95    BoolComparison::WithSuggestion(simplified)
96}
97
98fn can_negate_without_parens(expr: &Expr<'_>) -> bool {
99    matches!(
100        expr.peel_parens().kind,
101        ExprKind::Call(..)
102            | ExprKind::CallOptions(..)
103            | ExprKind::Ident(_)
104            | ExprKind::Index(..)
105            | ExprKind::Lit(..)
106            | ExprKind::Member(..)
107    )
108}