use std::collections::{HashMap, HashSet};
use crate::ast::{Expr, FnDef, Literal, Spanned, TopLevel, TypeDef};
use crate::codegen::common::expr_to_dotted_name;
use crate::codegen::recursion::RecursionPlan;
use crate::codegen::{CodegenContext, ModuleInfo};
use crate::ir::proof_ir::{
DecreaseProof, FnContract, Measure, NativeIntCountdownBody, Predicate, PreservationProof,
ProofIR, QuantifierType, RecursionContract, RefinedTypeDecl,
};
pub struct ProofLowerInputs<'a> {
pub entry_items: &'a [TopLevel],
pub dep_modules: &'a [ModuleInfo],
pub module_prefixes: &'a HashSet<String>,
pub recursive_fns: &'a HashSet<crate::ir::FnId>,
pub symbol_table: &'a crate::ir::SymbolTable,
pub program_shape: Option<&'a crate::analysis::shape::ProgramShape>,
}
impl<'a> ProofLowerInputs<'a> {
pub fn from_ctx(ctx: &'a CodegenContext) -> Self {
Self {
entry_items: &ctx.items,
dep_modules: &ctx.modules,
module_prefixes: &ctx.module_prefixes,
recursive_fns: &ctx.recursive_fns,
symbol_table: &ctx.symbol_table,
program_shape: ctx.program_shape.as_ref(),
}
}
pub fn pure_fns(&self) -> Vec<&'a FnDef> {
self.dep_modules
.iter()
.flat_map(|m| m.fn_defs.iter())
.chain(self.entry_items.iter().filter_map(|item| match item {
TopLevel::FnDef(fd) => Some(fd),
_ => None,
}))
.filter(|fd| crate::codegen::common::is_pure_fn(fd))
.collect()
}
pub fn recursive_pure_fn_names(&self) -> HashSet<String> {
let symbols = self.symbol_table;
let pure_ids: HashSet<crate::ir::FnId> = self
.pure_fns()
.into_iter()
.filter_map(|fd| {
let scope = self
.dep_modules
.iter()
.find(|m| m.fn_defs.iter().any(|d| std::ptr::eq(d, fd)))
.map(|m| m.prefix.as_str());
let key = match scope {
Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), &fd.name),
None => crate::ir::FnKey::entry(&fd.name),
};
symbols.fn_id_of(&key)
})
.collect();
self.recursive_fns
.intersection(&pure_ids)
.map(|id| symbols.fn_entry(*id).key.name.clone())
.collect()
}
pub fn pure_fns_in_scope(&self, scope: Option<&str>) -> Vec<&'a FnDef> {
match scope {
None => self
.entry_items
.iter()
.filter_map(|item| match item {
TopLevel::FnDef(fd) => Some(fd),
_ => None,
})
.filter(|fd| crate::codegen::common::is_pure_fn(fd))
.collect(),
Some(prefix) => self
.dep_modules
.iter()
.filter(|m| m.prefix == prefix)
.flat_map(|m| m.fn_defs.iter())
.filter(|fd| crate::codegen::common::is_pure_fn(fd))
.collect(),
}
}
pub fn recursive_pure_fn_names_in_scope(&self, scope: Option<&str>) -> HashSet<String> {
let symbols = self.symbol_table;
let pure_ids: HashSet<crate::ir::FnId> = self
.pure_fns_in_scope(scope)
.into_iter()
.filter_map(|fd| {
let key = match scope {
Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), &fd.name),
None => crate::ir::FnKey::entry(&fd.name),
};
symbols.fn_id_of(&key)
})
.collect();
self.recursive_fns
.intersection(&pure_ids)
.map(|id| symbols.fn_entry(*id).key.name.clone())
.collect()
}
pub fn scopes(&self) -> Vec<Option<String>> {
let mut out = vec![None];
for m in self.dep_modules {
out.push(Some(m.prefix.clone()));
}
out
}
pub fn fn_owning_scope(&self, fd: &FnDef) -> Option<&'a str> {
for m in self.dep_modules {
for f in &m.fn_defs {
if std::ptr::eq(f, fd) {
return Some(m.prefix.as_str());
}
}
}
None
}
pub fn resolve_expr(
&self,
expr: &crate::ast::Spanned<crate::ast::Expr>,
scope: Option<&str>,
) -> crate::ast::Spanned<crate::ir::hir::ResolvedExpr> {
use crate::ir::hir::{ResolveCtx, ResolvedStmt};
let mut rctx = ResolveCtx::new(self.symbol_table);
rctx.current_module = scope.map(String::from);
let stmt = crate::ast::Stmt::Expr(expr.clone());
match crate::ir::hir::resolve::resolve_stmt_external(&rctx, &stmt) {
ResolvedStmt::Expr(s) => s,
ResolvedStmt::Binding { value, .. } => value,
}
}
pub fn recursive_type_names(&self) -> HashSet<String> {
self.entry_items
.iter()
.filter_map(|item| match item {
TopLevel::TypeDef(td) => Some(td),
_ => None,
})
.chain(self.dep_modules.iter().flat_map(|m| m.type_defs.iter()))
.filter(|td| crate::codegen::common::is_recursive_type_def(td))
.map(|td| crate::codegen::common::type_def_name(td).to_string())
.collect()
}
pub fn find_fn_def_by_call_name(&self, call_name: &str) -> Option<&'a FnDef> {
let find_exact = |name: &str| -> Option<&'a FnDef> {
self.dep_modules
.iter()
.flat_map(|m| m.fn_defs.iter())
.chain(self.entry_items.iter().filter_map(|item| match item {
TopLevel::FnDef(fd) => Some(fd),
_ => None,
}))
.find(|fd| fd.name == name)
};
find_exact(call_name).or_else(|| {
let short = call_name.rsplit('.').next()?;
find_exact(short)
})
}
pub fn find_type_def(&self, type_name: &str) -> Option<&'a TypeDef> {
self.entry_items
.iter()
.filter_map(|item| match item {
TopLevel::TypeDef(td) => Some(td),
_ => None,
})
.chain(self.dep_modules.iter().flat_map(|m| m.type_defs.iter()))
.find(|td| crate::codegen::common::type_def_name(td) == type_name)
}
}
pub fn lower(inputs: &ProofLowerInputs) -> ProofIR {
let mut ir = ProofIR::default();
populate_refined_types(inputs, &mut ir);
populate_fn_contracts(inputs, &mut ir);
populate_law_theorems(inputs, &mut ir);
ir
}
pub fn populate_refined_types(inputs: &ProofLowerInputs, ir: &mut ProofIR) {
let symbols = inputs.symbol_table;
let entry_typedefs = inputs.entry_items.iter().filter_map(|item| match item {
TopLevel::TypeDef(td) => Some((None::<&str>, td)),
_ => None,
});
let module_typedefs = inputs.dep_modules.iter().flat_map(|m| {
m.type_defs
.iter()
.map(move |td| (Some(m.prefix.as_str()), td))
});
for (module_prefix, td) in entry_typedefs.chain(module_typedefs) {
let TypeDef::Product { name, fields, .. } = td else {
continue;
};
if fields.len() != 1 {
continue;
}
let type_key = match module_prefix {
Some(prefix) => crate::ir::TypeKey::in_module(prefix.to_string(), name),
None => crate::ir::TypeKey::entry(name),
};
let Some(canonical_key) = symbols.type_id_of(&type_key) else {
continue;
};
if ir.refined_types.contains_key(&canonical_key) {
continue;
}
let Some(info) =
crate::codegen::common::refinement_info_for_in_scope(name, inputs, module_prefix)
else {
continue;
};
let invariant = Predicate {
free_vars: vec![(
info.param_name.to_string(),
crate::ir::proof_ir::QuantifierType::Plain(info.carrier_type.to_string()),
)],
expr: inputs.resolve_expr(info.predicate, module_prefix),
};
let witness = pick_witness(
name,
canonical_key,
inputs,
info.predicate,
info.param_name,
module_prefix,
);
let Some(witness) = witness else {
continue;
};
ir.refined_types.insert(
canonical_key,
RefinedTypeDecl {
name: name.clone(),
carrier_type: info.carrier_type.to_string(),
carrier_field: info.carrier_field.to_string(),
predicate_param: info.param_name.to_string(),
invariant,
witness: Some(witness),
interval: None,
op_classes: Vec::new(),
},
);
}
populate_refined_type_intervals(inputs, ir);
}
fn populate_refined_type_intervals(inputs: &ProofLowerInputs, ir: &mut ProofIR) {
let analysis = crate::ir::interval::analyze(&ir.refined_types, inputs);
for (type_id, decl) in ir.refined_types.iter_mut() {
let Some(per_type) = analysis.types.get(type_id) else {
continue;
};
decl.interval = per_type.interval_known.then_some(per_type.interval);
decl.op_classes = per_type.ops.clone();
}
}
pub fn carrier_interval_table(
inputs: &ProofLowerInputs,
) -> HashMap<String, (crate::ir::interval::Interval, bool)> {
let mut table = HashMap::new();
let entry_typedefs = inputs.entry_items.iter().filter_map(|item| match item {
TopLevel::TypeDef(td) => Some((None::<&str>, td)),
_ => None,
});
let module_typedefs = inputs.dep_modules.iter().flat_map(|m| {
m.type_defs
.iter()
.map(move |td| (Some(m.prefix.as_str()), td))
});
for (module_prefix, td) in entry_typedefs.chain(module_typedefs) {
let TypeDef::Product { name, fields, .. } = td else {
continue;
};
if fields.len() != 1 {
continue;
}
let Some(info) =
crate::codegen::common::refinement_info_for_in_scope(name, inputs, module_prefix)
else {
continue;
};
let invariant = Predicate {
free_vars: vec![(
info.param_name.to_string(),
QuantifierType::Plain(info.carrier_type.to_string()),
)],
expr: inputs.resolve_expr(info.predicate, module_prefix),
};
let (interval, interval_known) = crate::ir::interval::interval_of_invariant(&invariant);
if !interval_known {
continue;
}
table
.entry(name.clone())
.and_modify(|(iv, known): &mut (crate::ir::interval::Interval, bool)| {
*iv = iv.intersect(interval);
*known = true;
})
.or_insert((interval, true));
}
table
}
pub fn field_carrier_interval_table(
inputs: &ProofLowerInputs,
) -> HashMap<(String, String), (crate::ir::interval::Interval, bool)> {
let mut table = HashMap::new();
let entry_typedefs = inputs.entry_items.iter().filter_map(|item| match item {
TopLevel::TypeDef(td) => Some((None::<&str>, td)),
_ => None,
});
let module_typedefs = inputs.dep_modules.iter().flat_map(|m| {
m.type_defs
.iter()
.map(move |td| (Some(m.prefix.as_str()), td))
});
for (module_prefix, td) in entry_typedefs.chain(module_typedefs) {
let TypeDef::Product { name, fields, .. } = td else {
continue;
};
if fields.len() < 2 {
continue;
}
let Some((ctor, ctor_prefix)) =
find_multi_field_smart_ctor(name, fields, inputs, module_prefix)
else {
continue;
};
let field_to_param = match ctor_field_param_map(ctor, name, fields) {
Some(m) => m,
None => continue,
};
let guard = ctor_guard_predicate(ctor);
let Some(guard) = guard else { continue };
for (fname, ftype) in fields {
if ftype.trim() != "Int" {
continue;
}
let Some(param) = field_to_param.get(fname) else {
continue;
};
let resolved_guard = inputs.resolve_expr(guard, ctor_prefix);
let leaves =
crate::codegen::common::flatten_bool_and_conjuncts_resolved(&resolved_guard);
let single_var: Vec<_> = leaves
.into_iter()
.filter(|leaf| resolved_leaf_mentions_only(leaf, param))
.collect();
if single_var.is_empty() {
continue;
}
let conj = rebuild_bool_and(single_var);
let invariant = Predicate {
free_vars: vec![(param.clone(), QuantifierType::Plain("Int".to_string()))],
expr: conj,
};
let (interval, interval_known) = crate::ir::interval::interval_of_invariant(&invariant);
if !interval_known {
continue;
}
table
.entry((name.clone(), fname.clone()))
.and_modify(|(iv, known): &mut (crate::ir::interval::Interval, bool)| {
*iv = iv.intersect(interval);
*known = true;
})
.or_insert((interval, true));
}
}
table
}
fn find_multi_field_smart_ctor<'a>(
record_name: &str,
fields: &[(String, String)],
inputs: &ProofLowerInputs<'a>,
record_scope: Option<&str>,
) -> Option<(&'a FnDef, Option<&'a str>)> {
let entry_fns = inputs.entry_items.iter().filter_map(|item| match item {
TopLevel::FnDef(fd) => Some((None::<&str>, fd)),
_ => None,
});
let module_fns = inputs.dep_modules.iter().flat_map(|m| {
m.fn_defs
.iter()
.map(move |fd| (Some(m.prefix.as_str()), fd))
});
for (scope, fd) in entry_fns.chain(module_fns) {
if scope != record_scope {
continue;
}
if !fd.return_type.starts_with("Result<") {
continue;
}
if !fd.return_type[7..].starts_with(record_name) {
continue;
}
if fd.params.len() < 2 {
continue;
}
let stmts = fd.body.stmts();
if stmts.len() != 1 {
continue;
}
let crate::ast::Stmt::Expr(body_expr) = &stmts[0] else {
continue;
};
let Expr::Match { arms, .. } = &body_expr.node else {
continue;
};
if !is_multi_field_ok_err_match(arms, record_name, fields) {
continue;
}
return Some((fd, scope));
}
None
}
fn is_multi_field_ok_err_match(
arms: &[crate::ast::MatchArm],
record_name: &str,
fields: &[(String, String)],
) -> bool {
if arms.len() != 2 {
return false;
}
let mut true_ok = false;
let mut false_err = false;
for arm in arms {
match &arm.pattern {
crate::ast::Pattern::Literal(Literal::Bool(true)) => {
if multi_field_ok_constructor(&arm.body, record_name, fields).is_some() {
true_ok = true;
}
}
crate::ast::Pattern::Literal(Literal::Bool(false)) => {
if multi_field_is_err_constructor(&arm.body) {
false_err = true;
}
}
_ => return false,
}
}
true_ok && false_err
}
fn multi_field_is_err_constructor(expr: &Spanned<Expr>) -> bool {
match &expr.node {
Expr::Constructor(name, Some(_)) => name == "Result.Err",
Expr::FnCall(callee, args) if args.len() == 1 => {
matches!(expr_to_dotted_name(&callee.node), Some(name) if name == "Result.Err")
}
_ => false,
}
}
fn multi_field_ok_constructor(
expr: &Spanned<Expr>,
record_name: &str,
fields: &[(String, String)],
) -> Option<HashMap<String, String>> {
let (ctor_name, ctor_arg_node) = match &expr.node {
Expr::Constructor(name, Some(arg)) => (name.clone(), &arg.node),
Expr::FnCall(callee, args) if args.len() == 1 => {
let name = expr_to_dotted_name(&callee.node)?;
(name, &args[0].node)
}
_ => return None,
};
if ctor_name != "Result.Ok" {
return None;
}
let (t, create_fields) = match ctor_arg_node {
Expr::RecordCreate { type_name, fields } => (type_name.as_str(), fields),
_ => return None,
};
if t != record_name || create_fields.len() != fields.len() {
return None;
}
let mut map = HashMap::new();
for (fname, fvalue) in create_fields {
let param = match &fvalue.node {
Expr::Ident(n) | Expr::Resolved { name: n, .. } => n.clone(),
_ => return None,
};
map.insert(fname.clone(), param);
}
if fields.iter().any(|(fname, _)| !map.contains_key(fname)) {
return None;
}
Some(map)
}
fn ctor_field_param_map(
fd: &FnDef,
record_name: &str,
fields: &[(String, String)],
) -> Option<HashMap<String, String>> {
let stmts = fd.body.stmts();
let crate::ast::Stmt::Expr(body_expr) = stmts.first()? else {
return None;
};
let Expr::Match { arms, .. } = &body_expr.node else {
return None;
};
for arm in arms {
if matches!(
&arm.pattern,
crate::ast::Pattern::Literal(Literal::Bool(true))
) {
return multi_field_ok_constructor(&arm.body, record_name, fields);
}
}
None
}
fn ctor_guard_predicate(fd: &FnDef) -> Option<&Spanned<Expr>> {
let stmts = fd.body.stmts();
let crate::ast::Stmt::Expr(body_expr) = stmts.first()? else {
return None;
};
let Expr::Match { subject, .. } = &body_expr.node else {
return None;
};
Some(subject)
}
fn resolved_leaf_mentions_only(leaf: &Spanned<crate::ir::hir::ResolvedExpr>, param: &str) -> bool {
let mut only = true;
let mut saw = false;
collect_resolved_idents(leaf, &mut |name| {
if name == param {
saw = true;
} else {
only = false;
}
});
only && saw
}
fn collect_resolved_idents(e: &Spanned<crate::ir::hir::ResolvedExpr>, f: &mut impl FnMut(&str)) {
use crate::ir::hir::ResolvedExpr;
match &e.node {
ResolvedExpr::Ident(n) | ResolvedExpr::Resolved { name: n, .. } => f(n),
ResolvedExpr::BinOp(_, l, r) => {
collect_resolved_idents(l, f);
collect_resolved_idents(r, f);
}
ResolvedExpr::Neg(i) => collect_resolved_idents(i, f),
ResolvedExpr::Call(_, args) => {
for a in args {
collect_resolved_idents(a, f);
}
}
ResolvedExpr::Attr(o, _) => collect_resolved_idents(o, f),
_ => {}
}
}
fn rebuild_bool_and(
mut leaves: Vec<Spanned<crate::ir::hir::ResolvedExpr>>,
) -> Spanned<crate::ir::hir::ResolvedExpr> {
use crate::ir::hir::{ResolvedCallee, ResolvedExpr};
let mut acc = leaves.remove(0);
for leaf in leaves {
let line = acc.line;
acc = Spanned::new(
ResolvedExpr::Call(
ResolvedCallee::Builtin("Bool.and".to_string()),
vec![acc, leaf],
),
line,
);
}
acc
}
pub fn field_carrier_eligible_intervals(
inputs: &ProofLowerInputs,
instantiations: &crate::ir::mir::InstantiationRegistry,
) -> HashMap<(String, String), (crate::ir::interval::Interval, bool)> {
let table = field_carrier_interval_table(inputs);
if table.is_empty() {
return table;
}
let record_candidates: HashSet<String> = table
.iter()
.filter(|(_, (iv, known))| *known && iv.fits_i64())
.map(|((rec, _), _)| rec.clone())
.collect();
if std::env::var("AVER_CARRIER_I64_SKIP_DEMOTION").is_ok() {
return table
.into_iter()
.filter(|((rec, _), (iv, known))| {
*known && iv.fits_i64() && record_candidates.contains(rec)
})
.collect();
}
let demoted_records =
multi_field_record_demotions(inputs, &record_candidates, &table, instantiations);
table
.into_iter()
.filter(|((rec, _), (iv, known))| {
*known
&& iv.fits_i64()
&& record_candidates.contains(rec)
&& !demoted_records.contains(rec)
})
.collect()
}
fn multi_field_record_demotions(
inputs: &ProofLowerInputs,
candidates: &HashSet<String>,
field_intervals: &HashMap<(String, String), (crate::ir::interval::Interval, bool)>,
instantiations: &crate::ir::mir::InstantiationRegistry,
) -> HashSet<String> {
let mut demoted: HashSet<String> = HashSet::new();
if candidates.is_empty() {
return demoted;
}
let mut ctor_fn_of: HashMap<String, String> = HashMap::new();
let record_defs = collect_product_defs(inputs);
for name in candidates {
let Some((fields, scope)) = record_defs.get(name) else {
demoted.insert(name.clone());
continue;
};
match find_multi_field_smart_ctor(name, fields, inputs, *scope) {
Some((ctor, _)) => {
ctor_fn_of.insert(name.clone(), ctor.name.clone());
}
None => {
demoted.insert(name.clone());
}
}
}
let all_fn_defs = inputs
.entry_items
.iter()
.filter_map(|it| match it {
TopLevel::FnDef(fd) => Some(fd),
_ => None,
})
.chain(inputs.dep_modules.iter().flat_map(|m| m.fn_defs.iter()));
for fd in all_fn_defs {
for stmt in fd.body.stmts() {
let expr = match stmt {
crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => e,
};
carrier_walk_expr(expr, &mut |e| {
let (type_name, create_fields): (&String, &[(String, Spanned<Expr>)]) = match e {
Expr::RecordCreate { type_name, fields } => (type_name, fields),
Expr::RecordUpdate {
type_name, updates, ..
} => (type_name, updates),
_ => return,
};
if !candidates.contains(type_name) {
return;
}
if ctor_fn_of.get(type_name) == Some(&fd.name) {
return;
}
let all_safe = create_fields.iter().all(|(fname, value)| {
match field_intervals.get(&(type_name.clone(), fname.clone())) {
None => true,
Some((iv, true)) => literal_in_interval(value, *iv),
Some((_, false)) => false,
}
});
if !all_safe {
demoted.insert(type_name.clone());
}
});
}
}
let record_fields = collect_record_fields(inputs);
for (key, _value) in &instantiations.maps {
carriers_reachable_from(
key,
candidates,
&record_fields,
&mut HashSet::new(),
&mut demoted,
);
}
for ty_str in all_type_annotations(inputs) {
let ty = crate::types::parse_type_str(&ty_str);
collect_map_key_carriers(&ty, candidates, &record_fields, &mut demoted);
}
let mut map_value_seen: HashSet<String> = HashSet::new();
for (_key, value) in &instantiations.maps {
carriers_reachable_as_map_value(
value,
candidates,
&record_fields,
&mut map_value_seen,
&mut demoted,
);
}
demoted
}
fn carriers_reachable_as_map_value(
value: &crate::ast::Type,
candidates: &HashSet<String>,
record_fields: &HashMap<String, Vec<String>>,
seen: &mut HashSet<String>,
demoted: &mut HashSet<String>,
) {
use crate::ast::Type;
let Some(name) = value.named_name() else {
match value {
Type::Option(a) => {
carriers_reachable_as_map_value(a, candidates, record_fields, seen, demoted);
}
Type::Result(a, b) => {
carriers_reachable_as_map_value(a, candidates, record_fields, seen, demoted);
carriers_reachable_as_map_value(b, candidates, record_fields, seen, demoted);
}
Type::Map(_k, v) => {
carriers_reachable_as_map_value(v, candidates, record_fields, seen, demoted);
}
Type::List(_) | Type::Vector(_) | Type::Tuple(_) => {}
_ => {}
}
return;
};
if !seen.insert(name.to_string()) {
return;
}
if candidates.contains(name) {
demoted.insert(name.to_string());
}
if let Some(fields) = record_fields.get(name) {
for field_ty in fields {
let parsed = crate::types::parse_type_str(field_ty);
carriers_reachable_as_map_value(&parsed, candidates, record_fields, seen, demoted);
}
}
}
type ProductDef<'a> = (&'a [(String, String)], Option<&'a str>);
fn collect_product_defs<'a>(inputs: &ProofLowerInputs<'a>) -> HashMap<String, ProductDef<'a>> {
let mut out: HashMap<String, ProductDef<'a>> = HashMap::new();
for item in inputs.entry_items {
if let TopLevel::TypeDef(TypeDef::Product { name, fields, .. }) = item {
out.entry(name.clone()).or_insert((fields.as_slice(), None));
}
}
for m in inputs.dep_modules {
for td in &m.type_defs {
if let TypeDef::Product { name, fields, .. } = td {
out.entry(name.clone())
.or_insert((fields.as_slice(), Some(m.prefix.as_str())));
}
}
}
out
}
fn literal_in_interval(value: &Spanned<Expr>, iv: crate::ir::interval::Interval) -> bool {
let k: i128 = match &value.node {
Expr::Literal(Literal::Int(n)) => *n as i128,
Expr::Neg(inner) => match &inner.node {
Expr::Literal(Literal::Int(n)) => -(*n as i128),
_ => return false,
},
_ => return false,
};
iv.contains_point(k)
}
pub fn carrier_eligibility_demotions(
inputs: &ProofLowerInputs,
candidates: &HashSet<String>,
intervals: &HashMap<String, (crate::ir::interval::Interval, bool)>,
resolved_map_keys: &[crate::ast::Type],
) -> HashSet<String> {
let mut demoted: HashSet<String> = HashSet::new();
if candidates.is_empty() {
return demoted;
}
let mut ctor_fn_of: HashMap<String, String> = HashMap::new();
for name in candidates {
if let Some(info) = crate::codegen::common::refinement_info_for_in_scope(name, inputs, None)
{
ctor_fn_of.insert(name.clone(), info.constructor_fn.to_string());
} else {
for m in inputs.dep_modules {
if let Some(info) = crate::codegen::common::refinement_info_for_in_scope(
name,
inputs,
Some(m.prefix.as_str()),
) {
ctor_fn_of.insert(name.clone(), info.constructor_fn.to_string());
break;
}
}
}
}
let all_fn_defs = inputs
.entry_items
.iter()
.filter_map(|it| match it {
TopLevel::FnDef(fd) => Some(fd),
_ => None,
})
.chain(inputs.dep_modules.iter().flat_map(|m| m.fn_defs.iter()));
for fd in all_fn_defs {
for stmt in fd.body.stmts() {
let expr = match stmt {
crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => e,
};
carrier_walk_expr(expr, &mut |e| {
let (type_name, fields): (&String, &[(String, Spanned<Expr>)]) = match e {
Expr::RecordCreate { type_name, fields } => (type_name, fields),
Expr::RecordUpdate {
type_name, updates, ..
} => (type_name, updates),
_ => return,
};
if !candidates.contains(type_name) {
return;
}
if ctor_fn_of.get(type_name) == Some(&fd.name) {
return;
}
let safe_literal = matches!(
intervals.get(type_name),
Some((iv, true)) if construct_arg_is_in_interval(fields, *iv)
);
if !safe_literal {
demoted.insert(type_name.clone());
}
});
}
}
let record_fields = collect_record_fields(inputs);
for key in resolved_map_keys {
carriers_reachable_from(
key,
candidates,
&record_fields,
&mut HashSet::new(),
&mut demoted,
);
}
for ty_str in all_type_annotations(inputs) {
let ty = crate::types::parse_type_str(&ty_str);
collect_map_key_carriers(&ty, candidates, &record_fields, &mut demoted);
}
demoted
}
fn construct_arg_is_in_interval(
fields: &[(String, Spanned<Expr>)],
iv: crate::ir::interval::Interval,
) -> bool {
let [(_, value)] = fields else {
return false;
};
let k: i128 = match &value.node {
Expr::Literal(Literal::Int(n)) => *n as i128,
Expr::Neg(inner) => match &inner.node {
Expr::Literal(Literal::Int(n)) => -(*n as i128),
_ => return false,
},
_ => return false,
};
iv.contains_point(k)
}
fn carrier_walk_expr(expr: &Spanned<Expr>, visit: &mut impl FnMut(&Expr)) {
visit(&expr.node);
match &expr.node {
Expr::FnCall(func, args) => {
carrier_walk_expr(func, visit);
for arg in args {
carrier_walk_expr(arg, visit);
}
}
Expr::TailCall(boxed) => {
for arg in &boxed.args {
carrier_walk_expr(arg, visit);
}
}
Expr::Attr(obj, _) => carrier_walk_expr(obj, visit),
Expr::BinOp(_, l, r) => {
carrier_walk_expr(l, visit);
carrier_walk_expr(r, visit);
}
Expr::Neg(inner) | Expr::ErrorProp(inner) => carrier_walk_expr(inner, visit),
Expr::Match { subject, arms, .. } => {
carrier_walk_expr(subject, visit);
for arm in arms {
carrier_walk_expr(&arm.body, visit);
}
}
Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
for item in items {
carrier_walk_expr(item, visit);
}
}
Expr::MapLiteral(entries) => {
for (k, v) in entries {
carrier_walk_expr(k, visit);
carrier_walk_expr(v, visit);
}
}
Expr::Constructor(_, maybe) => {
if let Some(inner) = maybe {
carrier_walk_expr(inner, visit);
}
}
Expr::InterpolatedStr(parts) => {
for part in parts {
if let crate::ast::StrPart::Parsed(e) = part {
carrier_walk_expr(e, visit);
}
}
}
Expr::RecordCreate { fields, .. } => {
for (_, e) in fields {
carrier_walk_expr(e, visit);
}
}
Expr::RecordUpdate { base, updates, .. } => {
carrier_walk_expr(base, visit);
for (_, e) in updates {
carrier_walk_expr(e, visit);
}
}
Expr::Literal(_) | Expr::Ident(_) | Expr::Resolved { .. } => {}
}
}
fn collect_record_fields(inputs: &ProofLowerInputs) -> HashMap<String, Vec<String>> {
let mut out: HashMap<String, Vec<String>> = HashMap::new();
let entry_tds = inputs.entry_items.iter().filter_map(|it| match it {
TopLevel::TypeDef(td) => Some(td),
_ => None,
});
let dep_tds = inputs.dep_modules.iter().flat_map(|m| m.type_defs.iter());
for td in entry_tds.chain(dep_tds) {
if let TypeDef::Product { name, fields, .. } = td {
out.entry(name.clone())
.or_default()
.extend(fields.iter().map(|(_, ty)| ty.clone()));
}
}
out
}
fn all_type_annotations(inputs: &ProofLowerInputs) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
let push_fn = |fd: &FnDef, out: &mut Vec<String>| {
for (_, ty) in &fd.params {
out.push(ty.clone());
}
out.push(fd.return_type.clone());
};
for it in inputs.entry_items {
match it {
TopLevel::FnDef(fd) => push_fn(fd, &mut out),
TopLevel::TypeDef(TypeDef::Product { fields, .. }) => {
out.extend(fields.iter().map(|(_, ty)| ty.clone()));
}
_ => {}
}
}
for m in inputs.dep_modules {
for fd in &m.fn_defs {
push_fn(fd, &mut out);
}
for td in &m.type_defs {
if let TypeDef::Product { fields, .. } = td {
out.extend(fields.iter().map(|(_, ty)| ty.clone()));
}
}
}
out
}
fn collect_map_key_carriers(
ty: &crate::ast::Type,
candidates: &HashSet<String>,
record_fields: &HashMap<String, Vec<String>>,
demoted: &mut HashSet<String>,
) {
use crate::ast::Type;
match ty {
Type::Map(key, value) => {
carriers_reachable_from(key, candidates, record_fields, &mut HashSet::new(), demoted);
collect_map_key_carriers(key, candidates, record_fields, demoted);
collect_map_key_carriers(value, candidates, record_fields, demoted);
}
Type::Result(a, b) => {
collect_map_key_carriers(a, candidates, record_fields, demoted);
collect_map_key_carriers(b, candidates, record_fields, demoted);
}
Type::Option(a) | Type::List(a) | Type::Vector(a) => {
collect_map_key_carriers(a, candidates, record_fields, demoted);
}
Type::Tuple(items) => {
for t in items {
collect_map_key_carriers(t, candidates, record_fields, demoted);
}
}
Type::Fn(params, ret, _) => {
for p in params {
collect_map_key_carriers(p, candidates, record_fields, demoted);
}
collect_map_key_carriers(ret, candidates, record_fields, demoted);
}
_ => {}
}
}
fn carriers_reachable_from(
key: &crate::ast::Type,
candidates: &HashSet<String>,
record_fields: &HashMap<String, Vec<String>>,
seen: &mut HashSet<String>,
demoted: &mut HashSet<String>,
) {
use crate::ast::Type;
let Some(name) = key.named_name() else {
match key {
Type::Option(a) | Type::List(a) | Type::Vector(a) => {
carriers_reachable_from(a, candidates, record_fields, seen, demoted);
}
Type::Tuple(items) => {
for t in items {
carriers_reachable_from(t, candidates, record_fields, seen, demoted);
}
}
Type::Map(k, v) => {
carriers_reachable_from(k, candidates, record_fields, seen, demoted);
carriers_reachable_from(v, candidates, record_fields, seen, demoted);
}
Type::Result(a, b) => {
carriers_reachable_from(a, candidates, record_fields, seen, demoted);
carriers_reachable_from(b, candidates, record_fields, seen, demoted);
}
_ => {}
}
return;
};
if !seen.insert(name.to_string()) {
return;
}
if candidates.contains(name) {
demoted.insert(name.to_string());
}
if let Some(fields) = record_fields.get(name) {
for field_ty in fields {
let parsed = crate::types::parse_type_str(field_ty);
carriers_reachable_from(&parsed, candidates, record_fields, seen, demoted);
}
}
}
pub fn populate_fn_contracts(inputs: &ProofLowerInputs, ir: &mut ProofIR) {
for scope in inputs.scopes() {
let (plans, issues) =
crate::codegen::recursion::analyze_plans_in_scope(inputs, scope.as_deref(), false);
ir.unclassified_fns
.extend(issues.into_iter().map(|issue| crate::ir::UnclassifiedFn {
line: issue.line,
message: issue.message,
}));
populate_fn_contracts_for_scope(inputs, ir, scope.as_deref(), &plans);
}
}
fn populate_fn_contracts_for_scope(
inputs: &ProofLowerInputs,
ir: &mut ProofIR,
scope: Option<&str>,
plans: &HashMap<String, RecursionPlan>,
) {
let scoped_fns: Vec<&FnDef> = inputs.pure_fns_in_scope(scope);
let qualify = |bare: &str| -> crate::ir::FnKey {
match scope {
Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), bare),
None => crate::ir::FnKey::entry(bare),
}
};
let symbols = inputs.symbol_table;
for (fn_name, plan) in plans {
let Some(fd) = scoped_fns.iter().find(|fd| fd.name == *fn_name) else {
continue;
};
let fn_key = qualify(fn_name);
let Some(canonical_key) = symbols.fn_id_of(&fn_key) else {
continue;
};
if let RecursionPlan::IntCountdown { param_index } = plan {
if let Some((param_name, _)) = fd.params.get(*param_index) {
ir.fn_contracts.insert(
canonical_key,
FnContract {
source_name: fn_name.clone(),
recursion: Some(RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::NatAbsPlusOne {
param: param_name.clone(),
},
}),
},
);
}
continue;
}
if let RecursionPlan::IntFloorDivCountdown {
param_index,
divisor,
helper_fn,
} = plan
{
if let Some((param_name, _)) = fd.params.get(*param_index) {
ir.fn_contracts.insert(
canonical_key,
FnContract {
source_name: fn_name.clone(),
recursion: Some(RecursionContract::WellFoundedToNat {
param: param_name.clone(),
floor_div: Some(crate::ir::FloorDivShrink {
divisor: *divisor,
helper_fn: helper_fn.clone(),
}),
}),
},
);
}
continue;
}
if let RecursionPlan::IntAscending { param_index, bound } = plan {
if let Some((param_name, _)) = fd.params.get(*param_index) {
ir.fn_contracts.insert(
canonical_key,
FnContract {
source_name: fn_name.clone(),
recursion: Some(RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::BoundMinusParamNatAbsPlusOne {
param: param_name.clone(),
bound: inputs.resolve_expr(bound, scope),
},
}),
},
);
}
continue;
}
if let RecursionPlan::ListStructural { param_index } = plan {
if let Some((param_name, _)) = fd.params.get(*param_index) {
ir.fn_contracts.insert(
canonical_key,
FnContract {
source_name: fn_name.clone(),
recursion: Some(RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::SeqLenPlusOne {
param: param_name.clone(),
},
}),
},
);
}
continue;
}
if matches!(plan, RecursionPlan::SizeOfStructural) {
ir.fn_contracts.insert(
canonical_key,
FnContract {
source_name: fn_name.clone(),
recursion: Some(RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::SizeOfPlusOne,
}),
},
);
continue;
}
if matches!(plan, RecursionPlan::StringPosAdvance) {
if let (Some((string_param, _)), Some((pos_param, _))) =
(fd.params.first(), fd.params.get(1))
{
ir.fn_contracts.insert(
canonical_key,
FnContract {
source_name: fn_name.clone(),
recursion: Some(RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::StringLenMinusPos {
string_param: string_param.clone(),
pos_param: pos_param.clone(),
},
}),
},
);
}
continue;
}
match plan {
RecursionPlan::MutualIntCountdown => {
let params = fd
.params
.first()
.map(|(n, _)| vec![n.clone()])
.unwrap_or_default();
ir.fn_contracts.insert(
canonical_key,
FnContract {
source_name: fn_name.clone(),
recursion: Some(RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::Lex { params, rank: 0 },
}),
},
);
continue;
}
RecursionPlan::MutualStringPosAdvance { rank } => {
let params = fd.params.iter().take(2).map(|(n, _)| n.clone()).collect();
ir.fn_contracts.insert(
canonical_key,
FnContract {
source_name: fn_name.clone(),
recursion: Some(RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::Lex {
params,
rank: *rank,
},
}),
},
);
continue;
}
RecursionPlan::MutualSizeOfRanked { rank } => {
ir.fn_contracts.insert(
canonical_key,
FnContract {
source_name: fn_name.clone(),
recursion: Some(RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::Lex {
params: Vec::new(),
rank: *rank,
},
}),
},
);
continue;
}
RecursionPlan::LinearRecurrence2 => {
ir.fn_contracts.insert(
canonical_key,
FnContract {
source_name: fn_name.clone(),
recursion: Some(RecursionContract::LinearRecurrence2),
},
);
continue;
}
_ => {}
}
let RecursionPlan::IntCountdownGuarded {
param_index,
base_arm_literal,
base_arm_body,
wildcard_arm_body,
precondition,
} = plan
else {
continue;
};
let Some((countdown_param_name, _)) = fd.params.get(*param_index) else {
continue;
};
let precondition_predicates: Vec<Predicate> = precondition
.iter()
.map(|clause| Predicate {
free_vars: vec![(
countdown_param_name.clone(),
QuantifierType::Plain("Int".to_string()),
)],
expr: inputs.resolve_expr(clause, scope),
})
.collect();
ir.fn_contracts.insert(
canonical_key,
FnContract {
source_name: fn_name.clone(),
recursion: Some(RecursionContract::Native {
precondition: precondition_predicates,
measure: Measure::NatAbsInt {
param: countdown_param_name.clone(),
},
preservation: PreservationProof::IntCountdownLiteralZero,
decrease: DecreaseProof::NatAbsCountdown,
body: NativeIntCountdownBody {
base_arm_literal: *base_arm_literal,
base_arm_body: inputs.resolve_expr(base_arm_body, scope),
wildcard_arm_body: inputs.resolve_expr(wildcard_arm_body, scope),
},
}),
},
);
}
}
pub fn populate_law_theorems(inputs: &ProofLowerInputs, ir: &mut ProofIR) {
use crate::ast::{TopLevel, VerifyKind};
use crate::ir::{LawTheorem, Predicate, Quantifier, QuantifierType};
let symbols = inputs.symbol_table;
let entry_verifies = inputs.entry_items.iter().filter_map(|item| match item {
TopLevel::Verify(vb) => Some((None, vb)),
_ => None,
});
let dep_verifies = inputs.dep_modules.iter().flat_map(|m| {
m.verify_laws
.iter()
.map(move |vb| (Some(m.prefix.as_str()), vb))
});
for (owning_prefix, vb) in entry_verifies.chain(dep_verifies) {
let VerifyKind::Law(law) = &vb.kind else {
continue;
};
let quantifiers: Vec<Quantifier> = law
.givens
.iter()
.map(|g| Quantifier {
name: g.name.clone(),
binder_type: QuantifierType::Plain(g.type_name.clone()),
})
.collect();
let target_key = match owning_prefix {
Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), &vb.fn_name),
None => crate::ir::FnKey::entry(&vb.fn_name),
};
let Some(fn_id) = symbols.fn_id_of(&target_key) else {
continue;
};
let law_scope: Option<String> = symbols
.fn_entry(fn_id)
.key
.scope_str()
.map(|s| s.to_string());
let law_scope_ref = law_scope.as_deref();
let premises: Vec<Predicate> = match &law.when {
Some(when_expr) => vec![Predicate {
free_vars: quantifiers
.iter()
.map(|q| (q.name.clone(), q.binder_type.clone()))
.collect(),
expr: inputs.resolve_expr(when_expr, law_scope_ref),
}],
None => Vec::new(),
};
let strategy = classify_law_strategy(
law,
&vb.fn_name,
inputs,
&ir.refined_types,
&ir.fn_contracts,
law_scope_ref,
);
ir.law_theorems.push(LawTheorem {
fn_id,
law_name: law.name.clone(),
quantifiers,
premises,
claim_lhs: inputs.resolve_expr(&law.lhs, law_scope_ref),
claim_rhs: inputs.resolve_expr(&law.rhs, law_scope_ref),
strategy,
});
}
let window_pow_fns: HashSet<String> = ir
.law_theorems
.iter()
.filter_map(|t| match &t.strategy {
crate::ir::ProofStrategy::FloorDivWindow { figure } => Some(match figure {
crate::ir::FloorWindowFigure::PowPositive { pow_fn } => pow_fn.clone(),
crate::ir::FloorWindowFigure::PowSumSplit { pow_fn } => pow_fn.clone(),
crate::ir::FloorWindowFigure::SigWindow { pow_fn, .. } => pow_fn.clone(),
crate::ir::FloorWindowFigure::ProductWindow { pow_fn, .. } => pow_fn.clone(),
}),
_ => None,
})
.collect();
for pow_fn in window_pow_fns {
let Some(fn_id) = symbols.fn_id_of(&crate::ir::FnKey::entry(&pow_fn)) else {
continue;
};
let Some(contract) = ir.fn_contracts.get_mut(&fn_id) else {
continue;
};
if let Some(crate::ir::RecursionContract::Fuel {
fuel_metric: crate::ir::FuelMetric::NatAbsPlusOne { param },
}) = &contract.recursion
{
contract.recursion = Some(crate::ir::RecursionContract::WellFoundedToNat {
param: param.clone(),
floor_div: None,
});
}
}
}
fn classify_law_strategy(
law: &crate::ast::VerifyLaw,
fn_name: &str,
inputs: &ProofLowerInputs,
refined_types: &std::collections::HashMap<crate::ir::TypeId, crate::ir::RefinedTypeDecl>,
fn_contracts: &std::collections::HashMap<crate::ir::FnId, crate::ir::FnContract>,
scope: Option<&str>,
) -> crate::ir::ProofStrategy {
use crate::ir::ProofStrategy;
if law.when.is_none()
&& let Some(s) = detect_result_pipeline_chain_equivalence(law, fn_name, inputs)
{
return s;
}
if law.when.is_none()
&& let Some(s) = detect_wrapper_over_recursion(law, fn_name, inputs)
{
return s;
}
if law.when.is_none()
&& let Some(s) = detect_tailrec_fixed_base_fold(law, inputs)
{
return s;
}
if law.when.is_none()
&& let Some(param) = detect_induction_target(law, inputs)
{
return ProofStrategy::Induction { param };
}
if law.lhs == law.rhs {
return ProofStrategy::Reflexive;
}
if let Some(op) = wrapper_binop(fn_name, inputs) {
if detect_wrapper_commutative(law, fn_name, op) {
return ProofStrategy::Commutative { op };
}
if detect_wrapper_associative(law, fn_name, op) {
return ProofStrategy::Associative { op };
}
if detect_wrapper_identity(law, fn_name, op) {
return ProofStrategy::IdentityElement { op };
}
if matches!(op, crate::ast::BinOp::Sub) && detect_wrapper_sub_right_identity(law, fn_name) {
return ProofStrategy::IdentityElement { op };
}
if matches!(op, crate::ast::BinOp::Sub)
&& let Some(neg_on_rhs) = detect_wrapper_sub_anti_commutative(law, fn_name)
{
return ProofStrategy::AntiCommutative { op, neg_on_rhs };
}
}
if let Some(inner_fn) = detect_wrapper_unary_equivalence(law, fn_name, inputs) {
return ProofStrategy::UnaryEqualsBinary { inner_fn };
}
if let Some((axiom, args)) = detect_map_set_axiom(law) {
let resolved_args: Vec<_> = args.iter().map(|a| inputs.resolve_expr(a, scope)).collect();
return ProofStrategy::LibraryAxiom {
axiom,
args: resolved_args,
};
}
if let Some(inc) = detect_map_key_tracked_increment(law, fn_name, inputs) {
return ProofStrategy::MapKeyTrackedIncrement {
outer_fn: inc.outer_fn,
map_arg: inputs.resolve_expr(&inc.map_arg, scope),
key_arg: inputs.resolve_expr(&inc.key_arg, scope),
};
}
if let Some(post) = detect_map_update_postcondition(law, fn_name, inputs) {
return ProofStrategy::MapUpdatePostcondition {
outer_fn: post.outer_fn,
kind: post.kind,
map_arg: inputs.resolve_expr(&post.map_arg, scope),
key_arg: inputs.resolve_expr(&post.key_arg, scope),
extra_unfolds: post.extra_unfolds,
};
}
if let Some(extra_unfolds) = detect_spec_equivalence(law, fn_name, inputs) {
return ProofStrategy::SpecEquivalence { extra_unfolds };
}
if let Some(extra_unfolds) = detect_simp_normalized_spec_equivalence(law, fn_name, inputs) {
return ProofStrategy::SpecEquivalenceSimpNormalized { extra_unfolds };
}
if let Some((unfolded_impl, unfolded_spec)) =
detect_linear_int_spec_equivalence(law, fn_name, inputs)
{
return ProofStrategy::LinearIntSpecEquivalence {
unfolded_impl: inputs.resolve_expr(&unfolded_impl, scope),
unfolded_spec: inputs.resolve_expr(&unfolded_spec, scope),
};
}
if let Some(spec_fn) = detect_effectful_spec_equivalence(law, fn_name, inputs) {
return ProofStrategy::EffectfulSpecEquivalence {
impl_fn: fn_name.to_string(),
spec_fn,
};
}
if let Some((spec_fn, helper_fn)) =
detect_linear_recurrence2_spec_equivalence(law, fn_name, inputs)
{
return ProofStrategy::LinearRecurrence2SpecEquivalence {
impl_fn: fn_name.to_string(),
spec_fn,
helper_fn,
};
}
if let Some(plan) = detect_simp_omega_unfold(law, fn_name, inputs, refined_types) {
return ProofStrategy::LinearArithmetic {
unfold_fns: plan.unfold_fns,
wrapper_return: plan.wrapper_return,
smart_guard: plan.smart_guard,
lifted: plan.lifted,
};
}
if law.when.is_none()
&& let Some(unfold_fns) = detect_enum_constant_fold(law, fn_name, inputs)
{
return ProofStrategy::EnumConstantFold { unfold_fns };
}
if law.when.is_none()
&& let Some(givens) = detect_finite_domain_cases(law, inputs)
{
return ProofStrategy::FiniteDomainCases { givens };
}
if law.when.is_none()
&& let Some(unfold_fns) = detect_ring_identity(law, fn_name, inputs)
{
return ProofStrategy::RingIdentity { unfold_fns };
}
if law.when.is_none()
&& let Some(s) = detect_int_decimal_roundtrip(law, fn_name, inputs, fn_contracts)
{
return s;
}
if law.when.is_none()
&& let Some(s) = detect_string_escape_roundtrip(law, inputs, fn_contracts)
{
return s;
}
if let Some(figure) = detect_floor_window(law, fn_name, inputs, fn_contracts) {
return ProofStrategy::FloorDivWindow { figure };
}
if law.when.is_none()
&& let Some(s) = detect_simp_over_prelude_lemmas(law, fn_name, inputs, fn_contracts)
{
return s;
}
ProofStrategy::BackendDispatch
}
mod finite_domain;
mod floor_window;
mod induction;
mod int_decimal_roundtrip;
mod map_laws;
mod refinement;
mod ring;
mod simp;
mod spec_equivalence;
mod string_escape_roundtrip;
mod wrapper_laws;
pub(crate) use induction::LawProofCone;
use finite_domain::*;
use floor_window::*;
use induction::*;
use int_decimal_roundtrip::*;
use map_laws::*;
use refinement::*;
use ring::*;
use simp::*;
use spec_equivalence::*;
use string_escape_roundtrip::*;
use wrapper_laws::*;