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
189 }
190
191 fn handle_expr(
192 &mut self,
193 arenas: &Arenas<'db>,
194 curr_expr_id: ExprId,
195 current: &mut Usage<'db>,
196 ) {
197 match &arenas.exprs[curr_expr_id] {
198 Expr::Tuple(expr) => {
199 for expr_id in &expr.items {
200 self.handle_expr(arenas, *expr_id, current);
201 }
202 }
203 Expr::FixedSizeArray(expr) => match &expr.items {
204 FixedSizeArrayItems::Items(items) => {
205 for expr_id in items {
206 self.handle_expr(arenas, *expr_id, current);
207 }
208 }
209 FixedSizeArrayItems::ValueAndSize(value, _) => {
210 self.handle_expr(arenas, *value, current);
211 }
212 },
213 Expr::Snapshot(expr) => {
214 let expr_id = expr.inner;
215
216 match &arenas.exprs[expr_id] {
217 Expr::Var(expr_var) => {
218 current.snap_usage.insert(
219 MemberPath::Var(expr_var.var),
220 ExprVarMemberPath::Var(expr_var.clone()),
221 );
222 }
223 Expr::MemberAccess(expr) => {
224 if let Some(member_path) = &expr.member_path {
225 current.snap_usage.insert(member_path.into(), member_path.clone());
226 } else {
227 self.handle_expr(arenas, expr.expr, current);
228 }
229 }
230 _ => self.handle_expr(arenas, expr_id, current),
231 }
232 }
233 Expr::Desnap(expr) => self.handle_expr(arenas, expr.inner, current),
234 Expr::Assignment(expr) => {
235 self.handle_expr(arenas, expr.rhs, current);
236 current.usage.insert((&expr.ref_arg).into(), expr.ref_arg.clone());
237 current.changes.insert((&expr.ref_arg).into(), expr.ref_arg.clone());
238 }
239 Expr::LogicalOperator(expr) => {
240 self.handle_expr(arenas, expr.lhs, current);
241 self.handle_expr(arenas, expr.rhs, current);
242 }
243 Expr::Block(expr) => {
244 let mut usage = Default::default();
245 for stmt in &expr.statements {
246 match &arenas.statements[*stmt] {
247 Statement::Let(StatementLet {
248 pattern,
249 expr,
250 else_clause,
251 stable_ptr: _,
252 }) => {
253 self.handle_expr(arenas, *expr, &mut usage);
254 Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
255
256 if let Some(else_clause) = else_clause {
257 self.handle_expr(arenas, *else_clause, &mut usage);
258 }
259 }
260 Statement::Expr(StatementExpr { expr, stable_ptr: _ }) => {
261 self.handle_expr(arenas, *expr, &mut usage)
262 }
263 Statement::Continue(_) => (),
264 Statement::Return(StatementReturn { expr_option, stable_ptr: _ }) => {
265 usage.has_early_return = true;
266 if let Some(expr) = expr_option {
267 self.handle_expr(arenas, *expr, &mut usage)
268 };
269 }
270 Statement::Break(StatementBreak { expr_option, stable_ptr: _ }) => {
271 if let Some(expr) = expr_option {
272 self.handle_expr(arenas, *expr, &mut usage)
273 };
274 }
275 Statement::Item(_) => {}
276 };
277 }
278 if let Some(expr_id) = expr.tail {
279 self.handle_expr(arenas, expr_id, &mut usage)
280 }
281 usage.finalize_as_scope();
282 current.add_usage_and_changes(&usage);
283 }
284 Expr::Loop(ExprLoop { body, ty: _, stable_ptr: _ }) => {
285 let mut usage = Default::default();
286 self.handle_expr(arenas, *body, &mut usage);
287 current.add_usage_and_changes(&usage);
288 self.usages.insert(curr_expr_id, usage);
289 }
290 Expr::While(ExprWhile { condition, body, stable_ptr: _, ty: _ }) => {
291 let mut usage = Default::default();
292 match condition {
293 Condition::BoolExpr(expr) => {
294 self.handle_expr(arenas, *expr, &mut usage);
295 }
296 Condition::Let(expr, patterns) => {
297 self.handle_expr(arenas, *expr, &mut usage);
298 for pattern in patterns {
299 Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
300 }
301 }
302 }
303 self.handle_expr(arenas, *body, &mut usage);
304 usage.finalize_as_scope();
305 current.add_usage_and_changes(&usage);
306
307 self.usages.insert(curr_expr_id, usage);
308 }
309 Expr::For(ExprFor {
310 expr_id,
311 into_iter_member_path,
312 pattern,
313 body,
314 stable_ptr: _,
315 into_iter: _,
316 next_function_id: _,
317 ty: _,
318 }) => {
319 self.handle_expr(arenas, *expr_id, current);
320 current
321 .introductions
322 .insert(extract_matches!(into_iter_member_path, ExprVarMemberPath::Var).var);
323 let mut usage: Usage<'_> = Default::default();
324 usage.usage.insert(into_iter_member_path.into(), into_iter_member_path.clone());
325 usage.changes.insert(into_iter_member_path.into(), into_iter_member_path.clone());
326 Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
327 self.handle_expr(arenas, *body, &mut usage);
328 usage.finalize_as_scope();
329 current.add_usage_and_changes(&usage);
330 self.usages.insert(curr_expr_id, usage);
331 }
332 Expr::ExprClosure(ExprClosure { body, params, stable_ptr: _, ty: _ }) => {
333 let usage = self.handle_closure(arenas, params, *body);
334
335 current.add_usage_and_changes(&usage);
336 self.usages.insert(curr_expr_id, usage);
337 }
338 Expr::FunctionCall(ExprFunctionCall {
339 args,
340 function: _,
341 coupon_arg: _,
342 stable_ptr: _,
343 ty: _,
344 }) => {
345 for arg in args {
346 match arg {
347 ExprFunctionCallArg::Reference(member_path) => {
348 current.usage.insert(member_path.into(), member_path.clone());
349 current.changes.insert(member_path.into(), member_path.clone());
350 }
351 ExprFunctionCallArg::Value(expr)
352 | ExprFunctionCallArg::TempReference(expr) => {
353 self.handle_expr(arenas, *expr, current)
354 }
355 }
356 }
357 }
358 Expr::Match(expr) => {
359 self.handle_expr(arenas, expr.matched_expr, current);
360 for arm in &expr.arms {
361 for pattern in &arm.patterns {
362 Self::handle_pattern(&arenas.patterns, *pattern, current);
363 }
364 self.handle_expr(arenas, arm.expression, current);
365 }
366 }
367 Expr::If(expr) => {
368 for condition in &expr.conditions {
369 match condition {
370 Condition::BoolExpr(expr) => {
371 self.handle_expr(arenas, *expr, current);
372 }
373 Condition::Let(expr, patterns) => {
374 self.handle_expr(arenas, *expr, current);
375 for pattern in patterns {
376 Self::handle_pattern(&arenas.patterns, *pattern, current);
377 }
378 }
379 }
380 }
381
382 self.handle_expr(arenas, expr.if_block, current);
383 if let Some(expr) = expr.else_block {
384 self.handle_expr(arenas, expr, current);
385 }
386 }
387 Expr::Var(expr) => {
388 current
389 .usage
390 .insert(MemberPath::Var(expr.var), ExprVarMemberPath::Var(expr.clone()));
391 }
392 Expr::Literal(_) | Expr::StringLiteral(_) => {}
393 Expr::MemberAccess(expr) => {
394 if let Some(member_path) = &expr.member_path {
395 current.usage.insert(member_path.into(), member_path.clone());
396 } else {
397 self.handle_expr(arenas, expr.expr, current);
398 }
399 }
400 Expr::StructCtor(expr) => {
401 for (expr_id, _) in &expr.members {
402 self.handle_expr(arenas, *expr_id, current);
403 }
404 if let Some(base) = &expr.base_struct {
405 self.handle_expr(arenas, *base, current);
406 }
407 }
408 Expr::EnumVariantCtor(expr) => self.handle_expr(arenas, expr.value_expr, current),
409 Expr::PropagateError(expr) => {
410 current.has_early_return = true;
411 self.handle_expr(arenas, expr.inner, current)
412 }
413 Expr::Constant(_) => {}
414 Expr::Missing(_) => {}
415 }
416 }
417
418 fn handle_pattern(arena: &PatternArena<'db>, pattern: PatternId, current: &mut Usage<'db>) {
419 let pattern = &arena[pattern];
420 match pattern {
421 Pattern::Literal(_) | Pattern::StringLiteral(_) => {}
422 Pattern::Variable(pattern) => {
423 current.introductions.insert(VarId::Local(pattern.var.id));
424 }
425 Pattern::Struct(pattern) => {
426 for (pattern, _) in &pattern.field_patterns {
427 Self::handle_pattern(arena, *pattern, current);
428 }
429 }
430 Pattern::Tuple(pattern) => {
431 for pattern in &pattern.field_patterns {
432 Self::handle_pattern(arena, *pattern, current);
433 }
434 }
435 Pattern::FixedSizeArray(pattern) => {
436 for pattern in &pattern.elements_patterns {
437 Self::handle_pattern(arena, *pattern, current);
438 }
439 }
440 Pattern::EnumVariant(pattern) => {
441 if let Some(inner_pattern) = &pattern.inner_pattern {
442 Self::handle_pattern(arena, *inner_pattern, current);
443 }
444 }
445 Pattern::Otherwise(_) => {}
446 Pattern::Missing(_) => {}
447 }
448 }
449}