Skip to main content

cairo_lint/lints/performance/
inefficient_while_comp.rs

1use cairo_lang_defs::ids::ModuleItemId;
2use cairo_lang_defs::plugin::PluginDiagnostic;
3use cairo_lang_diagnostics::Severity;
4use cairo_lang_semantic::{Arenas, Condition, Expr, ExprWhile};
5
6use crate::context::{CairoLintKind, Lint};
7
8use crate::queries::{get_all_function_bodies, get_all_while_expressions};
9use salsa::Database;
10
11pub struct InefficientWhileComparison;
12
13/// ## What it does
14///
15/// Checks if the while loop exit condition is using relational (`<`, `<=`, `>=`, `>`) operators.
16///
17/// ## Example
18///
19/// ```cairo
20/// fn main() {
21///     let mut a = 1_u32;
22///     while a <= 10 {
23///         a += 1;
24///     }
25/// }
26/// ```
27///
28/// Can be optimized to:
29///
30/// ```cairo
31/// fn main() {
32///     let mut a = 1_u32;
33///     while a != 10 {
34///         a += 1;
35///     }
36/// }
37/// ```
38impl Lint for InefficientWhileComparison {
39    fn allowed_name(&self) -> &'static str {
40        "inefficient_while_comp"
41    }
42
43    fn diagnostic_message(&self) -> &'static str {
44        "using [`<`, `<=`, `>=`, `>`] exit conditions is inefficient. Consider \
45                                              switching to `!=` or using ArrayTrait::multi_pop_front."
46    }
47
48    fn kind(&self) -> CairoLintKind {
49        CairoLintKind::Performance
50    }
51
52    fn is_enabled(&self) -> bool {
53        false
54    }
55}
56
57// Match all types implementing PartialOrd
58const PARTIAL_ORD_PATTERNS: [&str; 4] = [
59    "PartialOrd::lt\"",
60    "PartialOrd::le\"",
61    "PartialOrd::gt\"",
62    "PartialOrd::ge\"",
63];
64
65#[tracing::instrument(skip_all, level = "trace")]
66pub fn check_inefficient_while_comp<'db>(
67    db: &'db dyn Database,
68    item: &ModuleItemId<'db>,
69    diagnostics: &mut Vec<PluginDiagnostic<'db>>,
70) {
71    let function_bodies = get_all_function_bodies(db, item);
72    for function_body in function_bodies {
73        let while_exprs = get_all_while_expressions(function_body);
74        let arenas = &function_body.arenas;
75        for while_expr in while_exprs.iter() {
76            check_single_inefficient_while_comp(db, while_expr, diagnostics, arenas);
77        }
78    }
79}
80
81fn check_single_inefficient_while_comp<'db>(
82    db: &'db dyn Database,
83    while_expr: &ExprWhile<'db>,
84    diagnostics: &mut Vec<PluginDiagnostic<'db>>,
85    arenas: &Arenas<'db>,
86) {
87    // It might be a false positive, because there can be cases when:
88    //  - The rhs arguments is changed in the loop body
89    //  - The lhs argument can "skip" the moment where lhs == rhs
90    if let Condition::BoolExpr(expr_cond) = while_expr.condition {
91        check_expression(db, &arenas.exprs[expr_cond], diagnostics, arenas);
92    }
93}
94
95fn check_expression<'db>(
96    db: &'db dyn Database,
97    expr: &Expr<'db>,
98    diagnostics: &mut Vec<PluginDiagnostic<'db>>,
99    arenas: &Arenas<'db>,
100) {
101    match expr {
102        Expr::FunctionCall(func_call) => {
103            let func_name = func_call.function.name(db);
104            if PARTIAL_ORD_PATTERNS.iter().any(|p| func_name.ends_with(p)) {
105                diagnostics.push(PluginDiagnostic {
106                    stable_ptr: func_call.stable_ptr.into(),
107                    message: InefficientWhileComparison.diagnostic_message().to_owned(),
108                    severity: Severity::Warning,
109                    inner_span: None,
110                    error_code: None,
111                });
112            }
113        }
114        Expr::LogicalOperator(expr_logical) => {
115            check_expression(db, &arenas.exprs[expr_logical.lhs], diagnostics, arenas);
116            check_expression(db, &arenas.exprs[expr_logical.rhs], diagnostics, arenas);
117        }
118        _ => {}
119    }
120}