mod dataflow;
mod detector;
mod gate;
mod rewriter;
use gate::GateDecision;
#[derive(Debug)]
pub enum ExtractionOutcome {
Committed {
fn_ident: String,
helper_ident: String,
},
SkippedRefuted {
fn_ident: String,
cx: crate::smt::Counterexample,
},
SkippedUnsupported {
fn_ident: String,
reason: String,
},
}
pub fn extract_pure_blocks(file: &mut syn::File, max_lines: usize) -> Vec<ExtractionOutcome> {
let mut outcomes = Vec::new();
let mut idx = 0usize;
while idx < file.items.len() {
let func = match &file.items[idx] {
syn::Item::Fn(f) => f.clone(),
_ => {
idx += 1;
continue;
}
};
if detector::rendered_line_count(&func) <= max_lines {
idx += 1;
continue;
}
let fn_ident = func.sig.ident.to_string();
let candidate = match detector::find_candidate(&func, max_lines) {
Some(c) => c,
None => {
outcomes.push(ExtractionOutcome::SkippedUnsupported {
fn_ident,
reason: "no qualifying pure-integer run found".to_string(),
});
idx += 1;
continue;
}
};
let existing = existing_fn_idents(file);
let rewrite = match rewriter::rewrite(&func, &candidate, &existing) {
Some(r) => r,
None => {
outcomes.push(ExtractionOutcome::SkippedUnsupported {
fn_ident,
reason: "could not synthesise a helper for the candidate run".to_string(),
});
idx += 1;
continue;
}
};
match gate::verify_and_decide(&func, &candidate, &rewrite) {
GateDecision::Commit { block_verified } => {
let helper_ident = rewrite.helper_ident.clone();
let proof_path = if block_verified {
"block-verified"
} else {
"whole-function-verified"
};
eprintln!(
"smt-extract: proof for `{}` -> `{}`: {proof_path}",
func.sig.ident, rewrite.helper_ident
);
file.items[idx] = syn::Item::Fn(rewrite.rewritten_fn);
file.items.insert(idx, syn::Item::Fn(rewrite.helper));
outcomes.push(ExtractionOutcome::Committed {
fn_ident,
helper_ident,
});
idx += 2;
}
GateDecision::Refuted(cx) => {
outcomes.push(ExtractionOutcome::SkippedRefuted { fn_ident, cx });
idx += 1;
}
GateDecision::Skip { reason } => {
outcomes.push(ExtractionOutcome::SkippedUnsupported { fn_ident, reason });
idx += 1;
}
}
}
outcomes
}
#[doc(hidden)]
#[derive(Debug)]
pub enum GateVerdictForTest {
WouldCommit,
Refuted(crate::smt::Counterexample),
Skipped(String),
}
#[doc(hidden)]
pub fn verify_rewrite_for_test(
original_src: &str,
max_lines: usize,
helper_override_src: Option<&str>,
) -> Option<GateVerdictForTest> {
let func: syn::ItemFn = syn::parse_str(original_src).ok()?;
let cand = detector::find_candidate(&func, max_lines)?;
let mut rewrite = rewriter::rewrite(&func, &cand, &[func.sig.ident.to_string()])?;
if let Some(src) = helper_override_src {
rewrite.helper = syn::parse_str(src).ok()?;
}
let verdict = match gate::verify_and_decide(&func, &cand, &rewrite) {
GateDecision::Commit { .. } => GateVerdictForTest::WouldCommit,
GateDecision::Refuted(cx) => GateVerdictForTest::Refuted(cx),
GateDecision::Skip { reason } => GateVerdictForTest::Skipped(reason),
};
Some(verdict)
}
fn existing_fn_idents(file: &syn::File) -> Vec<String> {
file.items
.iter()
.filter_map(|item| match item {
syn::Item::Fn(f) => Some(f.sig.ident.to_string()),
_ => None,
})
.collect()
}
pub fn print_report(outcomes: &[ExtractionOutcome]) {
if outcomes.is_empty() {
println!("smt-extract: no over-budget free functions considered.");
return;
}
for outcome in outcomes {
match outcome {
ExtractionOutcome::Committed {
fn_ident,
helper_ident,
} => {
println!(
"smt-extract: COMMITTED {fn_ident} -> extracted `{helper_ident}` (proof: Verified)"
);
}
ExtractionOutcome::SkippedRefuted { fn_ident, cx } => {
println!(
"smt-extract: SKIPPED {fn_ident} -> REFUTED by the oracle (rewriter bug caught)"
);
let inputs = cx
.inputs
.iter()
.map(|(n, v)| format!("{n}={v}"))
.collect::<Vec<_>>()
.join(", ");
println!(" counterexample inputs: {inputs}");
if let (Some(l), Some(r)) = (&cx.left_output, &cx.right_output) {
println!(" original={l} helper={r}");
}
}
ExtractionOutcome::SkippedUnsupported { fn_ident, reason } => {
println!("smt-extract: SKIPPED {fn_ident} -> {reason}");
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_file(src: &str) -> syn::File {
syn::parse_str(src).expect("parse file")
}
#[test]
fn commits_and_inserts_helper() {
let mut file = parse_file(
"fn calc(a: u32, b: u32) -> u32 {\n\
let s = a + b;\n\
let t = s * 2;\n\
let u = t + a;\n\
u\n\
}",
);
let outcomes = extract_pure_blocks(&mut file, 1);
assert!(
outcomes
.iter()
.any(|o| matches!(o, ExtractionOutcome::Committed { .. })),
"expected at least one Committed outcome, got {outcomes:?}"
);
let rendered = prettyplease::unparse(&file);
assert!(
rendered.contains("calc_extracted"),
"helper missing:\n{rendered}"
);
syn::parse_file(&rendered).expect("mutated file must re-parse");
}
#[test]
fn impure_call_is_not_committed() {
let mut file = parse_file(
"fn withcall(a: u32) -> u32 {\n\
let t = helper(a);\n\
let u = helper(t);\n\
u + 1\n\
}\n\
fn helper(x: u32) -> u32 { other(x) }",
);
let before = prettyplease::unparse(&file);
let outcomes = extract_pure_blocks(&mut file, 1);
assert!(
!outcomes
.iter()
.any(|o| matches!(o, ExtractionOutcome::Committed { .. })),
"must not commit an impure extraction, got {outcomes:?}"
);
let after = prettyplease::unparse(&file);
assert_eq!(before, after, "file must be unchanged when nothing commits");
}
}