1use 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#[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#[derive(Clone, Debug, Default, DebugWithDb)]
50#[debug_db(ExprFormatter<'db>)]
51pub struct Usage<'db> {
52 pub usage: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
54 pub changes: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
56 pub snap_usage: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
58 pub introductions: OrderedHashSet<VarId<'db>>,
60 pub has_early_return: bool,
62}
63
64impl<'db> Usage<'db> {
65 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 pub fn finalize_as_scope(&mut self) {
82 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 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 for member_path in prune_and_get_candidates(&mut self.changes, |k| {
101 self.introductions.contains(&k.base_var())
102 }) {
103 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 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 for member_path in prune_and_get_candidates(&mut self.usage, |k| {
128 self.introductions.contains(&k.base_var())
129 }) {
130 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
143fn 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#[derive(Debug, DebugWithDb)]
164#[debug_db(ExprFormatter<'db>)]
165pub struct Usages<'db> {
166 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 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}