1use 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#[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#[derive(Clone, Debug, Default, DebugWithDb)]
59#[debug_db(ExprFormatter<'db>)]
60pub struct Usage<'db> {
61 pub usage: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
63 pub changes: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
65 pub snap_usage: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
67 pub introductions: OrderedHashSet<VarId<'db>>,
69 pub has_early_return: bool,
71}
72
73impl<'db> Usage<'db> {
74 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 pub fn finalize_as_scope(&mut self) {
91 for member_path in prune_and_get_candidates(&mut self.usage, |k| {
93 self.introductions.contains(&k.base_var())
94 }) {
95 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 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 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 for member_path in prune_and_get_candidates(&mut self.changes, |k| {
123 self.introductions.contains(&k.base_var())
124 }) {
125 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 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
148fn 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#[derive(Debug, DebugWithDb)]
169#[debug_db(ExprFormatter<'db>)]
170pub struct Usages<'db> {
171 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}