use crate::parser::contracts::{Contract, ContractType};
use syn::Expr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StaticCheckResult {
Passed,
Failed,
RequiresZ3,
}
pub fn check_contract_statically(contract: &Contract) -> Option<StaticCheckResult> {
match contract.contract_type {
ContractType::Requires => check_requires_statically(&contract.condition),
ContractType::Ensures => check_ensures_statically(&contract.condition),
ContractType::Axiom => check_requires_statically(&contract.condition),
}
}
fn check_requires_statically(expr: &Expr) -> Option<StaticCheckResult> {
match expr {
Expr::Binary(bin) if matches!(bin.op, syn::BinOp::Eq(_)) => {
check_constant_equality(&bin.left, &bin.right)
}
Expr::Binary(bin) if matches!(bin.op, syn::BinOp::Lt(_)) => {
check_bounds(&bin.left, &bin.right)
}
Expr::Binary(bin) if matches!(bin.op, syn::BinOp::Ge(_)) => {
check_non_negative(&bin.left, &bin.right)
}
Expr::MethodCall(method) => check_option_method(method),
_ => None,
}
}
fn check_ensures_statically(expr: &Expr) -> Option<StaticCheckResult> {
match expr {
Expr::Binary(bin) if matches!(bin.op, syn::BinOp::Eq(_)) => {
check_constant_equality(&bin.left, &bin.right)
}
Expr::Binary(bin) if matches!(bin.op, syn::BinOp::Ge(_)) => {
check_non_negative(&bin.left, &bin.right)
}
_ => None,
}
}
fn check_constant_equality(left: &Expr, right: &Expr) -> Option<StaticCheckResult> {
let (_constant, _variable) = match (left, right) {
(Expr::Lit(_), _) => (left, right),
(_, Expr::Lit(_)) => (right, left),
_ => return None, };
Some(StaticCheckResult::RequiresZ3)
}
fn check_bounds(_left: &Expr, _right: &Expr) -> Option<StaticCheckResult> {
Some(StaticCheckResult::RequiresZ3)
}
fn check_non_negative(left: &Expr, right: &Expr) -> Option<StaticCheckResult> {
if let Expr::Lit(lit) = right {
if let syn::Lit::Int(int_lit) = &lit.lit {
if int_lit.base10_digits() == "0" {
return Some(StaticCheckResult::RequiresZ3);
}
}
}
if let Expr::Lit(lit) = left {
if let syn::Lit::Int(int_lit) = &lit.lit {
if int_lit.base10_digits() == "0" {
return Some(StaticCheckResult::RequiresZ3);
}
}
}
None
}
fn check_option_method(method: &syn::ExprMethodCall) -> Option<StaticCheckResult> {
let method_name = method.method.to_string();
if method_name == "is_some" || method_name == "is_none" {
return Some(StaticCheckResult::RequiresZ3);
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_constant_equality() {
let expr: Expr = parse_quote! { x == 5 };
let contract = Contract {
contract_type: ContractType::Requires,
condition: expr,
comment: None,
};
let result = check_contract_statically(&contract);
assert_eq!(result, Some(StaticCheckResult::RequiresZ3));
}
#[test]
fn test_non_negative() {
let expr: Expr = parse_quote! { x >= 0 };
let contract = Contract {
contract_type: ContractType::Requires,
condition: expr,
comment: None,
};
let result = check_contract_statically(&contract);
assert_eq!(result, Some(StaticCheckResult::RequiresZ3));
}
}