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_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        // Prune introductions from usages.
92        for member_path in prune_and_get_candidates(&mut self.usage, |k| {
93            self.introductions.contains(&k.base_var())
94        }) {
95            // Prune usages that are members of other usages.
96            let mut current_path = &member_path;
97            while let MemberPath::Member { parent, .. } = current_path {
98                current_path = parent.as_ref();
99                if self.usage.contains_key(current_path) {
100                    self.usage.swap_remove(&member_path);
101                    break;
102                }
103            }
104        }
105        // Prune usages and introdictions from snap_usage.
106        for member_path in prune_and_get_candidates(&mut self.snap_usage, |k| {
107            self.usage.contains_key(k) || self.introductions.contains(&k.base_var())
108        }) {
109            // Prune snap_usage that are members of other snap_usage or usages.
110            let mut current_path = &member_path;
111            while let MemberPath::Member { parent, .. } = current_path {
112                current_path = parent.as_ref();
113                if self.snap_usage.contains_key(current_path)
114                    | self.usage.contains_key(current_path)
115                {
116                    self.snap_usage.swap_remove(&member_path);
117                    break;
118                }
119            }
120        }
121        // Prune introductions from changes.
122        for member_path in prune_and_get_candidates(&mut self.changes, |k| {
123            self.introductions.contains(&k.base_var())
124        }) {
125            // Prune changes that are members of other changes.
126            // Also if a child is changed and its parent is used, then we change the parent.
127            // TODO(TomerStarkware): Deconstruct the parent, and snap_use other members.
128            let mut current_path = &member_path;
129            while let MemberPath::Member { parent, .. } = current_path {
130                current_path = parent.as_ref();
131                if self.snap_usage.contains_key(current_path) {
132                    // Note that current_path must be top most usage as we prune snap_usage and
133                    // usage.
134                    if let Some(value) = self.snap_usage.swap_remove(current_path) {
135                        self.usage.insert(current_path.clone(), value.clone());
136                        self.changes.insert(current_path.clone(), value);
137                    };
138                }
139                if self.changes.contains_key(current_path) {
140                    self.changes.swap_remove(&member_path);
141                    break;
142                }
143            }
144        }
145    }
146}
147
148/// Prunes the map by removing entries for which `filter` returns true.
149/// From the remaining entries, collects the `MemberPath::Member` variants and returns them.
150fn prune_and_get_candidates<'db>(
151    map: &mut OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
152    filter: impl Fn(&MemberPath<'db>) -> bool,
153) -> Vec<MemberPath<'db>> {
154    let mut candidates = Vec::new();
155    map.retain(|k, _| {
156        if filter(k) {
157            return false;
158        }
159        if matches!(k, MemberPath::Member { .. }) {
160            candidates.push(k.clone());
161        }
162        true
163    });
164    candidates
165}
166
167/// Usages of member paths in expressions of interest, currently loops and closures.
168#[derive(Debug, DebugWithDb)]
169#[debug_db(ExprFormatter<'db>)]
170pub struct Usages<'db> {
171    /// Mapping from an [ExprId] to its [Usage].
172    pub usages: OrderedHashMap<ExprId, Usage<'db>>,
173}
174impl<'db> Usages<'db> {
175    pub fn from_function_body(function_body: &FunctionBody<'db>) -> Self {
176        let mut current = Usage::default();
177        let mut usages = Self { usages: Default::default() };
178        usages.handle_expr(&function_body.arenas, function_body.body_expr, &mut current);
179        usages
180    }
181
182    pub fn handle_closure(
183        &mut self,
184        arenas: &Arenas<'db>,
185        param_ids: &[Parameter<'db>],
186        body: ExprId,
187    ) -> Usage<'db> {
188        let mut usage: Usage<'_> = Default::default();
189
190        usage.introductions.extend(param_ids.iter().map(|param| VarId::Param(param.id)));
191        self.handle_expr(arenas, body, &mut usage);
192        usage.finalize_as_scope();
193        usage
194    }
195
196    fn handle_expr(
197        &mut self,
198        arenas: &Arenas<'db>,
199        curr_expr_id: ExprId,
200        current: &mut Usage<'db>,
201    ) {
202        match &arenas.exprs[curr_expr_id] {
203            Expr::Tuple(expr) => {
204                for expr_id in &expr.items {
205                    self.handle_expr(arenas, *expr_id, current);
206                }
207            }
208            Expr::FixedSizeArray(expr) => match &expr.items {
209                FixedSizeArrayItems::Items(items) => {
210                    for expr_id in items {
211                        self.handle_expr(arenas, *expr_id, current);
212                    }
213                }
214                FixedSizeArrayItems::ValueAndSize(value, _) => {
215                    self.handle_expr(arenas, *value, current);
216                }
217            },
218            Expr::Snapshot(expr) => {
219                let expr_id = expr.inner;
220
221                match &arenas.exprs[expr_id] {
222                    Expr::Var(expr_var) => {
223                        current.snap_usage.insert(
224                            MemberPath::Var(expr_var.var),
225                            ExprVarMemberPath::Var(expr_var.clone()),
226                        );
227                    }
228                    Expr::MemberAccess(expr) => {
229                        if let Some(member_path) = &expr.member_path {
230                            current.snap_usage.insert(member_path.into(), member_path.clone());
231                        } else {
232                            self.handle_expr(arenas, expr.expr, current);
233                        }
234                    }
235                    _ => self.handle_expr(arenas, expr_id, current),
236                }
237            }
238            Expr::Desnap(expr) => self.handle_expr(arenas, expr.inner, current),
239            Expr::Assignment(expr) => {
240                self.handle_expr(arenas, expr.rhs, current);
241                current.usage.insert((&expr.ref_arg).into(), expr.ref_arg.clone());
242                current.changes.insert((&expr.ref_arg).into(), expr.ref_arg.clone());
243            }
244            Expr::LogicalOperator(expr) => {
245                self.handle_expr(arenas, expr.lhs, current);
246                self.handle_expr(arenas, expr.rhs, current);
247            }
248            Expr::Block(expr) => {
249                let mut usage = Default::default();
250                for stmt in &expr.statements {
251                    match &arenas.statements[*stmt] {
252                        Statement::Let(StatementLet {
253                            pattern,
254                            expr,
255                            else_clause,
256                            stable_ptr: _,
257                        }) => {
258                            self.handle_expr(arenas, *expr, &mut usage);
259                            Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
260
261                            if let Some(else_clause) = else_clause {
262                                self.handle_expr(arenas, *else_clause, &mut usage);
263                            }
264                        }
265                        Statement::Expr(StatementExpr { expr, stable_ptr: _ }) => {
266                            self.handle_expr(arenas, *expr, &mut usage)
267                        }
268                        Statement::Continue(_) => (),
269                        Statement::Return(StatementReturn { expr_option, stable_ptr: _ }) => {
270                            usage.has_early_return = true;
271                            if let Some(expr) = expr_option {
272                                self.handle_expr(arenas, *expr, &mut usage)
273                            };
274                        }
275                        Statement::Break(StatementBreak { expr_option, stable_ptr: _ }) => {
276                            if let Some(expr) = expr_option {
277                                self.handle_expr(arenas, *expr, &mut usage)
278                            };
279                        }
280                        Statement::Item(_) => {}
281                    };
282                }
283                if let Some(expr_id) = expr.tail {
284                    self.handle_expr(arenas, expr_id, &mut usage)
285                }
286                usage.finalize_as_scope();
287                current.add_usage_and_changes(&usage);
288            }
289            Expr::Loop(ExprLoop { body, ty: _, stable_ptr: _ }) => {
290                let mut usage = Default::default();
291                self.handle_expr(arenas, *body, &mut usage);
292                current.add_usage_and_changes(&usage);
293                self.usages.insert(curr_expr_id, usage);
294            }
295            Expr::While(ExprWhile { condition, body, stable_ptr: _, ty: _ }) => {
296                let mut usage = Default::default();
297                match condition {
298                    Condition::BoolExpr(expr) => {
299                        self.handle_expr(arenas, *expr, &mut usage);
300                    }
301                    Condition::Let(expr, patterns) => {
302                        self.handle_expr(arenas, *expr, &mut usage);
303                        for pattern in patterns {
304                            Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
305                        }
306                    }
307                }
308                self.handle_expr(arenas, *body, &mut usage);
309                usage.finalize_as_scope();
310                current.add_usage_and_changes(&usage);
311
312                self.usages.insert(curr_expr_id, usage);
313            }
314            Expr::For(ExprFor {
315                expr_id,
316                into_iter_member_path,
317                pattern,
318                body,
319                stable_ptr: _,
320                into_iter: _,
321                next_function_id: _,
322                ty: _,
323            }) => {
324                self.handle_expr(arenas, *expr_id, current);
325                current
326                    .introductions
327                    .insert(extract_matches!(into_iter_member_path, ExprVarMemberPath::Var).var);
328                let mut usage: Usage<'_> = Default::default();
329                usage.usage.insert(into_iter_member_path.into(), into_iter_member_path.clone());
330                usage.changes.insert(into_iter_member_path.into(), into_iter_member_path.clone());
331                Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
332                self.handle_expr(arenas, *body, &mut usage);
333                usage.finalize_as_scope();
334                current.add_usage_and_changes(&usage);
335                self.usages.insert(curr_expr_id, usage);
336            }
337            Expr::ExprClosure(ExprClosure { body, params, stable_ptr: _, ty: _ }) => {
338                let usage = self.handle_closure(arenas, params, *body);
339
340                current.add_usage_and_changes(&usage);
341                self.usages.insert(curr_expr_id, usage);
342            }
343            Expr::FunctionCall(ExprFunctionCall {
344                args,
345                function: _,
346                coupon_arg: _,
347                stable_ptr: _,
348                ty: _,
349            }) => {
350                for arg in args {
351                    match arg {
352                        ExprFunctionCallArg::Reference(member_path) => {
353                            current.usage.insert(member_path.into(), member_path.clone());
354                            current.changes.insert(member_path.into(), member_path.clone());
355                        }
356                        ExprFunctionCallArg::Value(expr)
357                        | ExprFunctionCallArg::TempReference(expr) => {
358                            self.handle_expr(arenas, *expr, current)
359                        }
360                    }
361                }
362            }
363            Expr::Match(expr) => {
364                self.handle_expr(arenas, expr.matched_expr, current);
365                for arm in &expr.arms {
366                    for pattern in &arm.patterns {
367                        Self::handle_pattern(&arenas.patterns, *pattern, current);
368                    }
369                    self.handle_expr(arenas, arm.expression, current);
370                }
371            }
372            Expr::If(expr) => {
373                for condition in &expr.conditions {
374                    match condition {
375                        Condition::BoolExpr(expr) => {
376                            self.handle_expr(arenas, *expr, current);
377                        }
378                        Condition::Let(expr, patterns) => {
379                            self.handle_expr(arenas, *expr, current);
380                            for pattern in patterns {
381                                Self::handle_pattern(&arenas.patterns, *pattern, current);
382                            }
383                        }
384                    }
385                }
386
387                self.handle_expr(arenas, expr.if_block, current);
388                if let Some(expr) = expr.else_block {
389                    self.handle_expr(arenas, expr, current);
390                }
391            }
392            Expr::Var(expr) => {
393                current
394                    .usage
395                    .insert(MemberPath::Var(expr.var), ExprVarMemberPath::Var(expr.clone()));
396            }
397            Expr::Literal(_) | Expr::StringLiteral(_) => {}
398            Expr::MemberAccess(expr) => {
399                if let Some(member_path) = &expr.member_path {
400                    current.usage.insert(member_path.into(), member_path.clone());
401                } else {
402                    self.handle_expr(arenas, expr.expr, current);
403                }
404            }
405            Expr::StructCtor(expr) => {
406                for (expr_id, _) in &expr.members {
407                    self.handle_expr(arenas, *expr_id, current);
408                }
409                if let Some(base) = &expr.base_struct {
410                    self.handle_expr(arenas, *base, current);
411                }
412            }
413            Expr::EnumVariantCtor(expr) => self.handle_expr(arenas, expr.value_expr, current),
414            Expr::PropagateError(expr) => {
415                current.has_early_return = true;
416                self.handle_expr(arenas, expr.inner, current)
417            }
418            Expr::Constant(_) => {}
419            Expr::Missing(_) => {}
420        }
421    }
422
423    fn handle_pattern(arena: &PatternArena<'db>, pattern: PatternId, current: &mut Usage<'db>) {
424        let pattern = &arena[pattern];
425        match pattern {
426            Pattern::Literal(_) | Pattern::StringLiteral(_) => {}
427            Pattern::Variable(pattern) => {
428                current.introductions.insert(VarId::Local(pattern.var.id));
429            }
430            Pattern::Struct(pattern) => {
431                for (pattern, _) in &pattern.field_patterns {
432                    Self::handle_pattern(arena, *pattern, current);
433                }
434            }
435            Pattern::Tuple(pattern) => {
436                for pattern in &pattern.field_patterns {
437                    Self::handle_pattern(arena, *pattern, current);
438                }
439            }
440            Pattern::FixedSizeArray(pattern) => {
441                for pattern in &pattern.elements_patterns {
442                    Self::handle_pattern(arena, *pattern, current);
443                }
444            }
445            Pattern::EnumVariant(pattern) => {
446                if let Some(inner_pattern) = &pattern.inner_pattern {
447                    Self::handle_pattern(arena, *inner_pattern, current);
448                }
449            }
450            Pattern::Otherwise(_) => {}
451            Pattern::Missing(_) => {}
452        }
453    }
454}