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