use super::*;
pub(super) fn detect_ring_identity(
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 == "Int" || is_all_int_record(&g.type_name, inputs))
{
return None;
}
if !law
.givens
.iter()
.any(|g| is_all_int_record(&g.type_name, inputs))
{
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 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 == "Int" || is_all_int_record(t, inputs))
{
return None;
}
let [crate::ast::Stmt::Expr(body)] = fd.body.stmts() else {
return None;
};
let ret = fd.return_type.as_str();
if ret == "Bool" {
let crate::ast::Expr::BinOp(crate::ast::BinOp::Eq, l, r) = &body.node else {
return None;
};
if !is_ring_value_expr(l, inputs, &cone) || !is_ring_value_expr(r, inputs, &cone) {
return None;
}
} else {
if ret != "Int" && !is_all_int_record(ret, inputs) {
return None;
}
if !is_ring_value_expr(body, inputs, &cone) {
return None;
}
}
}
let lhs_is_comparator_call = match &law.lhs.node {
crate::ast::Expr::FnCall(callee, args) => expr_to_dotted_name(&callee.node)
.and_then(|n| resolve_user_fn(&n))
.is_some_and(|fd| {
fd.return_type == "Bool"
&& cone.contains(&fd.name)
&& args.iter().all(|a| is_ring_value_expr(a, inputs, &cone))
}),
_ => false,
};
if lhs_is_comparator_call {
if !matches!(
law.rhs.node,
crate::ast::Expr::Literal(crate::ast::Literal::Bool(true))
) {
return None;
}
} else if !is_ring_value_expr(&law.lhs, inputs, &cone)
|| !is_ring_value_expr(&law.rhs, 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 is_all_int_record(type_name: &str, inputs: &ProofLowerInputs) -> bool {
matches!(
inputs.find_type_def(type_name),
Some(crate::ast::TypeDef::Product { fields, .. })
if fields.len() >= 2 && fields.iter().all(|(_, t)| t.trim() == "Int")
)
}
fn is_ring_value_expr(
expr: &Spanned<crate::ast::Expr>,
inputs: &ProofLowerInputs,
cone: &std::collections::BTreeSet<String>,
) -> bool {
use crate::ast::{BinOp, Expr, Literal};
match &expr.node {
Expr::Literal(Literal::Int(_)) => true,
Expr::Ident(_) | Expr::Resolved { .. } => true,
Expr::Neg(inner) => is_ring_value_expr(inner, inputs, cone),
Expr::Attr(obj, field) => {
field.chars().next().is_some_and(|c| c.is_lowercase())
&& is_ring_value_expr(obj, inputs, cone)
}
Expr::BinOp(BinOp::Add | BinOp::Sub | BinOp::Mul, l, r) => {
is_ring_value_expr(l, inputs, cone) && is_ring_value_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 != "Bool"
&& args.iter().all(|a| is_ring_value_expr(a, inputs, cone))
}
Expr::RecordCreate { type_name, fields } => {
is_all_int_record(type_name, inputs)
&& fields
.iter()
.all(|(_, e)| is_ring_value_expr(e, inputs, cone))
}
_ => false,
}
}