use super::*;
pub(super) fn detect_simp_over_prelude_lemmas(
law: &crate::ast::VerifyLaw,
fn_name: &str,
inputs: &ProofLowerInputs,
fn_contracts: &std::collections::HashMap<crate::ir::FnId, crate::ir::FnContract>,
) -> Option<crate::ir::ProofStrategy> {
use std::collections::BTreeSet;
if law.givens.is_empty() {
return None;
}
let resolve_user_fn = |name: &str| -> Option<&FnDef> {
let fd = inputs.find_fn_def_by_call_name(name)?;
if !fd.effects.is_empty() || fd.name == "main" {
return None;
}
Some(fd)
};
resolve_user_fn(fn_name)?;
let recursive = inputs.recursive_pure_fn_names();
let mut lhs_calls: BTreeSet<String> = BTreeSet::new();
collect_fn_calls_expr(&law.lhs, &mut lhs_calls);
let mut cone: BTreeSet<String> = BTreeSet::new();
let mut fuel_fns: BTreeSet<String> = BTreeSet::new();
for name in &lhs_calls {
let Some(fd) = resolve_user_fn(name) else {
continue;
};
if !recursive.contains(&fd.name) {
cone.insert(fd.name.clone());
continue;
}
let fn_key = match inputs.fn_owning_scope(fd) {
Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), &fd.name),
None => crate::ir::FnKey::entry(&fd.name),
};
let classified = inputs
.symbol_table
.fn_id_of(&fn_key)
.is_some_and(|id| fn_contracts.contains_key(&id));
if !classified || !calls_have_measure_closed_args(&law.lhs, &fd.name, inputs) {
return None;
}
fuel_fns.insert(fd.name.clone());
}
if !cone.contains(fn_name) && !fuel_fns.contains(fn_name) {
return None;
}
loop {
let before = cone.len();
let snapshot: Vec<String> = cone.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) = resolve_user_fn(&c)
&& !recursive.contains(&callee.name)
{
cone.insert(callee.name.clone());
}
}
}
if cone.len() == before {
break;
}
}
let mut builtins: BTreeSet<String> = BTreeSet::new();
collect_builtin_calls_expr(&law.lhs, &mut builtins);
collect_builtin_calls_expr(&law.rhs, &mut builtins);
for name in cone.iter().chain(fuel_fns.iter()) {
if let Some(fd) = resolve_user_fn(name) {
for stmt in fd.body.stmts() {
match stmt {
crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
collect_builtin_calls_expr(e, &mut builtins);
}
}
}
}
}
let mut unfold_fns: Vec<String> = Vec::new();
if cone.contains(fn_name) {
unfold_fns.push(fn_name.to_string());
}
unfold_fns.extend(cone.iter().filter(|n| *n != fn_name).cloned());
Some(crate::ir::ProofStrategy::SimpOverPreludeLemmas {
unfold_fns,
fuel_fns: fuel_fns.into_iter().collect(),
builtins: builtins.into_iter().collect(),
})
}
pub(super) fn calls_have_measure_closed_args(
expr: &Spanned<crate::ast::Expr>,
target: &str,
inputs: &ProofLowerInputs,
) -> bool {
use crate::ast::Expr;
let self_ok = match &expr.node {
Expr::FnCall(callee, args) => {
let is_target = expr_to_dotted_name(&callee.node)
.is_some_and(|n| n.rsplit('.').next().unwrap_or(&n) == target);
!is_target || args.iter().all(|a| expr_is_measure_closed(a, inputs))
}
_ => true,
};
if !self_ok {
return false;
}
match &expr.node {
Expr::FnCall(callee, args) => {
calls_have_measure_closed_args(callee, target, inputs)
&& args
.iter()
.all(|a| calls_have_measure_closed_args(a, target, inputs))
}
Expr::BinOp(_, l, r) => {
calls_have_measure_closed_args(l, target, inputs)
&& calls_have_measure_closed_args(r, target, inputs)
}
Expr::Neg(inner) | Expr::ErrorProp(inner) => {
calls_have_measure_closed_args(inner, target, inputs)
}
Expr::Attr(obj, _) => calls_have_measure_closed_args(obj, target, inputs),
Expr::Constructor(_, Some(inner)) => calls_have_measure_closed_args(inner, target, inputs),
Expr::List(elems) | Expr::Tuple(elems) => elems
.iter()
.all(|e| calls_have_measure_closed_args(e, target, inputs)),
_ => true,
}
}
pub(super) fn expr_is_measure_closed(
expr: &Spanned<crate::ast::Expr>,
inputs: &ProofLowerInputs,
) -> bool {
use crate::ast::Expr;
let payload_atom = |e: &Spanned<Expr>| -> bool {
matches!(
e.node,
Expr::Literal(_) | Expr::Ident(_) | Expr::Resolved { .. }
) || matches!(&e.node, Expr::Neg(inner) if matches!(inner.node, Expr::Literal(_)))
};
let variant_fields_scalar = |ctor_name: &str| -> bool {
let (type_name, variant_name) = match ctor_name.rsplit_once('.') {
Some(pair) => pair,
None => return false,
};
let Some(crate::ast::TypeDef::Sum { variants, .. }) = inputs.find_type_def(type_name)
else {
return false;
};
variants.iter().any(|v| {
v.name == variant_name
&& v.fields
.iter()
.all(|f| matches!(f.trim(), "Int" | "Float" | "String" | "Bool"))
})
};
match &expr.node {
Expr::Literal(_) => true,
Expr::Neg(inner) => matches!(inner.node, Expr::Literal(_)),
Expr::Constructor(name, payload) => {
let payload_ok = match payload.as_deref() {
None => true,
Some(Spanned {
node: Expr::Tuple(items),
..
}) => items.iter().all(payload_atom),
Some(single) => payload_atom(single),
};
payload_ok && variant_fields_scalar(name)
}
Expr::FnCall(callee, args) => expr_to_dotted_name(&callee.node).is_some_and(|n| {
n.rsplit('.')
.next()
.is_some_and(|leaf| leaf.chars().next().is_some_and(|c| c.is_uppercase()))
&& variant_fields_scalar(&n)
&& args.iter().all(payload_atom)
}),
Expr::Attr(obj, field) => {
let head = match &obj.node {
Expr::Ident(n) => n.clone(),
_ => return false,
};
field.chars().next().is_some_and(|c| c.is_uppercase())
&& variant_fields_scalar(&format!("{head}.{field}"))
}
_ => false,
}
}
pub(super) fn collect_builtin_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)
&& name.contains('.')
{
let head = name.split('.').next().unwrap_or(&name);
let leaf = name.rsplit('.').next().unwrap_or(&name);
if head.chars().next().is_some_and(|c| c.is_uppercase())
&& leaf.chars().next().is_some_and(|c| c.is_lowercase())
{
out.insert(name);
}
}
collect_builtin_calls_expr(f, out);
for arg in args {
collect_builtin_calls_expr(arg, out);
}
}
Expr::BinOp(op, l, r) => {
let is_stringy = |e: &Spanned<Expr>| {
matches!(
&e.node,
Expr::Literal(crate::ast::Literal::Str(_)) | Expr::InterpolatedStr(_)
)
};
if matches!(op, crate::ast::BinOp::Add) && (is_stringy(l) || is_stringy(r)) {
out.insert("String.concat".to_string());
}
collect_builtin_calls_expr(l, out);
collect_builtin_calls_expr(r, out);
}
Expr::Attr(obj, _) => collect_builtin_calls_expr(obj, out),
Expr::Match { subject, arms, .. } => {
collect_builtin_calls_expr(subject, out);
for arm in arms {
collect_builtin_calls_expr(&arm.body, out);
}
}
Expr::TailCall(boxed) => {
for arg in &boxed.args {
collect_builtin_calls_expr(arg, out);
}
}
Expr::ErrorProp(inner) | Expr::Neg(inner) => collect_builtin_calls_expr(inner, out),
Expr::Constructor(_, Some(arg)) => collect_builtin_calls_expr(arg, out),
Expr::RecordCreate { fields, .. } => {
for (_, e) in fields {
collect_builtin_calls_expr(e, out);
}
}
Expr::List(elems) | Expr::Tuple(elems) | Expr::IndependentProduct(elems, _) => {
for e in elems {
collect_builtin_calls_expr(e, out);
}
}
_ => {}
}
}
pub(super) struct SimpOmegaPlan {
pub(super) unfold_fns: Vec<String>,
pub(super) wrapper_return: bool,
pub(super) smart_guard: Option<crate::ir::SmartGuard>,
pub(super) lifted: bool,
}
pub(super) fn detect_simp_omega_unfold(
law: &crate::ast::VerifyLaw,
fn_name: &str,
inputs: &ProofLowerInputs,
refined_types: &std::collections::HashMap<crate::ir::TypeId, crate::ir::RefinedTypeDecl>,
) -> Option<SimpOmegaPlan> {
use std::collections::BTreeSet;
let outer_fd = inputs.find_fn_def_by_call_name(fn_name)?;
if law.givens.is_empty() || law.givens.iter().any(|g| g.type_name != "Int") {
return None;
}
let symbols = inputs.symbol_table;
let lifted = law.givens.iter().any(|g| {
refinement_lift_for_given_ir(
&g.name,
&law.lhs,
&law.rhs,
refined_types,
symbols,
inputs.dep_modules,
)
.is_some()
});
if !lifted && outer_fd.params.iter().any(|(_, t)| t != "Int") {
return None;
}
let mut fn_names: BTreeSet<String> = BTreeSet::new();
collect_fn_calls_expr(&law.lhs, &mut fn_names);
collect_fn_calls_expr(&law.rhs, &mut fn_names);
fn_names.insert(fn_name.to_string());
loop {
let before = fn_names.len();
let snapshot: Vec<String> = fn_names.iter().cloned().collect();
for fd in iter_all_fn_defs(inputs) {
if !snapshot.contains(&fd.name) {
continue;
}
for stmt in fd.body.stmts() {
match stmt {
crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
collect_fn_calls_expr(e, &mut fn_names);
}
}
}
}
if fn_names.len() == before {
break;
}
}
if fn_names
.iter()
.any(|n| inputs.find_fn_def_by_call_name(n).is_none())
{
return None;
}
let mut wrapper_return = false;
for fd in iter_all_fn_defs(inputs) {
if !fn_names.contains(&fd.name) {
continue;
}
let mut self_only: BTreeSet<String> = BTreeSet::new();
self_only.insert(fd.name.clone());
if body_calls_any_of_inputs(&fd.body, &self_only) {
return None;
}
if fd.name == fn_name && !lifted && fd.params.iter().any(|(_, t)| t != "Int") {
return None;
}
let ret = fd.return_type.as_str();
if ret != "Int" && ret != "Float" {
wrapper_return = true;
}
}
if !(lifted || (wrapper_return && law.when.is_some())) {
let mut nonlinear = expr_has_var_product(&law.lhs) || expr_has_var_product(&law.rhs);
if let Some(when_expr) = &law.when {
nonlinear = nonlinear || expr_has_var_product(when_expr);
}
if !nonlinear {
for fd in iter_all_fn_defs(inputs) {
if !fn_names.contains(&fd.name) {
continue;
}
let body_nonlinear = fd.body.stmts().iter().any(|stmt| match stmt {
crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
expr_has_var_product(e)
}
});
if body_nonlinear {
nonlinear = true;
break;
}
}
}
if nonlinear {
return None;
}
}
let mut ordered: Vec<String> = Vec::new();
if fn_names.contains(fn_name) {
ordered.push(fn_name.to_string());
}
for n in &fn_names {
if n != fn_name {
ordered.push(n.clone());
}
}
let smart_guard = extract_smart_constructor_guard(&fn_names, inputs);
Some(SimpOmegaPlan {
unfold_fns: ordered,
wrapper_return,
smart_guard,
lifted,
})
}