Skip to main content

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