use super::*;
pub(super) fn detect_wrapper_over_recursion(
law: &crate::ast::VerifyLaw,
fn_name: &str,
inputs: &ProofLowerInputs,
) -> Option<crate::ir::ProofStrategy> {
use crate::analysis::shape::ModulePattern;
use crate::ast::{BinOp, Expr, Stmt};
fn ident_name(e: &Spanned<Expr>) -> Option<&str> {
match &e.node {
Expr::Ident(n) => Some(n.as_str()),
Expr::Resolved { name, .. } => Some(name.as_str()),
_ => None,
}
}
let shape = inputs.program_shape?;
if law.givens.len() != 1 {
return None;
}
let given_name = &law.givens[0].name;
let (inner_fn, fold_driver_type, value_first, step_op, step_combine_fn) =
shape.patterns.iter().find_map(|p| match p {
ModulePattern::AccumulatorFold {
wrapper_fn,
loop_fn,
step_op,
step_fn,
finish_fn: None,
driver_type,
step_value_first,
..
} if wrapper_fn == fn_name => Some((
loop_fn.clone(),
driver_type.clone(),
*step_value_first,
*step_op,
step_fn.clone(),
)),
_ => None,
})?;
let wrapper_fn = fn_name.to_string();
let (combine_op, driver, combine_fn) = match fold_driver_type {
None => {
let op = step_op.filter(|op| matches!(op, BinOp::Add | BinOp::Mul))?;
(op, crate::ir::WrapperDriver::List, None)
}
Some(type_name) => {
let combine = step_combine_fn?;
let combine_fd = inputs.find_fn_def_by_call_name(&combine)?;
let op = nat_monoid_op_of(combine_fd)?;
(
op,
crate::ir::WrapperDriver::PeanoNat {
type_name,
value_first,
},
Some(combine),
)
}
};
let extract = |expr: &Spanned<crate::ast::Expr>| -> Option<String> {
let Expr::FnCall(callee, args) = &expr.node else {
return None;
};
let name = ident_name(callee)?;
if args.len() != 1 {
return None;
}
if ident_name(&args[0])? != given_name {
return None;
}
Some(name.to_string())
};
let lhs_call = extract(&law.lhs);
let rhs_call = extract(&law.rhs);
let other_fn = match (lhs_call, rhs_call) {
(Some(l), Some(r)) if l == wrapper_fn && r != wrapper_fn => r,
(Some(l), Some(r)) if r == wrapper_fn && l != wrapper_fn => l,
_ => return None,
};
let wrapper_fd = inputs.find_fn_def_by_call_name(&wrapper_fn)?;
let wstmts = wrapper_fd.body.stmts();
if wstmts.len() != 1 {
return None;
}
let Stmt::Expr(wbody) = &wstmts[0] else {
return None;
};
let (wcallee_name, wargs) = match &wbody.node {
Expr::FnCall(c, a) => (ident_name(c)?, a.clone()),
Expr::TailCall(td) => (td.target.as_str(), td.args.clone()),
_ => return None,
};
if wcallee_name != inner_fn {
return None;
}
if wargs.len() != 2 {
return None;
}
let neutral_matches = match &driver {
crate::ir::WrapperDriver::List => {
let expected_neutral: i64 = match combine_op {
BinOp::Add | BinOp::Sub => 0,
BinOp::Mul => 1,
_ => return None,
};
matches!(
&wargs[1].node,
Expr::Literal(crate::ast::Literal::Int(n)) if *n == expected_neutral
)
}
crate::ir::WrapperDriver::PeanoNat { .. } => match combine_op {
BinOp::Mul => is_peano_one(&wargs[1]),
BinOp::Add => is_peano_zero(&wargs[1]),
_ => return None,
},
};
if !neutral_matches {
return None;
}
Some(crate::ir::ProofStrategy::WrapperOverRecursion {
wrapper_fn,
inner_fn,
other_fn,
combine_op,
driver,
combine_fn,
})
}
pub(super) fn detect_tailrec_fixed_base_fold(
law: &crate::ast::VerifyLaw,
inputs: &ProofLowerInputs,
) -> Option<crate::ir::ProofStrategy> {
use crate::ast::{Expr, Pattern, Stmt};
fn ident_name(e: &Spanned<Expr>) -> Option<&str> {
match &e.node {
Expr::Ident(n) => Some(n.as_str()),
Expr::Resolved { name, .. } => Some(name.as_str()),
_ => None,
}
}
let is_neutral_one = |e: &Spanned<Expr>| -> bool {
if is_peano_one(e) {
return true;
}
if let Expr::FnCall(c, a) = &e.node
&& a.is_empty()
&& let Some(name) = expr_to_dotted_name(&c.node)
&& let Some(fd) = inputs.find_fn_def_by_call_name(&name)
&& let Some(b) = body_terminal_expr(fd.body.as_ref())
{
return is_peano_one(b);
}
false
};
let is_neutral_zero = |e: &Spanned<Expr>| -> bool {
if is_peano_zero(e) {
return true;
}
if let Expr::FnCall(c, a) = &e.node
&& a.is_empty()
&& let Some(name) = expr_to_dotted_name(&c.node)
&& let Some(fd) = inputs.find_fn_def_by_call_name(&name)
&& let Some(b) = body_terminal_expr(fd.body.as_ref())
{
return is_peano_zero(b);
}
false
};
if law.givens.len() != 2 || law.when.is_some() {
return None;
}
let g0 = law.givens[0].name.as_str();
let g1 = law.givens[1].name.as_str();
let type_name = law.givens[1].type_name.clone();
let as_spec = |e: &Spanned<Expr>| -> Option<String> {
let Expr::FnCall(c, a) = &e.node else {
return None;
};
if a.len() != 2 || ident_name(&a[0])? != g0 || ident_name(&a[1])? != g1 {
return None;
}
expr_to_dotted_name(&c.node)
};
fn as_loop<'e>(
e: &'e Spanned<Expr>,
g0: &str,
g1: &str,
) -> Option<(String, &'e Spanned<Expr>)> {
let id = |x: &Spanned<Expr>| -> Option<String> {
match &x.node {
Expr::Ident(n) => Some(n.clone()),
Expr::Resolved { name, .. } => Some(name.clone()),
_ => None,
}
};
let Expr::FnCall(c, a) = &e.node else {
return None;
};
if a.len() != 3 || id(&a[0]).as_deref() != Some(g0) || id(&a[1]).as_deref() != Some(g1) {
return None;
}
Some((expr_to_dotted_name(&c.node)?, &a[2]))
}
let (spec_fn, loop_fn, neutral) = match (as_spec(&law.lhs), as_loop(&law.rhs, g0, g1)) {
(Some(s), Some((l, n))) => (s, l, n),
_ => match (as_spec(&law.rhs), as_loop(&law.lhs, g0, g1)) {
(Some(s), Some((l, n))) => (s, l, n),
_ => return None,
},
};
if spec_fn == loop_fn {
return None;
}
let spec_fd = inputs.find_fn_def_by_call_name(&spec_fn)?;
let loop_fd = inputs.find_fn_def_by_call_name(&loop_fn)?;
if spec_fd.params.len() != 2 || loop_fd.params.len() != 3 {
return None;
}
let base_p = loop_fd.params[0].0.as_str();
let drv_p = loop_fd.params[1].0.as_str();
let acc_p = loop_fd.params[2].0.as_str();
let spec_base = spec_fd.params[0].0.as_str();
let spec_drv = spec_fd.params[1].0.as_str();
let [Stmt::Expr(sbody)] = spec_fd.body.stmts() else {
return None;
};
let Expr::Match { subject, arms } = &sbody.node else {
return None;
};
if ident_name(subject) != Some(spec_drv) || arms.len() != 2 {
return None;
}
let mut spec_combine: Option<String> = None;
for arm in arms {
let Pattern::Constructor(_, binders) = &arm.pattern else {
return None;
};
match binders.len() {
0 => {} 1 => {
let Expr::FnCall(cc, cargs) = &arm.body.node else {
return None;
};
let cname = expr_to_dotted_name(&cc.node)?;
if cargs.len() != 2 || ident_name(&cargs[0])? != spec_base {
return None;
}
let Expr::FnCall(rc, rargs) = &cargs[1].node else {
return None;
};
if expr_to_dotted_name(&rc.node)? != spec_fn
|| rargs.len() != 2
|| ident_name(&rargs[0])? != spec_base
|| ident_name(&rargs[1])? != binders[0].as_str()
{
return None;
}
spec_combine = Some(cname);
}
_ => return None,
}
}
let spec_combine = spec_combine?;
let [Stmt::Expr(lbody)] = loop_fd.body.stmts() else {
return None;
};
let Expr::Match {
subject: lsubj,
arms: larms,
} = &lbody.node
else {
return None;
};
if ident_name(lsubj) != Some(drv_p) || larms.len() != 2 {
return None;
}
let mut loop_combine: Option<String> = None;
for arm in larms {
let Pattern::Constructor(_, binders) = &arm.pattern else {
return None;
};
match binders.len() {
0 => {
if ident_name(&arm.body) != Some(acc_p) {
return None;
}
}
1 => {
let (callee, cargs) = match &arm.body.node {
Expr::FnCall(c, a) => (expr_to_dotted_name(&c.node)?, a.clone()),
Expr::TailCall(td) => (td.target.clone(), td.args.clone()),
_ => return None,
};
if callee != loop_fn
|| cargs.len() != 3
|| ident_name(&cargs[0])? != base_p
|| ident_name(&cargs[1])? != binders[0].as_str()
{
return None;
}
let Expr::FnCall(sc, sargs) = &cargs[2].node else {
return None;
};
if expr_to_dotted_name(&sc.node)? != spec_combine
|| sargs.len() != 2
|| ident_name(&sargs[0])? != base_p
|| ident_name(&sargs[1])? != acc_p
{
return None;
}
loop_combine = Some(expr_to_dotted_name(&sc.node)?);
}
_ => return None,
}
}
if loop_combine.as_deref() != Some(spec_combine.as_str()) {
return None;
}
let combine_fd = inputs.find_fn_def_by_call_name(&spec_combine)?;
let combine_op = nat_monoid_op_of(combine_fd)?;
let neutral_ok = match combine_op {
crate::ast::BinOp::Mul => is_neutral_one(neutral),
crate::ast::BinOp::Add => is_neutral_zero(neutral),
_ => return None,
};
if !neutral_ok {
return None;
}
Some(crate::ir::ProofStrategy::TailRecFixedBaseFold {
spec_fn,
loop_fn,
combine_fn: spec_combine,
combine_op,
type_name,
})
}
fn peano_ctor_value(
e: &Spanned<crate::ast::Expr>,
) -> Option<(String, Vec<&Spanned<crate::ast::Expr>>)> {
use crate::ast::Expr;
let short = |name: &str| name.rsplit('.').next().unwrap_or(name).to_string();
match &e.node {
Expr::FnCall(callee, args) => {
let name = expr_to_dotted_name(&callee.node)?;
Some((short(&name), args.iter().collect()))
}
Expr::Constructor(name, arg) => {
Some((short(name), arg.iter().map(|b| b.as_ref()).collect()))
}
Expr::Attr(..) => expr_to_dotted_name(&e.node).map(|n| (short(&n), Vec::new())),
_ => None,
}
}
fn nat_monoid_op_of(fd: &crate::ast::FnDef) -> Option<crate::ast::BinOp> {
use crate::ast::{BinOp, Expr, Pattern, Stmt};
if fd.params.len() != 2 {
return None;
}
let second = fd.params[1].0.as_str();
let [Stmt::Expr(body)] = fd.body.stmts() else {
return None;
};
let Expr::Match { arms, .. } = &body.node else {
return None;
};
for arm in arms {
let Pattern::Constructor(_, binders) = &arm.pattern else {
continue;
};
if !binders.is_empty() {
continue;
}
if matches_ident_expr(&arm.body, second) {
return Some(BinOp::Add);
}
if peano_ctor_value(&arm.body).is_some_and(|(_, args)| args.is_empty()) {
return Some(BinOp::Mul);
}
}
None
}
fn is_peano_zero(e: &Spanned<crate::ast::Expr>) -> bool {
peano_ctor_value(e).is_some_and(|(_, args)| args.is_empty())
}
fn is_peano_one(e: &Spanned<crate::ast::Expr>) -> bool {
peano_ctor_value(e).is_some_and(|(_, args)| args.len() == 1 && is_peano_zero(args[0]))
}
pub(super) fn wrapper_binop(fn_name: &str, inputs: &ProofLowerInputs) -> Option<crate::ast::BinOp> {
use crate::ast::{BinOp, Expr};
let fd = inputs.find_fn_def_by_call_name(fn_name)?;
if fd.params.len() != 2 || fd.return_type != "Int" {
return None;
}
let (p1, t1) = &fd.params[0];
let (p2, t2) = &fd.params[1];
if t1 != "Int" || t2 != "Int" {
return None;
}
let expr = body_terminal_expr(fd.body.as_ref())?;
let Expr::BinOp(op, left, right) = &expr.node else {
return None;
};
if !matches_ident_expr(left, p1) || !matches_ident_expr(right, p2) {
return None;
}
matches!(op, BinOp::Add | BinOp::Mul | BinOp::Sub).then_some(*op)
}
pub(super) fn detect_wrapper_commutative(
law: &crate::ast::VerifyLaw,
fn_name: &str,
_op: crate::ast::BinOp,
) -> bool {
if law.givens.len() != 2 || law.givens.iter().any(|g| g.type_name != "Int") {
return false;
}
let a = &law.givens[0].name;
let b = &law.givens[1].name;
matches_binary_call(&law.lhs, fn_name, a, b) && matches_binary_call(&law.rhs, fn_name, b, a)
|| matches_binary_call(&law.lhs, fn_name, b, a)
&& matches_binary_call(&law.rhs, fn_name, a, b)
}
pub(super) fn detect_wrapper_associative(
law: &crate::ast::VerifyLaw,
fn_name: &str,
_op: crate::ast::BinOp,
) -> bool {
if law.givens.len() != 3 || law.givens.iter().any(|g| g.type_name != "Int") {
return false;
}
let a = &law.givens[0].name;
let b = &law.givens[1].name;
let c = &law.givens[2].name;
let nested = |side| matches_assoc_nested(side, fn_name, a, b, c);
let flat = |side| matches_assoc_flat(side, fn_name, a, b, c);
(nested(&law.lhs) && flat(&law.rhs)) || (nested(&law.rhs) && flat(&law.lhs))
}
pub(super) fn detect_wrapper_unary_equivalence(
law: &crate::ast::VerifyLaw,
fn_name: &str,
inputs: &ProofLowerInputs,
) -> Option<String> {
if law.givens.len() != 1 || law.givens[0].type_name != "Int" {
return None;
}
let unary = unary_int_wrapper(fn_name, inputs)?;
let g = &law.givens[0].name;
let try_side = |call_side: &Spanned<crate::ast::Expr>,
other_side: &Spanned<crate::ast::Expr>|
-> Option<String> {
if !matches_unary_call(call_side, fn_name, g) {
return None;
}
let (callee_name, var_first, lit) = binary_call_var_const(other_side, g)?;
if lit != unary.constant || var_first != unary.var_first {
return None;
}
let inner_op = wrapper_binop(&callee_name, inputs)?;
if inner_op != unary.op {
return None;
}
Some(callee_name)
};
try_side(&law.lhs, &law.rhs).or_else(|| try_side(&law.rhs, &law.lhs))
}
#[derive(Debug, Clone, Copy)]
pub(super) struct UnaryIntWrapper {
op: crate::ast::BinOp,
constant: i64,
var_first: bool,
}
pub(super) fn unary_int_wrapper(
fn_name: &str,
inputs: &ProofLowerInputs,
) -> Option<UnaryIntWrapper> {
use crate::ast::{Expr, Literal};
let fd = inputs.find_fn_def_by_call_name(fn_name)?;
if fd.params.len() != 1 || fd.return_type != "Int" {
return None;
}
let (param, param_ty) = &fd.params[0];
if param_ty != "Int" {
return None;
}
let expr = body_terminal_expr(fd.body.as_ref())?;
let Expr::BinOp(op, left, right) = &expr.node else {
return None;
};
let lit_of = |e: &Spanned<Expr>| -> Option<i64> {
match &e.node {
Expr::Literal(Literal::Int(n)) => Some(*n),
_ => None,
}
};
if matches_ident_expr(left, param) {
let n = lit_of(right)?;
return Some(UnaryIntWrapper {
op: *op,
constant: n,
var_first: true,
});
}
if matches_ident_expr(right, param) {
let n = lit_of(left)?;
return Some(UnaryIntWrapper {
op: *op,
constant: n,
var_first: false,
});
}
None
}
pub(super) fn matches_unary_call(
expr: &Spanned<crate::ast::Expr>,
fn_name: &str,
arg: &str,
) -> bool {
use crate::ast::Expr;
let Expr::FnCall(callee, args) = &expr.node else {
return false;
};
args.len() == 1 && callee_matches_name(callee, fn_name) && matches_ident_expr(&args[0], arg)
}
pub(super) fn binary_call_var_const(
expr: &Spanned<crate::ast::Expr>,
var_name: &str,
) -> Option<(String, bool, i64)> {
use crate::ast::{Expr, Literal};
let Expr::FnCall(callee, args) = &expr.node else {
return None;
};
if args.len() != 2 {
return None;
}
let callee_name = expr_to_dotted_name(&callee.node)?;
match (&args[0].node, &args[1].node) {
(Expr::Ident(v) | Expr::Resolved { name: v, .. }, Expr::Literal(Literal::Int(n)))
if v == var_name =>
{
Some((callee_name, true, *n))
}
(Expr::Literal(Literal::Int(n)), Expr::Ident(v) | Expr::Resolved { name: v, .. })
if v == var_name =>
{
Some((callee_name, false, *n))
}
_ => None,
}
}
pub(super) fn detect_wrapper_sub_right_identity(
law: &crate::ast::VerifyLaw,
fn_name: &str,
) -> bool {
if law.givens.len() != 1 || law.givens[0].type_name != "Int" {
return false;
}
let g = &law.givens[0].name;
matches_sub_right_identity_side(&law.lhs, &law.rhs, fn_name, g)
|| matches_sub_right_identity_side(&law.rhs, &law.lhs, fn_name, g)
}
pub(super) fn detect_wrapper_sub_anti_commutative(
law: &crate::ast::VerifyLaw,
fn_name: &str,
) -> Option<bool> {
if law.givens.len() != 2 || law.givens.iter().any(|g| g.type_name != "Int") {
return None;
}
let a = &law.givens[0].name;
let b = &law.givens[1].name;
if matches_binary_call(&law.lhs, fn_name, a, b)
&& matches_neg_binary_call(&law.rhs, fn_name, b, a)
{
return Some(true);
}
if matches_binary_call(&law.rhs, fn_name, a, b)
&& matches_neg_binary_call(&law.lhs, fn_name, b, a)
{
return Some(false);
}
None
}
pub(super) fn detect_wrapper_identity(
law: &crate::ast::VerifyLaw,
fn_name: &str,
op: crate::ast::BinOp,
) -> bool {
if law.givens.len() != 1 || law.givens[0].type_name != "Int" {
return false;
}
let identity = match op {
crate::ast::BinOp::Add => 0,
crate::ast::BinOp::Mul => 1,
_ => return false,
};
let g = &law.givens[0].name;
matches_identity_side(&law.lhs, &law.rhs, fn_name, g, identity)
|| matches_identity_side(&law.rhs, &law.lhs, fn_name, g, identity)
}
pub(super) fn body_terminal_expr(body: &crate::ast::FnBody) -> Option<&Spanned<crate::ast::Expr>> {
use crate::ast::Stmt;
match body.stmts() {
[Stmt::Expr(expr)] => Some(expr),
_ => None,
}
}
pub(super) fn simplify_identity_expr(
expr: &Spanned<crate::ast::Expr>,
) -> Spanned<crate::ast::Expr> {
use crate::ast::{BinOp, Expr, Literal};
let line = expr.line;
let int_lit = |e: &Expr| -> Option<i64> {
match e {
Expr::Literal(Literal::Int(n)) => Some(*n),
_ => None,
}
};
let new_node = match &expr.node {
Expr::BinOp(op, left, right) => {
let left = simplify_identity_expr(left);
let right = simplify_identity_expr(right);
match op {
BinOp::Add => {
if int_lit(&left.node) == Some(0) {
return right;
} else if int_lit(&right.node) == Some(0) {
return left;
} else {
Expr::BinOp(*op, Box::new(left), Box::new(right))
}
}
BinOp::Sub => {
if int_lit(&right.node) == Some(0) {
return left;
} else {
Expr::BinOp(*op, Box::new(left), Box::new(right))
}
}
BinOp::Mul => {
if int_lit(&left.node) == Some(0) || int_lit(&right.node) == Some(0) {
Expr::Literal(Literal::Int(0))
} else if int_lit(&left.node) == Some(1) {
return right;
} else if int_lit(&right.node) == Some(1) {
return left;
} else {
Expr::BinOp(*op, Box::new(left), Box::new(right))
}
}
_ => Expr::BinOp(*op, Box::new(left), Box::new(right)),
}
}
Expr::Neg(inner) => Expr::Neg(Box::new(simplify_identity_expr(inner))),
Expr::Attr(base, field) => {
Expr::Attr(Box::new(simplify_identity_expr(base)), field.clone())
}
Expr::FnCall(callee, args) => Expr::FnCall(
Box::new(simplify_identity_expr(callee)),
args.iter().map(simplify_identity_expr).collect(),
),
Expr::Match { subject, arms } => Expr::Match {
subject: Box::new(simplify_identity_expr(subject)),
arms: arms
.iter()
.map(|arm| crate::ast::MatchArm {
pattern: arm.pattern.clone(),
body: Box::new(simplify_identity_expr(&arm.body)),
binding_slots: arm.binding_slots.clone(),
})
.collect(),
},
other => other.clone(),
};
Spanned::new(new_node, line)
}
pub(super) fn matches_ident_expr(expr: &Spanned<crate::ast::Expr>, name: &str) -> bool {
use crate::ast::Expr;
matches!(&expr.node, Expr::Ident(n) | Expr::Resolved { name: n, .. } if n == name)
}
pub(super) fn callee_matches_name(expr: &Spanned<crate::ast::Expr>, target: &str) -> bool {
let Some(name) = expr_to_dotted_name(&expr.node) else {
return false;
};
name == target
}
pub(super) fn call2_args<'a>(
expr: &'a Spanned<crate::ast::Expr>,
fn_name: &str,
) -> Option<(&'a Spanned<crate::ast::Expr>, &'a Spanned<crate::ast::Expr>)> {
use crate::ast::Expr;
let Expr::FnCall(callee, args) = &expr.node else {
return None;
};
if args.len() != 2 || !callee_matches_name(callee, fn_name) {
return None;
}
Some((&args[0], &args[1]))
}
pub(super) fn matches_binary_call(
expr: &Spanned<crate::ast::Expr>,
fn_name: &str,
a: &str,
b: &str,
) -> bool {
let Some((x, y)) = call2_args(expr, fn_name) else {
return false;
};
matches_ident_expr(x, a) && matches_ident_expr(y, b)
}
pub(super) fn matches_assoc_nested(
expr: &Spanned<crate::ast::Expr>,
fn_name: &str,
a: &str,
b: &str,
c: &str,
) -> bool {
let Some((ab, z)) = call2_args(expr, fn_name) else {
return false;
};
let Some((x, y)) = call2_args(ab, fn_name) else {
return false;
};
matches_ident_expr(x, a) && matches_ident_expr(y, b) && matches_ident_expr(z, c)
}
pub(super) fn matches_assoc_flat(
expr: &Spanned<crate::ast::Expr>,
fn_name: &str,
a: &str,
b: &str,
c: &str,
) -> bool {
let Some((x, bc)) = call2_args(expr, fn_name) else {
return false;
};
let Some((y, z)) = call2_args(bc, fn_name) else {
return false;
};
matches_ident_expr(x, a) && matches_ident_expr(y, b) && matches_ident_expr(z, c)
}
pub(super) fn matches_sub_right_identity_side(
call_side: &Spanned<crate::ast::Expr>,
ident_side: &Spanned<crate::ast::Expr>,
fn_name: &str,
given_name: &str,
) -> bool {
use crate::ast::{Expr, Literal};
if !matches_ident_expr(ident_side, given_name) {
return false;
}
let Some((x, y)) = call2_args(call_side, fn_name) else {
return false;
};
matches_ident_expr(x, given_name)
&& matches!(&y.node, Expr::Literal(Literal::Int(n)) if *n == 0)
}
pub(super) fn matches_neg_binary_call(
expr: &Spanned<crate::ast::Expr>,
fn_name: &str,
a: &str,
b: &str,
) -> bool {
use crate::ast::Expr;
match &expr.node {
Expr::Neg(inner) => matches_binary_call(inner, fn_name, a, b),
_ => false,
}
}
pub(super) fn matches_identity_side(
call_side: &Spanned<crate::ast::Expr>,
ident_side: &Spanned<crate::ast::Expr>,
fn_name: &str,
given_name: &str,
identity: i64,
) -> bool {
use crate::ast::{Expr, Literal};
if !matches_ident_expr(ident_side, given_name) {
return false;
}
let Some((x, y)) = call2_args(call_side, fn_name) else {
return false;
};
let is_int_lit = |e: &Spanned<Expr>, n: i64| -> bool {
matches!(&e.node, Expr::Literal(Literal::Int(m)) if *m == n)
};
(matches_ident_expr(x, given_name) && is_int_lit(y, identity))
|| (is_int_lit(x, identity) && matches_ident_expr(y, given_name))
}