forge_lint/sol/info/
boolean_equal.rs1use 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}