Skip to main content

forge_lint/sol/med/
tautological_compare.rs

1use super::TautologicalCompare;
2use crate::{
3    linter::{LateLintPass, LintContext},
4    sol::{Severity, SolLint, analysis::cast_type},
5};
6use solar::{
7    ast::{BinOpKind, Lit, LitKind},
8    sema::{
9        Gcx,
10        hir::{self, Expr, ExprKind},
11        ty::TyKind,
12    },
13};
14
15declare_forge_lint!(
16    TAUTOLOGICAL_COMPARE,
17    Severity::Med,
18    "tautological-compare",
19    "comparing an expression with itself is always true or false"
20);
21
22impl<'gcx> LateLintPass<'gcx> for TautologicalCompare {
23    fn check_expr(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, expr: &'gcx hir::Expr<'gcx>) {
24        // A UDVT can only be compared through a user-defined operator (`using {f as ==} for T`),
25        // which dispatches to an arbitrary function, so `x == x` is not tautological for it.
26        if let ExprKind::Binary(left, op, right) = &expr.kind
27            && matches!(
28                op.kind,
29                BinOpKind::Lt
30                    | BinOpKind::Le
31                    | BinOpKind::Gt
32                    | BinOpKind::Ge
33                    | BinOpKind::Eq
34                    | BinOpKind::Ne
35            )
36            && exprs_equal(left, right)
37            && !gcx
38                .type_of_expr(left.peel_parens().id)
39                .is_some_and(|ty| matches!(ty.peel_refs().kind, TyKind::Udvt(..)))
40        {
41            ctx.emit(&TAUTOLOGICAL_COMPARE, expr.span);
42        }
43    }
44}
45
46/// Structural equality for side-effect-free expressions. Anything else (notably arbitrary calls,
47/// which may return different values, and the mutating `++`/`--`) is treated as unequal.
48fn exprs_equal<'gcx>(a: &Expr<'gcx>, b: &Expr<'gcx>) -> bool {
49    match (&a.peel_parens().kind, &b.peel_parens().kind) {
50        (ExprKind::Ident(ra), ExprKind::Ident(rb)) => ra == rb,
51        (ExprKind::Lit(la), ExprKind::Lit(lb)) => literals_equal(la, lb),
52        (ExprKind::Member(ba, na), ExprKind::Member(bb, nb)) => {
53            na.name == nb.name && exprs_equal(ba, bb)
54        }
55        (ExprKind::Index(ba, ia), ExprKind::Index(bb, ib)) => {
56            exprs_equal(ba, bb)
57                && match (ia, ib) {
58                    (Some(ia), Some(ib)) => exprs_equal(ia, ib),
59                    (None, None) => true,
60                    _ => false,
61                }
62        }
63        (ExprKind::Binary(la, opa, ra), ExprKind::Binary(lb, opb, rb)) => {
64            opa.kind == opb.kind && exprs_equal(la, lb) && exprs_equal(ra, rb)
65        }
66        // Only casts to the *same* elementary type are pure conversions: `uint256(x) == uint8(x)`
67        // is not tautological because the narrower cast can truncate.
68        (ExprKind::Call(ca, args_a, _), ExprKind::Call(cb, args_b, _)) => {
69            matches!((cast_type(ca), cast_type(cb)), (Some(ea), Some(eb)) if ea == eb)
70                && args_a.len() == 1
71                && args_b.len() == 1
72                && args_a.exprs().zip(args_b.exprs()).all(|(ia, ib)| exprs_equal(ia, ib))
73        }
74        (ExprKind::Payable(a), ExprKind::Payable(b)) => exprs_equal(a, b),
75        (ExprKind::Unary(opa, a), ExprKind::Unary(opb, b)) => {
76            opa.kind == opb.kind && !opa.kind.has_side_effects() && exprs_equal(a, b)
77        }
78        (ExprKind::Ternary(ca, ta, fa), ExprKind::Ternary(cb, tb, fb)) => {
79            exprs_equal(ca, cb) && exprs_equal(ta, tb) && exprs_equal(fa, fb)
80        }
81        _ => false,
82    }
83}
84
85fn literals_equal(a: &Lit<'_>, b: &Lit<'_>) -> bool {
86    match (&a.kind, &b.kind) {
87        (LitKind::Str(ak, av, _), LitKind::Str(bk, bv, _)) => ak == bk && av == bv,
88        (LitKind::Number(a), LitKind::Number(b)) => a == b,
89        (LitKind::Rational(a), LitKind::Rational(b)) => a == b,
90        (LitKind::Address(a), LitKind::Address(b)) => a == b,
91        (LitKind::Bool(a), LitKind::Bool(b)) => a == b,
92        _ => false,
93    }
94}