use super::*;
pub(super) fn iter_all_fn_defs<'a>(
inputs: &'a ProofLowerInputs<'a>,
) -> impl Iterator<Item = &'a FnDef> {
inputs
.entry_items
.iter()
.filter_map(|item| match item {
TopLevel::FnDef(fd) => Some(fd),
_ => None,
})
.chain(inputs.dep_modules.iter().flat_map(|m| m.fn_defs.iter()))
}
pub(super) fn body_calls_any_of_inputs(
body: &crate::ast::FnBody,
names: &std::collections::BTreeSet<String>,
) -> bool {
let mut called = std::collections::BTreeSet::new();
for stmt in body.stmts() {
match stmt {
crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
collect_fn_calls_expr(e, &mut called);
}
}
}
called.iter().any(|c| names.contains(c))
}
pub(super) fn collect_fn_calls_expr(
expr: &Spanned<crate::ast::Expr>,
out: &mut std::collections::BTreeSet<String>,
) {
use crate::ast::Expr;
match &expr.node {
Expr::FnCall(f, args) => {
if let Some(name) = expr_to_dotted_name(&f.node) {
let last = name.rsplit('.').next().unwrap_or(&name);
let head = name.split('.').next().unwrap_or(&name);
let is_builtin_scalar_ns = matches!(head, "Int" | "Float" | "Bool" | "String");
if !is_builtin_scalar_ns && last.chars().next().is_some_and(|c| c.is_lowercase()) {
out.insert(name);
}
}
for arg in args {
collect_fn_calls_expr(arg, out);
}
}
Expr::BinOp(_, l, r) => {
collect_fn_calls_expr(l, out);
collect_fn_calls_expr(r, out);
}
Expr::Attr(obj, _) => collect_fn_calls_expr(obj, out),
Expr::Match { subject, arms, .. } => {
collect_fn_calls_expr(subject, out);
for arm in arms {
collect_fn_calls_expr(&arm.body, out);
}
}
Expr::TailCall(boxed) => {
out.insert(boxed.target.clone());
for arg in &boxed.args {
collect_fn_calls_expr(arg, out);
}
}
Expr::ErrorProp(inner) | Expr::Neg(inner) => collect_fn_calls_expr(inner, out),
Expr::Constructor(_, Some(arg)) => collect_fn_calls_expr(arg, out),
Expr::RecordCreate { fields, .. } => {
for (_, e) in fields {
collect_fn_calls_expr(e, out);
}
}
Expr::List(elems) | Expr::Tuple(elems) | Expr::IndependentProduct(elems, _) => {
for e in elems {
collect_fn_calls_expr(e, out);
}
}
_ => {}
}
}
pub(crate) struct LawProofCone<'a> {
pure_fns: Vec<&'a FnDef>,
}
impl<'a> LawProofCone<'a> {
pub(crate) fn compute(
law: &crate::ast::VerifyLaw,
outer_fn: &str,
inputs: &ProofLowerInputs<'a>,
) -> Self {
use std::collections::BTreeSet;
let resolve_user_fn = |name: &str| -> Option<&'a FnDef> {
let fd = inputs.find_fn_def_by_call_name(name)?;
if !fd.effects.is_empty() || fd.name == "main" {
return None;
}
Some(fd)
};
let mut raw: BTreeSet<String> = BTreeSet::new();
collect_fn_calls_expr(&law.lhs, &mut raw);
collect_fn_calls_expr(&law.rhs, &mut raw);
if let Some(when_expr) = &law.when {
collect_fn_calls_expr(when_expr, &mut raw);
}
let mut names: BTreeSet<String> = raw
.into_iter()
.filter_map(|n| resolve_user_fn(&n).map(|fd| fd.name.clone()))
.collect();
loop {
let before = names.len();
let snapshot: Vec<String> = names.iter().cloned().collect();
for name in snapshot {
let Some(fd) = resolve_user_fn(&name) else {
continue;
};
let mut called: BTreeSet<String> = BTreeSet::new();
for stmt in fd.body.stmts() {
match stmt {
crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
collect_fn_calls_expr(e, &mut called);
}
}
}
for c in called {
if let Some(callee_fd) = resolve_user_fn(&c) {
names.insert(callee_fd.name.clone());
}
}
}
if names.len() == before {
break;
}
}
names.remove(outer_fn);
let pure_fns: Vec<&'a FnDef> = names.iter().filter_map(|n| resolve_user_fn(n)).collect();
Self { pure_fns }
}
fn unfold_names(&self) -> Vec<String> {
self.pure_fns.iter().map(|fd| fd.name.clone()).collect()
}
pub(crate) fn pure_fns(&self) -> &[&'a FnDef] {
&self.pure_fns
}
}
pub(super) fn law_helper_unfolds(
law: &crate::ast::VerifyLaw,
outer_fn: &str,
inputs: &ProofLowerInputs,
) -> Vec<String> {
LawProofCone::compute(law, outer_fn, inputs).unfold_names()
}
pub(super) fn detect_induction_target(
law: &crate::ast::VerifyLaw,
inputs: &ProofLowerInputs,
) -> Option<String> {
if let Some(shape) = inputs.program_shape {
for given in &law.givens {
if shape.inductable_sum_types.contains(&given.type_name) {
return Some(given.name.clone());
}
}
if let Some(given) = list_induction_given(law) {
return Some(given);
}
return None;
}
detect_induction_target_legacy(law, inputs)
}
pub(super) fn list_induction_given(law: &crate::ast::VerifyLaw) -> Option<String> {
law.givens
.iter()
.find(|g| g.type_name.trim().starts_with("List<"))
.map(|g| g.name.clone())
}
pub(super) fn detect_induction_target_legacy(
law: &crate::ast::VerifyLaw,
inputs: &ProofLowerInputs,
) -> Option<String> {
use crate::ast::TypeDef;
for given in &law.givens {
let Some(TypeDef::Sum {
name: type_name,
variants,
..
}) = inputs.find_type_def(&given.type_name)
else {
continue;
};
let direct_rec = variants.iter().any(|variant| {
variant.fields.iter().any(|field| {
let f = field.trim();
f == type_name
|| f.contains(&format!("<{}", type_name))
|| f.contains(&format!("{}>", type_name))
|| f.contains(&format!(", {}", type_name))
|| f.contains(&format!("{},", type_name))
})
});
if !direct_rec {
continue;
}
if has_indirect_rec_variants(variants, type_name) {
continue;
}
return Some(given.name.clone());
}
list_induction_given(law)
}
pub(super) fn has_indirect_rec_variants(
variants: &[crate::ast::TypeVariant],
type_name: &str,
) -> bool {
for variant in variants {
for field in &variant.fields {
let f = field.trim();
if f == type_name {
continue;
}
let opens = f.matches('<').count();
if opens > 1 && f.contains(type_name) {
return true;
}
}
}
false
}