use super::super::super::expr::aver_name_to_lean;
use super::super::super::tactic_ir::Tactic;
use super::super::AutoProof;
use super::super::shared::law_simp_defs;
use super::floor_bound::{emit_rational_floor_family, rational_floor_shape, rf_bare_basename};
use super::*;
use crate::ast::{VerifyBlock, VerifyLaw};
use crate::codegen::CodegenContext;
fn enclosing_verify_blocks<'a>(vb: &VerifyBlock, ctx: &'a CodegenContext) -> Vec<&'a VerifyBlock> {
use crate::ast::TopLevel;
for module in &ctx.modules {
if module
.verify_laws
.iter()
.any(|b| b.line == vb.line && b.fn_name == vb.fn_name)
{
return module.verify_laws.iter().collect();
}
}
ctx.items
.iter()
.filter_map(|it| match it {
TopLevel::Verify(b) => Some(b),
_ => None,
})
.collect()
}
pub(in crate::codegen::lean) fn recognize_pool_composition_generic(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> bool {
if law.when.is_none()
&& (keystone_pow2_fn(vb, law, ctx).is_none()
|| keystone_pool_names(vb, law, ctx).is_empty())
{
return false;
}
if find_list_induction_target(law).is_some() {
return false;
}
if recognize_conditional_comparison_bridge(law, ctx) {
return false;
}
if super::super::recognize_interval_monotonicity(vb, law, ctx) {
return false;
}
if super::super::recognize_frac_positivity(vb, law, ctx)
|| super::super::recognize_frac_geone(vb, law, ctx)
|| super::super::recognize_frac_monotone_compose(vb, law, ctx)
|| super::super::recognize_denom_positive(vb, law, ctx)
|| super::super::recognize_monotone_reflect(vb, law, ctx)
|| super::super::recognize_magnitude_bracket_reflect(vb, law, ctx)
{
return false;
}
if super::super::recognize_recursive_positive(vb, law, ctx)
|| super::super::recognize_recursive_monotone(vb, law, ctx)
{
return false;
}
if super::super::recognize_frac_order_chain(vb, law, ctx) {
return false;
}
if super::super::recognize_frac_order_transitivity(vb, law, ctx) {
return false;
}
if !matches!(&law.lhs.node, crate::ast::Expr::FnCall(..)) {
return false;
}
if law.givens.iter().any(|g| {
crate::codegen::common::refinement_lift_for_given(
&g.name,
&g.type_name,
&law.lhs,
&law.rhs,
ctx,
)
.is_some()
}) {
return false;
}
let has_pool = !keystone_pool_names(vb, law, ctx).is_empty()
|| !keystone_order_bridge_citations(vb, law, ctx).is_empty();
let rational_floor = rational_floor_shape(vb, law, ctx).is_some();
if !has_pool && !rational_floor {
use crate::ir::ProofStrategy;
match super::super::law_strategy_for(ctx, &vb.fn_name, &law.name) {
None
| Some(ProofStrategy::BackendDispatch)
| Some(ProofStrategy::LinearArithmetic { .. }) => {}
_ => return false,
}
}
let id = format!("{}.{}", vb.fn_name, law.name);
super::super::super::tactic_ir::speculative::admits(&id, false)
}
fn keystone_pool_names(vb: &VerifyBlock, law: &VerifyLaw, ctx: &CodegenContext) -> Vec<String> {
use crate::ast::VerifyKind;
let inputs = crate::codegen::proof_lower::ProofLowerInputs::from_ctx(ctx);
let cone = crate::codegen::proof_lower::LawProofCone::compute(law, &vb.fn_name, &inputs);
let cone_fns: std::collections::HashSet<String> =
cone.pure_fns().iter().map(|fd| fd.name.clone()).collect();
let mut out = Vec::new();
for prev in enclosing_verify_blocks(vb, ctx) {
if prev.line == vb.line && prev.fn_name == vb.fn_name {
break;
}
let VerifyKind::Law(prev_law) = &prev.kind else {
continue;
};
if !cone_fns.contains(&prev.fn_name) {
continue;
}
if let Some((name, _stmt)) =
crate::codegen::lean::toplevel::law_as_lemma_statement(prev, prev_law, ctx)
{
out.push(name);
}
}
out
}
fn keystone_pool_subject_fns(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Vec<String> {
use crate::ast::VerifyKind;
let inputs = crate::codegen::proof_lower::ProofLowerInputs::from_ctx(ctx);
let cone = crate::codegen::proof_lower::LawProofCone::compute(law, &vb.fn_name, &inputs);
let cone_fns: std::collections::HashSet<String> =
cone.pure_fns().iter().map(|fd| fd.name.clone()).collect();
let mut out = Vec::new();
for prev in enclosing_verify_blocks(vb, ctx) {
if prev.line == vb.line && prev.fn_name == vb.fn_name {
break;
}
let VerifyKind::Law(prev_law) = &prev.kind else {
continue;
};
if !cone_fns.contains(&prev.fn_name) {
continue;
}
if crate::codegen::lean::toplevel::law_as_lemma_statement(prev, prev_law, ctx).is_some() {
out.push(aver_name_to_lean(&prev.fn_name));
}
}
out
}
fn keystone_finite_domain_pool(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Vec<String> {
use crate::ast::VerifyKind;
let inputs = crate::codegen::proof_lower::ProofLowerInputs::from_ctx(ctx);
let cone_fns: std::collections::HashSet<String> =
crate::codegen::proof_lower::LawProofCone::compute(law, &vb.fn_name, &inputs)
.pure_fns()
.iter()
.map(|fd| fd.name.clone())
.collect();
let mut out = Vec::new();
for prev in enclosing_verify_blocks(vb, ctx) {
if prev.line == vb.line && prev.fn_name == vb.fn_name {
break;
}
let VerifyKind::Law(prev_law) = &prev.kind else {
continue;
};
if !super::super::recognize_finite_int_domain(prev, prev_law, ctx) {
continue;
}
let pc: std::collections::HashSet<String> =
crate::codegen::proof_lower::LawProofCone::compute(prev_law, &prev.fn_name, &inputs)
.pure_fns()
.iter()
.map(|fd| fd.name.clone())
.collect();
if pc.is_disjoint(&cone_fns) {
continue;
}
if let Some((name, _stmt)) =
crate::codegen::lean::toplevel::law_as_lemma_statement(prev, prev_law, ctx)
{
out.push(name);
}
}
out
}
pub(in crate::codegen::lean) fn keystone_dep_bridge_cites(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Vec<(String, String)> {
use crate::ast::{Expr, Literal, VerifyKind};
if keystone_finite_domain_pool(vb, law, ctx).is_empty() {
return Vec::new();
}
let inputs = crate::codegen::proof_lower::ProofLowerInputs::from_ctx(ctx);
let cone = crate::codegen::proof_lower::LawProofCone::compute(law, &vb.fn_name, &inputs);
let cone_fns: std::collections::HashSet<String> =
cone.pure_fns().iter().map(|fd| fd.name.clone()).collect();
let mut out: Vec<(String, String)> = Vec::new();
for module in &ctx.modules {
ctx.with_module_scope(Some(module.prefix.as_str()), || {
for dep in &module.verify_laws {
let VerifyKind::Law(dep_law) = &dep.kind else {
continue;
};
if dep_law.when.is_none() || !cone_fns.contains(&dep.fn_name) {
continue;
}
if !matches!(dep_law.rhs.node, Expr::Literal(Literal::Bool(true)))
|| !matches!(dep_law.lhs.node, Expr::FnCall(..))
{
continue;
}
out.push((
module.prefix.clone(),
crate::codegen::lean::toplevel::law_theorem_base(dep, dep_law, ctx),
));
}
});
}
out.sort();
out.dedup();
out
}
fn keystone_self_registering_pool_names(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> std::collections::HashSet<String> {
use crate::ast::VerifyKind;
use crate::ir::{FloorWindowFigure, ProofStrategy};
let inputs = crate::codegen::proof_lower::ProofLowerInputs::from_ctx(ctx);
let cone = crate::codegen::proof_lower::LawProofCone::compute(law, &vb.fn_name, &inputs);
let cone_fns: std::collections::HashSet<String> =
cone.pure_fns().iter().map(|fd| fd.name.clone()).collect();
let mut out = std::collections::HashSet::new();
for prev in enclosing_verify_blocks(vb, ctx) {
if prev.line == vb.line && prev.fn_name == vb.fn_name {
break;
}
let VerifyKind::Law(prev_law) = &prev.kind else {
continue;
};
if !cone_fns.contains(&prev.fn_name) {
continue;
}
if !matches!(
super::super::law_strategy_for(ctx, &prev.fn_name, &prev_law.name),
Some(ProofStrategy::FloorDivWindow {
figure: FloorWindowFigure::FloorPow2Cancel { .. }
})
) {
continue;
}
if let Some((name, _stmt)) =
crate::codegen::lean::toplevel::law_as_lemma_statement(prev, prev_law, ctx)
{
out.insert(name);
}
}
out
}
pub(super) fn keystone_pow2_fn(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Option<String> {
use crate::ast::VerifyKind;
use crate::ir::{FloorWindowFigure, ProofStrategy};
let inputs = crate::codegen::proof_lower::ProofLowerInputs::from_ctx(ctx);
let cone = crate::codegen::proof_lower::LawProofCone::compute(law, &vb.fn_name, &inputs);
let cone_fns: std::collections::HashSet<String> =
cone.pure_fns().iter().map(|fd| fd.name.clone()).collect();
for prev in enclosing_verify_blocks(vb, ctx) {
if prev.line == vb.line && prev.fn_name == vb.fn_name {
break;
}
let VerifyKind::Law(prev_law) = &prev.kind else {
continue;
};
if !cone_fns.contains(&prev.fn_name) {
continue;
}
let Some(ProofStrategy::FloorDivWindow { figure }) =
super::super::law_strategy_for(ctx, &prev.fn_name, &prev_law.name)
else {
continue;
};
let pow_fn = match figure {
FloorWindowFigure::PowPositive { pow_fn }
| FloorWindowFigure::PowSumSplit { pow_fn }
| FloorWindowFigure::SigWindow { pow_fn, .. }
| FloorWindowFigure::ProductWindow { pow_fn, .. }
| FloorWindowFigure::FloorPow2Window { pow_fn, .. }
| FloorWindowFigure::FloorPow2Cancel { pow_fn, .. } => pow_fn,
};
return Some(aver_name_to_lean(&pow_fn));
}
None
}
struct Pow2SignedCite {
sgn: String,
pow: String,
hom_fn: String,
cited: String,
}
fn keystone_pow2signed_cite(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Option<Pow2SignedCite> {
use crate::ast::{Expr, Literal, VerifyKind};
let int_pow = keystone_pow2_fn(vb, law, ctx)?;
let inputs = crate::codegen::proof_lower::ProofLowerInputs::from_ctx(ctx);
let cone = crate::codegen::proof_lower::LawProofCone::compute(law, &vb.fn_name, &inputs);
let cone_fns: std::collections::HashSet<String> =
cone.pure_fns().iter().map(|fd| fd.name.clone()).collect();
let all_fns: Vec<&crate::ast::FnDef> = inputs.pure_fns();
for prev in enclosing_verify_blocks(vb, ctx) {
if prev.line == vb.line && prev.fn_name == vb.fn_name {
break;
}
let VerifyKind::Law(prev_law) = &prev.kind else {
continue;
};
if !matches!(&prev_law.rhs.node, Expr::Literal(Literal::Bool(true))) {
continue;
}
let Some(inlined) = inline_fn_call(&prev_law.lhs, ctx) else {
continue;
};
let Some(sgn_dotted) = match_signed_homomorphism_shape(&inlined.node) else {
continue;
};
let sgn_basename = sgn_dotted
.rsplit('.')
.next()
.unwrap_or(&sgn_dotted)
.to_string();
if !cone_fns.contains(&sgn_basename) {
continue;
}
let Some(sgn_fd) = all_fns.iter().copied().find(|fd| fd.name == sgn_basename) else {
continue;
};
if !signed_pow2_shape(sgn_fd, &int_pow) {
continue;
}
if !cone_has_additive_record(&cone_fns, &sgn_basename, &prev.fn_name, &all_fns) {
continue;
}
let Some((thm_name, _stmt)) =
crate::codegen::lean::toplevel::law_as_lemma_statement(prev, prev_law, ctx)
else {
continue;
};
return Some(Pow2SignedCite {
sgn: aver_name_to_lean(&sgn_basename),
pow: int_pow,
hom_fn: aver_name_to_lean(&prev.fn_name),
cited: thm_name,
});
}
None
}
fn match_signed_homomorphism_shape(e: &crate::ast::Expr) -> Option<String> {
use crate::ast::{BinOp, Expr};
let Expr::FnCall(callee, args) = e else {
return None;
};
if args.len() != 2 {
return None;
}
let cname = super::super::shared::expr_dotted_name(callee)?;
if cname.rsplit('.').next() != Some("sameValue") {
return None;
}
let Expr::FnCall(f0, f0args) = &args[0].node else {
return None;
};
if f0args.len() != 1 {
return None;
}
let f0name = super::super::shared::expr_dotted_name(f0)?;
let Expr::BinOp(BinOp::Add, a, b) = &f0args[0].node else {
return None;
};
let Expr::FnCall(tcallee, targs) = &args[1].node else {
return None;
};
if targs.len() != 2 {
return None;
}
let tname = super::super::shared::expr_dotted_name(tcallee)?;
if tname.rsplit('.').next() != Some("times") {
return None;
}
let Expr::FnCall(fa, faargs) = &targs[0].node else {
return None;
};
let Expr::FnCall(fb, fbargs) = &targs[1].node else {
return None;
};
if faargs.len() != 1 || fbargs.len() != 1 {
return None;
}
if super::super::shared::expr_dotted_name(fa)? != f0name
|| super::super::shared::expr_dotted_name(fb)? != f0name
{
return None;
}
if faargs[0].node != a.node || fbargs[0].node != b.node {
return None;
}
Some(f0name)
}
pub(super) fn signed_pow2_shape(fd: &crate::ast::FnDef, int_pow: &str) -> bool {
use crate::ast::{BinOp, Expr, Stmt};
let [(p, ty)] = fd.params.as_slice() else {
return false;
};
if ty != "Int" || fd.return_type.rsplit('.').next() != Some("Fraction") {
return false;
}
let [Stmt::Expr(body)] = fd.body.stmts() else {
return false;
};
let Expr::Match { subject, arms } = &body.node else {
return false;
};
let Expr::BinOp(BinOp::Lt, sl, sr) = &subject.node else {
return false;
};
if super::super::shared::expr_dotted_name(sl).as_deref() != Some(p)
|| !matches!(&sr.node, Expr::Literal(crate::ast::Literal::Int(0)))
{
return false;
}
if arms.len() != 2 {
return false;
}
let mut called: Vec<String> = Vec::new();
for arm in arms {
collect_fncall_names(&arm.body.node, &mut called);
}
let want = rf_bare_basename(int_pow);
called
.iter()
.any(|n| rf_bare_basename(&aver_name_to_lean(n)) == want)
}
fn collect_fncall_names(e: &crate::ast::Expr, out: &mut Vec<String>) {
use crate::ast::Expr;
match e {
Expr::FnCall(callee, args) => {
if let Some(n) = super::super::shared::expr_dotted_name(callee) {
out.push(n);
}
for a in args {
collect_fncall_names(&a.node, out);
}
}
Expr::BinOp(_, a, b) => {
collect_fncall_names(&a.node, out);
collect_fncall_names(&b.node, out);
}
Expr::Neg(a) => collect_fncall_names(&a.node, out),
Expr::Attr(b, _) => collect_fncall_names(&b.node, out),
Expr::RecordCreate { fields, .. } => {
for (_, v) in fields {
collect_fncall_names(&v.node, out);
}
}
Expr::Match { subject, arms } => {
collect_fncall_names(&subject.node, out);
for arm in arms {
collect_fncall_names(&arm.body.node, out);
}
}
_ => {}
}
}
fn cone_has_additive_record(
cone_fns: &std::collections::HashSet<String>,
sgn_basename: &str,
hom_basename: &str,
all_fns: &[&crate::ast::FnDef],
) -> bool {
for fd in all_fns {
if !cone_fns.contains(&fd.name) || fd.name == sgn_basename || fd.name == hom_basename {
continue;
}
for stmt in fd.body.stmts() {
if let crate::ast::Stmt::Expr(e) = stmt
&& expr_has_additive_record_field(&e.node)
{
return true;
}
}
}
false
}
fn expr_has_additive_record_field(e: &crate::ast::Expr) -> bool {
use crate::ast::Expr;
match e {
Expr::RecordCreate { fields, .. } => fields.iter().any(|(_, v)| {
matches!(&v.node, Expr::BinOp(crate::ast::BinOp::Add, _, _))
|| expr_has_additive_record_field(&v.node)
}),
Expr::FnCall(c, args) => {
expr_has_additive_record_field(&c.node)
|| args.iter().any(|a| expr_has_additive_record_field(&a.node))
}
Expr::BinOp(_, a, b) => {
expr_has_additive_record_field(&a.node) || expr_has_additive_record_field(&b.node)
}
Expr::Neg(a) => expr_has_additive_record_field(&a.node),
Expr::Attr(b, _) => expr_has_additive_record_field(&b.node),
Expr::Match { subject, arms } => {
expr_has_additive_record_field(&subject.node)
|| arms
.iter()
.any(|arm| expr_has_additive_record_field(&arm.body.node))
}
Expr::RecordUpdate { base, updates, .. } => {
updates.iter().any(|(_, v)| {
matches!(&v.node, Expr::BinOp(crate::ast::BinOp::Add, _, _))
|| expr_has_additive_record_field(&v.node)
}) || expr_has_additive_record_field(&base.node)
}
_ => false,
}
}
struct OrderBridgeCitation {
name: String,
support: Vec<String>,
}
fn comparison_op_lean(op: crate::ast::BinOp) -> Option<&'static str> {
use crate::ast::BinOp;
match op {
BinOp::Lt => Some("<"),
BinOp::Gt => Some(">"),
BinOp::Lte => Some("<="),
BinOp::Gte => Some(">="),
_ => None,
}
}
pub(super) fn expr_var_name(e: &crate::ast::Expr) -> Option<&str> {
match e {
crate::ast::Expr::Ident(n) => Some(n),
crate::ast::Expr::Resolved { name, .. } => Some(name),
_ => None,
}
}
fn expr_is_atomic(e: &crate::ast::Expr) -> bool {
expr_var_name(e).is_some() || matches!(e, crate::ast::Expr::Literal(_))
}
pub(super) fn arith_eq(a: &crate::ast::Expr, b: &crate::ast::Expr) -> bool {
use crate::ast::Expr;
match (expr_var_name(a), expr_var_name(b)) {
(Some(na), Some(nb)) => return na == nb,
(None, None) => {}
_ => return false,
}
match (a, b) {
(Expr::Literal(x), Expr::Literal(y)) => x == y,
(Expr::BinOp(o1, a1, b1), Expr::BinOp(o2, a2, b2)) => {
o1 == o2 && arith_eq(&a1.node, &a2.node) && arith_eq(&b1.node, &b2.node)
}
(Expr::Neg(x), Expr::Neg(y)) => arith_eq(&x.node, &y.node),
(Expr::FnCall(c1, a1), Expr::FnCall(c2, a2)) => {
a1.len() == a2.len()
&& arith_eq(&c1.node, &c2.node)
&& a1.iter().zip(a2).all(|(p, q)| arith_eq(&p.node, &q.node))
}
(Expr::Attr(b1, f1), Expr::Attr(b2, f2)) => f1 == f2 && arith_eq(&b1.node, &b2.node),
_ => false,
}
}
fn ident_occurs(e: &crate::ast::Expr, name: &str) -> bool {
use crate::ast::Expr;
if expr_var_name(e) == Some(name) {
return true;
}
match e {
Expr::BinOp(_, a, b) => ident_occurs(&a.node, name) || ident_occurs(&b.node, name),
Expr::Neg(a) => ident_occurs(&a.node, name),
Expr::FnCall(c, args) => {
ident_occurs(&c.node, name) || args.iter().any(|x| ident_occurs(&x.node, name))
}
Expr::Attr(b, _) => ident_occurs(&b.node, name),
_ => false,
}
}
fn collect_arith_subterms(
expr: &crate::ast::Spanned<crate::ast::Expr>,
out: &mut Vec<crate::ast::Expr>,
) {
use crate::ast::Expr;
out.push(expr.node.clone());
match &expr.node {
Expr::BinOp(_, a, b) => {
collect_arith_subterms(a, out);
collect_arith_subterms(b, out);
}
Expr::Neg(a) => collect_arith_subterms(a, out),
Expr::FnCall(c, args) => {
collect_arith_subterms(c, out);
for a in args {
collect_arith_subterms(a, out);
}
}
Expr::Attr(b, _) => collect_arith_subterms(b, out),
_ => {}
}
}
pub(super) fn substitute_idents(
e: &crate::ast::Spanned<crate::ast::Expr>,
map: &std::collections::HashMap<String, crate::ast::Spanned<crate::ast::Expr>>,
) -> crate::ast::Spanned<crate::ast::Expr> {
use crate::ast::{Expr, Spanned};
let node = match &e.node {
Expr::Ident(n) => {
if let Some(rep) = map.get(n) {
return rep.clone();
}
Expr::Ident(n.clone())
}
Expr::Resolved { name, .. } => {
if let Some(rep) = map.get(name) {
return rep.clone();
}
e.node.clone()
}
Expr::BinOp(op, a, b) => Expr::BinOp(
*op,
Box::new(substitute_idents(a, map)),
Box::new(substitute_idents(b, map)),
),
Expr::Neg(a) => Expr::Neg(Box::new(substitute_idents(a, map))),
Expr::FnCall(c, args) => Expr::FnCall(
Box::new(substitute_idents(c, map)),
args.iter().map(|x| substitute_idents(x, map)).collect(),
),
Expr::Attr(b, f) => Expr::Attr(Box::new(substitute_idents(b, map)), f.clone()),
Expr::RecordCreate { type_name, fields } => Expr::RecordCreate {
type_name: type_name.clone(),
fields: fields
.iter()
.map(|(fname, v)| (fname.clone(), substitute_idents(v, map)))
.collect(),
},
Expr::RecordUpdate {
type_name,
base,
updates,
} => Expr::RecordUpdate {
type_name: type_name.clone(),
base: Box::new(substitute_idents(base, map)),
updates: updates
.iter()
.map(|(fname, v)| (fname.clone(), substitute_idents(v, map)))
.collect(),
},
Expr::Match { subject, arms } => Expr::Match {
subject: Box::new(substitute_idents(subject, map)),
arms: arms
.iter()
.map(|arm| crate::ast::MatchArm {
body: Box::new(substitute_idents(&arm.body, map)),
..arm.clone()
})
.collect(),
},
other => other.clone(),
};
Spanned::bare(node)
}
pub(super) fn inline_fn_call(
call: &crate::ast::Spanned<crate::ast::Expr>,
ctx: &CodegenContext,
) -> Option<crate::ast::Spanned<crate::ast::Expr>> {
use crate::ast::Expr;
let Expr::FnCall(callee, args) = &call.node else {
return None;
};
let name = super::super::shared::expr_dotted_name(callee)?;
let fd = ctx.fn_def_by_name(&name, ctx.active_module_scope().as_deref())?;
if fd.params.len() != args.len() {
return None;
}
let body = fd.body.tail_expr()?;
let mut map: std::collections::HashMap<String, crate::ast::Spanned<crate::ast::Expr>> =
std::collections::HashMap::new();
for ((pname, _), arg) in fd.params.iter().zip(args.iter()) {
map.insert(pname.clone(), arg.clone());
}
Some(substitute_idents(body, &map))
}
fn match_arith_pattern(
pat: &crate::ast::Expr,
term: &crate::ast::Expr,
vars: &std::collections::HashSet<String>,
subst: &mut std::collections::HashMap<String, crate::ast::Expr>,
) -> bool {
use crate::ast::Expr;
if let Some(pname) = expr_var_name(pat) {
if vars.contains(pname) {
return match subst.get(pname) {
Some(prev) => arith_eq(prev, term),
None => {
subst.insert(pname.to_string(), term.clone());
true
}
};
}
return expr_var_name(term) == Some(pname);
}
match pat {
Expr::Literal(l) => matches!(term, Expr::Literal(tl) if tl == l),
Expr::Neg(a) => {
if let Expr::Neg(ta) = term {
match_arith_pattern(&a.node, &ta.node, vars, subst)
} else {
false
}
}
Expr::BinOp(op, a, b) => {
if let Expr::BinOp(top, ta, tb) = term {
op == top
&& match_arith_pattern(&a.node, &ta.node, vars, subst)
&& match_arith_pattern(&b.node, &tb.node, vars, subst)
} else {
false
}
}
Expr::FnCall(c, args) => {
if let Expr::FnCall(tc, targs) = term {
args.len() == targs.len()
&& match_arith_pattern(&c.node, &tc.node, vars, subst)
&& args
.iter()
.zip(targs.iter())
.all(|(p, t)| match_arith_pattern(&p.node, &t.node, vars, subst))
} else {
false
}
}
Expr::Attr(b, f) => {
if let Expr::Attr(tb, tf) = term {
f == tf && match_arith_pattern(&b.node, &tb.node, vars, subst)
} else {
false
}
}
_ => false,
}
}
fn order_sides_match_subterms(
lhs_side: &crate::ast::Expr,
rhs_side: &crate::ast::Expr,
vars: &std::collections::HashSet<String>,
subterms: &[crate::ast::Expr],
) -> bool {
for t1 in subterms {
let mut s = std::collections::HashMap::new();
if match_arith_pattern(lhs_side, t1, vars, &mut s) {
for t2 in subterms {
let mut s2 = s.clone();
if match_arith_pattern(rhs_side, t2, vars, &mut s2) {
return true;
}
}
}
}
false
}
fn keystone_order_bridge_citations(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Vec<OrderBridgeCitation> {
use crate::ast::{Expr, Literal, Spanned, VerifyKind};
let mut subterms: Vec<Expr> = Vec::new();
if let Some(when) = &law.when {
collect_arith_subterms(when, &mut subterms);
}
if let Some(goal) = inline_fn_call(&law.lhs, ctx) {
collect_arith_subterms(&goal, &mut subterms);
}
collect_arith_subterms(&law.rhs, &mut subterms);
if subterms.is_empty() {
return Vec::new();
}
let citing_uid = format!(
"{}_{}",
aver_name_to_lean(&vb.fn_name),
aver_name_to_lean(&law.name)
);
let mut out = Vec::new();
for prev in enclosing_verify_blocks(vb, ctx) {
if prev.line == vb.line && prev.fn_name == vb.fn_name {
break;
}
let VerifyKind::Law(prev_law) = &prev.kind else {
continue;
};
if !matches!(&prev_law.rhs.node, Expr::Literal(Literal::Bool(true))) {
continue;
}
let Some((thm_name, _stmt)) =
crate::codegen::lean::toplevel::law_as_lemma_statement(prev, prev_law, ctx)
else {
continue;
};
let Some(inlined) = inline_fn_call(&prev_law.lhs, ctx) else {
continue;
};
let Expr::BinOp(op, l_box, r_box) = &inlined.node else {
continue;
};
let Some(op_lean) = comparison_op_lean(*op) else {
continue;
};
let lhs_side = &l_box.node;
let rhs_side = &r_box.node;
if expr_is_atomic(lhs_side) || expr_is_atomic(rhs_side) {
continue;
}
let given_names: Vec<String> = prev_law.givens.iter().map(|g| g.name.clone()).collect();
if given_names.is_empty() {
continue;
}
if !given_names
.iter()
.all(|g| ident_occurs(lhs_side, g) || ident_occurs(rhs_side, g))
{
continue;
}
let vars: std::collections::HashSet<String> = given_names.iter().cloned().collect();
if !order_sides_match_subterms(lhs_side, rhs_side, &vars, &subterms) {
continue;
}
let rname = format!(
"{}_order_{}_{}",
citing_uid,
aver_name_to_lean(&prev.fn_name),
aver_name_to_lean(&prev_law.name)
);
let cited_fn_lean = aver_name_to_lean(&prev.fn_name);
let quant = prev_law
.givens
.iter()
.map(|g| {
format!(
"({} : {})",
aver_name_to_lean(&g.name),
crate::codegen::lean::types::type_annotation_to_lean(&g.type_name)
)
})
.collect::<Vec<_>>()
.join(" ");
let names_csv = given_names
.iter()
.map(|n| aver_name_to_lean(n))
.collect::<Vec<_>>()
.join(" ");
let l_span: &Spanned<Expr> = l_box;
let r_span: &Spanned<Expr> = r_box;
let l_lean = super::super::super::expr::emit_expr_legacy(l_span, ctx, None);
let r_lean = super::super::super::expr::emit_expr_legacy(r_span, ctx, None);
let (premise_clause, intro, apply_args) = match &prev_law.when {
Some(when) => {
let p = super::super::super::expr::emit_expr_legacy(when, ctx, None);
(
format!("{p} = true -> "),
format!(" intro {names_csv} hp"),
format!("{names_csv} hp"),
)
}
None => (
String::new(),
format!(" intro {names_csv}"),
names_csv.clone(),
),
};
let support = vec![
format!(
"theorem {rname} : ∀ {quant}, {premise_clause}({l_lean}) {op_lean} ({r_lean}) := by"
),
intro,
format!(
" simpa only [{cited_fn_lean}, decide_eq_true_eq] using ({thm_name} {apply_args})"
),
format!("grind_pattern {rname} => ({l_lean}), ({r_lean})"),
];
out.push(OrderBridgeCitation {
name: rname,
support,
});
}
out
}
pub(in crate::codegen::lean) fn emit_pool_composition_generic_law(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
intro_names: &[String],
) -> Option<AutoProof> {
if !recognize_pool_composition_generic(vb, law, ctx) {
return None;
}
if let Some(proof) = emit_rational_floor_family(vb, law, ctx, intro_names) {
return Some(proof);
}
fn bare_basename(n: &str) -> &str {
let n = n.strip_prefix("_root_.").unwrap_or(n);
n.rsplit('.').next().unwrap_or(n)
}
let mut abstract_fns: std::collections::HashSet<String> =
super::super::recursive_pure_fn_names(ctx)
.iter()
.map(|n| bare_basename(&aver_name_to_lean(n)).to_string())
.collect();
for subj in keystone_pool_subject_fns(vb, law, ctx) {
abstract_fns.insert(bare_basename(&subj).to_string());
}
let pow2signed = keystone_pow2signed_cite(vb, law, ctx);
if let Some(cite) = &pow2signed {
abstract_fns.insert(bare_basename(&cite.sgn).to_string());
}
let defs: Vec<String> = law_simp_defs(ctx, vb, law)
.into_iter()
.filter(|d| !abstract_fns.contains(bare_basename(d)))
.collect();
let defs_csv = defs.join(", ");
let self_registering = keystone_self_registering_pool_names(vb, law, ctx);
let pool_names: Vec<String> = keystone_pool_names(vb, law, ctx)
.into_iter()
.filter(|n| !self_registering.contains(n))
.collect();
let order_citations = keystone_order_bridge_citations(vb, law, ctx);
let mut hints: Vec<String> = pool_names.clone();
hints.extend(order_citations.iter().map(|c| c.name.clone()));
let mut support_lines: Vec<String> = Vec::new();
for citation in &order_citations {
support_lines.extend(citation.support.iter().cloned());
}
let pow2_normalizer = keystone_pow2_fn(vb, law, ctx).map(|pow| {
let nlbase = format!(
"{}_law_{}__pow2lf",
aver_name_to_lean(&vb.fn_name),
law.name
);
let support =
super::super::floor_window::pow2_linear_form_normalizer_support(&nlbase, &pow);
(nlbase, support)
});
if let Some((nlbase, support)) = &pow2_normalizer {
support_lines.extend(support.lines().map(|l| l.to_string()));
hints.push(format!("{nlbase}__pow_add"));
hints.push(format!("{nlbase}__pow_succ"));
}
if let Some(cite) = &pow2signed {
let sgnbase = format!(
"{}_law_{}__pow2sgn",
aver_name_to_lean(&vb.fn_name),
law.name
);
let support = super::super::floor_window::pow2_signed_homomorphism_normalizer_support(
&sgnbase,
&cite.pow,
&cite.sgn,
&cite.hom_fn,
&cite.cited,
);
support_lines.extend(support.lines().map(|l| l.to_string()));
hints.push(format!("{sgnbase}__sgn_add"));
hints.push(format!("{sgnbase}__sgn_add_succ"));
}
let grind_hints = hints.join(", ");
let grind_call = if grind_hints.is_empty() {
"grind".to_string()
} else {
format!("grind [{grind_hints}]")
};
let (intro, simp_at) = if law.when.is_some() {
(
format!(" intro {} h_when", intro_names.join(" ")),
"at h_when ⊢",
)
} else {
(format!(" intro {}", intro_names.join(" ")), "at ⊢")
};
let bridge_extra = if pow2signed.is_some() {
", beq_iff_eq"
} else {
""
};
let close = if pow2_normalizer.is_some() {
format!(
" | (simp only [{defs_csv}, Bool.and_eq_true, decide_eq_true_eq{bridge_extra}] {simp_at} <;> (first | ((repeat' split) <;> {grind_call}) | {grind_call}))"
)
} else {
format!(
" | (simp only [{defs_csv}, Bool.and_eq_true, decide_eq_true_eq{bridge_extra}] {simp_at} <;> {grind_call})"
)
};
let floor = if super::super::super::tactic_ir::speculative::probing() {
let id = format!("{}.{}", vb.fn_name, law.name);
super::super::super::tactic_ir::speculative::record_probed(&id);
format!(" | (trace \"AVERSPEC_SORRY:{id}\"; sorry)")
} else {
" | sorry".to_string()
};
Some(AutoProof {
support_lines,
body: Tactic::raw(vec![intro, " first".to_string(), close, floor]),
replaces_theorem: false,
})
}