use super::*;
pub(super) fn detect_nonlinear_nonneg(
law: &crate::ast::VerifyLaw,
fn_name: &str,
inputs: &ProofLowerInputs,
) -> Option<Vec<String>> {
use std::collections::BTreeSet;
if law.givens.is_empty() {
return None;
}
if !law.givens.iter().all(|g| g.type_name.trim() == "Int") {
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)
};
let subject = resolve_user_fn(fn_name)?;
if subject.return_type != "Bool" {
return None;
}
let recursive = inputs.recursive_pure_fn_names();
let lhs_is_subject_call = match &law.lhs.node {
crate::ast::Expr::FnCall(callee, _args) => expr_to_dotted_name(&callee.node)
.and_then(|n| inputs.find_fn_def_by_call_name(&n))
.is_some_and(|fd| fd.name == subject.name),
_ => false,
};
if !lhs_is_subject_call {
return None;
}
if !matches!(
law.rhs.node,
crate::ast::Expr::Literal(crate::ast::Literal::Bool(true))
) {
return None;
}
let [crate::ast::Stmt::Expr(body)] = subject.body.stmts() else {
return None;
};
let operands = recognized_comparison(&body.node)?;
let mut cone: BTreeSet<String> = BTreeSet::new();
collect_fn_calls_expr(&law.lhs, &mut cone);
collect_fn_calls_expr(&law.rhs, &mut cone);
loop {
let before = cone.len();
let snapshot: Vec<String> = cone.iter().cloned().collect();
for name in snapshot {
let fd = resolve_user_fn(&name)?;
if recursive.contains(&fd.name) {
return None;
}
for stmt in fd.body.stmts() {
match stmt {
crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
collect_fn_calls_expr(e, &mut cone);
}
}
}
}
if cone.len() == before {
break;
}
}
if !cone.contains(fn_name) {
return None;
}
for name in &cone {
let fd = resolve_user_fn(name)?;
if !fd.params.iter().all(|(_, t)| t.trim() == "Int") {
return None;
}
let [crate::ast::Stmt::Expr(cone_body)] = fd.body.stmts() else {
return None;
};
match fd.return_type.as_str() {
"Bool" => {
recognized_comparison(&cone_body.node)?;
}
"Int" => {
if !is_int_arith_expr(cone_body, inputs, &cone) {
return None;
}
}
_ => return None,
}
}
for operand in &operands {
if !is_int_arith_expr_node(operand, inputs, &cone) {
return None;
}
}
let mut unfold_fns: Vec<String> = vec![fn_name.to_string()];
unfold_fns.extend(cone.iter().filter(|n| *n != fn_name).cloned());
Some(unfold_fns)
}
fn recognized_comparison(expr: &crate::ast::Expr) -> Option<Vec<&crate::ast::Expr>> {
use crate::ast::{BinOp, Expr};
match expr {
Expr::BinOp(BinOp::Gte, l, r) if is_int_zero(&r.node) => {
has_nonlinear_product(&l.node).then(|| vec![&l.node])
}
Expr::BinOp(BinOp::Lte, l, r) if is_int_zero(&l.node) => {
has_nonlinear_product(&r.node).then(|| vec![&r.node])
}
Expr::BinOp(BinOp::Lte | BinOp::Gte, l, r)
if is_product(&l.node)
&& is_product(&r.node)
&& (has_nonlinear_product(&l.node) || has_nonlinear_product(&r.node)) =>
{
Some(vec![&l.node, &r.node])
}
_ => None,
}
}
fn is_product(expr: &crate::ast::Expr) -> bool {
matches!(expr, crate::ast::Expr::BinOp(crate::ast::BinOp::Mul, _, _))
}
fn is_int_zero(expr: &crate::ast::Expr) -> bool {
matches!(expr, crate::ast::Expr::Literal(crate::ast::Literal::Int(0)))
}
fn has_nonlinear_product(expr: &crate::ast::Expr) -> bool {
use crate::ast::{BinOp, Expr};
match expr {
Expr::BinOp(BinOp::Mul, l, r) => {
(mentions_variable(&l.node) && mentions_variable(&r.node))
|| has_nonlinear_product(&l.node)
|| has_nonlinear_product(&r.node)
}
Expr::BinOp(BinOp::Add | BinOp::Sub, l, r) => {
has_nonlinear_product(&l.node) || has_nonlinear_product(&r.node)
}
Expr::Neg(inner) => has_nonlinear_product(&inner.node),
_ => false,
}
}
fn mentions_variable(expr: &crate::ast::Expr) -> bool {
use crate::ast::{BinOp, Expr};
match expr {
Expr::Ident(_) | Expr::Resolved { .. } | Expr::FnCall(..) => true,
Expr::Neg(inner) => mentions_variable(&inner.node),
Expr::BinOp(BinOp::Add | BinOp::Sub | BinOp::Mul, l, r) => {
mentions_variable(&l.node) || mentions_variable(&r.node)
}
_ => false,
}
}
fn is_int_arith_expr(
expr: &Spanned<crate::ast::Expr>,
inputs: &ProofLowerInputs,
cone: &std::collections::BTreeSet<String>,
) -> bool {
is_int_arith_expr_node(&expr.node, inputs, cone)
}
fn is_int_arith_expr_node(
expr: &crate::ast::Expr,
inputs: &ProofLowerInputs,
cone: &std::collections::BTreeSet<String>,
) -> bool {
use crate::ast::{BinOp, Expr, Literal};
match expr {
Expr::Literal(Literal::Int(_)) | Expr::Literal(Literal::BigInt(_)) => true,
Expr::Ident(_) | Expr::Resolved { .. } => true,
Expr::Neg(inner) => is_int_arith_expr(inner, inputs, cone),
Expr::BinOp(BinOp::Add | BinOp::Sub | BinOp::Mul, l, r) => {
is_int_arith_expr(l, inputs, cone) && is_int_arith_expr(r, inputs, cone)
}
Expr::FnCall(callee, args) => {
let Some(name) = expr_to_dotted_name(&callee.node) else {
return false;
};
let Some(fd) = inputs.find_fn_def_by_call_name(&name) else {
return false;
};
cone.contains(&fd.name)
&& fd.return_type == "Int"
&& args.iter().all(|a| is_int_arith_expr(a, inputs, cone))
}
_ => false,
}
}