use super::VerifyEmitMode;
use super::expr::{aver_name_to_lean, emit_expr_legacy};
use super::law_auto::{emit_verify_law_forall_auto_proof, emit_verify_law_support_theorems};
use super::types::type_annotation_to_lean;
use crate::ast::*;
use crate::codegen::CodegenContext;
use crate::verify_law::canonical_spec_ref;
const BOUNDED_LAW_DOMAIN_VALUE_EDGE: usize = 128;
const BOUNDED_LAW_DOMAIN_CASE_EDGE: usize = 512;
const LAW_SAMPLE_CAP: usize = 512;
fn bounded_oracle_subtype_for(method: &str) -> Option<&'static str> {
match method {
"Random.int" => Some("RandomIntInBounds"),
"Random.float" => Some("RandomFloatInUnit"),
"Time.unixMs" => Some("TimeUnixMsNonneg"),
_ => None,
}
}
pub(super) fn emit_sample_guard(guard: &Spanned<Expr>, ctx: &CodegenContext) -> String {
let active = ctx.active_module_scope();
let resolved = ctx.resolve_expr(guard, active.as_deref());
emit_sample_guard_resolved(&resolved, ctx)
}
fn emit_sample_guard_resolved(
expr: &Spanned<crate::ir::hir::ResolvedExpr>,
ctx: &CodegenContext,
) -> String {
use crate::ir::hir::{ResolvedCallee, ResolvedExpr};
match &expr.node {
ResolvedExpr::Literal(Literal::Int(i)) => format!("({} : Int)", i),
ResolvedExpr::Neg(inner) => format!("(-{})", emit_sample_guard_resolved(inner, ctx)),
ResolvedExpr::Call(callee, args)
if matches!(callee, ResolvedCallee::Builtin(n) if n == "Bool.and")
&& args.len() == 2 =>
{
format!(
"({} && {})",
emit_sample_guard_resolved(&args[0], ctx),
emit_sample_guard_resolved(&args[1], ctx)
)
}
ResolvedExpr::Call(callee, args)
if matches!(callee, ResolvedCallee::Builtin(n) if n == "Bool.or")
&& args.len() == 2 =>
{
format!(
"({} || {})",
emit_sample_guard_resolved(&args[0], ctx),
emit_sample_guard_resolved(&args[1], ctx)
)
}
ResolvedExpr::Call(callee, args)
if matches!(callee, ResolvedCallee::Builtin(n) if n == "Bool.not")
&& args.len() == 1 =>
{
format!("(!{})", emit_sample_guard_resolved(&args[0], ctx))
}
ResolvedExpr::BinOp(op, left, right) => {
let l = emit_sample_guard_resolved(left, ctx);
let r = emit_sample_guard_resolved(right, ctx);
let op_str = match op {
BinOp::Add => "+",
BinOp::Sub => "-",
BinOp::Mul => "*",
BinOp::Div => "/",
BinOp::Eq => "==",
BinOp::Neq => "!=",
BinOp::Lt => "<",
BinOp::Gt => ">",
BinOp::Lte => "<=",
BinOp::Gte => ">=",
};
format!("({} {} {})", l, op_str, r)
}
_ => super::expr::emit_expr(expr, ctx),
}
}
pub fn emit_verify_block(
vb: &VerifyBlock,
ctx: &CodegenContext,
verify_mode: VerifyEmitMode,
case_index_start: usize,
) -> (String, usize) {
if let VerifyKind::Law(law) = &vb.kind {
return emit_verify_law_block(vb, law, ctx, verify_mode, case_index_start);
}
if vb.trace {
return emit_verify_trace_block_proofs(vb, ctx, verify_mode, case_index_start);
}
let mut lines = Vec::new();
for (idx, (left, right)) in vb.cases.iter().enumerate() {
let left_str = emit_expr_legacy(left, ctx, None);
let right_str = super::sample_literal::ground_truth_rhs(vb, ctx, case_index_start + idx)
.unwrap_or_else(|| emit_expr_legacy(right, ctx, None));
match verify_mode {
VerifyEmitMode::NativeDecide => {
lines.push(format!(
"example : {} = {} := by native_decide",
left_str, right_str
));
}
VerifyEmitMode::Sorry => {
lines.push(format!(
"example : {} = {} := by sorry",
left_str, right_str
));
}
VerifyEmitMode::TheoremSkeleton => {
let theorem_name = format!(
"{}_verify_{}",
aver_name_to_lean(&vb.fn_name),
case_index_start + idx + 1
);
lines.push(format!(
"theorem {} : {} = {} := by",
theorem_name, left_str, right_str
));
lines.push(" sorry".to_string());
}
}
}
(lines.join("\n"), case_index_start + vb.cases.len())
}
fn emit_verify_trace_block_proofs(
vb: &VerifyBlock,
ctx: &CodegenContext,
verify_mode: VerifyEmitMode,
case_index_start: usize,
) -> (String, usize) {
use crate::ast::Expr;
let mut lines = Vec::new();
let case_total = vb.cases.len();
let synthetic_law = crate::ast::VerifyLaw {
name: String::new(),
givens: vb.cases_givens.clone(),
when: None,
lhs: vb.cases.first().map(|(l, _)| l.clone()).unwrap_or_else(|| {
crate::ast::Spanned::new(Expr::Literal(crate::ast::Literal::Unit), vb.line)
}),
rhs: vb.cases.first().map(|(_, r)| r.clone()).unwrap_or_else(|| {
crate::ast::Spanned::new(Expr::Literal(crate::ast::Literal::Unit), vb.line)
}),
sample_guards: Vec::new(),
};
for (idx, (left, right)) in vb.cases.iter().enumerate() {
let result_fn_call = match &left.node {
Expr::Attr(inner, field) if field == "result" => match &inner.node {
Expr::FnCall(_, _) => Some((**inner).clone()),
_ => None,
},
_ => None,
};
let Some(fn_call) = result_fn_call else {
let lhs_summary = emit_expr_legacy(left, ctx, None);
lines.push(format!(
"-- verify {} trace case {}/{}: `{}` is runtime-only (see docs/oracle.md)",
vb.fn_name,
idx + 1,
case_total,
lhs_summary,
));
continue;
};
let case_bindings = vb.case_givens.get(idx).map(|v| v.as_slice()).unwrap_or(&[]);
let mode = crate::codegen::common::OracleInjectionMode::SampleCaseBinding(case_bindings);
let lhs_rw = crate::codegen::common::rewrite_effectful_calls_in_law(
&fn_call,
&synthetic_law,
|n| ctx.fn_def_by_name(n, ctx.active_module_scope().as_deref()),
mode.clone(),
);
let rhs_rw = crate::codegen::common::rewrite_effectful_calls_in_law(
right,
&synthetic_law,
|n| ctx.fn_def_by_name(n, ctx.active_module_scope().as_deref()),
mode,
);
let lhs_str = emit_expr_legacy(&lhs_rw, ctx, None);
let rhs_str = emit_expr_legacy(&rhs_rw, ctx, None);
match verify_mode {
VerifyEmitMode::NativeDecide => {
lines.push(format!(
"example : {} = {} := by native_decide",
lhs_str, rhs_str
));
}
VerifyEmitMode::Sorry => {
lines.push(format!("example : {} = {} := by sorry", lhs_str, rhs_str));
}
VerifyEmitMode::TheoremSkeleton => {
let theorem_name = format!(
"{}_trace_{}",
aver_name_to_lean(&vb.fn_name),
case_index_start + idx + 1
);
lines.push(format!(
"theorem {} : {} = {} := by",
theorem_name, lhs_str, rhs_str
));
lines.push(" sorry".to_string());
}
}
}
(lines.join("\n"), case_index_start + case_total)
}
fn emit_verify_law_block(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
verify_mode: VerifyEmitMode,
case_index_start: usize,
) -> (String, usize) {
let mut lines = Vec::new();
let fn_name = aver_name_to_lean(&vb.fn_name);
let law_name = aver_name_to_lean(&law.name);
if crate::codegen::common::law_lhs_has_trace_projection(&law.lhs) {
let header = match canonical_spec_ref(&vb.fn_name, law, ctx) {
Some(spec_ref) => format!(
"-- verify law {}.spec {}: trace-projection LHS is runtime-only (see docs/oracle.md)",
fn_name, spec_ref.spec_fn_name,
),
None => format!(
"-- verify law {}.{}: trace-projection LHS is runtime-only (see docs/oracle.md)",
fn_name, law_name,
),
};
return (header, case_index_start + vb.cases.len());
}
let spec_ref = canonical_spec_ref(&vb.fn_name, law, ctx);
let theorem_base = match &spec_ref {
Some(spec_ref) => format!(
"{}_eq_{}",
fn_name,
aver_name_to_lean(&spec_ref.spec_fn_name)
),
None => format!("{}_law_{}", fn_name, law_name),
};
let law_lhs = crate::codegen::common::rewrite_effectful_calls_in_law(
&law.lhs,
law,
|n| ctx.fn_def_by_name(n, ctx.active_module_scope().as_deref()),
crate::codegen::common::OracleInjectionMode::LemmaBindingProjected,
);
let law_rhs = crate::codegen::common::rewrite_effectful_calls_in_law(
&law.rhs,
law,
|n| ctx.fn_def_by_name(n, ctx.active_module_scope().as_deref()),
crate::codegen::common::OracleInjectionMode::LemmaBindingProjected,
);
let mut lifted_vars: std::collections::HashMap<String, String> =
std::collections::HashMap::new();
for given in &law.givens {
if let Some(refined) = crate::codegen::common::refinement_lift_for_given(
&given.name,
&given.type_name,
&law_lhs,
&law_rhs,
ctx,
) {
lifted_vars.insert(given.name.clone(), refined.to_string());
}
}
let law_lhs = if lifted_vars.is_empty() {
law_lhs
} else {
crate::codegen::common::strip_refinement_wrappers(&law_lhs, &lifted_vars, ctx)
};
let law_rhs = if lifted_vars.is_empty() {
law_rhs
} else {
crate::codegen::common::strip_refinement_wrappers(&law_rhs, &lifted_vars, ctx)
};
let lhs_template = emit_expr_legacy(&law_lhs, ctx, None);
let rhs_template = emit_expr_legacy(&law_rhs, ctx, None);
let when_template = law.when.as_ref().map(|expr| {
let oracle_projected = crate::codegen::common::rewrite_effectful_calls_in_law(
expr,
law,
|n| ctx.fn_def_by_name(n, ctx.active_module_scope().as_deref()),
crate::codegen::common::OracleInjectionMode::LemmaBindingProjected,
);
let val_projected =
crate::codegen::common::project_lifted_idents_to_val(&oracle_projected, &lifted_vars);
emit_expr_legacy(&val_projected, ctx, None)
});
let quant_params = law
.givens
.iter()
.map(|given| {
let type_text = if let Some(refined) = lifted_vars.get(&given.name) {
refined.clone()
} else if let Some(subtype) = bounded_oracle_subtype_for(&given.type_name) {
subtype.to_string()
} else {
match crate::types::checker::effect_classification::oracle_signature(
&given.type_name,
) {
Some(oracle_ty) => crate::codegen::lean::types::type_to_lean(&oracle_ty),
None => type_annotation_to_lean(&given.type_name),
}
};
format!("({} : {})", aver_name_to_lean(&given.name), type_text)
})
.collect::<Vec<_>>()
.join(" ");
match &spec_ref {
Some(spec_ref) => lines.push(format!(
"-- verify law {}.spec {} ({} cases)",
fn_name,
spec_ref.spec_fn_name,
vb.cases.len()
)),
None => lines.push(format!(
"-- verify law {}.{} ({} cases)",
fn_name,
law_name,
vb.cases.len()
)),
}
for given in &law.givens {
lines.push(format!(
"-- given {}: {} = {}",
aver_name_to_lean(&given.name),
given.type_name,
law_given_domain_to_lean(&given.domain, ctx)
));
}
if let Some(when_expr) = &law.when {
let when_comment = emit_expr_legacy(when_expr, ctx, None).replace('\n', " ");
lines.push(format!("-- when {when_comment}"));
}
let pinned_law_strategy = ctx
.law_target_fn_id(&vb.fn_name)
.and_then(|fn_id| {
ctx.proof_ir
.law_theorems
.iter()
.find(|t| t.fn_id == fn_id && t.law_name == law.name)
})
.map(|t| &t.strategy);
let ir_strategy_closes_const_rhs = pinned_law_strategy.is_some_and(|s| {
!matches!(
s,
crate::ir::ProofStrategy::Induction { .. }
| crate::ir::ProofStrategy::SimpOverLemmas(_)
| crate::ir::ProofStrategy::BackendDispatch
| crate::ir::ProofStrategy::Sorry
| crate::ir::ProofStrategy::SimpOverPreludeLemmas { .. }
| crate::ir::ProofStrategy::RingIdentity { .. }
| crate::ir::ProofStrategy::IntDecimalRoundtrip { .. }
| crate::ir::ProofStrategy::StringEscapeRoundtrip(_)
)
});
let singleton_const_rhs = !ir_strategy_closes_const_rhs
&& crate::codegen::common::all_givens_are_singletons(law)
&& crate::codegen::common::law_rhs_is_independent_of_givens(law);
let unclassified = crate::codegen::common::unclassified_fn_names(ctx);
let calls_fuel_bounded = crate::codegen::common::law_calls_unclassified_fn(law, &unclassified);
let calls_foreign_acc_fold =
crate::codegen::common::law_calls_foreign_accumulator_fold(ctx, law, &vb.fn_name);
let pinned_self_universal = matches!(
pinned_law_strategy,
Some(crate::ir::ProofStrategy::FiniteDomainCases { .. })
| Some(crate::ir::ProofStrategy::TailRecFixedBaseFold { .. })
);
let skip_universal = singleton_const_rhs
|| ((calls_fuel_bounded || calls_foreign_acc_fold) && !pinned_self_universal);
let law_for_auto_proof = crate::ast::VerifyLaw {
name: law.name.clone(),
givens: law.givens.clone(),
when: law.when.clone(),
lhs: law_lhs.clone(),
rhs: law_rhs.clone(),
sample_guards: law.sample_guards.clone(),
};
let conditional_universal = law.when.is_some()
&& lifted_vars.is_empty()
&& (super::law_auto::recognize_conditional_comparison_bridge(&law_for_auto_proof, ctx)
|| super::law_auto::recognize_conditional_inductive_generic(
vb,
&law_for_auto_proof,
ctx,
));
if !quant_params.is_empty() && !skip_universal {
lines.extend(emit_verify_law_support_theorems(
vb,
law,
ctx,
&theorem_base,
));
let theorem_parts = law_theorem_parts(
law,
ctx,
&theorem_base,
&lhs_template,
&rhs_template,
when_template.as_deref(),
&lifted_vars,
conditional_universal,
);
let floor_window_universal = matches!(
pinned_law_strategy,
Some(crate::ir::ProofStrategy::FloorDivWindow { .. })
| Some(crate::ir::ProofStrategy::TailRecFixedBaseFold { .. })
);
lines.push(format!(
"{}{} {} {}.{}",
super::LAW_CLASS_MARKER_PREFIX,
theorem_base,
if theorem_parts.iter().any(|part| part.bounded_domain) && !floor_window_universal {
super::LAW_CLASS_BOUNDED_DOMAIN
} else {
super::LAW_CLASS_UNIVERSAL
},
vb.fn_name,
law.name,
));
if floor_window_universal
&& let Some(auto_proof) = emit_verify_law_forall_auto_proof(
vb,
&law_for_auto_proof,
ctx,
verify_mode,
&theorem_base,
&quant_params,
&theorem_parts[0].prop,
)
{
lines.extend(auto_proof.support_lines);
if !auto_proof.replaces_theorem {
lines.push(format!(
"theorem {} : ∀ {}, {} := by",
theorem_base, quant_params, theorem_parts[0].prop
));
}
lines.extend(auto_proof.body.render_body());
} else {
let mut part_bodies: Vec<Vec<String>> = Vec::with_capacity(theorem_parts.len());
let mut support_lines: Vec<String> = Vec::new();
let partitioned = theorem_parts.len() > 1;
for part in &theorem_parts {
let law_for_part = crate::ast::VerifyLaw {
givens: part.law.givens.clone(),
..law_for_auto_proof.clone()
};
let mut body: Vec<String> = Vec::new();
let header_prefix = if partitioned {
"set_option maxHeartbeats 800000 in\n"
} else {
""
};
if let Some(auto_proof) = emit_verify_law_forall_auto_proof(
vb,
&law_for_part,
ctx,
verify_mode,
&theorem_base,
&quant_params,
&part.prop,
) {
if auto_proof.replaces_theorem {
body.extend(auto_proof.support_lines);
} else {
for line in auto_proof.support_lines {
if !support_lines.contains(&line) {
support_lines.push(line);
}
}
body.push(format!(
"{}theorem {} : ∀ {}, {} := by",
header_prefix, part.name, quant_params, part.prop
));
}
body.extend(auto_proof.body.render_body());
} else {
body.push(format!(
"{}theorem {} : ∀ {}, {} := by",
header_prefix, part.name, quant_params, part.prop
));
body.push(
" -- verify law is sampled; universal proof must be provided manually"
.to_string(),
);
body.push(" sorry".to_string());
}
part_bodies.push(body);
}
lines.extend(support_lines);
for body in part_bodies {
lines.extend(body);
}
}
}
if !vb.cases.is_empty() && lifted_vars.is_empty() {
let domain_theorem_name = format!("{}_checked_domain", theorem_base);
let domain_conjuncts: Vec<String> = vb
.cases
.iter()
.enumerate()
.map(|(idx, (left, right))| {
let case_bindings = vb.case_givens.get(idx).map(|v| v.as_slice()).unwrap_or(&[]);
let mode =
crate::codegen::common::OracleInjectionMode::SampleCaseBinding(case_bindings);
let left_rw = crate::codegen::common::rewrite_effectful_calls_in_law(
left,
law,
|n| ctx.fn_def_by_name(n, ctx.active_module_scope().as_deref()),
mode.clone(),
);
let right_rw = crate::codegen::common::rewrite_effectful_calls_in_law(
right,
law,
|n| ctx.fn_def_by_name(n, ctx.active_module_scope().as_deref()),
mode,
);
let left_str = emit_expr_legacy(&left_rw, ctx, None);
let right_str =
super::sample_literal::ground_truth_rhs(vb, ctx, case_index_start + idx)
.unwrap_or_else(|| emit_expr_legacy(&right_rw, ctx, None));
if let Some(guard) = law.sample_guards.get(idx) {
format!(
"({} = true -> {} = {})",
emit_sample_guard(guard, ctx),
left_str,
right_str
)
} else {
format!("({} = {})", left_str, right_str)
}
})
.collect::<Vec<_>>();
const CHECKED_DOMAIN_CHUNK: usize = 32;
let checked_domain_statements: Vec<(String, String)> = if domain_conjuncts.len() > 36 {
domain_conjuncts
.chunks(CHECKED_DOMAIN_CHUNK)
.enumerate()
.map(|(part_idx, chunk)| {
(
format!("{}_part{}", domain_theorem_name, part_idx + 1),
chunk.join(" ∧ "),
)
})
.collect()
} else {
vec![(domain_theorem_name.clone(), domain_conjuncts.join(" ∧ "))]
};
match verify_mode {
VerifyEmitMode::NativeDecide => {
let heartbeats_budget = if law.when.is_some() && vb.cases.len() > 36 {
"set_option maxHeartbeats 800000 in\n"
} else {
""
};
for (part_name, part_prop) in &checked_domain_statements {
lines.push(format!(
"{}set_option synthInstance.maxSize 4096 in\ntheorem {} : {} := by native_decide",
heartbeats_budget, part_name, part_prop
));
}
}
VerifyEmitMode::Sorry => {
for (part_name, part_prop) in &checked_domain_statements {
lines.push(format!("theorem {} : {} := by sorry", part_name, part_prop));
}
}
VerifyEmitMode::TheoremSkeleton => {
for (part_name, part_prop) in &checked_domain_statements {
lines.push(format!("theorem {} : {} := by", part_name, part_prop));
lines.push(" sorry".to_string());
}
}
}
}
let sample_indices = sample_indices(vb.cases.len());
if sample_indices.len() < vb.cases.len() {
lines.push(format!(
"-- aver:samples-capped {}/{}",
sample_indices.len(),
vb.cases.len()
));
}
for idx in sample_indices {
let (left, right) = &vb.cases[idx];
let theorem_name = format!("{}_sample_{}", theorem_base, case_index_start + idx + 1);
let case_bindings = vb.case_givens.get(idx).map(|v| v.as_slice()).unwrap_or(&[]);
let mode = crate::codegen::common::OracleInjectionMode::SampleCaseBinding(case_bindings);
let left_rw = crate::codegen::common::rewrite_effectful_calls_in_law(
left,
law,
|n| ctx.fn_def_by_name(n, ctx.active_module_scope().as_deref()),
mode.clone(),
);
let right_rw = crate::codegen::common::rewrite_effectful_calls_in_law(
right,
law,
|n| ctx.fn_def_by_name(n, ctx.active_module_scope().as_deref()),
mode,
);
let left_str = emit_expr_legacy(&left_rw, ctx, None);
let right_str = if lifted_vars.is_empty() {
super::sample_literal::ground_truth_rhs(vb, ctx, case_index_start + idx)
.unwrap_or_else(|| emit_expr_legacy(&right_rw, ctx, None))
} else {
emit_expr_legacy(&right_rw, ctx, None)
};
let sample_prop = if let Some(guard) = law.sample_guards.get(idx) {
format!(
"{} = true -> {} = {}",
emit_sample_guard(guard, ctx),
left_str,
right_str
)
} else {
format!("{} = {}", left_str, right_str)
};
match verify_mode {
VerifyEmitMode::NativeDecide => {
lines.push(format!(
"theorem {} : {} := by native_decide",
theorem_name, sample_prop
));
}
VerifyEmitMode::Sorry => {
lines.push(format!(
"theorem {} : {} := by sorry",
theorem_name, sample_prop
));
}
VerifyEmitMode::TheoremSkeleton => {
lines.push(format!("theorem {} : {} := by", theorem_name, sample_prop));
lines.push(" sorry".to_string());
}
}
}
(lines.join("\n"), case_index_start + vb.cases.len())
}
pub(crate) fn law_as_lemma_statement(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Option<(String, String)> {
if law.when.is_some()
&& !(super::law_auto::recognize_conditional_comparison_bridge(law, ctx)
|| super::law_auto::recognize_conditional_inductive_generic(vb, law, ctx))
{
return None;
}
if crate::codegen::common::law_lhs_has_trace_projection(&law.lhs) {
return None;
}
if law.givens.is_empty() {
return None;
}
let ir_strategy_closes_const_rhs = ctx
.law_target_fn_id(&vb.fn_name)
.and_then(|fn_id| {
ctx.proof_ir
.law_theorems
.iter()
.find(|t| t.fn_id == fn_id && t.law_name == law.name)
})
.is_some_and(|t| {
!matches!(
t.strategy,
crate::ir::ProofStrategy::Induction { .. }
| crate::ir::ProofStrategy::SimpOverLemmas(_)
| crate::ir::ProofStrategy::BackendDispatch
| crate::ir::ProofStrategy::Sorry
)
});
let singleton_const_rhs = !ir_strategy_closes_const_rhs
&& crate::codegen::common::all_givens_are_singletons(law)
&& crate::codegen::common::law_rhs_is_independent_of_givens(law);
let unclassified = crate::codegen::common::unclassified_fn_names(ctx);
if singleton_const_rhs || crate::codegen::common::law_calls_unclassified_fn(law, &unclassified)
{
return None;
}
let fn_name = aver_name_to_lean(&vb.fn_name);
let law_name = aver_name_to_lean(&law.name);
let spec_ref = canonical_spec_ref(&vb.fn_name, law, ctx);
let theorem_base = match &spec_ref {
Some(spec_ref) => format!(
"{}_eq_{}",
fn_name,
aver_name_to_lean(&spec_ref.spec_fn_name)
),
None => format!("{}_law_{}", fn_name, law_name),
};
let law_lhs = crate::codegen::common::rewrite_effectful_calls_in_law(
&law.lhs,
law,
|n| ctx.fn_def_by_name(n, ctx.active_module_scope().as_deref()),
crate::codegen::common::OracleInjectionMode::LemmaBindingProjected,
);
let law_rhs = crate::codegen::common::rewrite_effectful_calls_in_law(
&law.rhs,
law,
|n| ctx.fn_def_by_name(n, ctx.active_module_scope().as_deref()),
crate::codegen::common::OracleInjectionMode::LemmaBindingProjected,
);
for given in &law.givens {
if crate::codegen::common::refinement_lift_for_given(
&given.name,
&given.type_name,
&law_lhs,
&law_rhs,
ctx,
)
.is_some()
{
return None;
}
}
let lhs = emit_expr_legacy(&law_lhs, ctx, None);
let rhs = emit_expr_legacy(&law_rhs, ctx, None);
match &law.when {
Some(when) => {
let premise = emit_expr_legacy(when, ctx, None);
Some((theorem_base, format!("{premise} = true -> {lhs} = {rhs}")))
}
None => Some((theorem_base, format!("{lhs} = {rhs}"))),
}
}
#[derive(Clone)]
struct LawTheoremPart {
name: String,
prop: String,
bounded_domain: bool,
law: VerifyLaw,
}
fn sample_indices(total: usize) -> Vec<usize> {
if total <= LAW_SAMPLE_CAP || LAW_SAMPLE_CAP == 0 {
return (0..total).collect();
}
if LAW_SAMPLE_CAP == 1 {
return vec![0];
}
(0..LAW_SAMPLE_CAP)
.map(|i| i * (total - 1) / (LAW_SAMPLE_CAP - 1))
.collect()
}
fn emitted_domain_values(
law: &VerifyLaw,
lifted_vars: &std::collections::HashMap<String, String>,
) -> Vec<Option<Vec<Spanned<Expr>>>> {
law.givens
.iter()
.map(|given| {
(!lifted_vars.contains_key(&given.name)).then(|| law_given_domain_values(&given.domain))
})
.collect()
}
fn bounded_law_slice(
domain_values: &[Option<Vec<Spanned<Expr>>>],
) -> Option<(usize, usize, Vec<Spanned<Expr>>)> {
let mut total_cases = 1usize;
let mut largest: Option<(usize, Vec<Spanned<Expr>>)> = None;
for (idx, values) in domain_values.iter().enumerate() {
let Some(values) = values else { continue };
total_cases = total_cases.saturating_mul(values.len());
if largest
.as_ref()
.is_none_or(|(_, current)| values.len() > current.len())
{
largest = Some((idx, values.clone()));
}
}
let (idx, values) = largest?;
if values.is_empty() {
return None;
}
let other_cases = (total_cases / values.len()).max(1);
let chunk_by_value = if values.len() > BOUNDED_LAW_DOMAIN_VALUE_EDGE {
BOUNDED_LAW_DOMAIN_VALUE_EDGE
} else if total_cases > BOUNDED_LAW_DOMAIN_CASE_EDGE {
(BOUNDED_LAW_DOMAIN_CASE_EDGE / other_cases).max(1)
} else {
values.len()
};
(chunk_by_value < values.len()).then_some((idx, chunk_by_value, values))
}
fn law_with_sliced_domain(
law: &VerifyLaw,
given_idx: usize,
values: &[Spanned<Expr>],
) -> VerifyLaw {
let mut part = law.clone();
part.givens[given_idx].domain = VerifyGivenDomain::Explicit(values.to_vec());
part
}
#[allow(clippy::too_many_arguments)]
fn law_theorem_parts(
law: &VerifyLaw,
ctx: &CodegenContext,
theorem_base: &str,
lhs_template: &str,
rhs_template: &str,
when_template: Option<&str>,
lifted_vars: &std::collections::HashMap<String, String>,
omit_domain: bool,
) -> Vec<LawTheoremPart> {
let (prop, bounded_domain) = law_theorem_prop(
law,
ctx,
lhs_template,
rhs_template,
when_template,
lifted_vars,
omit_domain,
);
let single = || {
vec![LawTheoremPart {
name: theorem_base.to_string(),
prop: prop.clone(),
bounded_domain,
law: law.clone(),
}]
};
if !bounded_domain {
return single();
}
let domain_values = emitted_domain_values(law, lifted_vars);
let Some((given_idx, chunk_size, values)) = bounded_law_slice(&domain_values) else {
return single();
};
values
.chunks(chunk_size)
.enumerate()
.map(|(part_idx, chunk)| {
let part_law = law_with_sliced_domain(law, given_idx, chunk);
let (part_prop, part_bounded) = law_theorem_prop(
&part_law,
ctx,
lhs_template,
rhs_template,
when_template,
lifted_vars,
false,
);
LawTheoremPart {
name: format!("{}_part{}", theorem_base, part_idx + 1),
prop: part_prop,
bounded_domain: part_bounded,
law: part_law,
}
})
.collect()
}
pub(in crate::codegen::lean) fn law_theorem_prop(
law: &VerifyLaw,
ctx: &CodegenContext,
lhs_template: &str,
rhs_template: &str,
when_template: Option<&str>,
lifted_vars: &std::collections::HashMap<String, String>,
omit_domain: bool,
) -> (String, bool) {
let mut premises = Vec::new();
let when_redundant_with_lifts = law
.when
.as_ref()
.map(|w| {
crate::codegen::common::when_is_redundant_with_refinement_lifts(w, lifted_vars, ctx)
})
.unwrap_or(false);
if law.when.is_some() && !omit_domain {
premises.extend(law.givens.iter().filter_map(|given| {
if lifted_vars.contains_key(&given.name) {
None
} else {
Some(law_given_domain_prop(given, ctx))
}
}));
}
let bounded_domain = !premises.is_empty();
if let Some(when_expr) = when_template
&& !when_redundant_with_lifts
{
premises.push(format!("{when_expr} = true"));
}
let conclusion = format!("{lhs_template} = {rhs_template}");
let prop = if premises.is_empty() {
conclusion
} else {
format!("{} -> {}", premises.join(" -> "), conclusion)
};
(prop, bounded_domain)
}
fn law_given_domain_to_lean(domain: &VerifyGivenDomain, ctx: &CodegenContext) -> String {
match domain {
VerifyGivenDomain::IntRange { start, end } => format!("{}..{}", start, end),
VerifyGivenDomain::Explicit(values) => format!(
"[{}]",
values
.iter()
.map(|v| emit_expr_legacy(v, ctx, None))
.collect::<Vec<_>>()
.join(", ")
),
}
}
fn law_given_domain_prop(given: &VerifyGiven, ctx: &CodegenContext) -> String {
let raw_name = aver_name_to_lean(&given.name);
let given_name = if bounded_oracle_subtype_for(&given.type_name).is_some() {
format!("{raw_name}.val")
} else {
raw_name
};
let values = law_given_domain_values(&given.domain);
match values.as_slice() {
[] => "False".to_string(),
[value] => format!("{given_name} = {}", emit_expr_legacy(value, ctx, None)),
_ => values
.iter()
.map(|value| format!("{given_name} = {}", emit_expr_legacy(value, ctx, None)))
.collect::<Vec<_>>()
.join(" ∨ "),
}
}
pub(in crate::codegen::lean) fn law_given_domain_values(
domain: &VerifyGivenDomain,
) -> Vec<Spanned<Expr>> {
match domain {
VerifyGivenDomain::IntRange { start, end } => (*start..=*end)
.map(|n| Spanned::bare(Expr::Literal(Literal::Int(n))))
.collect(),
VerifyGivenDomain::Explicit(values) => values.clone(),
}
}
pub fn emit_decision(db: &DecisionBlock) -> String {
let mut lines = Vec::new();
lines.push(format!("/- Decision: {}", db.name));
lines.push(format!(" Date: {}", db.date));
lines.push(format!(" Reason: {}", db.reason));
lines.push(format!(" Chosen: {}", db.chosen.node.as_context_string()));
if !db.rejected.is_empty() {
lines.push(format!(
" Rejected: {}",
db.rejected
.iter()
.map(|r| r.node.as_context_string())
.collect::<Vec<_>>()
.join(", ")
));
}
if !db.impacts.is_empty() {
let impacts = db
.impacts
.iter()
.map(|impact| impact.node.as_context_string())
.collect::<Vec<_>>()
.join(", ");
lines.push(format!(" Impacts: {}", impacts));
}
if let Some(author) = &db.author {
lines.push(format!(" Author: {}", author));
}
lines.push("-/".to_string());
lines.join("\n")
}