use anyhow::{Context, Result};
use crate::smt::{prove_equivalent, Verdict};
#[derive(Debug, Clone)]
pub enum VerifyOutcome {
Verified,
Refuted(crate::smt::Counterexample),
Unsupported {
reason: String,
},
}
impl From<Verdict> for VerifyOutcome {
fn from(v: Verdict) -> Self {
match v {
Verdict::Verified => VerifyOutcome::Verified,
Verdict::Refuted(cx) => VerifyOutcome::Refuted(cx),
Verdict::Unsupported { reason } => VerifyOutcome::Unsupported { reason },
}
}
}
pub fn run_verify_equiv(left: Option<&str>, right: Option<&str>) -> Result<()> {
let left = left.ok_or_else(|| {
anyhow::anyhow!(
"--verify-equiv requires --left FILE::FN.\n\
Example: splitrs --verify-equiv --left a.rs::f --right b.rs::g"
)
})?;
let right = right.ok_or_else(|| {
anyhow::anyhow!(
"--verify-equiv requires --right FILE::FN.\n\
Example: splitrs --verify-equiv --left a.rs::f --right b.rs::g"
)
})?;
let outcome = run_verify_equiv_core(left, right)?;
print_outcome(&outcome);
Ok(())
}
pub fn run_verify_equiv_core(left: &str, right: &str) -> Result<VerifyOutcome> {
let (left_file, left_fn) = split_file_fn(left)?;
let (right_file, right_fn) = split_file_fn(right)?;
let fn_a = load_function(left_file, left_fn)?;
let fn_b = load_function(right_file, right_fn)?;
let verdict = prove_equivalent(&fn_a, &fn_b);
Ok(verdict.into())
}
fn split_file_fn(spec: &str) -> Result<(&str, &str)> {
let idx = spec.rfind("::").ok_or_else(|| {
anyhow::anyhow!(
"expected FILE::FN (got `{spec}`).\n\
Example: src/lib.rs::my_function"
)
})?;
let file = &spec[..idx];
let func = &spec[idx + 2..];
if file.is_empty() || func.is_empty() {
anyhow::bail!("expected a non-empty FILE and FN in `{spec}`");
}
Ok((file, func))
}
fn load_function(file: &str, name: &str) -> Result<syn::ItemFn> {
let source = std::fs::read_to_string(file).with_context(|| {
format!(
"Failed to read source file `{file}` for verify-equiv.\n\
Please ensure the file exists and is readable."
)
})?;
let parsed = syn::parse_file(&source).with_context(|| {
format!(
"Failed to parse Rust source in `{file}`.\n\
Please ensure the file contains valid Rust code."
)
})?;
for item in parsed.items {
if let syn::Item::Fn(func) = item {
if func.sig.ident == name {
return Ok(func);
}
}
}
anyhow::bail!("function `{name}` not found in `{file}`")
}
fn print_outcome(outcome: &VerifyOutcome) {
match outcome {
VerifyOutcome::Verified => {
println!("✓ Proven equivalent for all inputs (QF_BV).");
}
VerifyOutcome::Refuted(cx) => {
println!("✗ Not equivalent — counterexample found:");
println!(" ┌─ inputs ─────────────────────────");
for (name, value) in &cx.inputs {
println!(" │ {name} = {value}");
}
println!(" ├─ outputs ────────────────────────");
println!(" │ left = {}", cx.left_output.as_deref().unwrap_or("?"));
println!(" │ right = {}", cx.right_output.as_deref().unwrap_or("?"));
println!(" └──────────────────────────────────");
}
VerifyOutcome::Unsupported { reason } => {
println!("⚠ Cannot prove: {reason}");
}
}
}