use super::*;
pub(super) fn detect_finite_domain_cases(
law: &crate::ast::VerifyLaw,
inputs: &ProofLowerInputs,
) -> Option<Vec<String>> {
if law.givens.is_empty() {
return None;
}
let mut domain_product: usize = 1;
for given in &law.givens {
let size = finite_domain_size(&given.type_name, inputs)?;
domain_product = domain_product.checked_mul(size)?;
if domain_product > 16 {
return None;
}
}
Some(law.givens.iter().map(|g| g.name.clone()).collect())
}
pub(super) fn finite_domain_size(type_name: &str, inputs: &ProofLowerInputs) -> Option<usize> {
if type_name == "Bool" {
return Some(2);
}
match inputs.find_type_def(type_name)? {
crate::ast::TypeDef::Sum { variants, .. }
if variants.iter().all(|v| v.fields.is_empty()) =>
{
Some(variants.len())
}
_ => None,
}
}
pub(super) fn detect_enum_constant_fold(
law: &crate::ast::VerifyLaw,
fn_name: &str,
inputs: &ProofLowerInputs,
) -> Option<Vec<String>> {
use std::collections::BTreeSet;
let outer_fd = inputs.find_fn_def_by_call_name(fn_name)?;
let non_int_params: Vec<&str> = outer_fd
.params
.iter()
.enumerate()
.filter(|(_, (_, t))| t != "Int")
.map(|(i, _)| outer_fd.params[i].1.as_str())
.collect();
if non_int_params.is_empty() {
return None;
}
let ret = outer_fd.return_type.as_str();
if ret != "Int" && ret != "Bool" {
return None;
}
let mut saw_call = false;
let mut ok = true;
check_enum_calls_pinned(&law.lhs, fn_name, outer_fd, &mut saw_call, &mut ok);
check_enum_calls_pinned(&law.rhs, fn_name, outer_fd, &mut saw_call, &mut ok);
if !saw_call || !ok {
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;
}
}
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;
}
}
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());
}
}
Some(ordered)
}
pub(super) fn check_enum_calls_pinned(
expr: &Spanned<crate::ast::Expr>,
fn_name: &str,
fd: &FnDef,
saw_call: &mut bool,
ok: &mut bool,
) {
use crate::ast::Expr;
match &expr.node {
Expr::FnCall(callee, args) => {
if let Some(name) = expr_to_dotted_name(&callee.node) {
let leaf = name.rsplit('.').next().unwrap_or(&name);
if leaf == fn_name && args.len() == fd.params.len() {
*saw_call = true;
for (i, (_, ptype)) in fd.params.iter().enumerate() {
if ptype != "Int" && !is_constructor_literal(&args[i].node) {
*ok = false;
}
}
}
}
check_enum_calls_pinned(callee, fn_name, fd, saw_call, ok);
for a in args {
check_enum_calls_pinned(a, fn_name, fd, saw_call, ok);
}
}
Expr::BinOp(_, l, r) => {
check_enum_calls_pinned(l, fn_name, fd, saw_call, ok);
check_enum_calls_pinned(r, fn_name, fd, saw_call, ok);
}
Expr::Neg(inner) | Expr::ErrorProp(inner) => {
check_enum_calls_pinned(inner, fn_name, fd, saw_call, ok);
}
Expr::Attr(obj, _) => check_enum_calls_pinned(obj, fn_name, fd, saw_call, ok),
Expr::List(elems) | Expr::Tuple(elems) => {
for e in elems {
check_enum_calls_pinned(e, fn_name, fd, saw_call, ok);
}
}
_ => {}
}
}
pub(super) fn is_constructor_literal(expr: &crate::ast::Expr) -> bool {
use crate::ast::Expr;
match expr {
Expr::Constructor(_, _) => true,
Expr::Attr(obj, field) => {
let head_upper = matches!(
&obj.node,
Expr::Ident(n) if n.chars().next().is_some_and(|c| c.is_uppercase())
);
let field_upper = field.chars().next().is_some_and(|c| c.is_uppercase());
head_upper && field_upper
}
_ => false,
}
}
pub(super) fn expr_is_const_int(expr: &Spanned<crate::ast::Expr>) -> bool {
use crate::ast::Expr;
match &expr.node {
Expr::Literal(crate::ast::Literal::Int(_)) => true,
Expr::Neg(inner) => expr_is_const_int(inner),
Expr::BinOp(_, l, r) => expr_is_const_int(l) && expr_is_const_int(r),
_ => false,
}
}
pub(super) fn expr_has_var_product(expr: &Spanned<crate::ast::Expr>) -> bool {
use crate::ast::Expr;
match &expr.node {
Expr::BinOp(crate::ast::BinOp::Mul, l, r) => {
(!expr_is_const_int(l) && !expr_is_const_int(r))
|| expr_has_var_product(l)
|| expr_has_var_product(r)
}
Expr::BinOp(_, l, r) => expr_has_var_product(l) || expr_has_var_product(r),
Expr::Neg(inner) | Expr::Attr(inner, _) | Expr::ErrorProp(inner) => {
expr_has_var_product(inner)
}
Expr::FnCall(callee, args) => {
expr_has_var_product(callee) || args.iter().any(expr_has_var_product)
}
Expr::Match { subject, arms } => {
expr_has_var_product(subject) || arms.iter().any(|a| expr_has_var_product(&a.body))
}
Expr::Constructor(_, Some(inner)) => expr_has_var_product(inner),
Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
items.iter().any(expr_has_var_product)
}
Expr::MapLiteral(entries) => entries
.iter()
.any(|(k, v)| expr_has_var_product(k) || expr_has_var_product(v)),
Expr::RecordCreate { fields, .. } => fields.iter().any(|(_, e)| expr_has_var_product(e)),
Expr::RecordUpdate { base, updates, .. } => {
expr_has_var_product(base) || updates.iter().any(|(_, e)| expr_has_var_product(e))
}
Expr::InterpolatedStr(parts) => parts.iter().any(|p| match p {
crate::ast::StrPart::Parsed(inner) => expr_has_var_product(inner),
_ => false,
}),
Expr::TailCall(boxed) => boxed.args.iter().any(expr_has_var_product),
_ => false,
}
}