#![cfg(feature = "smt")]
use splitrs::smt::{prove_equivalent, Verdict};
use syn::parse_quote;
fn assert_verified(a: syn::ItemFn, b: syn::ItemFn) {
match prove_equivalent(&a, &b) {
Verdict::Verified => {}
other => panic!("expected Verified, got {other:?}"),
}
}
fn assert_unsupported(a: syn::ItemFn, b: syn::ItemFn) {
match prove_equivalent(&a, &b) {
Verdict::Unsupported { .. } => {}
other => panic!("expected Unsupported, got {other:?}"),
}
}
#[test]
fn add_is_commutative() {
let a: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32) -> u32 { a + b } };
let b: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32) -> u32 { b + a } };
assert_verified(a, b);
}
#[test]
fn double_equals_self_add() {
let a: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { 2 * x } };
let b: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { x + x } };
assert_verified(a, b);
}
#[test]
fn add_is_associative() {
let a: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32, c: u32) -> u32 { (a + b) + c } };
let b: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32, c: u32) -> u32 { a + (b + c) } };
assert_verified(a, b);
}
#[test]
fn mul_by_two_equals_shift_left_unsigned() {
let a: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { x * 2 } };
let b: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { x << 1 } };
assert_verified(a, b);
}
#[test]
fn mul_by_three_is_not_identity() {
let a: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { x * 3 } };
let b: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { x } };
match prove_equivalent(&a, &b) {
Verdict::Refuted(cx) => {
assert!(
!cx.inputs.is_empty(),
"a refutation must carry a concrete witness for x"
);
}
other => panic!("`x * 3` must be Refuted against `x`, got {other:?}"),
}
}
#[test]
fn mul_by_three_equals_repeated_addition() {
let a: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { x * 3 } };
let b: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { x + x + x } };
assert_verified(a, b);
}
#[test]
fn mul_with_let_binding_is_not_identity() {
let a: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { let t = x * 3; t } };
let b: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { x } };
match prove_equivalent(&a, &b) {
Verdict::Refuted(_) => {}
other => panic!("`let t = x * 3; t` must be Refuted against `x`, got {other:?}"),
}
}
#[test]
fn if_swap_with_negated_condition() {
let a: syn::ItemFn = parse_quote! {
fn f(a: i32, b: i32, c: bool) -> i32 { if c { a } else { b } }
};
let b: syn::ItemFn = parse_quote! {
fn f(a: i32, b: i32, c: bool) -> i32 { if !c { b } else { a } }
};
assert_verified(a, b);
}
#[test]
fn de_morgan_law() {
let a: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32) -> u32 { !(a & b) } };
let b: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32) -> u32 { (!a) | (!b) } };
assert_verified(a, b);
}
#[test]
fn cast_narrow_widen_equals_mask() {
let a: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { (x as u8) as u32 } };
let b: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { x & 0xFF } };
assert_verified(a, b);
}
#[test]
fn refute_add_vs_sub_with_counterexample() {
let a: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32) -> u32 { a + b } };
let b: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32) -> u32 { a - b } };
match prove_equivalent(&a, &b) {
Verdict::Refuted(cx) => {
assert_eq!(cx.inputs.len(), 2, "expected two input bindings");
let b_val = cx
.inputs
.iter()
.find(|(name, _)| name == "b")
.map(|(_, v)| v.clone())
.expect("counterexample must bind `b`");
assert_ne!(
b_val, "0",
"counterexample must have b != 0, got b = {b_val}"
);
}
other => panic!("expected Refuted, got {other:?}"),
}
}
#[test]
fn unsupported_function_call() {
let a: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { foo(x) } };
let b: syn::ItemFn = parse_quote! { fn f(x: u32) -> u32 { x } };
assert_unsupported(a, b);
}
#[test]
fn unsupported_float_param() {
let a: syn::ItemFn = parse_quote! { fn f(x: f32) -> f32 { x } };
let b: syn::ItemFn = parse_quote! { fn f(x: f32) -> f32 { x } };
assert_unsupported(a, b);
}
#[test]
fn signed_shift_uses_arithmetic_shift() {
let arithmetic: syn::ItemFn = parse_quote! { fn f(x: i32) -> i32 { x >> 1 } };
let logical: syn::ItemFn = parse_quote! { fn f(x: i32) -> i32 { ((x as u32) >> 1) as i32 } };
match prove_equivalent(&arithmetic, &logical) {
Verdict::Refuted(cx) => {
let x_val = cx
.inputs
.iter()
.find(|(name, _)| name == "x")
.map(|(_, v)| v.clone())
.expect("counterexample must bind `x`");
assert!(
x_val.starts_with('-'),
"ashr vs lshr should diverge on a negative x, got x = {x_val}"
);
}
other => {
panic!("expected Refuted (proving signed >> is arithmetic, not logical), got {other:?}")
}
}
}
#[test]
fn signed_arithmetic_shift_self_identity() {
let a: syn::ItemFn = parse_quote! { fn f(x: i32) -> i32 { x >> 2 } };
let b: syn::ItemFn = parse_quote! { fn f(x: i32) -> i32 { (x >> 1) >> 1 } };
assert_verified(a, b);
}
#[test]
fn let_binding_and_shadowing() {
let a: syn::ItemFn = parse_quote! {
fn f(a: u32, b: u32) -> u32 {
let s = a + b;
let s = s + s;
s
}
};
let b: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32) -> u32 { (a + b) * 2 } };
assert_verified(a, b);
}
#[test]
fn trailing_return_is_allowed() {
let a: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32) -> u32 { return a + b; } };
let b: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32) -> u32 { b + a } };
assert_verified(a, b);
}
#[test]
fn division_is_unsupported() {
let a: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32) -> u32 { a / b } };
let b: syn::ItemFn = parse_quote! { fn f(a: u32, b: u32) -> u32 { a / b } };
assert_unsupported(a, b);
}