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