1pub mod detect;
14
15use std::collections::HashSet;
16
17use crate::ast::{Expr, FnBody, MatchArm, Spanned, Stmt, StrPart, TailCallData};
18use crate::codegen::common::expr_to_dotted_name;
19
20pub use detect::analyze_plans_in_scope;
21
22#[derive(Clone, Debug, PartialEq)]
31pub enum RecursionPlan {
32 IntCountdown { param_index: usize },
35 IntCountdownGuarded {
46 param_index: usize,
47 base_arm_literal: i64,
50 base_arm_body: Spanned<Expr>,
51 wildcard_arm_body: Spanned<Expr>,
52 precondition: Vec<Spanned<Expr>>,
70 },
71 IntAscending {
76 param_index: usize,
77 bound: Spanned<Expr>,
78 },
79 LinearRecurrence2,
83 ListStructural { param_index: usize },
86 SizeOfStructural,
89 StringPosAdvance,
93 MutualIntCountdown,
96 MutualStringPosAdvance { rank: usize },
100 MutualSizeOfRanked { rank: usize },
103}
104
105#[derive(Clone, Debug, Eq, PartialEq)]
111pub struct ProofModeIssue {
112 pub line: usize,
113 pub message: String,
114}
115
116pub fn fuel_helper_name(name: &str) -> String {
120 format!("{}__fuel", name)
121}
122
123pub fn native_aux_name(name: &str) -> String {
127 format!("{}__aux", name)
128}
129
130pub const OMEGA_PROOF_SENTINEL: &str = "__aver_omega_proof__";
136
137pub fn flip_comparison_binop(expr: &Spanned<Expr>) -> Option<Spanned<Expr>> {
146 let Expr::BinOp(op, left, right) = &expr.node else {
147 return None;
148 };
149 use crate::ast::BinOp::*;
150 let flipped = match op {
151 Lt => Gte,
152 Gt => Lte,
153 Lte => Gt,
154 Gte => Lt,
155 Eq => Neq,
156 Neq => Eq,
157 _ => return None,
158 };
159 Some(Spanned::new(
160 Expr::BinOp(flipped, left.clone(), right.clone()),
161 expr.line,
162 ))
163}
164
165pub use crate::codegen::common::substitute_ident_in_expr;
172
173pub fn expr_references_ident(expr: &Spanned<Expr>, name: &str) -> bool {
179 match &expr.node {
180 Expr::Ident(n) | Expr::Resolved { name: n, .. } => n == name,
181 Expr::Literal(_) => false,
182 Expr::Attr(obj, _) => expr_references_ident(obj, name),
183 Expr::FnCall(callee, args) => {
184 expr_references_ident(callee, name)
185 || args.iter().any(|a| expr_references_ident(a, name))
186 }
187 Expr::BinOp(_, l, r) => expr_references_ident(l, name) || expr_references_ident(r, name),
188 Expr::Neg(inner) => expr_references_ident(inner, name),
189 Expr::Match { subject, arms } => {
190 expr_references_ident(subject, name)
191 || arms.iter().any(|a| expr_references_ident(&a.body, name))
192 }
193 Expr::Constructor(_, arg) => arg
194 .as_deref()
195 .is_some_and(|a| expr_references_ident(a, name)),
196 Expr::ErrorProp(inner) => expr_references_ident(inner, name),
197 Expr::InterpolatedStr(parts) => parts.iter().any(|p| match p {
198 StrPart::Parsed(inner) => expr_references_ident(inner, name),
199 _ => false,
200 }),
201 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
202 items.iter().any(|i| expr_references_ident(i, name))
203 }
204 Expr::MapLiteral(entries) => entries
205 .iter()
206 .any(|(k, v)| expr_references_ident(k, name) || expr_references_ident(v, name)),
207 Expr::RecordCreate { fields, .. } => {
208 fields.iter().any(|(_, v)| expr_references_ident(v, name))
209 }
210 Expr::RecordUpdate { base, updates, .. } => {
211 expr_references_ident(base, name)
212 || updates.iter().any(|(_, v)| expr_references_ident(v, name))
213 }
214 Expr::TailCall(boxed) => boxed.args.iter().any(|a| expr_references_ident(a, name)),
215 }
216}
217
218pub fn rewrite_recursive_calls_expr(
223 expr: &Spanned<Expr>,
224 targets: &HashSet<String>,
225 fuel_var: &str,
226) -> Spanned<Expr> {
227 let line = expr.line;
228 let new_node = match &expr.node {
229 Expr::Literal(_) | Expr::Ident(_) | Expr::Resolved { .. } => return expr.clone(),
230 Expr::Attr(obj, field) => Expr::Attr(
231 Box::new(rewrite_recursive_calls_expr(obj, targets, fuel_var)),
232 field.clone(),
233 ),
234 Expr::FnCall(callee, args) => {
235 let rewritten_args: Vec<Spanned<Expr>> = args
236 .iter()
237 .map(|arg| rewrite_recursive_calls_expr(arg, targets, fuel_var))
238 .collect();
239 if let Some(name) = expr_to_dotted_name(&callee.node)
240 && targets.contains(&name)
241 {
242 let mut call_args = Vec::with_capacity(rewritten_args.len() + 1);
243 call_args.push(Spanned::new(Expr::Ident(fuel_var.to_string()), line));
244 call_args.extend(rewritten_args);
245 Expr::FnCall(
246 Box::new(Spanned::new(Expr::Ident(fuel_helper_name(&name)), line)),
247 call_args,
248 )
249 } else {
250 Expr::FnCall(
251 Box::new(rewrite_recursive_calls_expr(callee, targets, fuel_var)),
252 rewritten_args,
253 )
254 }
255 }
256 Expr::BinOp(op, left, right) => Expr::BinOp(
257 *op,
258 Box::new(rewrite_recursive_calls_expr(left, targets, fuel_var)),
259 Box::new(rewrite_recursive_calls_expr(right, targets, fuel_var)),
260 ),
261 Expr::Neg(inner) => Expr::Neg(Box::new(rewrite_recursive_calls_expr(
262 inner, targets, fuel_var,
263 ))),
264 Expr::Match { subject, arms } => Expr::Match {
265 subject: Box::new(rewrite_recursive_calls_expr(subject, targets, fuel_var)),
266 arms: arms
267 .iter()
268 .map(|arm| MatchArm {
269 pattern: arm.pattern.clone(),
270 body: Box::new(rewrite_recursive_calls_expr(&arm.body, targets, fuel_var)),
271 binding_slots: std::sync::OnceLock::new(),
272 })
273 .collect(),
274 },
275 Expr::Constructor(name, arg) => Expr::Constructor(
276 name.clone(),
277 arg.as_ref()
278 .map(|inner| Box::new(rewrite_recursive_calls_expr(inner, targets, fuel_var))),
279 ),
280 Expr::ErrorProp(inner) => Expr::ErrorProp(Box::new(rewrite_recursive_calls_expr(
281 inner, targets, fuel_var,
282 ))),
283 Expr::InterpolatedStr(parts) => Expr::InterpolatedStr(
284 parts
285 .iter()
286 .map(|part| match part {
287 StrPart::Literal(_) => part.clone(),
288 StrPart::Parsed(inner) => StrPart::Parsed(Box::new(
289 rewrite_recursive_calls_expr(inner, targets, fuel_var),
290 )),
291 })
292 .collect(),
293 ),
294 Expr::List(items) => Expr::List(
295 items
296 .iter()
297 .map(|item| rewrite_recursive_calls_expr(item, targets, fuel_var))
298 .collect(),
299 ),
300 Expr::Tuple(items) => Expr::Tuple(
301 items
302 .iter()
303 .map(|item| rewrite_recursive_calls_expr(item, targets, fuel_var))
304 .collect(),
305 ),
306 Expr::IndependentProduct(items, flag) => Expr::IndependentProduct(
307 items
308 .iter()
309 .map(|item| rewrite_recursive_calls_expr(item, targets, fuel_var))
310 .collect(),
311 *flag,
312 ),
313 Expr::MapLiteral(entries) => Expr::MapLiteral(
314 entries
315 .iter()
316 .map(|(k, v)| {
317 (
318 rewrite_recursive_calls_expr(k, targets, fuel_var),
319 rewrite_recursive_calls_expr(v, targets, fuel_var),
320 )
321 })
322 .collect(),
323 ),
324 Expr::RecordCreate { type_name, fields } => Expr::RecordCreate {
325 type_name: type_name.clone(),
326 fields: fields
327 .iter()
328 .map(|(name, value)| {
329 (
330 name.clone(),
331 rewrite_recursive_calls_expr(value, targets, fuel_var),
332 )
333 })
334 .collect(),
335 },
336 Expr::RecordUpdate {
337 type_name,
338 base,
339 updates,
340 } => Expr::RecordUpdate {
341 type_name: type_name.clone(),
342 base: Box::new(rewrite_recursive_calls_expr(base, targets, fuel_var)),
343 updates: updates
344 .iter()
345 .map(|(name, value)| {
346 (
347 name.clone(),
348 rewrite_recursive_calls_expr(value, targets, fuel_var),
349 )
350 })
351 .collect(),
352 },
353 Expr::TailCall(boxed) => {
354 let TailCallData { target, args, .. } = boxed.as_ref();
355 let rewritten_args: Vec<Spanned<Expr>> = args
356 .iter()
357 .map(|arg| rewrite_recursive_calls_expr(arg, targets, fuel_var))
358 .collect();
359 if targets.contains(target) {
360 let mut call_args = Vec::with_capacity(rewritten_args.len() + 1);
361 call_args.push(Spanned::new(Expr::Ident(fuel_var.to_string()), line));
362 call_args.extend(rewritten_args);
363 Expr::FnCall(
364 Box::new(Spanned::new(Expr::Ident(fuel_helper_name(target)), line)),
365 call_args,
366 )
367 } else {
368 Expr::TailCall(Box::new(TailCallData::new(target.clone(), rewritten_args)))
369 }
370 }
371 };
372 Spanned::new(new_node, line)
373}
374
375pub fn rewrite_native_guarded_calls_expr(
382 expr: &Spanned<Expr>,
383 fn_name: &str,
384 aux_name: &str,
385) -> Spanned<Expr> {
386 let line = expr.line;
387 let new_node = match &expr.node {
388 Expr::Literal(_) | Expr::Ident(_) | Expr::Resolved { .. } => return expr.clone(),
389 Expr::Attr(obj, field) => Expr::Attr(
390 Box::new(rewrite_native_guarded_calls_expr(obj, fn_name, aux_name)),
391 field.clone(),
392 ),
393 Expr::FnCall(callee, args) => {
394 let rewritten_args: Vec<Spanned<Expr>> = args
395 .iter()
396 .map(|arg| rewrite_native_guarded_calls_expr(arg, fn_name, aux_name))
397 .collect();
398 if let Some(name) = expr_to_dotted_name(&callee.node)
399 && name == fn_name
400 {
401 let mut call_args = rewritten_args;
402 call_args.push(Spanned::new(
403 Expr::Ident(OMEGA_PROOF_SENTINEL.to_string()),
404 line,
405 ));
406 Expr::FnCall(
407 Box::new(Spanned::new(Expr::Ident(aux_name.to_string()), line)),
408 call_args,
409 )
410 } else {
411 Expr::FnCall(
412 Box::new(rewrite_native_guarded_calls_expr(callee, fn_name, aux_name)),
413 rewritten_args,
414 )
415 }
416 }
417 Expr::BinOp(op, left, right) => Expr::BinOp(
418 *op,
419 Box::new(rewrite_native_guarded_calls_expr(left, fn_name, aux_name)),
420 Box::new(rewrite_native_guarded_calls_expr(right, fn_name, aux_name)),
421 ),
422 Expr::Neg(inner) => Expr::Neg(Box::new(rewrite_native_guarded_calls_expr(
423 inner, fn_name, aux_name,
424 ))),
425 Expr::Match { subject, arms } => Expr::Match {
426 subject: Box::new(rewrite_native_guarded_calls_expr(
427 subject, fn_name, aux_name,
428 )),
429 arms: arms
430 .iter()
431 .map(|arm| MatchArm {
432 pattern: arm.pattern.clone(),
433 body: Box::new(rewrite_native_guarded_calls_expr(
434 &arm.body, fn_name, aux_name,
435 )),
436 binding_slots: std::sync::OnceLock::new(),
437 })
438 .collect(),
439 },
440 Expr::Constructor(name, arg) => Expr::Constructor(
441 name.clone(),
442 arg.as_ref()
443 .map(|inner| Box::new(rewrite_native_guarded_calls_expr(inner, fn_name, aux_name))),
444 ),
445 Expr::ErrorProp(inner) => Expr::ErrorProp(Box::new(rewrite_native_guarded_calls_expr(
446 inner, fn_name, aux_name,
447 ))),
448 Expr::InterpolatedStr(parts) => Expr::InterpolatedStr(
449 parts
450 .iter()
451 .map(|part| match part {
452 StrPart::Literal(_) => part.clone(),
453 StrPart::Parsed(inner) => StrPart::Parsed(Box::new(
454 rewrite_native_guarded_calls_expr(inner, fn_name, aux_name),
455 )),
456 })
457 .collect(),
458 ),
459 Expr::List(items) => Expr::List(
460 items
461 .iter()
462 .map(|item| rewrite_native_guarded_calls_expr(item, fn_name, aux_name))
463 .collect(),
464 ),
465 Expr::Tuple(items) => Expr::Tuple(
466 items
467 .iter()
468 .map(|item| rewrite_native_guarded_calls_expr(item, fn_name, aux_name))
469 .collect(),
470 ),
471 Expr::IndependentProduct(items, flag) => Expr::IndependentProduct(
472 items
473 .iter()
474 .map(|item| rewrite_native_guarded_calls_expr(item, fn_name, aux_name))
475 .collect(),
476 *flag,
477 ),
478 Expr::MapLiteral(entries) => Expr::MapLiteral(
479 entries
480 .iter()
481 .map(|(k, v)| {
482 (
483 rewrite_native_guarded_calls_expr(k, fn_name, aux_name),
484 rewrite_native_guarded_calls_expr(v, fn_name, aux_name),
485 )
486 })
487 .collect(),
488 ),
489 Expr::RecordCreate { type_name, fields } => Expr::RecordCreate {
490 type_name: type_name.clone(),
491 fields: fields
492 .iter()
493 .map(|(name, value)| {
494 (
495 name.clone(),
496 rewrite_native_guarded_calls_expr(value, fn_name, aux_name),
497 )
498 })
499 .collect(),
500 },
501 Expr::RecordUpdate {
502 type_name,
503 base,
504 updates,
505 } => Expr::RecordUpdate {
506 type_name: type_name.clone(),
507 base: Box::new(rewrite_native_guarded_calls_expr(base, fn_name, aux_name)),
508 updates: updates
509 .iter()
510 .map(|(name, value)| {
511 (
512 name.clone(),
513 rewrite_native_guarded_calls_expr(value, fn_name, aux_name),
514 )
515 })
516 .collect(),
517 },
518 Expr::TailCall(boxed) => {
519 let TailCallData { target, args, .. } = boxed.as_ref();
520 let rewritten_args: Vec<Spanned<Expr>> = args
521 .iter()
522 .map(|arg| rewrite_native_guarded_calls_expr(arg, fn_name, aux_name))
523 .collect();
524 if target == fn_name {
525 let mut call_args = rewritten_args;
526 call_args.push(Spanned::new(
527 Expr::Ident(OMEGA_PROOF_SENTINEL.to_string()),
528 line,
529 ));
530 Expr::FnCall(
531 Box::new(Spanned::new(Expr::Ident(aux_name.to_string()), line)),
532 call_args,
533 )
534 } else {
535 Expr::TailCall(Box::new(TailCallData::new(target.clone(), rewritten_args)))
536 }
537 }
538 };
539 Spanned::new(new_node, line)
540}
541
542pub fn rewrite_native_guarded_calls_resolved_expr(
562 expr: &Spanned<crate::ir::hir::ResolvedExpr>,
563 target_fn_id: crate::ir::FnId,
564 aux_name: &str,
565) -> Spanned<crate::ir::hir::ResolvedExpr> {
566 use crate::ir::hir::{ResolvedCallee, ResolvedExpr, ResolvedMatchArm, ResolvedStrPart};
567 let line = expr.line;
568 let rec = |e: &Spanned<ResolvedExpr>| {
569 rewrite_native_guarded_calls_resolved_expr(e, target_fn_id, aux_name)
570 };
571 let synth_aux_callee = |line: usize| -> ResolvedCallee {
572 ResolvedCallee::Unresolved {
573 callee: Box::new(Spanned::new(
574 ResolvedExpr::Ident(aux_name.to_string()),
575 line,
576 )),
577 }
578 };
579 let new_node = match &expr.node {
580 ResolvedExpr::Literal(_) | ResolvedExpr::Ident(_) | ResolvedExpr::Resolved { .. } => {
581 return expr.clone();
582 }
583 ResolvedExpr::Attr(obj, field) => ResolvedExpr::Attr(Box::new(rec(obj)), field.clone()),
584 ResolvedExpr::Call(callee, args) => {
585 let rewritten_args: Vec<Spanned<ResolvedExpr>> = args.iter().map(&rec).collect();
586 let target_matches =
587 matches!(callee, ResolvedCallee::Fn(callee_id) if *callee_id == target_fn_id);
588 if target_matches {
589 let mut call_args = rewritten_args;
590 call_args.push(Spanned::new(
591 ResolvedExpr::Ident(OMEGA_PROOF_SENTINEL.to_string()),
592 line,
593 ));
594 ResolvedExpr::Call(synth_aux_callee(line), call_args)
595 } else {
596 ResolvedExpr::Call(callee.clone(), rewritten_args)
597 }
598 }
599 ResolvedExpr::BinOp(op, left, right) => {
600 ResolvedExpr::BinOp(*op, Box::new(rec(left)), Box::new(rec(right)))
601 }
602 ResolvedExpr::Neg(inner) => ResolvedExpr::Neg(Box::new(rec(inner))),
603 ResolvedExpr::Match { subject, arms } => ResolvedExpr::Match {
604 subject: Box::new(rec(subject)),
605 arms: arms
606 .iter()
607 .map(|arm| ResolvedMatchArm {
608 pattern: arm.pattern.clone(),
609 body: Box::new(rec(&arm.body)),
610 binding_slots: std::sync::OnceLock::new(),
611 })
612 .collect(),
613 },
614 ResolvedExpr::Ctor(ctor, args) => {
615 ResolvedExpr::Ctor(ctor.clone(), args.iter().map(&rec).collect())
616 }
617 ResolvedExpr::ErrorProp(inner) => ResolvedExpr::ErrorProp(Box::new(rec(inner))),
618 ResolvedExpr::InterpolatedStr(parts) => ResolvedExpr::InterpolatedStr(
619 parts
620 .iter()
621 .map(|p| match p {
622 ResolvedStrPart::Literal(_) => p.clone(),
623 ResolvedStrPart::Parsed(inner) => ResolvedStrPart::Parsed(Box::new(rec(inner))),
624 })
625 .collect(),
626 ),
627 ResolvedExpr::List(items) => ResolvedExpr::List(items.iter().map(&rec).collect()),
628 ResolvedExpr::Tuple(items) => ResolvedExpr::Tuple(items.iter().map(&rec).collect()),
629 ResolvedExpr::IndependentProduct(items, flag) => {
630 ResolvedExpr::IndependentProduct(items.iter().map(&rec).collect(), *flag)
631 }
632 ResolvedExpr::MapLiteral(entries) => {
633 ResolvedExpr::MapLiteral(entries.iter().map(|(k, v)| (rec(k), rec(v))).collect())
634 }
635 ResolvedExpr::RecordCreate {
636 type_id,
637 type_name,
638 fields,
639 } => ResolvedExpr::RecordCreate {
640 type_id: *type_id,
641 type_name: type_name.clone(),
642 fields: fields.iter().map(|(n, v)| (n.clone(), rec(v))).collect(),
643 },
644 ResolvedExpr::RecordUpdate {
645 type_id,
646 type_name,
647 base,
648 updates,
649 } => ResolvedExpr::RecordUpdate {
650 type_id: *type_id,
651 type_name: type_name.clone(),
652 base: Box::new(rec(base)),
653 updates: updates.iter().map(|(n, v)| (n.clone(), rec(v))).collect(),
654 },
655 ResolvedExpr::TailCall { target, args } => {
656 let rewritten_args: Vec<Spanned<ResolvedExpr>> = args.iter().map(&rec).collect();
657 if *target == target_fn_id {
658 let mut call_args = rewritten_args;
659 call_args.push(Spanned::new(
660 ResolvedExpr::Ident(OMEGA_PROOF_SENTINEL.to_string()),
661 line,
662 ));
663 ResolvedExpr::Call(synth_aux_callee(line), call_args)
664 } else {
665 ResolvedExpr::TailCall {
666 target: *target,
667 args: rewritten_args,
668 }
669 }
670 }
671 };
672 Spanned::new(new_node, line)
673}
674
675pub fn rewrite_native_guarded_calls_body(body: &FnBody, fn_name: &str, aux_name: &str) -> FnBody {
677 FnBody::Block(
678 body.stmts()
679 .iter()
680 .map(|stmt| match stmt {
681 Stmt::Binding(name, ty, expr) => Stmt::Binding(
682 name.clone(),
683 ty.clone(),
684 rewrite_native_guarded_calls_expr(expr, fn_name, aux_name),
685 ),
686 Stmt::Expr(expr) => {
687 Stmt::Expr(rewrite_native_guarded_calls_expr(expr, fn_name, aux_name))
688 }
689 })
690 .collect(),
691 )
692}
693
694pub fn rewrite_recursive_calls_body(
696 body: &FnBody,
697 targets: &HashSet<String>,
698 fuel_var: &str,
699) -> FnBody {
700 FnBody::Block(
701 body.stmts()
702 .iter()
703 .map(|stmt| match stmt {
704 Stmt::Binding(name, ty, expr) => Stmt::Binding(
705 name.clone(),
706 ty.clone(),
707 rewrite_recursive_calls_expr(expr, targets, fuel_var),
708 ),
709 Stmt::Expr(expr) => {
710 Stmt::Expr(rewrite_recursive_calls_expr(expr, targets, fuel_var))
711 }
712 })
713 .collect(),
714 )
715}