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