cairo_lang_semantic/usage/
mod.rs

1//! Introduces [Usages], which is responsible for computing variables usage in semantic blocks\
2//! of a function.
3
4use cairo_lang_defs::ids::MemberId;
5use cairo_lang_proc_macros::DebugWithDb;
6use cairo_lang_utils::extract_matches;
7use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
8use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
9
10use crate::expr::fmt::ExprFormatter;
11use crate::expr::objects::Arenas;
12use crate::{
13    ConcreteStructId, Condition, Expr, ExprClosure, ExprFor, ExprFunctionCall, ExprFunctionCallArg,
14    ExprId, ExprLoop, ExprVarMemberPath, ExprWhile, FixedSizeArrayItems, FunctionBody, Parameter,
15    Pattern, PatternArena, PatternId, Statement, StatementBreak, StatementExpr, StatementLet,
16    StatementReturn, VarId,
17};
18
19#[cfg(test)]
20mod test;
21
22/// Member path (e.g. a.b.c). Unlike [ExprVarMemberPath], this is not an expression, and has no
23/// syntax pointers.
24#[derive(Clone, Debug, Hash, PartialEq, Eq, DebugWithDb)]
25#[debug_db(ExprFormatter<'db>)]
26pub enum MemberPath<'db> {
27    Var(VarId<'db>),
28    Member {
29        parent: Box<MemberPath<'db>>,
30        member_id: MemberId<'db>,
31        concrete_struct_id: ConcreteStructId<'db>,
32    },
33}
34impl<'db> MemberPath<'db> {
35    pub fn base_var(&self) -> VarId<'db> {
36        match self {
37            MemberPath::Var(var) => *var,
38            MemberPath::Member { parent, .. } => parent.base_var(),
39        }
40    }
41}
42impl<'db> From<&ExprVarMemberPath<'db>> for MemberPath<'db> {
43    fn from(value: &ExprVarMemberPath<'db>) -> Self {
44        match value {
45            ExprVarMemberPath::Var(expr) => MemberPath::Var(expr.var),
46            ExprVarMemberPath::Member { parent, member_id, concrete_struct_id, .. } => {
47                MemberPath::Member {
48                    parent: Box::new(parent.as_ref().into()),
49                    member_id: *member_id,
50                    concrete_struct_id: *concrete_struct_id,
51                }
52            }
53        }
54    }
55}
56
57/// Usages of variables and member paths in semantic code.
58#[derive(Clone, Debug, Default, DebugWithDb)]
59#[debug_db(ExprFormatter<'db>)]
60pub struct Usage<'db> {
61    /// Member paths that are read.
62    pub usage: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
63    /// Member paths that are assigned to.
64    pub changes: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
65    /// Member paths that are read as snapshots.
66    pub snap_usage: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
67    /// Variables that are defined.
68    pub introductions: OrderedHashSet<VarId<'db>>,
69    /// indicates that the expression has an early return.
70    pub has_early_return: bool,
71}
72
73impl<'db> Usage<'db> {
74    /// Adds the usage and changes from 'usage' to self, Ignoring `introductions`.
75    pub fn add_usage_and_changes(&mut self, usage: &Usage<'db>) {
76        for (path, expr) in usage.usage.iter() {
77            self.usage.insert(path.clone(), expr.clone());
78        }
79        for (path, expr) in usage.changes.iter() {
80            self.changes.insert(path.clone(), expr.clone());
81        }
82        for (path, expr) in usage.snap_usage.iter() {
83            self.snap_usage.insert(path.clone(), expr.clone());
84        }
85        self.has_early_return |= usage.has_early_return;
86    }
87
88    /// Removes usage that was introduced current block and usage that is already covered
89    /// by containing variables.
90    pub fn finalize_as_scope(&mut self) {
91        for (member_path, _) in self.usage.clone() {
92            // Prune introductions from usages.
93            if self.introductions.contains(&member_path.base_var()) {
94                self.usage.swap_remove(&member_path);
95                continue;
96            }
97
98            // Prune usages that are members of other usages.
99            let mut current_path = member_path.clone();
100            while let MemberPath::Member { parent, .. } = current_path {
101                current_path = *parent.clone();
102                if self.usage.contains_key(&current_path) {
103                    self.usage.swap_remove(&member_path);
104                    break;
105                }
106            }
107        }
108        for (member_path, _) in self.snap_usage.clone() {
109            // Prune usages from snap_usage.
110            if self.usage.contains_key(&member_path) {
111                self.snap_usage.swap_remove(&member_path);
112                continue;
113            }
114
115            // Prune introductions from snap_usage.
116            if self.introductions.contains(&member_path.base_var()) {
117                self.snap_usage.swap_remove(&member_path);
118            }
119
120            // Prune snap_usage that are members of other snap_usage or usages.
121            let mut current_path = member_path.clone();
122            while let MemberPath::Member { parent, .. } = current_path {
123                current_path = *parent.clone();
124                if self.snap_usage.contains_key(&current_path)
125                    | self.usage.contains_key(&current_path)
126                {
127                    self.snap_usage.swap_remove(&member_path);
128                    break;
129                }
130            }
131        }
132        for (member_path, _) in self.changes.clone() {
133            // Prune introductions from changes.
134            if self.introductions.contains(&member_path.base_var()) {
135                self.changes.swap_remove(&member_path);
136            }
137
138            // Prune changes that are members of other changes.
139            // Also if a child is changed and its parent is used, then we change the parent.
140            // TODO(TomerStarkware): Deconstruct the parent, and snap_use other members.
141            let mut current_path = member_path.clone();
142            while let MemberPath::Member { parent, .. } = current_path {
143                current_path = *parent.clone();
144                if self.snap_usage.contains_key(&current_path) {
145                    // Note that current_path must be top most usage as we prune snap_usage and
146                    // usage.
147                    if let Some(value) = self.snap_usage.swap_remove(&current_path) {
148                        self.usage.insert(current_path.clone(), value.clone());
149                        self.changes.insert(current_path.clone(), value);
150                    };
151                }
152                if self.changes.contains_key(&current_path) {
153                    self.changes.swap_remove(&member_path);
154                    break;
155                }
156            }
157        }
158    }
159}
160
161/// Usages of member paths in expressions of interest, currently loops and closures.
162#[derive(Debug, DebugWithDb)]
163#[debug_db(ExprFormatter<'db>)]
164pub struct Usages<'db> {
165    /// Mapping from an [ExprId] to its [Usage].
166    pub usages: OrderedHashMap<ExprId, Usage<'db>>,
167}
168impl<'db> Usages<'db> {
169    pub fn from_function_body(function_body: &FunctionBody<'db>) -> Self {
170        let mut current = Usage::default();
171        let mut usages = Self { usages: Default::default() };
172        usages.handle_expr(&function_body.arenas, function_body.body_expr, &mut current);
173        usages
174    }
175
176    pub fn handle_closure(
177        &mut self,
178        arenas: &Arenas<'db>,
179        param_ids: &[Parameter<'db>],
180        body: ExprId,
181    ) -> Usage<'db> {
182        let mut usage: Usage<'_> = Default::default();
183
184        usage.introductions.extend(param_ids.iter().map(|param| VarId::Param(param.id)));
185        self.handle_expr(arenas, body, &mut usage);
186        usage.finalize_as_scope();
187        usage
188    }
189
190    fn handle_expr(
191        &mut self,
192        arenas: &Arenas<'db>,
193        curr_expr_id: ExprId,
194        current: &mut Usage<'db>,
195    ) {
196        match &arenas.exprs[curr_expr_id] {
197            Expr::Tuple(expr) => {
198                for expr_id in &expr.items {
199                    self.handle_expr(arenas, *expr_id, current);
200                }
201            }
202            Expr::FixedSizeArray(expr) => match &expr.items {
203                FixedSizeArrayItems::Items(items) => {
204                    for expr_id in items {
205                        self.handle_expr(arenas, *expr_id, current);
206                    }
207                }
208                FixedSizeArrayItems::ValueAndSize(value, _) => {
209                    self.handle_expr(arenas, *value, current);
210                }
211            },
212            Expr::Snapshot(expr) => {
213                let expr_id = expr.inner;
214
215                match &arenas.exprs[expr_id] {
216                    Expr::Var(expr_var) => {
217                        current.snap_usage.insert(
218                            MemberPath::Var(expr_var.var),
219                            ExprVarMemberPath::Var(expr_var.clone()),
220                        );
221                    }
222                    Expr::MemberAccess(expr) => {
223                        if let Some(member_path) = &expr.member_path {
224                            current.snap_usage.insert(member_path.into(), member_path.clone());
225                        } else {
226                            self.handle_expr(arenas, expr.expr, current);
227                        }
228                    }
229                    _ => self.handle_expr(arenas, expr_id, current),
230                }
231            }
232            Expr::Desnap(expr) => self.handle_expr(arenas, expr.inner, current),
233            Expr::Assignment(expr) => {
234                self.handle_expr(arenas, expr.rhs, current);
235                current.usage.insert((&expr.ref_arg).into(), expr.ref_arg.clone());
236                current.changes.insert((&expr.ref_arg).into(), expr.ref_arg.clone());
237            }
238            Expr::LogicalOperator(expr) => {
239                self.handle_expr(arenas, expr.lhs, current);
240                self.handle_expr(arenas, expr.rhs, current);
241            }
242            Expr::Block(expr) => {
243                let mut usage = Default::default();
244                for stmt in &expr.statements {
245                    match &arenas.statements[*stmt] {
246                        Statement::Let(StatementLet {
247                            pattern,
248                            expr,
249                            else_clause,
250                            stable_ptr: _,
251                        }) => {
252                            self.handle_expr(arenas, *expr, &mut usage);
253                            Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
254
255                            if let Some(else_clause) = else_clause {
256                                self.handle_expr(arenas, *else_clause, &mut usage);
257                            }
258                        }
259                        Statement::Expr(StatementExpr { expr, stable_ptr: _ }) => {
260                            self.handle_expr(arenas, *expr, &mut usage)
261                        }
262                        Statement::Continue(_) => (),
263                        Statement::Return(StatementReturn { expr_option, stable_ptr: _ }) => {
264                            usage.has_early_return = true;
265                            if let Some(expr) = expr_option {
266                                self.handle_expr(arenas, *expr, &mut usage)
267                            };
268                        }
269                        Statement::Break(StatementBreak { expr_option, stable_ptr: _ }) => {
270                            if let Some(expr) = expr_option {
271                                self.handle_expr(arenas, *expr, &mut usage)
272                            };
273                        }
274                        Statement::Item(_) => {}
275                    };
276                }
277                if let Some(expr_id) = expr.tail {
278                    self.handle_expr(arenas, expr_id, &mut usage)
279                }
280                usage.finalize_as_scope();
281                current.add_usage_and_changes(&usage);
282            }
283            Expr::Loop(ExprLoop { body, ty: _, stable_ptr: _ }) => {
284                let mut usage = Default::default();
285                self.handle_expr(arenas, *body, &mut usage);
286                current.add_usage_and_changes(&usage);
287                self.usages.insert(curr_expr_id, usage);
288            }
289            Expr::While(ExprWhile { condition, body, stable_ptr: _, ty: _ }) => {
290                let mut usage = Default::default();
291                match condition {
292                    Condition::BoolExpr(expr) => {
293                        self.handle_expr(arenas, *expr, &mut usage);
294                    }
295                    Condition::Let(expr, patterns) => {
296                        self.handle_expr(arenas, *expr, &mut usage);
297                        for pattern in patterns {
298                            Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
299                        }
300                    }
301                }
302                self.handle_expr(arenas, *body, &mut usage);
303                usage.finalize_as_scope();
304                current.add_usage_and_changes(&usage);
305
306                self.usages.insert(curr_expr_id, usage);
307            }
308            Expr::For(ExprFor {
309                expr_id,
310                into_iter_member_path,
311                pattern,
312                body,
313                stable_ptr: _,
314                into_iter: _,
315                next_function_id: _,
316                ty: _,
317            }) => {
318                self.handle_expr(arenas, *expr_id, current);
319                current
320                    .introductions
321                    .insert(extract_matches!(into_iter_member_path, ExprVarMemberPath::Var).var);
322                let mut usage: Usage<'_> = Default::default();
323                usage.usage.insert(into_iter_member_path.into(), into_iter_member_path.clone());
324                usage.changes.insert(into_iter_member_path.into(), into_iter_member_path.clone());
325                Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
326                self.handle_expr(arenas, *body, &mut usage);
327                usage.finalize_as_scope();
328                current.add_usage_and_changes(&usage);
329                self.usages.insert(curr_expr_id, usage);
330            }
331            Expr::ExprClosure(ExprClosure { body, params, stable_ptr: _, ty: _ }) => {
332                let usage = self.handle_closure(arenas, params, *body);
333
334                current.add_usage_and_changes(&usage);
335                self.usages.insert(curr_expr_id, usage);
336            }
337            Expr::FunctionCall(ExprFunctionCall {
338                args,
339                function: _,
340                coupon_arg: _,
341                stable_ptr: _,
342                ty: _,
343            }) => {
344                for arg in args {
345                    match arg {
346                        ExprFunctionCallArg::Reference(member_path) => {
347                            current.usage.insert(member_path.into(), member_path.clone());
348                            current.changes.insert(member_path.into(), member_path.clone());
349                        }
350                        ExprFunctionCallArg::Value(expr) => {
351                            self.handle_expr(arenas, *expr, current)
352                        }
353                    }
354                }
355            }
356            Expr::Match(expr) => {
357                self.handle_expr(arenas, expr.matched_expr, current);
358                for arm in &expr.arms {
359                    for pattern in &arm.patterns {
360                        Self::handle_pattern(&arenas.patterns, *pattern, current);
361                    }
362                    self.handle_expr(arenas, arm.expression, current);
363                }
364            }
365            Expr::If(expr) => {
366                for condition in &expr.conditions {
367                    match condition {
368                        Condition::BoolExpr(expr) => {
369                            self.handle_expr(arenas, *expr, current);
370                        }
371                        Condition::Let(expr, patterns) => {
372                            self.handle_expr(arenas, *expr, current);
373                            for pattern in patterns {
374                                Self::handle_pattern(&arenas.patterns, *pattern, current);
375                            }
376                        }
377                    }
378                }
379
380                self.handle_expr(arenas, expr.if_block, current);
381                if let Some(expr) = expr.else_block {
382                    self.handle_expr(arenas, expr, current);
383                }
384            }
385            Expr::Var(expr) => {
386                current
387                    .usage
388                    .insert(MemberPath::Var(expr.var), ExprVarMemberPath::Var(expr.clone()));
389            }
390            Expr::Literal(_) | Expr::StringLiteral(_) => {}
391            Expr::MemberAccess(expr) => {
392                if let Some(member_path) = &expr.member_path {
393                    current.usage.insert(member_path.into(), member_path.clone());
394                } else {
395                    self.handle_expr(arenas, expr.expr, current);
396                }
397            }
398            Expr::StructCtor(expr) => {
399                for (expr_id, _) in &expr.members {
400                    self.handle_expr(arenas, *expr_id, current);
401                }
402                if let Some(base) = &expr.base_struct {
403                    self.handle_expr(arenas, *base, current);
404                }
405            }
406            Expr::EnumVariantCtor(expr) => self.handle_expr(arenas, expr.value_expr, current),
407            Expr::PropagateError(expr) => {
408                current.has_early_return = true;
409                self.handle_expr(arenas, expr.inner, current)
410            }
411            Expr::Constant(_) => {}
412            Expr::Missing(_) => {}
413        }
414    }
415
416    fn handle_pattern(arena: &PatternArena<'db>, pattern: PatternId, current: &mut Usage<'db>) {
417        let pattern = &arena[pattern];
418        match pattern {
419            Pattern::Literal(_) | Pattern::StringLiteral(_) => {}
420            Pattern::Variable(pattern) => {
421                current.introductions.insert(VarId::Local(pattern.var.id));
422            }
423            Pattern::Struct(pattern) => {
424                for (pattern, _) in &pattern.field_patterns {
425                    Self::handle_pattern(arena, *pattern, current);
426                }
427            }
428            Pattern::Tuple(pattern) => {
429                for pattern in &pattern.field_patterns {
430                    Self::handle_pattern(arena, *pattern, current);
431                }
432            }
433            Pattern::FixedSizeArray(pattern) => {
434                for pattern in &pattern.elements_patterns {
435                    Self::handle_pattern(arena, *pattern, current);
436                }
437            }
438            Pattern::EnumVariant(pattern) => {
439                if let Some(inner_pattern) = &pattern.inner_pattern {
440                    Self::handle_pattern(arena, *inner_pattern, current);
441                }
442            }
443            Pattern::Otherwise(_) => {}
444            Pattern::Missing(_) => {}
445        }
446    }
447}