use std::collections::HashSet;
use super::expr::aver_name_to_lean;
use super::fuel::{
contract_lex_params_rank, emit_fuelized_int_ascending_fn, emit_fuelized_int_countdown_fn,
emit_fuelized_mutual_int_countdown_group, emit_fuelized_mutual_sizeof_group,
emit_fuelized_mutual_string_pos_group, emit_fuelized_string_pos_fn,
emit_nat_linear_recurrence_fn, emit_native_guarded_int_countdown_fn,
emit_native_mutual_sizeof_group,
};
use super::is_pure_fn;
use super::lex_list::emit_native_mutual_lex_list_wf_group;
use super::recurrence::detect_second_order_int_linear_recurrence;
use super::render::{emit_fn_params, sanitize_doc};
use super::types::type_annotation_to_lean;
use crate::ast::*;
use crate::codegen::CodegenContext;
pub fn emit_fn_def(
fd: &FnDef,
recursive_fns: &HashSet<String>,
ctx: &CodegenContext,
) -> Option<String> {
if !is_pure_fn(fd) {
return None;
}
let mut lines = Vec::new();
if let Some(desc) = &fd.desc {
lines.push(format!("/-- {} -/", sanitize_doc(desc)));
}
let is_recursive = recursive_fns.contains(&fd.name);
let fn_name = aver_name_to_lean(&fd.name);
let params = emit_fn_params(&fd.params);
let ret_type = if fd.return_type.is_empty() {
"Unit".to_string()
} else {
type_annotation_to_lean(&fd.return_type)
};
let prefix = if is_recursive { "partial " } else { "" };
lines.push(format!(
"{}def {} {} : {} :=",
prefix, fn_name, params, ret_type
));
let lowered = lower_pure_question_bang_for_emit(fd);
let body = lowered
.as_ref()
.map(|lowered_fd| lowered_fd.body.as_ref())
.unwrap_or(fd.body.as_ref());
lines.push(emit_fn_body_for(fd, body, ctx));
Some(lines.join("\n"))
}
pub fn emit_fn_def_proof(fd: &FnDef, ctx: &CodegenContext) -> Option<String> {
if !is_pure_fn(fd) {
return None;
}
if let Some(contract) = crate::codegen::common::find_fn_contract_for_fn(ctx, fd)
&& matches!(
contract.recursion,
Some(crate::ir::RecursionContract::LinearRecurrence2)
)
&& let Some(shape) = detect_second_order_int_linear_recurrence(fd)
{
return Some(emit_nat_linear_recurrence_fn(fd, &shape, ctx));
}
if let Some(contract) = crate::codegen::common::find_fn_contract_for_fn(ctx, fd)
&& let Some(crate::ir::RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::NatAbsPlusOne { param },
}) = contract.recursion.as_ref()
&& let Some(param_index) = fd.params.iter().position(|(n, _)| n == param)
{
return Some(emit_fuelized_int_countdown_fn(fd, ctx, param_index));
}
if let Some(contract) = crate::codegen::common::find_fn_contract_for_fn(ctx, fd)
&& let Some(crate::ir::RecursionContract::WellFoundedToNat { param, floor_div }) =
contract.recursion.as_ref()
{
let mut lines = Vec::new();
if let Some(desc) = &fd.desc {
lines.push(format!("/-- {} -/", sanitize_doc(desc)));
}
let fn_name = aver_name_to_lean(&fd.name);
let params = emit_fn_params(&fd.params);
let ret_type = if fd.return_type.is_empty() {
"Unit".to_string()
} else {
type_annotation_to_lean(&fd.return_type)
};
lines.push(format!("def {} {} : {} :=", fn_name, params, ret_type));
let lowered = lower_pure_question_bang_for_emit(fd);
let body = lowered
.as_ref()
.map(|lowered_fd| lowered_fd.body.as_ref())
.unwrap_or(fd.body.as_ref());
lines.push(emit_fn_body_for(fd, body, ctx));
lines.push(format!("termination_by {}.toNat", aver_name_to_lean(param)));
lines.push("decreasing_by".to_string());
match floor_div {
Some(shrink) => match &shrink.helper_fn {
Some(helper) => lines.push(format!(
" all_goals (simp [{}, Except.withDefault] <;> omega)",
aver_name_to_lean(helper)
)),
None => lines.push(" all_goals (simp [Except.withDefault] <;> omega)".to_string()),
},
None => lines.push(" all_goals omega".to_string()),
}
return Some(lines.join("\n"));
}
if let Some(contract) = crate::codegen::common::find_fn_contract_for_fn(ctx, fd)
&& let Some(crate::ir::RecursionContract::Native {
precondition,
measure: crate::ir::Measure::NatAbsInt { param },
body,
..
}) = contract.recursion.as_ref()
{
if let Some(param_index) = fd.params.iter().position(|(n, _)| n == param) {
let precondition_clauses: Vec<crate::ast::Spanned<crate::ir::hir::ResolvedExpr>> =
precondition.iter().map(|p| p.expr.clone()).collect();
return Some(emit_native_guarded_int_countdown_fn(
fd,
ctx,
param_index,
body.base_arm_literal,
&body.base_arm_body,
&body.wildcard_arm_body,
&precondition_clauses,
));
}
}
if let Some(contract) = crate::codegen::common::find_fn_contract_for_fn(ctx, fd)
&& let Some(crate::ir::RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::BoundMinusParamNatAbsPlusOne { param, bound },
}) = contract.recursion.as_ref()
&& let Some(param_index) = fd.params.iter().position(|(n, _)| n == param)
{
let bound_lean = super::bound_expr_to_lean(bound);
return Some(emit_fuelized_int_ascending_fn(
fd,
ctx,
param_index,
&bound_lean,
));
}
if let Some(contract) = crate::codegen::common::find_fn_contract_for_fn(ctx, fd)
&& matches!(
contract.recursion,
Some(crate::ir::RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::StringLenMinusPos { .. },
})
)
{
return Some(emit_fuelized_string_pos_fn(fd, ctx));
}
let mut lines = Vec::new();
if let Some(desc) = &fd.desc {
lines.push(format!("/-- {} -/", sanitize_doc(desc)));
}
let fn_name = aver_name_to_lean(&fd.name);
let params = emit_fn_params(&fd.params);
let ret_type = if fd.return_type.is_empty() {
"Unit".to_string()
} else {
type_annotation_to_lean(&fd.return_type)
};
lines.push(format!("def {} {} : {} :=", fn_name, params, ret_type));
let lowered = lower_pure_question_bang_for_emit(fd);
let body = lowered
.as_ref()
.map(|lowered_fd| lowered_fd.body.as_ref())
.unwrap_or(fd.body.as_ref());
lines.push(emit_fn_body_for(fd, body, ctx));
if let Some(contract) = crate::codegen::common::find_fn_contract_for_fn(ctx, fd) {
match contract.recursion.as_ref() {
Some(crate::ir::RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::Lex { params, rank: 0 },
}) if params.len() == 1 => {
let lean_param = aver_name_to_lean(¶ms[0]);
lines.push(format!("termination_by Int.natAbs {}", lean_param));
lines.push("decreasing_by".to_string());
lines.push(" omega".to_string());
}
Some(crate::ir::RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::SeqLenPlusOne { param },
}) => {
let lean_param = aver_name_to_lean(param);
lines.push(format!("termination_by {}.length", lean_param));
lines.push("decreasing_by".to_string());
lines.push(" decreasing_tactic".to_string());
}
_ => {}
}
}
Some(lines.join("\n"))
}
pub(super) fn lower_pure_question_bang_for_emit(fd: &FnDef) -> Option<FnDef> {
crate::types::checker::effect_lifting::lower_pure_question_bang_fn(fd)
.ok()
.flatten()
}
fn expr_uses_error_prop(expr: &Spanned<Expr>) -> bool {
match &expr.node {
Expr::ErrorProp(_) => true,
Expr::FnCall(callee, args) => {
expr_uses_error_prop(callee) || args.iter().any(expr_uses_error_prop)
}
Expr::Attr(obj, _) => expr_uses_error_prop(obj),
Expr::BinOp(_, left, right) => expr_uses_error_prop(left) || expr_uses_error_prop(right),
Expr::Neg(inner) => expr_uses_error_prop(inner),
Expr::Match { subject, arms, .. } => {
expr_uses_error_prop(subject) || arms.iter().any(|arm| expr_uses_error_prop(&arm.body))
}
Expr::Constructor(_, Some(inner)) => expr_uses_error_prop(inner),
Expr::InterpolatedStr(parts) => parts.iter().any(|part| match part {
StrPart::Parsed(expr) => expr_uses_error_prop(expr),
StrPart::Literal(_) => false,
}),
Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
items.iter().any(expr_uses_error_prop)
}
Expr::MapLiteral(entries) => entries
.iter()
.any(|(key, value)| expr_uses_error_prop(key) || expr_uses_error_prop(value)),
Expr::RecordCreate { fields, .. } => {
fields.iter().any(|(_, value)| expr_uses_error_prop(value))
}
Expr::RecordUpdate { base, updates, .. } => {
expr_uses_error_prop(base)
|| updates.iter().any(|(_, value)| expr_uses_error_prop(value))
}
Expr::TailCall(boxed) => boxed.args.iter().any(expr_uses_error_prop),
Expr::Literal(_) | Expr::Ident(_) | Expr::Resolved { .. } | Expr::Constructor(_, None) => {
false
}
}
}
fn body_uses_error_prop(body: &FnBody) -> bool {
body.stmts().iter().any(|stmt| match stmt {
Stmt::Binding(_, _, expr) | Stmt::Expr(expr) => expr_uses_error_prop(expr),
})
}
fn fn_returns_result_typed(rfd: &crate::ir::hir::ResolvedFnDef) -> bool {
matches!(rfd.return_type, crate::types::Type::Result(_, _))
}
fn emit_do_stmt(stmt: &Stmt, ctx: &CodegenContext, is_last: bool) -> String {
use crate::ir::hir::ResolvedStmt;
let scope = ctx.active_module_scope();
let scope_ref = scope.as_deref();
let (is_err_prop, target_for_resolve): (bool, std::borrow::Cow<'_, Spanned<Expr>>) = match stmt
{
Stmt::Binding(_, _, expr) | Stmt::Expr(expr) => {
if let Expr::ErrorProp(inner) = &expr.node {
(true, std::borrow::Cow::Owned((**inner).clone()))
} else {
(false, std::borrow::Cow::Borrowed(expr))
}
}
};
let resolved_expr = ctx.resolve_expr(target_for_resolve.as_ref(), scope_ref);
let expr_str = super::expr::emit_expr(&resolved_expr, ctx);
match (stmt, is_err_prop, is_last) {
(Stmt::Binding(name, _, _), true, _) => {
format!(" let {} <- {}", aver_name_to_lean(name), expr_str)
}
(Stmt::Binding(name, _, _), false, _) => {
let resolved_stmt = ctx.resolve_stmt(stmt, scope_ref);
if let ResolvedStmt::Binding { name: n, value, .. } = &resolved_stmt {
format!(
" let {} := {}",
aver_name_to_lean(n),
super::expr::emit_expr(value, ctx)
)
} else {
format!(" let {} := {}", aver_name_to_lean(name), expr_str)
}
}
(Stmt::Expr(_), true, true) => format!(" {}", expr_str),
(Stmt::Expr(_), true, false) => format!(" let _ <- {}", expr_str),
(Stmt::Expr(_), false, true) => format!(" {}", expr_str),
(Stmt::Expr(_), false, false) => format!(" let _ := {}", expr_str),
}
}
fn emit_fn_body(body: &FnBody, ctx: &CodegenContext) -> String {
use crate::ir::hir::ResolvedStmt;
let scope = ctx.active_module_scope();
let scope_ref = scope.as_deref();
let stmts = body.stmts();
let mut lines = Vec::new();
for (i, stmt) in stmts.iter().enumerate() {
let is_last = i == stmts.len() - 1;
let resolved_stmt = ctx.resolve_stmt(stmt, scope_ref);
match &resolved_stmt {
ResolvedStmt::Binding { name, value, .. } => {
lines.push(format!(
" let {} := {}",
aver_name_to_lean(name),
super::expr::emit_expr(value, ctx)
));
}
ResolvedStmt::Expr(expr) => {
if is_last {
lines.push(format!(" {}", super::expr::emit_expr(expr, ctx)));
} else {
lines.push(format!(" let _ := {}", super::expr::emit_expr(expr, ctx)));
}
}
}
}
lines.join("\n")
}
fn emit_fn_body_result_do(body: &FnBody, ctx: &CodegenContext) -> String {
let stmts = body.stmts();
let mut lines = vec![" do".to_string()];
for (i, stmt) in stmts.iter().enumerate() {
lines.push(emit_do_stmt(stmt, ctx, i == stmts.len() - 1));
}
lines.join("\n")
}
pub(super) fn emit_fn_body_for(fd: &FnDef, body: &FnBody, ctx: &CodegenContext) -> String {
let resolved_fd = crate::codegen::common::fn_id_for_decl(ctx, fd)
.and_then(|id| ctx.resolved_program.fn_by_id(id));
let resolved_owned = match resolved_fd {
Some(_) => None,
None => Some(ctx.resolve_fn_def(fd, None)),
};
let rfd: &crate::ir::hir::ResolvedFnDef =
resolved_fd.unwrap_or_else(|| resolved_owned.as_ref().unwrap().as_ref());
if fn_returns_result_typed(rfd) && body_uses_error_prop(body) {
emit_fn_body_result_do(body, ctx)
} else {
emit_fn_body(body, ctx)
}
}
pub fn emit_mutual_group(fns: &[&FnDef], ctx: &CodegenContext) -> String {
let mut lines = Vec::new();
lines.push("mutual".to_string());
for fd in fns {
if !is_pure_fn(fd) {
continue;
}
if let Some(desc) = &fd.desc {
lines.push(format!(" /-- {} -/", sanitize_doc(desc)));
}
let fn_name = aver_name_to_lean(&fd.name);
let params = emit_fn_params(&fd.params);
let ret_type = if fd.return_type.is_empty() {
"Unit".to_string()
} else {
type_annotation_to_lean(&fd.return_type)
};
lines.push(format!(
" partial def {} {} : {} :=",
fn_name, params, ret_type
));
let body = emit_fn_body_for(fd, &fd.body, ctx);
for line in body.lines() {
lines.push(format!(" {}", line));
}
lines.push(String::new());
}
lines.push("end".to_string());
lines.join("\n")
}
pub fn emit_mutual_group_proof(fns: &[&FnDef], ctx: &CodegenContext) -> String {
let all_int_countdown = fns.iter().all(|fd| {
matches!(
contract_lex_params_rank(ctx, fd),
Some((params, 0)) if params.len() == 1
)
});
if all_int_countdown {
return emit_fuelized_mutual_int_countdown_group(fns, ctx);
}
let all_string_pos = fns.iter().all(|fd| {
matches!(
contract_lex_params_rank(ctx, fd),
Some((params, _)) if params.len() == 2
)
});
if all_string_pos {
return emit_fuelized_mutual_string_pos_group(fns, ctx);
}
let all_sizeof = fns.iter().all(|fd| {
matches!(
contract_lex_params_rank(ctx, fd),
Some((params, _)) if params.is_empty()
)
});
if all_sizeof {
if let Some(code) = emit_native_mutual_sizeof_group(fns, ctx) {
return code;
}
if let Some(code) = emit_native_mutual_lex_list_wf_group(fns, ctx) {
return code;
}
return emit_fuelized_mutual_sizeof_group(fns, ctx);
}
let mut lines = Vec::new();
lines.push("mutual".to_string());
for fd in fns {
if !is_pure_fn(fd) {
continue;
}
if let Some(desc) = &fd.desc {
lines.push(format!(" /-- {} -/", sanitize_doc(desc)));
}
let fn_name = aver_name_to_lean(&fd.name);
let params = emit_fn_params(&fd.params);
let ret_type = if fd.return_type.is_empty() {
"Unit".to_string()
} else {
type_annotation_to_lean(&fd.return_type)
};
lines.push(format!(" def {} {} : {} :=", fn_name, params, ret_type));
let body = emit_fn_body_for(fd, &fd.body, ctx);
for line in body.lines() {
lines.push(format!(" {}", line));
}
match contract_lex_params_rank(ctx, fd) {
Some((params, 0)) if params.len() == 1 => {
let lean_first = aver_name_to_lean(¶ms[0]);
lines.push(format!(" termination_by Int.natAbs {}", lean_first));
lines.push(" decreasing_by".to_string());
lines.push(" omega".to_string());
}
Some((params, rank)) if params.len() == 2 => {
let lean_s = aver_name_to_lean(¶ms[0]);
let lean_pos = aver_name_to_lean(¶ms[1]);
lines.push(format!(
" termination_by (({}.data.length) - ({}.toNat), {})",
lean_s, lean_pos, rank
));
lines.push(" decreasing_by".to_string());
lines.push(" simp_wf".to_string());
}
Some(([], _)) => {
}
_ => {}
}
lines.push(String::new());
}
lines.push("end".to_string());
lines.join("\n")
}