use crate::ast::*;
use crate::codegen::CodegenContext;
use super::expr::{aver_name_to_dafny, emit_expr_legacy};
fn concat_left_fold_render(
callee_name: &str,
second_dafny: &str,
ind_var_dafny: &str,
ctx: &CodegenContext,
) -> Option<(String, String)> {
let is_builtin = callee_name == "List.concat";
let is_wrapper = !is_builtin
&& ctx
.fn_def_by_name(callee_name, ctx.active_module_scope().as_deref())
.is_some_and(|fd| {
fd.params.len() == 2
&& fd.body.tail_expr().is_some_and(|tail| match &tail.node {
Expr::FnCall(c, args) => {
crate::codegen::common::expr_to_dotted_name(&c.node).as_deref()
== Some("List.concat")
&& args.len() == 2
&& crate::codegen::recursion::detect::local_name_of(&args[0])
.is_some_and(|n| n == fd.params[0].0)
&& crate::codegen::recursion::detect::local_name_of(&args[1])
.is_some_and(|n| n == fd.params[1].0)
}
_ => false,
})
});
if !(is_builtin || is_wrapper) {
return None;
}
if is_builtin {
Some((
format!("({} + {})", ind_var_dafny, second_dafny),
format!("({}[1..] + {})", ind_var_dafny, second_dafny),
))
} else {
let d = aver_name_to_dafny(callee_name);
Some((
format!("{}({}, {})", d, ind_var_dafny, second_dafny),
format!("{}({}[1..], {})", d, ind_var_dafny, second_dafny),
))
}
}
fn collect_concat_bridges(
expr: &Spanned<Expr>,
ind_var_src: &str,
ind_var_dafny: &str,
ctx: &CodegenContext,
out: &mut Vec<(String, String, String)>,
) {
if let Expr::FnCall(callee, args) = &expr.node
&& args.len() == 2
&& crate::codegen::recursion::detect::local_name_of(&args[0])
.is_some_and(|n| n == ind_var_src)
&& let Some(name) = crate::codegen::common::expr_to_dotted_name(&callee.node)
{
let second = emit_expr_legacy(&args[1], ctx, None);
if let Some((c_full, c_tail)) = concat_left_fold_render(&name, &second, ind_var_dafny, ctx)
{
let entry = (c_full, c_tail, second);
if !out.contains(&entry) {
out.push(entry);
}
}
}
match &expr.node {
Expr::FnCall(f, args) => {
collect_concat_bridges(f, ind_var_src, ind_var_dafny, ctx, out);
for a in args {
collect_concat_bridges(a, ind_var_src, ind_var_dafny, ctx, out);
}
}
Expr::BinOp(_, l, r) => {
collect_concat_bridges(l, ind_var_src, ind_var_dafny, ctx, out);
collect_concat_bridges(r, ind_var_src, ind_var_dafny, ctx, out);
}
Expr::Match { subject, arms, .. } => {
collect_concat_bridges(subject, ind_var_src, ind_var_dafny, ctx, out);
for arm in arms {
collect_concat_bridges(&arm.body, ind_var_src, ind_var_dafny, ctx, out);
}
}
Expr::ErrorProp(inner) | Expr::Neg(inner) | Expr::Constructor(_, Some(inner)) => {
collect_concat_bridges(inner, ind_var_src, ind_var_dafny, ctx, out)
}
Expr::Attr(obj, _) => collect_concat_bridges(obj, ind_var_src, ind_var_dafny, ctx, out),
Expr::RecordCreate { fields, .. } => {
for (_, e) in fields {
collect_concat_bridges(e, ind_var_src, ind_var_dafny, ctx, out);
}
}
Expr::List(elems) | Expr::Tuple(elems) | Expr::IndependentProduct(elems, _) => {
for e in elems {
collect_concat_bridges(e, ind_var_src, ind_var_dafny, ctx, out);
}
}
_ => {}
}
}
fn dafny_find_type_def<'a>(ctx: &'a CodegenContext, name: &str) -> Option<&'a TypeDef> {
let bare = name.rsplit('.').next().unwrap_or(name);
ctx.type_defs
.iter()
.chain(ctx.modules.iter().flat_map(|m| m.type_defs.iter()))
.find(|td| crate::codegen::common::type_def_name(td) == bare)
}
struct AdditiveOp {
name: String,
type_name: String,
base_ctor: String,
succ_ctor: String,
}
fn detect_additive_op(fd: &FnDef, ctx: &CodegenContext) -> Option<AdditiveOp> {
if fd.params.len() != 2 {
return None;
}
let (p0, t0) = &fd.params[0];
let (p1, t1) = &fd.params[1];
if t0 != t1 || &fd.return_type != t0 {
return None;
}
let TypeDef::Sum {
name: tname,
variants,
..
} = dafny_find_type_def(ctx, t0)?
else {
return None;
};
let tail = fd.body.tail_expr()?;
let Expr::Match { subject, arms, .. } = &tail.node else {
return None;
};
if crate::codegen::recursion::detect::local_name_of(subject)? != p0 || arms.len() != 2 {
return None;
}
let dotted = |e: &Spanned<Expr>| crate::codegen::common::expr_to_dotted_name(&e.node);
let mut base_ctor: Option<String> = None;
let mut succ_ctor: Option<String> = None;
for arm in arms {
match &arm.pattern {
Pattern::Constructor(cname, binders)
if binders.is_empty()
&& crate::codegen::recursion::detect::local_name_of(&arm.body)
.is_some_and(|n| n == p1) =>
{
base_ctor = Some(crate::codegen::proof_recognize::short_ctor(cname).to_string());
}
Pattern::Constructor(cname, binders) if binders.len() == 1 => {
let q = &binders[0];
if let Expr::FnCall(body_callee, body_args) = &arm.body.node
&& body_args.len() == 1
&& dotted(body_callee)
.as_deref()
.map(crate::codegen::proof_recognize::short_ctor)
== Some(crate::codegen::proof_recognize::short_ctor(cname))
&& let Expr::FnCall(rec_callee, rec_args) = &body_args[0].node
&& dotted(rec_callee)
.as_deref()
.map(crate::codegen::proof_recognize::short_ctor)
== Some(fd.name.as_str())
&& rec_args.len() == 2
&& crate::codegen::recursion::detect::local_name_of(&rec_args[0])
.is_some_and(|n| n == q)
&& crate::codegen::recursion::detect::local_name_of(&rec_args[1])
.is_some_and(|n| n == p1)
{
succ_ctor =
Some(crate::codegen::proof_recognize::short_ctor(cname).to_string());
}
}
_ => {}
}
}
let base_ctor = base_ctor?;
let succ_ctor = succ_ctor?;
let base_ok = variants
.iter()
.any(|v| v.name == base_ctor && v.fields.is_empty());
let succ_ok = variants
.iter()
.any(|v| v.name == succ_ctor && v.fields.len() == 1 && v.fields[0].trim() == tname);
if !base_ok || !succ_ok {
return None;
}
Some(AdditiveOp {
name: fd.name.clone(),
type_name: tname.clone(),
base_ctor,
succ_ctor,
})
}
fn collect_additive_ops_in_law(law: &VerifyLaw, ctx: &CodegenContext) -> Vec<AdditiveOp> {
let mut names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
crate::codegen::proof_recognize::collect_called_fns(&law.lhs, &mut names);
crate::codegen::proof_recognize::collect_called_fns(&law.rhs, &mut names);
let mut transitive: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for f in &names {
if let Some(fd) = ctx.fn_def_by_name(f, ctx.active_module_scope().as_deref()) {
crate::codegen::proof_recognize::collect_called_fns_in_body(&fd.body, &mut transitive);
}
}
names.extend(transitive);
names
.iter()
.filter_map(|f| ctx.fn_def_by_name(f, ctx.active_module_scope().as_deref()))
.filter_map(|fd| detect_additive_op(fd, ctx))
.collect()
}
fn additive_op_lemmas(ops: &[AdditiveOp], law_uid: &str) -> (Vec<String>, Vec<String>) {
let mut defs = Vec::new();
let mut lifts = Vec::new();
for op in ops {
let f = aver_name_to_dafny(&op.name);
let t = &op.type_name;
let base = &op.base_ctor;
let succ = &op.succ_ctor;
let rid = format!("{law_uid}_{f}_rid");
let succ_lemma = format!("{law_uid}_{f}_succ");
defs.push(format!(
"lemma {rid}(a: {t})\n ensures {f}(a, {t}.{base}) == a\n decreases a\n{{\n match a {{ case {base} => case {succ}(p) => {rid}(p); }}\n}}"
));
defs.push(format!(
"lemma {succ_lemma}(a: {t}, b: {t})\n ensures {f}(a, {t}.{succ}(b)) == {t}.{succ}({f}(a, b))\n decreases a\n{{\n match a {{ case {base} => case {succ}(p) => {succ_lemma}(p, b); }}\n}}"
));
lifts.push(format!(
" forall a: {t} ensures {f}(a, {t}.{base}) == a {{ {rid}(a); }}"
));
lifts.push(format!(
" forall a: {t}, b: {t} ensures {f}(a, {t}.{succ}(b)) == {t}.{succ}({f}(a, b)) {{ {succ_lemma}(a, b); }}"
));
}
(defs, lifts)
}
pub(super) struct AlgebraLemmas {
pub defs: Vec<String>,
pub lifts: Vec<String>,
}
pub(super) fn algebra_lemmas(
law: &VerifyLaw,
ctx: &CodegenContext,
law_uid: &str,
) -> AlgebraLemmas {
let additive = collect_additive_ops_in_law(law, ctx);
let (defs, lifts) = additive_op_lemmas(&additive, law_uid);
AlgebraLemmas { defs, lifts }
}
pub(super) struct ListBridges {
pub base: Vec<String>,
pub step: Vec<String>,
}
pub(super) fn list_bridges(
law: &VerifyLaw,
ctx: &CodegenContext,
list_param: &str,
ind_var_src: &str,
) -> ListBridges {
let mut cb = Vec::new();
collect_concat_bridges(&law.lhs, ind_var_src, list_param, ctx, &mut cb);
collect_concat_bridges(&law.rhs, ind_var_src, list_param, ctx, &mut cb);
let mut base = Vec::new();
let mut step = Vec::new();
for (c_full, _c_tail, second) in &cb {
base.push(format!(" assert {} == {};", c_full, second));
}
for (c_full, c_tail, _second) in &cb {
step.push(format!(
" assert {} == [{}[0]] + {};",
c_full, list_param, c_tail
));
}
ListBridges { base, step }
}