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 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
46fn 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 (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}