1use std::collections::HashSet;
6
7use super::*;
8use assura_ast::{ExprFolder, fold_arg_list, fold_joined, literal_to_string};
9
10pub(crate) const RESULT_VAR: &str = "__assura_result";
15
16pub(crate) const OLD_VAR_PREFIX: &str = "__assura_old_";
18
19fn has_u128_literal(expr: &SpExpr) -> bool {
22 match &expr.node {
23 Expr::Literal(Literal::Int(s)) => s.parse::<i128>().is_err() && s.parse::<u128>().is_ok(),
24 Expr::BinOp { lhs, rhs, .. } => has_u128_literal(lhs) || has_u128_literal(rhs),
25 Expr::UnaryOp { expr: e, .. }
26 | Expr::Old(e)
27 | Expr::Cast { expr: e, .. }
28 | Expr::Field(e, _) => has_u128_literal(e),
29 _ => false,
30 }
31}
32
33fn has_float_expr(expr: &SpExpr, float_vars: &HashSet<String>) -> bool {
37 match &expr.node {
38 Expr::Literal(Literal::Float(_)) => true,
39 Expr::Ident(name) => float_vars.contains(name.as_str()),
40 Expr::BinOp { lhs, rhs, .. } => {
41 has_float_expr(lhs, float_vars) || has_float_expr(rhs, float_vars)
42 }
43 Expr::UnaryOp { expr: e, .. }
44 | Expr::Old(e)
45 | Expr::Cast { expr: e, .. }
46 | Expr::Field(e, _) => has_float_expr(e, float_vars),
47 Expr::MethodCall { receiver, args, .. } => {
48 has_float_expr(receiver, float_vars)
49 || args.iter().any(|a| has_float_expr(a, float_vars))
50 }
51 Expr::Call { args, .. } => args.iter().any(|a| has_float_expr(a, float_vars)),
52 Expr::Let { body, .. } => has_float_expr(body, float_vars),
53 Expr::If {
54 then_branch,
55 else_branch,
56 ..
57 } => {
58 has_float_expr(then_branch, float_vars)
59 || else_branch
60 .as_ref()
61 .is_some_and(|e| has_float_expr(e, float_vars))
62 }
63 _ => false,
64 }
65}
66
67fn has_inner_i128(folded: &str) -> bool {
72 folded.contains("i128::from(") || folded.contains("_i128")
73}
74
75pub(crate) fn is_numeric_expr(expr: &SpExpr) -> bool {
79 match &expr.node {
80 Expr::Ident(_) | Expr::Literal(Literal::Int(_)) | Expr::Literal(Literal::Float(_)) => true,
81 Expr::Field(_, _) => true,
82 Expr::BinOp { op, .. } => op.is_arithmetic(),
83 Expr::UnaryOp {
84 op: UnaryOp::Neg, ..
85 } => true,
86 Expr::Old(e) | Expr::Cast { expr: e, .. } => is_numeric_expr(e),
87 Expr::Call { .. } | Expr::MethodCall { .. } | Expr::Index { .. } => true,
88 Expr::Let { body, .. } => is_numeric_expr(body),
89 Expr::If { then_branch, .. } => is_numeric_expr(then_branch),
90 Expr::Match { arms, .. } => arms.first().is_some_and(|a| is_numeric_expr(&a.body)),
91 Expr::Literal(Literal::Str(_) | Literal::Bool(_))
93 | Expr::UnaryOp {
94 op: UnaryOp::Not, ..
95 }
96 | Expr::Forall { .. }
97 | Expr::Exists { .. }
98 | Expr::List(_)
99 | Expr::Tuple(_)
100 | Expr::Ghost(_)
101 | Expr::Apply { .. }
102 | Expr::Block(_)
103 | Expr::Raw(_) => false,
104 }
105}
106
107fn is_range_domain(expr: &SpExpr) -> bool {
112 matches!(
113 &expr.node,
114 Expr::BinOp {
115 op: BinOp::Range,
116 ..
117 }
118 )
119}
120
121fn is_abstract_type_domain(expr: &SpExpr) -> bool {
127 match &expr.node {
128 Expr::Ident(name) => matches!(
129 name.as_str(),
130 "Int" | "Nat" | "Float" | "Bool" | "String" | "Bytes" | "Unit"
131 ),
132 Expr::Raw(tokens) if tokens.len() == 1 => matches!(
133 tokens[0].as_str(),
134 "Int" | "Nat" | "Float" | "Bool" | "String" | "Bytes" | "Unit"
135 ),
136 _ => false,
137 }
138}
139
140pub(crate) fn resolve_ordering_variant(body: &SpExpr) -> Option<&'static str> {
142 use assura_ast::MemoryOrdering;
143 let s = match &body.node {
144 Expr::Ident(s) => s.as_str(),
145 Expr::Raw(tokens) => {
146 return tokens
147 .iter()
148 .find_map(|t| MemoryOrdering::parse(t))
149 .map(|o| o.to_rust_ordering());
150 }
151 _ => return None,
152 };
153 MemoryOrdering::parse(s).map(|o| o.to_rust_ordering())
154}
155
156pub(crate) fn expr_to_rust(expr: &SpExpr) -> String {
158 RustCodegenFolder {
159 static_context: false,
160 float_vars: HashSet::new(),
161 }
162 .fold_expr(expr)
163}
164
165pub(crate) fn expr_to_rust_with_floats(expr: &SpExpr, float_vars: HashSet<String>) -> String {
169 RustCodegenFolder {
170 static_context: false,
171 float_vars,
172 }
173 .fold_expr(expr)
174}
175
176pub fn expr_to_rust_static(expr: &SpExpr) -> String {
186 RustCodegenFolder {
187 static_context: true,
188 float_vars: HashSet::new(),
189 }
190 .fold_expr(expr)
191}
192
193struct RustCodegenFolder {
200 static_context: bool,
201 float_vars: HashSet<String>,
205}
206
207impl ExprFolder for RustCodegenFolder {
208 type Output = String;
209
210 fn fold_literal(&mut self, lit: &Literal) -> String {
211 match lit {
212 Literal::Int(s) => {
217 if let Ok(v) = s.parse::<i128>() {
218 if v > i128::from(i64::MAX) || v < i128::from(i64::MIN) {
219 return format!("{s}_i128");
220 }
221 if v > i128::from(i32::MAX) || v < i128::from(i32::MIN) {
222 return format!("{s}_i64");
223 }
224 } else if s.parse::<u128>().is_ok() {
225 return format!("{s}_u128");
227 }
228 s.clone()
229 }
230 _ => literal_to_string(lit),
231 }
232 }
233
234 fn fold_ident(&mut self, name: &str) -> String {
235 if !self.static_context && name == "result" {
236 RESULT_VAR.to_string()
237 } else {
238 name.to_string()
239 }
240 }
241
242 fn fold_field(&mut self, base: &SpExpr, field: &str) -> String {
243 format!("{}.{field}", self.fold_expr(base))
244 }
245
246 fn fold_method_call(&mut self, receiver: &SpExpr, method: &str, args: &[SpExpr]) -> String {
247 if args.is_empty() && matches!(method, "length" | "len" | "size") {
249 return format!("{}.len() as u64", self.fold_expr(receiver));
250 }
251 format!(
252 "{}.{method}({})",
253 self.fold_expr(receiver),
254 fold_arg_list(self, args)
255 )
256 }
257
258 fn fold_call(&mut self, func: &SpExpr, args: &[SpExpr]) -> String {
259 if let Expr::Ident(name) = &func.node {
261 match (name.as_str(), args.len()) {
262 ("abs", 1) => return format!("{}.abs()", self.fold_expr(&args[0])),
263 ("min", 2) => {
264 return format!(
265 "{}.min({})",
266 self.fold_expr(&args[0]),
267 self.fold_expr(&args[1])
268 );
269 }
270 ("max", 2) => {
271 return format!(
272 "{}.max({})",
273 self.fold_expr(&args[0]),
274 self.fold_expr(&args[1])
275 );
276 }
277 _ => {}
278 }
279 }
280 format!("{}({})", self.fold_expr(func), fold_arg_list(self, args))
281 }
282
283 fn fold_index(&mut self, base: &SpExpr, index: &SpExpr) -> String {
284 let idx = self.fold_expr(index);
285 if !self.static_context && is_numeric_expr(index) {
286 format!("{}[({idx}) as usize]", self.fold_expr(base))
290 } else {
291 format!("{}[{idx}]", self.fold_expr(base))
292 }
293 }
294
295 fn fold_binop(&mut self, lhs: &SpExpr, op: &BinOp, rhs: &SpExpr) -> String {
296 if !self.static_context {
297 match op {
299 BinOp::Implies => {
300 return format!("(!{} || {})", self.fold_expr(lhs), self.fold_expr(rhs));
301 }
302 BinOp::In => {
303 return format!("{}.contains(&{})", self.fold_expr(rhs), self.fold_expr(lhs));
304 }
305 BinOp::NotIn => {
306 return format!(
307 "!{}.contains(&{})",
308 self.fold_expr(rhs),
309 self.fold_expr(lhs)
310 );
311 }
312 BinOp::Concat => {
313 return format!(
314 "[{}, {}].concat()",
315 self.fold_expr(lhs),
316 self.fold_expr(rhs)
317 );
318 }
319 _ => {}
320 }
321 let op_s = op.as_rust_str();
322 if (op.is_comparison() || op.is_arithmetic())
330 && is_numeric_expr(lhs)
331 && is_numeric_expr(rhs)
332 && !has_float_expr(lhs, &self.float_vars)
333 && !has_float_expr(rhs, &self.float_vars)
334 {
335 if has_u128_literal(lhs) || has_u128_literal(rhs) {
336 return format!(
337 "(({} as u128) {op_s} ({} as u128))",
338 self.fold_expr(lhs),
339 self.fold_expr(rhs)
340 );
341 }
342 return format!(
343 "(i128::from({}) {op_s} i128::from({}))",
344 self.fold_expr(lhs),
345 self.fold_expr(rhs)
346 );
347 }
348 }
349 format!(
350 "({} {} {})",
351 self.fold_expr(lhs),
352 op.as_rust_str(),
353 self.fold_expr(rhs)
354 )
355 }
356
357 fn fold_unary_op(&mut self, op: &UnaryOp, inner: &SpExpr) -> String {
358 let inner_s = self.fold_expr(inner);
359 if self.static_context {
360 format!("{}{inner_s}", op.as_rust_str())
361 } else {
362 format!("({}{})", op.as_rust_str(), inner_s)
363 }
364 }
365
366 fn fold_old(&mut self, inner: &SpExpr) -> String {
367 if self.static_context {
368 self.fold_expr(inner)
370 } else {
371 format!("{OLD_VAR_PREFIX}{}", old_var_name(inner))
372 }
373 }
374
375 fn fold_forall(&mut self, var: &str, domain: &SpExpr, body: &SpExpr) -> String {
376 if self.static_context || is_abstract_type_domain(domain) {
377 let d = self.fold_expr(domain);
378 let b = self.fold_expr(body);
379 format!("/* forall {var} in {d}: {b} */ true")
380 } else if is_range_domain(domain) {
381 format!(
382 "({}).into_iter().all(|{var}| {})",
383 self.fold_expr(domain),
384 self.fold_expr(body)
385 )
386 } else {
387 format!(
388 "{}.iter().copied().all(|{var}| {})",
389 self.fold_expr(domain),
390 self.fold_expr(body)
391 )
392 }
393 }
394
395 fn fold_exists(&mut self, var: &str, domain: &SpExpr, body: &SpExpr) -> String {
396 if self.static_context || is_abstract_type_domain(domain) {
397 let d = self.fold_expr(domain);
398 let b = self.fold_expr(body);
399 format!("/* exists {var} in {d}: {b} */ true")
400 } else if is_range_domain(domain) {
401 format!(
402 "({}).into_iter().any(|{var}| {})",
403 self.fold_expr(domain),
404 self.fold_expr(body)
405 )
406 } else {
407 format!(
408 "{}.iter().copied().any(|{var}| {})",
409 self.fold_expr(domain),
410 self.fold_expr(body)
411 )
412 }
413 }
414
415 fn fold_if(&mut self, cond: &SpExpr, then_br: &SpExpr, else_br: Option<&SpExpr>) -> String {
416 match else_br {
417 Some(eb) => {
418 let then_s = self.fold_expr(then_br);
419 let else_s = self.fold_expr(eb);
420 if !self.static_context
425 && is_numeric_expr(then_br)
426 && is_numeric_expr(eb)
427 && (has_inner_i128(&then_s) || has_inner_i128(&else_s))
428 && !has_float_expr(then_br, &self.float_vars)
429 && !has_float_expr(eb, &self.float_vars)
430 {
431 let t = if has_inner_i128(&then_s) {
432 then_s
433 } else {
434 format!("i128::from({then_s})")
435 };
436 let e = if has_inner_i128(&else_s) {
437 else_s
438 } else {
439 format!("i128::from({else_s})")
440 };
441 return format!("if {} {{ {} }} else {{ {} }}", self.fold_expr(cond), t, e);
442 }
443 format!(
444 "if {} {{ {} }} else {{ {} }}",
445 self.fold_expr(cond),
446 then_s,
447 else_s
448 )
449 }
450 None => format!(
451 "if {} {{ {} }}",
452 self.fold_expr(cond),
453 self.fold_expr(then_br)
454 ),
455 }
456 }
457
458 fn fold_list(&mut self, items: &[SpExpr]) -> String {
459 format!("vec![{}]", fold_joined(self, items, ", "))
460 }
461
462 fn fold_cast(&mut self, inner: &SpExpr, ty: &str) -> String {
463 if self.static_context {
464 format!("({} as {ty})", self.fold_expr(inner))
465 } else {
466 format!("({} as {})", self.fold_expr(inner), map_type_token(ty))
467 }
468 }
469
470 fn fold_block(&mut self, exprs: &[SpExpr]) -> String {
471 fold_joined(self, exprs, " ")
472 }
473
474 fn fold_ghost(&mut self, inner: &SpExpr) -> String {
475 if self.static_context {
476 let s = self.fold_expr(inner);
477 format!("/* ghost: {s} */ ()")
478 } else {
479 "/* ghost erased */()".to_string()
480 }
481 }
482
483 fn fold_apply(&mut self, lemma_name: &str, args: &[SpExpr]) -> String {
484 if self.static_context {
485 format!("/* apply {lemma_name}({}) */ ()", fold_arg_list(self, args))
486 } else {
487 format!("/* lemma {lemma_name} applied */")
488 }
489 }
490
491 fn fold_let(&mut self, name: &str, value: &SpExpr, body: &SpExpr) -> String {
492 format!(
493 "{{ let {} = {}; {} }}",
494 name,
495 self.fold_expr(value),
496 self.fold_expr(body)
497 )
498 }
499
500 fn fold_match(&mut self, scrutinee: &SpExpr, arms: &[assura_ast::MatchArm]) -> String {
501 let scrut = self.fold_expr(scrutinee);
502 if self.static_context {
503 let arm_strs: Vec<String> = arms
504 .iter()
505 .map(|arm| {
506 let pat = match &arm.pattern {
507 assura_ast::Pattern::Ident(s) => s.clone(),
508 assura_ast::Pattern::Wildcard => "_".to_string(),
509 assura_ast::Pattern::Literal(lit) => match lit {
510 Literal::Int(s) | Literal::Float(s) => s.clone(),
511 Literal::Str(s) => format!("\"{s}\""),
512 Literal::Bool(b) => b.to_string(),
513 },
514 assura_ast::Pattern::Constructor { name, fields } => {
515 if fields.is_empty() {
516 name.clone()
517 } else {
518 format!("{name}(..)")
519 }
520 }
521 assura_ast::Pattern::Tuple(pats) => {
522 let ps: Vec<&str> = pats.iter().map(|_| "_").collect();
523 format!("({})", ps.join(", "))
524 }
525 };
526 let body = self.fold_expr(&arm.body);
527 format!("{pat} => {body}")
528 })
529 .collect();
530 format!("match {scrut} {{ {} }}", arm_strs.join(", "))
531 } else {
532 let arms_code: Vec<String> = arms
533 .iter()
534 .map(|arm| {
535 let pat = match &arm.pattern {
536 assura_ast::Pattern::Ident(name) => name.clone(),
537 assura_ast::Pattern::Wildcard => "_".into(),
538 assura_ast::Pattern::Literal(lit) => match lit {
539 Literal::Int(s) | Literal::Float(s) => s.clone(),
540 Literal::Str(s) => format!("\"{s}\""),
541 Literal::Bool(b) => b.to_string(),
542 },
543 assura_ast::Pattern::Constructor { name, fields } => {
544 if fields.is_empty() {
545 name.clone()
546 } else {
547 let fs: Vec<String> = fields.iter().map(pattern_to_rust).collect();
548 format!("{name}({})", fs.join(", "))
549 }
550 }
551 assura_ast::Pattern::Tuple(pats) => {
552 let ps: Vec<String> = pats.iter().map(pattern_to_rust).collect();
553 format!("({})", ps.join(", "))
554 }
555 };
556 let body = self.fold_expr(&arm.body);
557 format!(" {pat} => {body},")
558 })
559 .collect();
560 let has_wildcard = arms.iter().any(|arm| {
561 matches!(
562 &arm.pattern,
563 assura_ast::Pattern::Wildcard | assura_ast::Pattern::Ident(_)
564 )
565 });
566 if !has_wildcard {
567 let mut all_arms = arms_code;
568 all_arms.push(" _ => unreachable!(\"non-exhaustive match\"),".to_string());
569 format!("match {} {{\n{}\n}}", scrut, all_arms.join("\n"))
570 } else {
571 format!("match {} {{\n{}\n}}", scrut, arms_code.join("\n"))
572 }
573 }
574 }
575
576 fn fold_tuple(&mut self, items: &[SpExpr]) -> String {
577 format!("({})", fold_joined(self, items, ", "))
578 }
579
580 fn fold_raw(&mut self, tokens: &[String]) -> String {
581 if self.static_context {
582 let clean: Vec<&str> = tokens.iter().map(|s| s.as_str()).collect();
583 if clean.len() == 1 {
584 return clean[0].to_string();
585 }
586 if clean.len() >= 2 && clean[0] == "=" {
587 return clean[1..].join(" ");
588 }
589 clean.join(" ")
590 } else {
591 raw_tokens_to_rust(tokens)
592 }
593 }
594}
595
596pub(crate) fn raw_tokens_to_rust(tokens: &[String]) -> String {
603 if tokens.is_empty() {
604 return String::new();
605 }
606 let first = tokens[0].as_str();
608 if matches!(first, "forall" | "exists")
609 && tokens.len() >= 5
610 && let Some(in_pos) = tokens[1..].iter().position(|t| t == "in")
611 {
612 let in_pos = in_pos + 1; let var = &tokens[1..in_pos].join("_");
614 if let Some(colon_offset) = tokens[in_pos + 1..].iter().position(|t| t == ":") {
616 let colon_pos = in_pos + 1 + colon_offset;
617 let domain_tokens = &tokens[in_pos + 1..colon_pos];
618 let body_tokens = &tokens[colon_pos + 1..];
619
620 let domain = {
621 let mapped: Vec<&str> = domain_tokens.iter().map(|t| map_type_token(t)).collect();
622 smart_join_type_tokens(&mapped)
623 };
624 let body = raw_tokens_to_rust(body_tokens);
625
626 let method = if first == "forall" { "all" } else { "any" };
627 return format!("{domain}.iter().copied().{method}(|{var}| {body})");
628 }
629 }
630
631 if let Some(at_pos) = tokens.iter().position(|t| t == "@") {
633 let before = &tokens[..at_pos];
634 let after = &tokens[at_pos + 1..];
635 let expr_s = raw_tokens_to_rust(before);
636 let state_s = after.join(" ");
637 return format!("true /* typestate: {expr_s} @ {state_s} */");
638 }
639
640 let mapped: Vec<String> = tokens
642 .iter()
643 .map(|t| {
644 if t == "result" {
645 RESULT_VAR.to_string()
646 } else {
647 map_type_token(t).to_string()
648 }
649 })
650 .collect();
651 let refs: Vec<&str> = mapped.iter().map(|s| s.as_str()).collect();
652 smart_join_type_tokens(&refs)
653}
654
655pub(crate) fn has_deep_field_access(expr: &str) -> bool {
665 let method_names = [
669 "iter",
670 "all",
671 "any",
672 "map",
673 "filter",
674 "len",
675 "is_empty",
676 "clone",
677 "count",
678 "sum",
679 "collect",
680 "flat_map",
681 "zip",
682 "enumerate",
683 "take",
684 "skip",
685 "find",
686 "fold",
687 "for_each",
688 "min",
689 "max",
690 "contains",
691 "position",
692 "into_iter",
693 "as_ref",
694 "as_mut",
695 "unwrap",
696 "unwrap_or",
697 "expect",
698 "ok",
699 "err",
700 "is_some",
701 "is_none",
702 "is_ok",
703 "is_err",
704 ];
705 for word in expr.split(|c: char| !c.is_alphanumeric() && c != '.' && c != '_') {
706 if word.contains('.') && !word.is_empty() {
707 let parts: Vec<&str> = word.split('.').collect();
708 if parts.len() >= 2
709 && parts[0]
710 .chars()
711 .next()
712 .is_some_and(|c| c.is_alphabetic() || c == '_')
713 {
714 let all_methods = parts[1..].iter().all(|p| method_names.contains(p));
716 if !all_methods {
717 return true;
718 }
719 }
720 }
721 }
722 let result_dot = format!("{RESULT_VAR}.");
724 if expr.contains(&result_dot) {
725 for chunk in expr.split(&result_dot) {
727 if chunk.is_empty() {
728 continue;
729 }
730 let after: String = chunk
731 .chars()
732 .take_while(|c| c.is_alphanumeric() || *c == '_')
733 .collect();
734 if !after.is_empty() && !method_names.contains(&after.as_str()) {
735 return true;
736 }
737 }
738 }
739 false
740}
741
742pub(crate) fn pattern_to_rust(pat: &assura_ast::Pattern) -> String {
744 match pat {
745 assura_ast::Pattern::Ident(name) => name.clone(),
746 assura_ast::Pattern::Wildcard => "_".into(),
747 assura_ast::Pattern::Literal(lit) => match lit {
748 Literal::Int(s) | Literal::Float(s) => s.clone(),
749 Literal::Str(s) => format!("\"{s}\""),
750 Literal::Bool(b) => b.to_string(),
751 },
752 assura_ast::Pattern::Constructor { name, fields } => {
753 if fields.is_empty() {
754 name.clone()
755 } else {
756 let fs: Vec<String> = fields.iter().map(pattern_to_rust).collect();
757 format!("{name}({})", fs.join(", "))
758 }
759 }
760 assura_ast::Pattern::Tuple(pats) => {
761 let ps: Vec<String> = pats.iter().map(pattern_to_rust).collect();
762 format!("({})", ps.join(", "))
763 }
764 }
765}
766
767pub(crate) fn old_var_name(expr: &SpExpr) -> String {
768 match &expr.node {
769 Expr::Ident(s) => s.clone(),
770 Expr::Field(recv, field) => format!("{}_{field}", old_var_name(recv)),
771 Expr::Call { func, .. } => old_var_name(func),
772 Expr::MethodCall {
773 receiver, method, ..
774 } => format!("{}_{method}", old_var_name(receiver)),
775 Expr::Index { expr: e, .. } => format!("{}_idx", old_var_name(e)),
776 Expr::Literal(lit) => match lit {
777 Literal::Int(s) | Literal::Float(s) => format!("lit_{s}"),
778 Literal::Str(s) => format!("lit_{}", s.trim_matches('"')),
779 Literal::Bool(b) => format!("lit_{b}"),
780 },
781 Expr::BinOp { lhs, op, rhs } => {
782 format!(
783 "{}_{}_{}",
784 old_var_name(lhs),
785 op.as_ident(),
786 old_var_name(rhs)
787 )
788 }
789 Expr::UnaryOp { op, expr: e } => {
790 let prefix = match op {
791 UnaryOp::Neg => "neg",
792 UnaryOp::Not => "not",
793 };
794 format!("{prefix}_{}", old_var_name(e))
795 }
796 Expr::Old(inner) => old_var_name(inner),
797 Expr::Cast { expr: e, .. } => old_var_name(e),
798 Expr::Ghost(inner) => format!("ghost_{}", old_var_name(inner)),
799 Expr::Forall { var, .. } => format!("forall_{var}"),
800 Expr::Exists { var, .. } => format!("exists_{var}"),
801 Expr::If { cond, .. } => format!("if_{}", old_var_name(cond)),
802 Expr::Let { name, .. } => format!("let_{name}"),
803 Expr::Match { scrutinee, .. } => format!("match_{}", old_var_name(scrutinee)),
804 Expr::Apply { lemma_name, .. } => format!("apply_{lemma_name}"),
805 Expr::List(_) => "list".to_string(),
806 Expr::Tuple(_) => "tuple".to_string(),
807 Expr::Block(exprs) => {
808 if let Some(first) = exprs.first() {
809 old_var_name(first)
810 } else {
811 "block".to_string()
812 }
813 }
814 Expr::Raw(tokens) => {
815 if let Some(first) = tokens.first() {
816 first.clone()
817 } else {
818 "raw".to_string()
819 }
820 }
821 }
822}
823
824pub(crate) fn collect_old_exprs(expr: &SpExpr) -> Vec<(String, String)> {
827 let mut result = Vec::new();
828 collect_old_exprs_inner(expr, &mut result);
829 result
830}
831
832pub(crate) fn collect_old_exprs_inner(expr: &SpExpr, out: &mut Vec<(String, String)>) {
833 match &expr.node {
834 Expr::Old(inner) => {
835 let var = old_var_name(inner);
836 let rust = expr_to_rust(inner);
837 if !out.iter().any(|(v, _)| v == &var) {
839 out.push((var, rust));
840 }
841 collect_old_exprs_inner(inner, out);
843 }
844 Expr::BinOp { lhs, rhs, .. } => {
845 collect_old_exprs_inner(lhs, out);
846 collect_old_exprs_inner(rhs, out);
847 }
848 Expr::UnaryOp { expr: e, .. } | Expr::Field(e, _) | Expr::Cast { expr: e, .. } => {
849 collect_old_exprs_inner(e, out);
850 }
851 Expr::Call { func, args } => {
852 collect_old_exprs_inner(func, out);
853 for a in args {
854 collect_old_exprs_inner(a, out);
855 }
856 }
857 Expr::MethodCall { receiver, args, .. } => {
858 collect_old_exprs_inner(receiver, out);
859 for a in args {
860 collect_old_exprs_inner(a, out);
861 }
862 }
863 Expr::Index { expr: e, index } => {
864 collect_old_exprs_inner(e, out);
865 collect_old_exprs_inner(index, out);
866 }
867 Expr::Forall { domain, body, .. } | Expr::Exists { domain, body, .. } => {
868 collect_old_exprs_inner(domain, out);
869 collect_old_exprs_inner(body, out);
870 }
871 Expr::If {
872 cond,
873 then_branch,
874 else_branch,
875 } => {
876 collect_old_exprs_inner(cond, out);
877 collect_old_exprs_inner(then_branch, out);
878 if let Some(eb) = else_branch {
879 collect_old_exprs_inner(eb, out);
880 }
881 }
882 Expr::List(items) | Expr::Block(items) => {
883 for item in items {
884 collect_old_exprs_inner(item, out);
885 }
886 }
887 Expr::Ghost(inner) => {
888 collect_old_exprs_inner(inner, out);
891 }
892 Expr::Apply { args, .. } => {
893 for a in args {
895 collect_old_exprs_inner(a, out);
896 }
897 }
898 Expr::Match { scrutinee, arms } => {
899 collect_old_exprs_inner(scrutinee, out);
900 for arm in arms {
901 collect_old_exprs_inner(&arm.body, out);
902 }
903 }
904 Expr::Let { value, body, .. } => {
905 collect_old_exprs_inner(value, out);
906 collect_old_exprs_inner(body, out);
907 }
908 Expr::Tuple(elems) => {
909 for e in elems {
910 collect_old_exprs_inner(e, out);
911 }
912 }
913 Expr::Literal(_) | Expr::Ident(_) | Expr::Raw(_) => {}
915 }
916}
917#[cfg(test)]
918#[path = "expr_tests.rs"]
919mod tests;