1use super::TautologicalCompare;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::{
7 ast::{BinOpKind, Lit, LitKind},
8 sema::{
9 Gcx,
10 hir::{self, ElementaryType, Expr, ExprKind, TypeKind},
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<'hir> LateLintPass<'hir> for TautologicalCompare {
23 fn check_expr(
24 &mut self,
25 ctx: &LintContext,
26 gcx: Gcx<'hir>,
27 _hir: &'hir hir::Hir<'hir>,
28 expr: &'hir hir::Expr<'hir>,
29 ) {
30 if let ExprKind::Binary(left, op, right) = &expr.kind
31 && matches!(
32 op.kind,
33 BinOpKind::Lt
34 | BinOpKind::Le
35 | BinOpKind::Gt
36 | BinOpKind::Ge
37 | BinOpKind::Eq
38 | BinOpKind::Ne
39 )
40 && exprs_equal(left, right)
41 && !operand_is_udvt(gcx, left)
42 {
43 ctx.emit(&TAUTOLOGICAL_COMPARE, expr.span);
44 }
45 }
46}
47
48fn operand_is_udvt<'hir>(gcx: Gcx<'hir>, expr: &Expr<'hir>) -> bool {
56 gcx.type_of_expr(expr.peel_parens().id)
57 .is_some_and(|ty| matches!(ty.peel_refs().kind, TyKind::Udvt(..)))
58}
59
60fn exprs_equal<'hir>(a: &Expr<'hir>, b: &Expr<'hir>) -> bool {
67 match (&a.peel_parens().kind, &b.peel_parens().kind) {
68 (ExprKind::Ident(ra), ExprKind::Ident(rb)) => ra == rb,
69 (ExprKind::Lit(la), ExprKind::Lit(lb)) => literals_equal(la, lb),
70 (ExprKind::Member(ba, na), ExprKind::Member(bb, nb)) => {
71 na.name == nb.name && exprs_equal(ba, bb)
72 }
73 (ExprKind::Index(ba, ia), ExprKind::Index(bb, ib)) => {
74 exprs_equal(ba, bb) && opt_exprs_equal(*ia, *ib)
75 }
76 (ExprKind::Binary(la, opa, ra), ExprKind::Binary(lb, opb, rb)) => {
79 opa.kind == opb.kind && exprs_equal(la, lb) && exprs_equal(ra, rb)
80 }
81 (ExprKind::Call(ca, args_a, _), ExprKind::Call(cb, args_b, _)) => {
87 match (cast_elem_type(ca), cast_elem_type(cb)) {
88 (Some(ea), Some(eb)) if ea == eb => {
89 args_a.len() == 1
90 && args_b.len() == 1
91 && match (args_a.exprs().next(), args_b.exprs().next()) {
92 (Some(ia), Some(ib)) => exprs_equal(ia, ib),
93 _ => false,
94 }
95 }
96 _ => false,
97 }
98 }
99 (ExprKind::Payable(a), ExprKind::Payable(b)) => exprs_equal(a, b),
102 (ExprKind::Unary(opa, a), ExprKind::Unary(opb, b)) => {
105 opa.kind == opb.kind && !opa.kind.has_side_effects() && exprs_equal(a, b)
106 }
107 (ExprKind::Ternary(ca, ta, fa), ExprKind::Ternary(cb, tb, fb)) => {
109 exprs_equal(ca, cb) && exprs_equal(ta, tb) && exprs_equal(fa, fb)
110 }
111 _ => false,
112 }
113}
114
115fn cast_elem_type<'a>(callee: &'a Expr<'_>) -> Option<&'a ElementaryType> {
118 match &callee.peel_parens().kind {
119 ExprKind::Type(hir::Type { kind: TypeKind::Elementary(e), .. }) => Some(e),
120 _ => None,
121 }
122}
123
124fn literals_equal(a: &Lit<'_>, b: &Lit<'_>) -> bool {
125 match (&a.kind, &b.kind) {
126 (LitKind::Str(ak, av, _), LitKind::Str(bk, bv, _)) => ak == bk && av == bv,
127 (LitKind::Number(a), LitKind::Number(b)) => a == b,
128 (LitKind::Rational(a), LitKind::Rational(b)) => a == b,
129 (LitKind::Address(a), LitKind::Address(b)) => a == b,
130 (LitKind::Bool(a), LitKind::Bool(b)) => a == b,
131 _ => false,
132 }
133}
134
135fn opt_exprs_equal<'hir>(a: Option<&Expr<'hir>>, b: Option<&Expr<'hir>>) -> bool {
136 match (a, b) {
137 (Some(a), Some(b)) => exprs_equal(a, b),
138 (None, None) => true,
139 _ => false,
140 }
141}