1use std::collections::{HashMap, HashSet};
74
75use crate::ast::{Expr, FnDef, Literal, Spanned, TopLevel, TypeDef};
76use crate::codegen::common::expr_to_dotted_name;
77use crate::codegen::recursion::RecursionPlan;
78use crate::codegen::{CodegenContext, ModuleInfo};
79use crate::ir::proof_ir::{
80 DecreaseProof, FnContract, Measure, NativeIntCountdownBody, Predicate, PreservationProof,
81 ProofIR, QuantifierType, RecursionContract, RefinedTypeDecl,
82};
83
84pub struct ProofLowerInputs<'a> {
98 pub entry_items: &'a [TopLevel],
100 pub dep_modules: &'a [ModuleInfo],
102 pub module_prefixes: &'a HashSet<String>,
104 pub recursive_fns: &'a HashSet<crate::ir::FnId>,
111 pub symbol_table: &'a crate::ir::SymbolTable,
119}
120
121impl<'a> ProofLowerInputs<'a> {
122 pub fn from_ctx(ctx: &'a CodegenContext) -> Self {
127 Self {
128 entry_items: &ctx.items,
129 dep_modules: &ctx.modules,
130 module_prefixes: &ctx.module_prefixes,
131 recursive_fns: &ctx.recursive_fns,
132 symbol_table: &ctx.symbol_table,
133 }
134 }
135
136 pub fn pure_fns(&self) -> Vec<&'a FnDef> {
141 self.dep_modules
147 .iter()
148 .flat_map(|m| m.fn_defs.iter())
149 .chain(self.entry_items.iter().filter_map(|item| match item {
150 TopLevel::FnDef(fd) => Some(fd),
151 _ => None,
152 }))
153 .filter(|fd| crate::codegen::common::is_pure_fn(fd))
154 .collect()
155 }
156
157 pub fn recursive_pure_fn_names(&self) -> HashSet<String> {
162 let symbols = self.symbol_table;
163 let pure_ids: HashSet<crate::ir::FnId> = self
164 .pure_fns()
165 .into_iter()
166 .filter_map(|fd| {
167 let scope = self
168 .dep_modules
169 .iter()
170 .find(|m| m.fn_defs.iter().any(|d| std::ptr::eq(d, fd)))
171 .map(|m| m.prefix.as_str());
172 let key = match scope {
178 Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), &fd.name),
179 None => crate::ir::FnKey::entry(&fd.name),
180 };
181 symbols.fn_id_of(&key)
182 })
183 .collect();
184 self.recursive_fns
185 .intersection(&pure_ids)
186 .map(|id| symbols.fn_entry(*id).key.name.clone())
187 .collect()
188 }
189
190 pub fn pure_fns_in_scope(&self, scope: Option<&str>) -> Vec<&'a FnDef> {
198 match scope {
199 None => self
200 .entry_items
201 .iter()
202 .filter_map(|item| match item {
203 TopLevel::FnDef(fd) => Some(fd),
204 _ => None,
205 })
206 .filter(|fd| crate::codegen::common::is_pure_fn(fd))
207 .collect(),
208 Some(prefix) => self
209 .dep_modules
210 .iter()
211 .filter(|m| m.prefix == prefix)
212 .flat_map(|m| m.fn_defs.iter())
213 .filter(|fd| crate::codegen::common::is_pure_fn(fd))
214 .collect(),
215 }
216 }
217
218 pub fn recursive_pure_fn_names_in_scope(&self, scope: Option<&str>) -> HashSet<String> {
224 let symbols = self.symbol_table;
225 let pure_ids: HashSet<crate::ir::FnId> = self
226 .pure_fns_in_scope(scope)
227 .into_iter()
228 .filter_map(|fd| {
229 let key = match scope {
236 Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), &fd.name),
237 None => crate::ir::FnKey::entry(&fd.name),
238 };
239 symbols.fn_id_of(&key)
240 })
241 .collect();
242 self.recursive_fns
243 .intersection(&pure_ids)
244 .map(|id| symbols.fn_entry(*id).key.name.clone())
245 .collect()
246 }
247
248 pub fn scopes(&self) -> Vec<Option<String>> {
251 let mut out = vec![None];
252 for m in self.dep_modules {
253 out.push(Some(m.prefix.clone()));
254 }
255 out
256 }
257
258 pub fn fn_owning_scope(&self, fd: &FnDef) -> Option<&'a str> {
263 for m in self.dep_modules {
264 for f in &m.fn_defs {
265 if std::ptr::eq(f, fd) {
266 return Some(m.prefix.as_str());
267 }
268 }
269 }
270 None
271 }
272
273 pub fn resolve_expr(
282 &self,
283 expr: &crate::ast::Spanned<crate::ast::Expr>,
284 scope: Option<&str>,
285 ) -> crate::ast::Spanned<crate::ir::hir::ResolvedExpr> {
286 use crate::ir::hir::{ResolveCtx, ResolvedStmt};
287 let mut rctx = ResolveCtx::new(self.symbol_table);
288 rctx.current_module = scope.map(String::from);
289 let stmt = crate::ast::Stmt::Expr(expr.clone());
290 match crate::ir::hir::resolve::resolve_stmt_external(&rctx, &stmt) {
291 ResolvedStmt::Expr(s) => s,
292 ResolvedStmt::Binding { value, .. } => value,
293 }
294 }
295
296 pub fn recursive_type_names(&self) -> HashSet<String> {
298 self.entry_items
299 .iter()
300 .filter_map(|item| match item {
301 TopLevel::TypeDef(td) => Some(td),
302 _ => None,
303 })
304 .chain(self.dep_modules.iter().flat_map(|m| m.type_defs.iter()))
305 .filter(|td| crate::codegen::common::is_recursive_type_def(td))
306 .map(|td| crate::codegen::common::type_def_name(td).to_string())
307 .collect()
308 }
309
310 pub fn find_fn_def_by_call_name(&self, call_name: &str) -> Option<&'a FnDef> {
314 let find_exact = |name: &str| -> Option<&'a FnDef> {
315 self.dep_modules
316 .iter()
317 .flat_map(|m| m.fn_defs.iter())
318 .chain(self.entry_items.iter().filter_map(|item| match item {
319 TopLevel::FnDef(fd) => Some(fd),
320 _ => None,
321 }))
322 .find(|fd| fd.name == name)
323 };
324 find_exact(call_name).or_else(|| {
325 let short = call_name.rsplit('.').next()?;
326 find_exact(short)
327 })
328 }
329
330 pub fn find_type_def(&self, type_name: &str) -> Option<&'a TypeDef> {
333 self.entry_items
334 .iter()
335 .filter_map(|item| match item {
336 TopLevel::TypeDef(td) => Some(td),
337 _ => None,
338 })
339 .chain(self.dep_modules.iter().flat_map(|m| m.type_defs.iter()))
340 .find(|td| crate::codegen::common::type_def_name(td) == type_name)
341 }
342}
343
344pub fn lower(inputs: &ProofLowerInputs) -> ProofIR {
349 let mut ir = ProofIR::default();
350 populate_refined_types(inputs, &mut ir);
351 populate_fn_contracts(inputs, &mut ir);
352 populate_law_theorems(inputs, &mut ir);
353 ir
354}
355
356pub fn populate_refined_types(inputs: &ProofLowerInputs, ir: &mut ProofIR) {
362 let symbols = inputs.symbol_table;
378
379 let entry_typedefs = inputs.entry_items.iter().filter_map(|item| match item {
380 TopLevel::TypeDef(td) => Some((None::<&str>, td)),
381 _ => None,
382 });
383 let module_typedefs = inputs.dep_modules.iter().flat_map(|m| {
384 m.type_defs
385 .iter()
386 .map(move |td| (Some(m.prefix.as_str()), td))
387 });
388
389 for (module_prefix, td) in entry_typedefs.chain(module_typedefs) {
390 let TypeDef::Product { name, fields, .. } = td else {
391 continue;
392 };
393 if fields.len() != 1 {
394 continue;
395 }
396 let type_key = match module_prefix {
397 Some(prefix) => crate::ir::TypeKey::in_module(prefix.to_string(), name),
398 None => crate::ir::TypeKey::entry(name),
399 };
400 let Some(canonical_key) = symbols.type_id_of(&type_key) else {
401 continue;
406 };
407 if ir.refined_types.contains_key(&canonical_key) {
408 continue;
413 }
414 let Some(info) =
422 crate::codegen::common::refinement_info_for_in_scope(name, inputs, module_prefix)
423 else {
424 continue;
425 };
426 let invariant = Predicate {
427 free_vars: vec![(
428 info.param_name.to_string(),
429 crate::ir::proof_ir::QuantifierType::Plain(info.carrier_type.to_string()),
430 )],
431 expr: inputs.resolve_expr(info.predicate, module_prefix),
432 };
433 let witness = pick_witness(
434 name,
435 canonical_key,
436 inputs,
437 info.predicate,
438 info.param_name,
439 module_prefix,
440 );
441 let Some(witness) = witness else {
450 continue;
451 };
452 ir.refined_types.insert(
453 canonical_key,
454 RefinedTypeDecl {
455 name: name.clone(),
456 carrier_type: info.carrier_type.to_string(),
457 carrier_field: info.carrier_field.to_string(),
458 predicate_param: info.param_name.to_string(),
459 invariant,
460 witness: Some(witness),
461 },
462 );
463 }
464}
465
466pub fn populate_fn_contracts(inputs: &ProofLowerInputs, ir: &mut ProofIR) {
478 for scope in inputs.scopes() {
485 let (plans, issues) =
486 crate::codegen::recursion::analyze_plans_in_scope(inputs, scope.as_deref(), false);
487 ir.unclassified_fns
488 .extend(issues.into_iter().map(|issue| crate::ir::UnclassifiedFn {
489 line: issue.line,
490 message: issue.message,
491 }));
492 populate_fn_contracts_for_scope(inputs, ir, scope.as_deref(), &plans);
493 }
494}
495
496fn populate_fn_contracts_for_scope(
497 inputs: &ProofLowerInputs,
498 ir: &mut ProofIR,
499 scope: Option<&str>,
500 plans: &HashMap<String, RecursionPlan>,
501) {
502 let scoped_fns: Vec<&FnDef> = inputs.pure_fns_in_scope(scope);
503 let qualify = |bare: &str| -> crate::ir::FnKey {
504 match scope {
505 Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), bare),
506 None => crate::ir::FnKey::entry(bare),
507 }
508 };
509 let symbols = inputs.symbol_table;
513
514 for (fn_name, plan) in plans {
515 let Some(fd) = scoped_fns.iter().find(|fd| fd.name == *fn_name) else {
516 continue;
517 };
518 let fn_key = qualify(fn_name);
519 let Some(canonical_key) = symbols.fn_id_of(&fn_key) else {
520 continue;
521 };
522
523 if let RecursionPlan::IntCountdown { param_index } = plan {
529 if let Some((param_name, _)) = fd.params.get(*param_index) {
530 ir.fn_contracts.insert(
531 canonical_key,
532 FnContract {
533 source_name: fn_name.clone(),
534 recursion: Some(RecursionContract::Fuel {
535 fuel_metric: crate::ir::FuelMetric::NatAbsPlusOne {
536 param: param_name.clone(),
537 },
538 }),
539 },
540 );
541 }
542 continue;
543 }
544
545 if let RecursionPlan::IntAscending { param_index, bound } = plan {
550 if let Some((param_name, _)) = fd.params.get(*param_index) {
551 ir.fn_contracts.insert(
552 canonical_key,
553 FnContract {
554 source_name: fn_name.clone(),
555 recursion: Some(RecursionContract::Fuel {
556 fuel_metric: crate::ir::FuelMetric::BoundMinusParamNatAbsPlusOne {
557 param: param_name.clone(),
558 bound: inputs.resolve_expr(bound, scope),
559 },
560 }),
561 },
562 );
563 }
564 continue;
565 }
566
567 if let RecursionPlan::ListStructural { param_index } = plan {
574 if let Some((param_name, _)) = fd.params.get(*param_index) {
575 ir.fn_contracts.insert(
576 canonical_key,
577 FnContract {
578 source_name: fn_name.clone(),
579 recursion: Some(RecursionContract::Fuel {
580 fuel_metric: crate::ir::FuelMetric::SeqLenPlusOne {
581 param: param_name.clone(),
582 },
583 }),
584 },
585 );
586 }
587 continue;
588 }
589
590 if matches!(plan, RecursionPlan::SizeOfStructural) {
595 ir.fn_contracts.insert(
596 canonical_key,
597 FnContract {
598 source_name: fn_name.clone(),
599 recursion: Some(RecursionContract::Fuel {
600 fuel_metric: crate::ir::FuelMetric::SizeOfPlusOne,
601 }),
602 },
603 );
604 continue;
605 }
606
607 if matches!(plan, RecursionPlan::StringPosAdvance) {
611 if let (Some((string_param, _)), Some((pos_param, _))) =
612 (fd.params.first(), fd.params.get(1))
613 {
614 ir.fn_contracts.insert(
615 canonical_key,
616 FnContract {
617 source_name: fn_name.clone(),
618 recursion: Some(RecursionContract::Fuel {
619 fuel_metric: crate::ir::FuelMetric::StringLenMinusPos {
620 string_param: string_param.clone(),
621 pos_param: pos_param.clone(),
622 },
623 }),
624 },
625 );
626 }
627 continue;
628 }
629
630 match plan {
645 RecursionPlan::MutualIntCountdown => {
646 let params = fd
647 .params
648 .first()
649 .map(|(n, _)| vec![n.clone()])
650 .unwrap_or_default();
651 ir.fn_contracts.insert(
652 canonical_key,
653 FnContract {
654 source_name: fn_name.clone(),
655 recursion: Some(RecursionContract::Fuel {
656 fuel_metric: crate::ir::FuelMetric::Lex { params, rank: 0 },
657 }),
658 },
659 );
660 continue;
661 }
662 RecursionPlan::MutualStringPosAdvance { rank } => {
663 let params = fd.params.iter().take(2).map(|(n, _)| n.clone()).collect();
664 ir.fn_contracts.insert(
665 canonical_key,
666 FnContract {
667 source_name: fn_name.clone(),
668 recursion: Some(RecursionContract::Fuel {
669 fuel_metric: crate::ir::FuelMetric::Lex {
670 params,
671 rank: *rank,
672 },
673 }),
674 },
675 );
676 continue;
677 }
678 RecursionPlan::MutualSizeOfRanked { rank } => {
679 ir.fn_contracts.insert(
680 canonical_key,
681 FnContract {
682 source_name: fn_name.clone(),
683 recursion: Some(RecursionContract::Fuel {
684 fuel_metric: crate::ir::FuelMetric::Lex {
685 params: Vec::new(),
686 rank: *rank,
687 },
688 }),
689 },
690 );
691 continue;
692 }
693 RecursionPlan::LinearRecurrence2 => {
694 ir.fn_contracts.insert(
695 canonical_key,
696 FnContract {
697 source_name: fn_name.clone(),
698 recursion: Some(RecursionContract::LinearRecurrence2),
699 },
700 );
701 continue;
702 }
703 _ => {}
704 }
705
706 let RecursionPlan::IntCountdownGuarded {
707 param_index,
708 base_arm_literal,
709 base_arm_body,
710 wildcard_arm_body,
711 precondition,
712 } = plan
713 else {
714 continue;
715 };
716 let Some((countdown_param_name, _)) = fd.params.get(*param_index) else {
717 continue;
718 };
719
720 let precondition_predicates: Vec<Predicate> = precondition
721 .iter()
722 .map(|clause| Predicate {
723 free_vars: vec![(
724 countdown_param_name.clone(),
725 QuantifierType::Plain("Int".to_string()),
726 )],
727 expr: inputs.resolve_expr(clause, scope),
728 })
729 .collect();
730
731 ir.fn_contracts.insert(
732 canonical_key,
733 FnContract {
734 source_name: fn_name.clone(),
735 recursion: Some(RecursionContract::Native {
736 precondition: precondition_predicates,
737 measure: Measure::NatAbsInt {
738 param: countdown_param_name.clone(),
739 },
740 preservation: PreservationProof::IntCountdownLiteralZero,
741 decrease: DecreaseProof::NatAbsCountdown,
742 body: NativeIntCountdownBody {
743 base_arm_literal: *base_arm_literal,
744 base_arm_body: inputs.resolve_expr(base_arm_body, scope),
745 wildcard_arm_body: inputs.resolve_expr(wildcard_arm_body, scope),
746 },
747 }),
748 },
749 );
750 }
751}
752
753pub fn populate_law_theorems(inputs: &ProofLowerInputs, ir: &mut ProofIR) {
769 use crate::ast::{TopLevel, VerifyKind};
770 use crate::ir::{LawTheorem, Predicate, Quantifier, QuantifierType};
771
772 let symbols = inputs.symbol_table;
773
774 let entry_verifies = inputs.entry_items.iter().filter_map(|item| match item {
775 TopLevel::Verify(vb) => Some(vb),
776 _ => None,
777 });
778 for vb in entry_verifies {
782 let VerifyKind::Law(law) = &vb.kind else {
783 continue;
784 };
785
786 let quantifiers: Vec<Quantifier> = law
787 .givens
788 .iter()
789 .map(|g| Quantifier {
790 name: g.name.clone(),
791 binder_type: QuantifierType::Plain(g.type_name.clone()),
792 })
793 .collect();
794
795 let law_scope: Option<String> = symbols
803 .fn_id_of(&crate::ir::FnKey::entry(&vb.fn_name))
804 .or_else(|| {
805 inputs.dep_modules.iter().find_map(|m| {
806 symbols.fn_id_of(&crate::ir::FnKey::in_module(m.prefix.clone(), &vb.fn_name))
807 })
808 })
809 .and_then(|id| symbols.fn_entry(id).key.scope_str().map(|s| s.to_string()));
810 let law_scope_ref = law_scope.as_deref();
811
812 let premises: Vec<Predicate> = match &law.when {
813 Some(when_expr) => vec![Predicate {
814 free_vars: quantifiers
815 .iter()
816 .map(|q| (q.name.clone(), q.binder_type.clone()))
817 .collect(),
818 expr: inputs.resolve_expr(when_expr, law_scope_ref),
819 }],
820 None => Vec::new(),
821 };
822
823 let strategy =
824 classify_law_strategy(law, &vb.fn_name, inputs, &ir.refined_types, law_scope_ref);
825
826 let Some(fn_id) = symbols.fn_id_of(&crate::ir::FnKey::entry(&vb.fn_name)) else {
834 continue;
835 };
836 ir.law_theorems.push(LawTheorem {
837 fn_id,
838 law_name: law.name.clone(),
839 quantifiers,
840 premises,
841 claim_lhs: inputs.resolve_expr(&law.lhs, law_scope_ref),
842 claim_rhs: inputs.resolve_expr(&law.rhs, law_scope_ref),
843 strategy,
844 });
845 }
846}
847
848fn classify_law_strategy(
867 law: &crate::ast::VerifyLaw,
868 fn_name: &str,
869 inputs: &ProofLowerInputs,
870 refined_types: &std::collections::HashMap<crate::ir::TypeId, crate::ir::RefinedTypeDecl>,
871 scope: Option<&str>,
872) -> crate::ir::ProofStrategy {
873 use crate::ir::ProofStrategy;
874
875 if law.when.is_none()
882 && let Some(param) = detect_induction_target(law, inputs)
883 {
884 return ProofStrategy::Induction { param };
885 }
886 if law.lhs == law.rhs {
887 return ProofStrategy::Reflexive;
888 }
889 if let Some(op) = wrapper_binop(fn_name, inputs) {
893 if detect_wrapper_commutative(law, fn_name, op) {
894 return ProofStrategy::Commutative { op };
895 }
896 if detect_wrapper_associative(law, fn_name, op) {
897 return ProofStrategy::Associative { op };
898 }
899 if detect_wrapper_identity(law, fn_name, op) {
900 return ProofStrategy::IdentityElement { op };
901 }
902 if matches!(op, crate::ast::BinOp::Sub) && detect_wrapper_sub_right_identity(law, fn_name) {
907 return ProofStrategy::IdentityElement { op };
908 }
909 if matches!(op, crate::ast::BinOp::Sub)
913 && let Some(neg_on_rhs) = detect_wrapper_sub_anti_commutative(law, fn_name)
914 {
915 return ProofStrategy::AntiCommutative { op, neg_on_rhs };
916 }
917 }
918 if let Some(inner_fn) = detect_wrapper_unary_equivalence(law, fn_name, inputs) {
921 return ProofStrategy::UnaryEqualsBinary { inner_fn };
922 }
923 if let Some((axiom, args)) = detect_map_set_axiom(law) {
926 let resolved_args: Vec<_> = args.iter().map(|a| inputs.resolve_expr(a, scope)).collect();
927 return ProofStrategy::LibraryAxiom {
928 axiom,
929 args: resolved_args,
930 };
931 }
932 if let Some(inc) = detect_map_key_tracked_increment(law, fn_name, inputs) {
936 return ProofStrategy::MapKeyTrackedIncrement {
937 outer_fn: inc.outer_fn,
938 map_arg: inputs.resolve_expr(&inc.map_arg, scope),
939 key_arg: inputs.resolve_expr(&inc.key_arg, scope),
940 };
941 }
942 if let Some(post) = detect_map_update_postcondition(law, fn_name, inputs) {
945 return ProofStrategy::MapUpdatePostcondition {
946 outer_fn: post.outer_fn,
947 kind: post.kind,
948 map_arg: inputs.resolve_expr(&post.map_arg, scope),
949 key_arg: inputs.resolve_expr(&post.key_arg, scope),
950 extra_unfolds: post.extra_unfolds,
951 };
952 }
953 if let Some(extra_unfolds) = detect_spec_equivalence(law, fn_name, inputs) {
956 return ProofStrategy::SpecEquivalence { extra_unfolds };
957 }
958 if let Some(extra_unfolds) = detect_simp_normalized_spec_equivalence(law, fn_name, inputs) {
963 return ProofStrategy::SpecEquivalenceSimpNormalized { extra_unfolds };
964 }
965 if let Some((unfolded_impl, unfolded_spec)) =
968 detect_linear_int_spec_equivalence(law, fn_name, inputs)
969 {
970 return ProofStrategy::LinearIntSpecEquivalence {
971 unfolded_impl: inputs.resolve_expr(&unfolded_impl, scope),
972 unfolded_spec: inputs.resolve_expr(&unfolded_spec, scope),
973 };
974 }
975 if let Some(spec_fn) = detect_effectful_spec_equivalence(law, fn_name, inputs) {
981 return ProofStrategy::EffectfulSpecEquivalence {
982 impl_fn: fn_name.to_string(),
983 spec_fn,
984 };
985 }
986 if let Some((spec_fn, helper_fn)) =
994 detect_linear_recurrence2_spec_equivalence(law, fn_name, inputs)
995 {
996 return ProofStrategy::LinearRecurrence2SpecEquivalence {
997 impl_fn: fn_name.to_string(),
998 spec_fn,
999 helper_fn,
1000 };
1001 }
1002 if let Some(plan) = detect_simp_omega_unfold(law, fn_name, inputs, refined_types) {
1005 return ProofStrategy::LinearArithmetic {
1006 unfold_fns: plan.unfold_fns,
1007 wrapper_return: plan.wrapper_return,
1008 smart_guard: plan.smart_guard,
1009 lifted: plan.lifted,
1010 };
1011 }
1012 ProofStrategy::BackendDispatch
1013}
1014
1015struct SimpOmegaPlan {
1019 unfold_fns: Vec<String>,
1020 wrapper_return: bool,
1021 smart_guard: Option<crate::ir::SmartGuard>,
1022 lifted: bool,
1027}
1028
1029fn detect_simp_omega_unfold(
1030 law: &crate::ast::VerifyLaw,
1031 fn_name: &str,
1032 inputs: &ProofLowerInputs,
1033 refined_types: &std::collections::HashMap<crate::ir::TypeId, crate::ir::RefinedTypeDecl>,
1034) -> Option<SimpOmegaPlan> {
1035 use std::collections::BTreeSet;
1036
1037 let outer_fd = inputs.find_fn_def_by_call_name(fn_name)?;
1038 if law.givens.is_empty() || law.givens.iter().any(|g| g.type_name != "Int") {
1040 return None;
1041 }
1042 let symbols = inputs.symbol_table;
1049 let lifted = law.givens.iter().any(|g| {
1050 refinement_lift_for_given_ir(
1051 &g.name,
1052 &law.lhs,
1053 &law.rhs,
1054 refined_types,
1055 symbols,
1056 inputs.dep_modules,
1057 )
1058 .is_some()
1059 });
1060 if !lifted && outer_fd.params.iter().any(|(_, t)| t != "Int") {
1061 return None;
1062 }
1063
1064 let mut fn_names: BTreeSet<String> = BTreeSet::new();
1066 collect_fn_calls_expr(&law.lhs, &mut fn_names);
1067 collect_fn_calls_expr(&law.rhs, &mut fn_names);
1068 fn_names.insert(fn_name.to_string());
1069
1070 loop {
1077 let before = fn_names.len();
1078 let snapshot: Vec<String> = fn_names.iter().cloned().collect();
1079 for fd in iter_all_fn_defs(inputs) {
1080 if !snapshot.contains(&fd.name) {
1081 continue;
1082 }
1083 for stmt in fd.body.stmts() {
1084 match stmt {
1085 crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
1086 collect_fn_calls_expr(e, &mut fn_names);
1087 }
1088 }
1089 }
1090 }
1091 if fn_names.len() == before {
1092 break;
1093 }
1094 }
1095
1096 let mut wrapper_return = false;
1101 for fd in iter_all_fn_defs(inputs) {
1102 if !fn_names.contains(&fd.name) {
1103 continue;
1104 }
1105 let mut self_only: BTreeSet<String> = BTreeSet::new();
1106 self_only.insert(fd.name.clone());
1107 if body_calls_any_of_inputs(&fd.body, &self_only) {
1108 return None;
1109 }
1110 if fd.name == fn_name && !lifted && fd.params.iter().any(|(_, t)| t != "Int") {
1114 return None;
1115 }
1116 let ret = fd.return_type.as_str();
1117 if ret != "Int" && ret != "Float" {
1118 wrapper_return = true;
1119 }
1120 }
1121
1122 let mut ordered: Vec<String> = Vec::new();
1126 if fn_names.contains(fn_name) {
1127 ordered.push(fn_name.to_string());
1128 }
1129 for n in &fn_names {
1130 if n != fn_name {
1131 ordered.push(n.clone());
1132 }
1133 }
1134
1135 let smart_guard = extract_smart_constructor_guard(&fn_names, inputs);
1136
1137 Some(SimpOmegaPlan {
1138 unfold_fns: ordered,
1139 wrapper_return,
1140 smart_guard,
1141 lifted,
1142 })
1143}
1144
1145fn refinement_lift_for_given_ir(
1157 given_name: &str,
1158 lhs: &Spanned<crate::ast::Expr>,
1159 rhs: &Spanned<crate::ast::Expr>,
1160 refined_types: &std::collections::HashMap<crate::ir::TypeId, crate::ir::RefinedTypeDecl>,
1161 symbols: &crate::ir::SymbolTable,
1162 dep_modules: &[crate::codegen::ModuleInfo],
1163) -> Option<String> {
1164 let mut result: Option<String> = None;
1165 walk_for_refinement_carrier(
1166 lhs,
1167 given_name,
1168 refined_types,
1169 symbols,
1170 dep_modules,
1171 &mut result,
1172 );
1173 walk_for_refinement_carrier(
1174 rhs,
1175 given_name,
1176 refined_types,
1177 symbols,
1178 dep_modules,
1179 &mut result,
1180 );
1181 result
1182}
1183
1184fn walk_for_refinement_carrier(
1193 expr: &Spanned<crate::ast::Expr>,
1194 given_name: &str,
1195 refined_types: &std::collections::HashMap<crate::ir::TypeId, crate::ir::RefinedTypeDecl>,
1196 symbols: &crate::ir::SymbolTable,
1197 dep_modules: &[crate::codegen::ModuleInfo],
1198 result: &mut Option<String>,
1199) {
1200 use crate::ast::Expr;
1201 if result.is_some() {
1202 return;
1203 }
1204 match &expr.node {
1205 Expr::RecordCreate { type_name, fields } if fields.len() == 1 => {
1206 let (_, fvalue) = &fields[0];
1207 let matches_var = matches!(
1208 &fvalue.node,
1209 Expr::Ident(n) | Expr::Resolved { name: n, .. } if n == given_name
1210 );
1211 if matches_var
1212 && let Some((type_id, _decl)) =
1213 crate::codegen::common::resolve_refined_type_in_with_key(
1214 refined_types,
1215 symbols,
1216 dep_modules,
1217 type_name,
1218 )
1219 {
1220 *result = Some(symbols.type_entry(type_id).key.canonical());
1225 return;
1226 }
1227 for (_, v) in fields {
1230 walk_for_refinement_carrier(
1231 v,
1232 given_name,
1233 refined_types,
1234 symbols,
1235 dep_modules,
1236 result,
1237 );
1238 }
1239 }
1240 Expr::FnCall(callee, args) => {
1241 walk_for_refinement_carrier(
1242 callee,
1243 given_name,
1244 refined_types,
1245 symbols,
1246 dep_modules,
1247 result,
1248 );
1249 for a in args {
1250 walk_for_refinement_carrier(
1251 a,
1252 given_name,
1253 refined_types,
1254 symbols,
1255 dep_modules,
1256 result,
1257 );
1258 }
1259 }
1260 Expr::BinOp(_, l, r) => {
1261 walk_for_refinement_carrier(l, given_name, refined_types, symbols, dep_modules, result);
1262 walk_for_refinement_carrier(r, given_name, refined_types, symbols, dep_modules, result);
1263 }
1264 Expr::Match { subject, arms, .. } => {
1265 walk_for_refinement_carrier(
1266 subject,
1267 given_name,
1268 refined_types,
1269 symbols,
1270 dep_modules,
1271 result,
1272 );
1273 for arm in arms {
1274 walk_for_refinement_carrier(
1275 &arm.body,
1276 given_name,
1277 refined_types,
1278 symbols,
1279 dep_modules,
1280 result,
1281 );
1282 }
1283 }
1284 Expr::Attr(obj, _) => {
1285 walk_for_refinement_carrier(
1286 obj,
1287 given_name,
1288 refined_types,
1289 symbols,
1290 dep_modules,
1291 result,
1292 );
1293 }
1294 _ => {}
1295 }
1296}
1297
1298fn iter_all_fn_defs<'a>(inputs: &'a ProofLowerInputs<'a>) -> impl Iterator<Item = &'a FnDef> {
1299 inputs
1300 .entry_items
1301 .iter()
1302 .filter_map(|item| match item {
1303 TopLevel::FnDef(fd) => Some(fd),
1304 _ => None,
1305 })
1306 .chain(inputs.dep_modules.iter().flat_map(|m| m.fn_defs.iter()))
1307}
1308
1309fn body_calls_any_of_inputs(
1310 body: &crate::ast::FnBody,
1311 names: &std::collections::BTreeSet<String>,
1312) -> bool {
1313 let mut called = std::collections::BTreeSet::new();
1314 for stmt in body.stmts() {
1315 match stmt {
1316 crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
1317 collect_fn_calls_expr(e, &mut called);
1318 }
1319 }
1320 }
1321 called.iter().any(|c| names.contains(c))
1322}
1323
1324fn collect_fn_calls_expr(
1325 expr: &Spanned<crate::ast::Expr>,
1326 out: &mut std::collections::BTreeSet<String>,
1327) {
1328 use crate::ast::Expr;
1329 match &expr.node {
1330 Expr::FnCall(f, args) => {
1331 if let Some(name) = expr_to_dotted_name(&f.node) {
1332 let last = name.rsplit('.').next().unwrap_or(&name);
1339 if last.chars().next().is_some_and(|c| c.is_lowercase()) {
1340 out.insert(name);
1341 }
1342 }
1343 for arg in args {
1344 collect_fn_calls_expr(arg, out);
1345 }
1346 }
1347 Expr::BinOp(_, l, r) => {
1348 collect_fn_calls_expr(l, out);
1349 collect_fn_calls_expr(r, out);
1350 }
1351 Expr::Attr(obj, _) => collect_fn_calls_expr(obj, out),
1352 Expr::Match { subject, arms, .. } => {
1353 collect_fn_calls_expr(subject, out);
1354 for arm in arms {
1355 collect_fn_calls_expr(&arm.body, out);
1356 }
1357 }
1358 Expr::TailCall(boxed) => {
1359 out.insert(boxed.target.clone());
1360 for arg in &boxed.args {
1361 collect_fn_calls_expr(arg, out);
1362 }
1363 }
1364 _ => {}
1365 }
1366}
1367
1368fn extract_smart_constructor_guard(
1373 fn_names: &std::collections::BTreeSet<String>,
1374 inputs: &ProofLowerInputs,
1375) -> Option<crate::ir::SmartGuard> {
1376 use crate::ast::{Expr, MatchArm, Pattern, Stmt};
1377 for fd in iter_all_fn_defs(inputs) {
1378 if !fn_names.contains(&fd.name) {
1379 continue;
1380 }
1381 if !fd.return_type.starts_with("Result<") {
1382 continue;
1383 }
1384 if fd.params.len() != 1 {
1385 continue;
1386 }
1387 let (param_name, param_type) = &fd.params[0];
1388 if param_type != "Int" {
1389 continue;
1390 }
1391 let stmts = fd.body.stmts();
1392 if stmts.len() != 1 {
1393 continue;
1394 }
1395 let Stmt::Expr(body_expr) = &stmts[0] else {
1396 continue;
1397 };
1398 let Expr::Match { subject, arms } = &body_expr.node else {
1399 continue;
1400 };
1401 if !arms_match_bool_ok_err(arms) {
1402 continue;
1403 }
1404 let scope = inputs.fn_owning_scope(fd);
1405 return Some(crate::ir::SmartGuard {
1406 param: param_name.clone(),
1407 predicate: inputs.resolve_expr(subject, scope),
1408 });
1409 #[allow(unreachable_code)]
1411 {
1412 let _: Option<&MatchArm> = None;
1413 let _: Option<&Pattern> = None;
1414 }
1415 }
1416 None
1417}
1418
1419fn arms_match_bool_ok_err(arms: &[crate::ast::MatchArm]) -> bool {
1420 use crate::ast::{Expr, Literal, Pattern};
1421 if arms.len() != 2 {
1422 return false;
1423 }
1424 let starts_with_ctor = |expr: &Spanned<Expr>, name: &str| -> bool {
1425 match &expr.node {
1426 Expr::Constructor(n, _) => n == name,
1427 Expr::FnCall(callee, _) => {
1428 if let Expr::Attr(obj, field) = &callee.node
1429 && let Expr::Ident(ns) = &obj.node
1430 {
1431 format!("{ns}.{field}") == name
1432 } else {
1433 false
1434 }
1435 }
1436 _ => false,
1437 }
1438 };
1439 let mut saw_true_ok = false;
1440 let mut saw_false_err = false;
1441 for arm in arms {
1442 match &arm.pattern {
1443 Pattern::Literal(Literal::Bool(true)) => {
1444 if starts_with_ctor(&arm.body, "Result.Ok") {
1445 saw_true_ok = true;
1446 }
1447 }
1448 Pattern::Literal(Literal::Bool(false)) => {
1449 if starts_with_ctor(&arm.body, "Result.Err") {
1450 saw_false_err = true;
1451 }
1452 }
1453 _ => return false,
1454 }
1455 }
1456 saw_true_ok && saw_false_err
1457}
1458
1459fn detect_map_set_axiom(
1465 law: &crate::ast::VerifyLaw,
1466) -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1467 let has_side = |side: &Spanned<crate::ast::Expr>,
1469 other: &Spanned<crate::ast::Expr>|
1470 -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1471 let (m, k, v) = map_has_set_parts(side)?;
1472 if !is_bool_true(other) {
1473 return None;
1474 }
1475 Some((
1476 "Map.has_set_self".to_string(),
1477 vec![m.clone(), k.clone(), v.clone()],
1478 ))
1479 };
1480 if let Some(found) = has_side(&law.lhs, &law.rhs).or_else(|| has_side(&law.rhs, &law.lhs)) {
1481 return Some(found);
1482 }
1483
1484 let get_side = |side: &Spanned<crate::ast::Expr>,
1486 other: &Spanned<crate::ast::Expr>|
1487 -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1488 let (m, k, v) = map_get_set_parts(side)?;
1489 let some_v = option_some_arg(other)?;
1490 if some_v.node != v.node {
1491 return None;
1492 }
1493 Some((
1494 "Map.get_set_self".to_string(),
1495 vec![m.clone(), k.clone(), v.clone()],
1496 ))
1497 };
1498 get_side(&law.lhs, &law.rhs).or_else(|| get_side(&law.rhs, &law.lhs))
1499}
1500
1501struct MapUpdatePostconditionPlan {
1504 outer_fn: String,
1505 kind: crate::ir::MapUpdatePostconditionKind,
1506 map_arg: Spanned<crate::ast::Expr>,
1507 key_arg: Spanned<crate::ast::Expr>,
1508 extra_unfolds: Vec<String>,
1509}
1510
1511fn detect_map_update_postcondition(
1517 law: &crate::ast::VerifyLaw,
1518 fn_name: &str,
1519 inputs: &ProofLowerInputs,
1520) -> Option<MapUpdatePostconditionPlan> {
1521 use crate::ir::MapUpdatePostconditionKind;
1522
1523 outer_fn_map_update_shape(fn_name, inputs)?;
1524
1525 let has_side = |side: &Spanned<crate::ast::Expr>,
1526 other: &Spanned<crate::ast::Expr>|
1527 -> Option<MapUpdatePostconditionPlan> {
1528 if !is_bool_true(other) {
1529 return None;
1530 }
1531 let (map_arg, key_arg) = map_has_after_fn_call(side, fn_name)?;
1532 Some(MapUpdatePostconditionPlan {
1533 outer_fn: fn_name.to_string(),
1534 kind: MapUpdatePostconditionKind::HasAfter,
1535 map_arg: map_arg.clone(),
1536 key_arg: key_arg.clone(),
1537 extra_unfolds: Vec::new(),
1538 })
1539 };
1540 if let Some(plan) = has_side(&law.lhs, &law.rhs).or_else(|| has_side(&law.rhs, &law.lhs)) {
1541 return Some(plan);
1542 }
1543
1544 let get_side = |side: &Spanned<crate::ast::Expr>,
1545 other: &Spanned<crate::ast::Expr>|
1546 -> Option<MapUpdatePostconditionPlan> {
1547 option_some_arg(other)?;
1548 let (map_arg, key_arg) = map_get_after_fn_call(side, fn_name)?;
1549 let extra_unfolds = law_helper_unfolds(law, fn_name, inputs);
1550 Some(MapUpdatePostconditionPlan {
1551 outer_fn: fn_name.to_string(),
1552 kind: MapUpdatePostconditionKind::GetAfter,
1553 map_arg: map_arg.clone(),
1554 key_arg: key_arg.clone(),
1555 extra_unfolds,
1556 })
1557 };
1558 get_side(&law.lhs, &law.rhs).or_else(|| get_side(&law.rhs, &law.lhs))
1559}
1560
1561fn law_helper_unfolds(
1568 law: &crate::ast::VerifyLaw,
1569 outer_fn: &str,
1570 inputs: &ProofLowerInputs,
1571) -> Vec<String> {
1572 use std::collections::BTreeSet;
1573
1574 let resolve_user_fn = |name: &str| -> Option<&FnDef> {
1575 let fd = inputs.find_fn_def_by_call_name(name)?;
1576 if !fd.effects.is_empty() || fd.name == "main" {
1577 return None;
1578 }
1579 Some(fd)
1580 };
1581
1582 let mut raw: BTreeSet<String> = BTreeSet::new();
1584 collect_fn_calls_expr(&law.lhs, &mut raw);
1585 collect_fn_calls_expr(&law.rhs, &mut raw);
1586 if let Some(when_expr) = &law.when {
1587 collect_fn_calls_expr(when_expr, &mut raw);
1588 }
1589 let mut names: BTreeSet<String> = raw
1590 .into_iter()
1591 .filter_map(|n| resolve_user_fn(&n).map(|fd| fd.name.clone()))
1592 .collect();
1593
1594 loop {
1596 let before = names.len();
1597 let snapshot: Vec<String> = names.iter().cloned().collect();
1598 for name in snapshot {
1599 let Some(fd) = resolve_user_fn(&name) else {
1600 continue;
1601 };
1602 let mut called: BTreeSet<String> = BTreeSet::new();
1603 for stmt in fd.body.stmts() {
1604 match stmt {
1605 crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
1606 collect_fn_calls_expr(e, &mut called);
1607 }
1608 }
1609 }
1610 for c in called {
1611 if let Some(callee_fd) = resolve_user_fn(&c) {
1612 names.insert(callee_fd.name.clone());
1613 }
1614 }
1615 }
1616 if names.len() == before {
1617 break;
1618 }
1619 }
1620 names.remove(outer_fn);
1621 names.into_iter().collect()
1622}
1623
1624fn detect_spec_equivalence(
1635 law: &crate::ast::VerifyLaw,
1636 fn_name: &str,
1637 inputs: &ProofLowerInputs,
1638) -> Option<Vec<String>> {
1639 use crate::ast::Expr;
1640 use std::collections::BTreeSet;
1641
1642 let spec_fn_name = &law.name;
1643 if spec_fn_name == fn_name {
1644 return None;
1645 }
1646 let spec_fd = inputs.find_fn_def_by_call_name(spec_fn_name)?;
1647 if !spec_fd.effects.is_empty() || spec_fd.name == "main" {
1648 return None;
1649 }
1650 let impl_fd = inputs.find_fn_def_by_call_name(fn_name)?;
1651
1652 let direct_call =
1653 |expr: &Spanned<crate::ast::Expr>| -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1654 let Expr::FnCall(callee, args) = &expr.node else {
1655 return None;
1656 };
1657 let name = match &callee.node {
1658 Expr::Ident(n) | Expr::Resolved { name: n, .. } => n.clone(),
1659 _ => return None,
1660 };
1661 Some((name, args.clone()))
1662 };
1663 let canonical_shape =
1664 |lhs: &Spanned<crate::ast::Expr>, rhs: &Spanned<crate::ast::Expr>| -> bool {
1665 let Some((l_name, l_args)) = direct_call(lhs) else {
1666 return false;
1667 };
1668 let Some((r_name, r_args)) = direct_call(rhs) else {
1669 return false;
1670 };
1671 l_name == fn_name && r_name == *spec_fn_name && l_args == r_args
1672 };
1673 if !canonical_shape(&law.lhs, &law.rhs) && !canonical_shape(&law.rhs, &law.lhs) {
1674 return None;
1675 }
1676
1677 let impl_body = body_terminal_expr(impl_fd.body.as_ref())?;
1678 let spec_body = body_terminal_expr(spec_fd.body.as_ref())?;
1679 if impl_body.node != spec_body.node {
1680 return None;
1681 }
1682
1683 let resolve_user_fn = |name: &str| -> Option<&FnDef> {
1687 let fd = inputs.find_fn_def_by_call_name(name)?;
1688 if !fd.effects.is_empty() || fd.name == "main" {
1689 return None;
1690 }
1691 Some(fd)
1692 };
1693 let mut names: BTreeSet<String> = BTreeSet::new();
1694 names.insert(fn_name.to_string());
1695 names.insert(spec_fn_name.clone());
1696 let mut seed: BTreeSet<String> = BTreeSet::new();
1697 collect_fn_calls_expr(&law.lhs, &mut seed);
1698 collect_fn_calls_expr(&law.rhs, &mut seed);
1699 if let Some(when_expr) = &law.when {
1700 collect_fn_calls_expr(when_expr, &mut seed);
1701 }
1702 for n in seed {
1703 if let Some(fd) = resolve_user_fn(&n) {
1704 names.insert(fd.name.clone());
1705 }
1706 }
1707 loop {
1708 let before = names.len();
1709 let snapshot: Vec<String> = names.iter().cloned().collect();
1710 for name in snapshot {
1711 let Some(fd) = resolve_user_fn(&name) else {
1712 continue;
1713 };
1714 let mut called: BTreeSet<String> = BTreeSet::new();
1715 for stmt in fd.body.stmts() {
1716 match stmt {
1717 crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
1718 collect_fn_calls_expr(e, &mut called);
1719 }
1720 }
1721 }
1722 for c in called {
1723 if let Some(callee_fd) = resolve_user_fn(&c) {
1724 names.insert(callee_fd.name.clone());
1725 }
1726 }
1727 }
1728 if names.len() == before {
1729 break;
1730 }
1731 }
1732 Some(names.into_iter().collect())
1733}
1734
1735fn detect_simp_normalized_spec_equivalence(
1742 law: &crate::ast::VerifyLaw,
1743 fn_name: &str,
1744 inputs: &ProofLowerInputs,
1745) -> Option<Vec<String>> {
1746 use crate::ast::Expr;
1747 use std::collections::BTreeSet;
1748
1749 let spec_fn_name = &law.name;
1750 if spec_fn_name == fn_name {
1751 return None;
1752 }
1753 let spec_fd = inputs.find_fn_def_by_call_name(spec_fn_name)?;
1754 if !spec_fd.effects.is_empty() || spec_fd.name == "main" {
1755 return None;
1756 }
1757 let impl_fd = inputs.find_fn_def_by_call_name(fn_name)?;
1758
1759 let direct_call =
1760 |expr: &Spanned<crate::ast::Expr>| -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1761 let Expr::FnCall(callee, args) = &expr.node else {
1762 return None;
1763 };
1764 let name = match &callee.node {
1765 Expr::Ident(n) | Expr::Resolved { name: n, .. } => n.clone(),
1766 _ => return None,
1767 };
1768 Some((name, args.clone()))
1769 };
1770 let canonical_shape_args = |lhs: &Spanned<crate::ast::Expr>,
1771 rhs: &Spanned<crate::ast::Expr>|
1772 -> Option<Vec<Spanned<crate::ast::Expr>>> {
1773 let (l_name, l_args) = direct_call(lhs)?;
1774 let (r_name, r_args) = direct_call(rhs)?;
1775 if l_name != fn_name || r_name != *spec_fn_name || l_args != r_args {
1776 return None;
1777 }
1778 if l_args.len() != impl_fd.params.len() || r_args.len() != spec_fd.params.len() {
1779 return None;
1780 }
1781 Some(l_args)
1782 };
1783 let call_args = canonical_shape_args(&law.lhs, &law.rhs)
1784 .or_else(|| canonical_shape_args(&law.rhs, &law.lhs))?;
1785
1786 let impl_body = body_terminal_expr(impl_fd.body.as_ref())?;
1787 let spec_body = body_terminal_expr(spec_fd.body.as_ref())?;
1788 if impl_body.node == spec_body.node {
1791 return None;
1792 }
1793 let impl_subst: std::collections::HashMap<String, Spanned<crate::ast::Expr>> = impl_fd
1794 .params
1795 .iter()
1796 .zip(call_args.iter())
1797 .map(|((n, _), arg)| (n.clone(), arg.clone()))
1798 .collect();
1799 let spec_subst: std::collections::HashMap<String, Spanned<crate::ast::Expr>> = spec_fd
1800 .params
1801 .iter()
1802 .zip(call_args.iter())
1803 .map(|((n, _), arg)| (n.clone(), arg.clone()))
1804 .collect();
1805 let impl_normalised = simplify_identity_expr(&crate::ast_rewrite::rewrite_idents_scoped(
1806 impl_body,
1807 |name| impl_subst.get(name).cloned(),
1808 ));
1809 let spec_normalised = simplify_identity_expr(&crate::ast_rewrite::rewrite_idents_scoped(
1810 spec_body,
1811 |name| spec_subst.get(name).cloned(),
1812 ));
1813 if impl_normalised.node != spec_normalised.node {
1814 return None;
1815 }
1816
1817 let resolve_user_fn = |name: &str| -> Option<&FnDef> {
1819 let fd = inputs.find_fn_def_by_call_name(name)?;
1820 if !fd.effects.is_empty() || fd.name == "main" {
1821 return None;
1822 }
1823 Some(fd)
1824 };
1825 let mut names: BTreeSet<String> = BTreeSet::new();
1826 names.insert(fn_name.to_string());
1827 names.insert(spec_fn_name.clone());
1828 let mut seed: BTreeSet<String> = BTreeSet::new();
1829 collect_fn_calls_expr(&law.lhs, &mut seed);
1830 collect_fn_calls_expr(&law.rhs, &mut seed);
1831 if let Some(when_expr) = &law.when {
1832 collect_fn_calls_expr(when_expr, &mut seed);
1833 }
1834 for n in seed {
1835 if let Some(fd) = resolve_user_fn(&n) {
1836 names.insert(fd.name.clone());
1837 }
1838 }
1839 loop {
1840 let before = names.len();
1841 let snapshot: Vec<String> = names.iter().cloned().collect();
1842 for name in snapshot {
1843 let Some(fd) = resolve_user_fn(&name) else {
1844 continue;
1845 };
1846 let mut called: BTreeSet<String> = BTreeSet::new();
1847 for stmt in fd.body.stmts() {
1848 match stmt {
1849 crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
1850 collect_fn_calls_expr(e, &mut called);
1851 }
1852 }
1853 }
1854 for c in called {
1855 if let Some(callee_fd) = resolve_user_fn(&c) {
1856 names.insert(callee_fd.name.clone());
1857 }
1858 }
1859 }
1860 if names.len() == before {
1861 break;
1862 }
1863 }
1864 Some(names.into_iter().collect())
1865}
1866
1867fn detect_linear_int_spec_equivalence(
1876 law: &crate::ast::VerifyLaw,
1877 fn_name: &str,
1878 inputs: &ProofLowerInputs,
1879) -> Option<(Spanned<crate::ast::Expr>, Spanned<crate::ast::Expr>)> {
1880 use crate::ast::Expr;
1881 use std::collections::HashSet;
1882
1883 if law.givens.is_empty() || !law.givens.iter().all(|g| g.type_name == "Int") {
1884 return None;
1885 }
1886 let spec_fn_name = &law.name;
1887 if spec_fn_name == fn_name {
1888 return None;
1889 }
1890 let spec_fd = inputs.find_fn_def_by_call_name(spec_fn_name)?;
1891 if !spec_fd.effects.is_empty() || spec_fd.name == "main" {
1892 return None;
1893 }
1894 let impl_fd = inputs.find_fn_def_by_call_name(fn_name)?;
1895 if impl_fd.return_type != "Int" || spec_fd.return_type != "Int" {
1896 return None;
1897 }
1898
1899 let direct_call =
1900 |expr: &Spanned<crate::ast::Expr>| -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1901 let Expr::FnCall(callee, args) = &expr.node else {
1902 return None;
1903 };
1904 let name = match &callee.node {
1905 Expr::Ident(n) | Expr::Resolved { name: n, .. } => n.clone(),
1906 _ => return None,
1907 };
1908 Some((name, args.clone()))
1909 };
1910 let canonical_shape_args = |lhs: &Spanned<crate::ast::Expr>,
1911 rhs: &Spanned<crate::ast::Expr>|
1912 -> Option<Vec<Spanned<crate::ast::Expr>>> {
1913 let (l_name, l_args) = direct_call(lhs)?;
1914 let (r_name, r_args) = direct_call(rhs)?;
1915 if l_name != fn_name || r_name != *spec_fn_name || l_args != r_args {
1916 return None;
1917 }
1918 if l_args.len() != impl_fd.params.len() || r_args.len() != spec_fd.params.len() {
1919 return None;
1920 }
1921 Some(l_args)
1922 };
1923 let call_args = canonical_shape_args(&law.lhs, &law.rhs)
1924 .or_else(|| canonical_shape_args(&law.rhs, &law.lhs))?;
1925
1926 let impl_body = body_terminal_expr(impl_fd.body.as_ref())?;
1927 let spec_body = body_terminal_expr(spec_fd.body.as_ref())?;
1928 let impl_subst: std::collections::HashMap<String, Spanned<crate::ast::Expr>> = impl_fd
1929 .params
1930 .iter()
1931 .zip(call_args.iter())
1932 .map(|((n, _), arg)| (n.clone(), arg.clone()))
1933 .collect();
1934 let spec_subst: std::collections::HashMap<String, Spanned<crate::ast::Expr>> = spec_fd
1935 .params
1936 .iter()
1937 .zip(call_args.iter())
1938 .map(|((n, _), arg)| (n.clone(), arg.clone()))
1939 .collect();
1940 let unfolded_impl =
1941 crate::ast_rewrite::rewrite_idents_scoped(impl_body, |name| impl_subst.get(name).cloned());
1942 let unfolded_spec =
1943 crate::ast_rewrite::rewrite_idents_scoped(spec_body, |name| spec_subst.get(name).cloned());
1944
1945 let allowed_idents: HashSet<&str> = law.givens.iter().map(|g| g.name.as_str()).collect();
1946 if !is_linear_int_expr(&unfolded_impl, &allowed_idents)
1947 || !is_linear_int_expr(&unfolded_spec, &allowed_idents)
1948 {
1949 return None;
1950 }
1951 Some((unfolded_impl, unfolded_spec))
1952}
1953
1954fn is_linear_int_expr(
1958 expr: &Spanned<crate::ast::Expr>,
1959 allowed_idents: &std::collections::HashSet<&str>,
1960) -> bool {
1961 use crate::ast::{BinOp, Expr, Literal};
1962 match &expr.node {
1963 Expr::Literal(Literal::Int(_)) => true,
1964 Expr::Ident(name) | Expr::Resolved { name, .. } => allowed_idents.contains(name.as_str()),
1965 Expr::BinOp(BinOp::Add | BinOp::Sub, left, right) => {
1966 is_linear_int_expr(left, allowed_idents) && is_linear_int_expr(right, allowed_idents)
1967 }
1968 _ => false,
1969 }
1970}
1971
1972fn detect_effectful_spec_equivalence(
1984 law: &crate::ast::VerifyLaw,
1985 fn_name: &str,
1986 inputs: &ProofLowerInputs,
1987) -> Option<String> {
1988 use crate::ast::Expr;
1989
1990 let impl_fd = inputs.find_fn_def_by_call_name(fn_name)?;
1991 if impl_fd.effects.is_empty() {
1992 return None;
1993 }
1994 if !impl_fd
1995 .effects
1996 .iter()
1997 .all(|e| crate::types::checker::effect_classification::is_classified(&e.node))
1998 {
1999 return None;
2000 }
2001
2002 let find_fn = |name: &str| -> Option<&crate::ast::FnDef> {
2003 inputs
2004 .entry_items
2005 .iter()
2006 .filter_map(|item| match item {
2007 TopLevel::FnDef(fd) => Some(fd),
2008 _ => None,
2009 })
2010 .find(|fd| fd.name == name)
2011 };
2012 let rewritten_lhs = crate::codegen::common::rewrite_effectful_calls_in_law(
2013 &law.lhs,
2014 law,
2015 find_fn,
2016 crate::codegen::common::OracleInjectionMode::LemmaBindingProjected,
2017 );
2018 let rewritten_rhs = crate::codegen::common::rewrite_effectful_calls_in_law(
2019 &law.rhs,
2020 law,
2021 find_fn,
2022 crate::codegen::common::OracleInjectionMode::LemmaBindingProjected,
2023 );
2024
2025 let direct_call =
2026 |expr: &Spanned<crate::ast::Expr>| -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
2027 let Expr::FnCall(callee, args) = &expr.node else {
2028 return None;
2029 };
2030 let name = match &callee.node {
2031 Expr::Ident(n) | Expr::Resolved { name: n, .. } => n.clone(),
2032 _ => return None,
2033 };
2034 Some((name, args.clone()))
2035 };
2036 let try_side = |impl_side: &Spanned<crate::ast::Expr>,
2037 spec_side: &Spanned<crate::ast::Expr>|
2038 -> Option<String> {
2039 let (l_name, l_args) = direct_call(impl_side)?;
2040 let (r_name, r_args) = direct_call(spec_side)?;
2041 if l_args != r_args || l_name == r_name || l_name != fn_name {
2042 return None;
2043 }
2044 Some(r_name)
2045 };
2046 try_side(&rewritten_lhs, &rewritten_rhs).or_else(|| try_side(&rewritten_rhs, &rewritten_lhs))
2047}
2048
2049fn detect_linear_recurrence2_spec_equivalence(
2059 law: &crate::ast::VerifyLaw,
2060 fn_name: &str,
2061 inputs: &ProofLowerInputs,
2062) -> Option<(String, String)> {
2063 use crate::codegen::lean::recurrence::{
2064 detect_second_order_int_linear_recurrence, detect_tailrec_int_linear_pair_worker,
2065 detect_tailrec_int_linear_pair_wrapper,
2066 };
2067
2068 let spec_fn_name = &law.name;
2069 if spec_fn_name == fn_name {
2070 return None;
2071 }
2072 if !law_references_fn(&law.lhs, spec_fn_name) && !law_references_fn(&law.rhs, spec_fn_name) {
2073 return None;
2074 }
2075
2076 let impl_fd = inputs.find_fn_def_by_call_name(fn_name)?;
2077 let spec_fd = inputs.find_fn_def_by_call_name(spec_fn_name)?;
2078 let impl_shape = detect_tailrec_int_linear_pair_wrapper(impl_fd)?;
2079 let spec_shape = detect_second_order_int_linear_recurrence(spec_fd)?;
2080
2081 if impl_shape.negative_branch.node != spec_shape.negative_branch.node
2084 || impl_shape.seed_prev.node != spec_shape.base0.node
2085 || impl_shape.seed_curr.node != spec_shape.base1.node
2086 {
2087 return None;
2088 }
2089
2090 let helper_fd = inputs.find_fn_def_by_call_name(&impl_shape.helper_fn_name)?;
2091 let helper_shape = detect_tailrec_int_linear_pair_worker(helper_fd)?;
2092 if helper_shape.recurrence != spec_shape.recurrence {
2093 return None;
2094 }
2095
2096 Some((spec_fn_name.clone(), impl_shape.helper_fn_name))
2097}
2098
2099fn law_references_fn(expr: &Spanned<crate::ast::Expr>, target: &str) -> bool {
2100 use crate::ast::Expr;
2101 match &expr.node {
2102 Expr::FnCall(callee, args) => {
2103 let name = match &callee.node {
2104 Expr::Ident(n) | Expr::Resolved { name: n, .. } => Some(n.as_str()),
2105 _ => None,
2106 };
2107 if name == Some(target) {
2108 return true;
2109 }
2110 args.iter().any(|a| law_references_fn(a, target))
2111 }
2112 Expr::BinOp(_, l, r) => law_references_fn(l, target) || law_references_fn(r, target),
2113 Expr::Attr(base, _) => law_references_fn(base, target),
2114 Expr::Match { subject, arms } => {
2115 law_references_fn(subject, target)
2116 || arms.iter().any(|arm| law_references_fn(&arm.body, target))
2117 }
2118 _ => false,
2119 }
2120}
2121
2122fn outer_fn_map_update_shape(fn_name: &str, inputs: &ProofLowerInputs) -> Option<()> {
2138 let fd = inputs.find_fn_def_by_call_name(fn_name)?;
2139 if fd.params.len() != 2 {
2140 return None;
2141 }
2142 let map_param = fd.params[0].0.as_str();
2143 let key_param = fd.params[1].0.as_str();
2144 map_update_body_matches(fd.body.stmts(), map_param, key_param).then_some(())
2145}
2146
2147fn map_update_body_matches(stmts: &[crate::ast::Stmt], map_param: &str, key_param: &str) -> bool {
2148 use crate::ast::Stmt;
2149 if stmts.len() < 2 {
2150 return matches!(stmts.first(), Some(Stmt::Expr(e)) if map_update_match_expr(e, map_param, key_param, None));
2153 }
2154 let Some(last) = stmts.last() else {
2155 return false;
2156 };
2157 let mut bound_name: Option<&str> = None;
2158 for stmt in &stmts[..stmts.len() - 1] {
2159 match stmt {
2160 Stmt::Binding(name, _, expr) => {
2161 if !is_map_get_of_params(expr, map_param, key_param) {
2162 return false;
2163 }
2164 bound_name = Some(name);
2165 }
2166 Stmt::Expr(_) => return false,
2167 }
2168 }
2169 match last {
2170 Stmt::Expr(expr) => map_update_match_expr(expr, map_param, key_param, bound_name),
2171 Stmt::Binding(_, _, _) => false,
2172 }
2173}
2174
2175fn map_update_match_expr(
2176 expr: &Spanned<crate::ast::Expr>,
2177 map_param: &str,
2178 key_param: &str,
2179 bound_name: Option<&str>,
2180) -> bool {
2181 use crate::ast::Expr;
2182 let Expr::Match { subject, arms } = &expr.node else {
2183 return false;
2184 };
2185 if arms.len() < 2 {
2186 return false;
2187 }
2188 let subject_ok = match bound_name {
2189 Some(name) => matches_ident_expr(subject, name),
2190 None => is_map_get_of_params(subject, map_param, key_param),
2191 };
2192 if !subject_ok {
2193 return false;
2194 }
2195 arms.iter()
2196 .all(|arm| is_map_set_of_params(&arm.body, map_param, key_param))
2197}
2198
2199fn is_map_get_of_params(
2200 expr: &Spanned<crate::ast::Expr>,
2201 map_param: &str,
2202 key_param: &str,
2203) -> bool {
2204 let Some(args) = call_named_args(expr, "Map.get") else {
2205 return false;
2206 };
2207 args.len() == 2
2208 && matches_ident_expr(&args[0], map_param)
2209 && matches_ident_expr(&args[1], key_param)
2210}
2211
2212fn is_map_set_of_params(
2213 expr: &Spanned<crate::ast::Expr>,
2214 map_param: &str,
2215 key_param: &str,
2216) -> bool {
2217 let Some(args) = call_named_args(expr, "Map.set") else {
2218 return false;
2219 };
2220 args.len() == 3
2221 && matches_ident_expr(&args[0], map_param)
2222 && matches_ident_expr(&args[1], key_param)
2223}
2224
2225struct MapKeyTrackedIncrementPlan {
2226 outer_fn: String,
2227 map_arg: Spanned<crate::ast::Expr>,
2228 key_arg: Spanned<crate::ast::Expr>,
2229}
2230
2231fn detect_map_key_tracked_increment(
2237 law: &crate::ast::VerifyLaw,
2238 fn_name: &str,
2239 inputs: &ProofLowerInputs,
2240) -> Option<MapKeyTrackedIncrementPlan> {
2241 use crate::ast::{BinOp, Expr};
2242
2243 outer_fn_map_increment_shape(fn_name, inputs)?;
2244
2245 let side = |after: &Spanned<crate::ast::Expr>,
2246 rhs: &Spanned<crate::ast::Expr>|
2247 -> Option<MapKeyTrackedIncrementPlan> {
2248 let (map_arg, key_arg, default_arg) = defaulted_map_get_after_fn_call(after, fn_name)?;
2249 if !is_int_lit(default_arg, 0) {
2250 return None;
2251 }
2252 let Expr::BinOp(BinOp::Add, base, one) = &rhs.node else {
2253 return None;
2254 };
2255 if !is_int_lit(one, 1) {
2256 return None;
2257 }
2258 let (base_map, base_key, base_default) = defaulted_map_get(base)?;
2259 if map_arg.node != base_map.node
2260 || key_arg.node != base_key.node
2261 || default_arg.node != base_default.node
2262 {
2263 return None;
2264 }
2265 Some(MapKeyTrackedIncrementPlan {
2266 outer_fn: fn_name.to_string(),
2267 map_arg: map_arg.clone(),
2268 key_arg: key_arg.clone(),
2269 })
2270 };
2271 side(&law.lhs, &law.rhs).or_else(|| side(&law.rhs, &law.lhs))
2272}
2273
2274fn outer_fn_map_increment_shape(fn_name: &str, inputs: &ProofLowerInputs) -> Option<()> {
2285 use crate::ast::{BinOp, Expr, Pattern, Stmt};
2286
2287 let fd = inputs.find_fn_def_by_call_name(fn_name)?;
2288 if fd.params.len() != 2 {
2289 return None;
2290 }
2291 let map_param = fd.params[0].0.as_str();
2292 let key_param = fd.params[1].0.as_str();
2293 let stmts = fd.body.stmts();
2294 if stmts.len() != 2 {
2295 return None;
2296 }
2297 let Stmt::Binding(current, _, bound_expr) = &stmts[0] else {
2298 return None;
2299 };
2300 if !is_map_get_of_params(bound_expr, map_param, key_param) {
2301 return None;
2302 }
2303 let Stmt::Expr(last_expr) = &stmts[1] else {
2304 return None;
2305 };
2306 let Expr::Match { subject, arms, .. } = &last_expr.node else {
2307 return None;
2308 };
2309 if !matches_ident_expr(subject, current) || arms.len() != 2 {
2310 return None;
2311 }
2312
2313 let some_arm = arms.iter().find_map(|arm| match &arm.pattern {
2314 Pattern::Constructor(name, vars) if name == "Option.Some" && vars.len() == 1 => {
2315 Some((vars[0].as_str(), arm.body.as_ref()))
2316 }
2317 _ => None,
2318 })?;
2319 let none_arm = arms.iter().find_map(|arm| match &arm.pattern {
2320 Pattern::Constructor(name, vars) if name == "Option.None" && vars.is_empty() => {
2321 Some(arm.body.as_ref())
2322 }
2323 _ => None,
2324 })?;
2325
2326 let (some_bound, some_body) = some_arm;
2327 let some_set = call_named_args(some_body, "Map.set")?;
2328 let none_set = call_named_args(none_arm, "Map.set")?;
2329 if some_set.len() != 3 || none_set.len() != 3 {
2330 return None;
2331 }
2332 if !matches_ident_expr(&some_set[0], map_param)
2333 || !matches_ident_expr(&some_set[1], key_param)
2334 || !matches_ident_expr(&none_set[0], map_param)
2335 || !matches_ident_expr(&none_set[1], key_param)
2336 {
2337 return None;
2338 }
2339 let Expr::BinOp(BinOp::Add, add_left, add_right) = &some_set[2].node else {
2340 return None;
2341 };
2342 if !matches_ident_expr(add_left, some_bound) || !is_int_lit(add_right, 1) {
2343 return None;
2344 }
2345 if !is_int_lit(&none_set[2], 1) {
2346 return None;
2347 }
2348 Some(())
2349}
2350
2351fn defaulted_map_get_after_fn_call<'a>(
2355 expr: &'a Spanned<crate::ast::Expr>,
2356 fn_name: &str,
2357) -> Option<(
2358 &'a Spanned<crate::ast::Expr>,
2359 &'a Spanned<crate::ast::Expr>,
2360 &'a Spanned<crate::ast::Expr>,
2361)> {
2362 let (inner, default) = option_with_default_args(expr)?;
2363 let (map_arg, key_arg) = map_get_after_fn_call(inner, fn_name)?;
2364 Some((map_arg, key_arg, default))
2365}
2366
2367fn defaulted_map_get(
2370 expr: &Spanned<crate::ast::Expr>,
2371) -> Option<(
2372 &Spanned<crate::ast::Expr>,
2373 &Spanned<crate::ast::Expr>,
2374 &Spanned<crate::ast::Expr>,
2375)> {
2376 let (inner, default) = option_with_default_args(expr)?;
2377 let get_args = call_named_args(inner, "Map.get")?;
2378 if get_args.len() != 2 {
2379 return None;
2380 }
2381 Some((&get_args[0], &get_args[1], default))
2382}
2383
2384fn option_with_default_args(
2385 expr: &Spanned<crate::ast::Expr>,
2386) -> Option<(&Spanned<crate::ast::Expr>, &Spanned<crate::ast::Expr>)> {
2387 let args = call_named_args(expr, "Option.withDefault")?;
2388 (args.len() == 2).then_some((&args[0], &args[1]))
2389}
2390
2391fn is_int_lit(expr: &Spanned<crate::ast::Expr>, n: i64) -> bool {
2392 use crate::ast::{Expr, Literal};
2393 matches!(&expr.node, Expr::Literal(Literal::Int(m)) if *m == n)
2394}
2395
2396fn map_has_after_fn_call<'a>(
2400 expr: &'a Spanned<crate::ast::Expr>,
2401 fn_name: &str,
2402) -> Option<(&'a Spanned<crate::ast::Expr>, &'a Spanned<crate::ast::Expr>)> {
2403 use crate::ast::Expr;
2404 let has_args = call_named_args(expr, "Map.has")?;
2405 if has_args.len() != 2 {
2406 return None;
2407 }
2408 let Expr::FnCall(callee, fn_args) = &has_args[0].node else {
2409 return None;
2410 };
2411 if fn_args.len() != 2
2412 || !callee_matches_name(callee, fn_name)
2413 || fn_args[1].node != has_args[1].node
2414 {
2415 return None;
2416 }
2417 Some((&fn_args[0], &fn_args[1]))
2418}
2419
2420fn map_get_after_fn_call<'a>(
2423 expr: &'a Spanned<crate::ast::Expr>,
2424 fn_name: &str,
2425) -> Option<(&'a Spanned<crate::ast::Expr>, &'a Spanned<crate::ast::Expr>)> {
2426 use crate::ast::Expr;
2427 let get_args = call_named_args(expr, "Map.get")?;
2428 if get_args.len() != 2 {
2429 return None;
2430 }
2431 let Expr::FnCall(callee, fn_args) = &get_args[0].node else {
2432 return None;
2433 };
2434 if fn_args.len() != 2
2435 || !callee_matches_name(callee, fn_name)
2436 || fn_args[1].node != get_args[1].node
2437 {
2438 return None;
2439 }
2440 Some((&fn_args[0], &fn_args[1]))
2441}
2442
2443fn map_has_set_parts(
2444 expr: &Spanned<crate::ast::Expr>,
2445) -> Option<(
2446 &Spanned<crate::ast::Expr>,
2447 &Spanned<crate::ast::Expr>,
2448 &Spanned<crate::ast::Expr>,
2449)> {
2450 let has_args = call_named_args(expr, "Map.has")?;
2451 if has_args.len() != 2 {
2452 return None;
2453 }
2454 let set_args = call_named_args(&has_args[0], "Map.set")?;
2455 if set_args.len() != 3 {
2456 return None;
2457 }
2458 if set_args[1].node != has_args[1].node {
2459 return None;
2460 }
2461 Some((&set_args[0], &set_args[1], &set_args[2]))
2462}
2463
2464fn map_get_set_parts(
2465 expr: &Spanned<crate::ast::Expr>,
2466) -> Option<(
2467 &Spanned<crate::ast::Expr>,
2468 &Spanned<crate::ast::Expr>,
2469 &Spanned<crate::ast::Expr>,
2470)> {
2471 let get_args = call_named_args(expr, "Map.get")?;
2472 if get_args.len() != 2 {
2473 return None;
2474 }
2475 let set_args = call_named_args(&get_args[0], "Map.set")?;
2476 if set_args.len() != 3 {
2477 return None;
2478 }
2479 if set_args[1].node != get_args[1].node {
2480 return None;
2481 }
2482 Some((&set_args[0], &set_args[1], &set_args[2]))
2483}
2484
2485fn option_some_arg(expr: &Spanned<crate::ast::Expr>) -> Option<&Spanned<crate::ast::Expr>> {
2486 let args = call_named_args(expr, "Option.Some")?;
2487 (args.len() == 1).then_some(&args[0])
2488}
2489
2490fn call_named_args<'a>(
2496 expr: &'a Spanned<crate::ast::Expr>,
2497 full_name: &str,
2498) -> Option<&'a [Spanned<crate::ast::Expr>]> {
2499 use crate::ast::Expr;
2500 let Expr::FnCall(callee, args) = &expr.node else {
2501 return None;
2502 };
2503 let callee_name = expr_to_dotted_name(&callee.node)?;
2504 if callee_name == full_name {
2505 Some(args.as_slice())
2506 } else {
2507 None
2508 }
2509}
2510
2511fn is_bool_true(expr: &Spanned<crate::ast::Expr>) -> bool {
2512 use crate::ast::{Expr, Literal};
2513 matches!(&expr.node, Expr::Literal(Literal::Bool(true)))
2514}
2515
2516fn detect_induction_target(
2527 law: &crate::ast::VerifyLaw,
2528 inputs: &ProofLowerInputs,
2529) -> Option<String> {
2530 use crate::ast::TypeDef;
2531 for given in &law.givens {
2532 let Some(TypeDef::Sum {
2533 name: type_name,
2534 variants,
2535 ..
2536 }) = inputs.find_type_def(&given.type_name)
2537 else {
2538 continue;
2539 };
2540 let direct_rec = variants.iter().any(|variant| {
2544 variant.fields.iter().any(|field| {
2545 let f = field.trim();
2546 f == type_name
2547 || f.contains(&format!("<{}", type_name))
2548 || f.contains(&format!("{}>", type_name))
2549 || f.contains(&format!(", {}", type_name))
2550 || f.contains(&format!("{},", type_name))
2551 })
2552 });
2553 if !direct_rec {
2554 continue;
2555 }
2556 if has_indirect_rec_variants(variants, type_name) {
2559 continue;
2560 }
2561 return Some(given.name.clone());
2562 }
2563 None
2564}
2565
2566fn has_indirect_rec_variants(variants: &[crate::ast::TypeVariant], type_name: &str) -> bool {
2573 for variant in variants {
2574 for field in &variant.fields {
2575 let f = field.trim();
2576 if f == type_name {
2578 continue;
2579 }
2580 let opens = f.matches('<').count();
2583 if opens > 1 && f.contains(type_name) {
2584 return true;
2585 }
2586 }
2587 }
2588 false
2589}
2590
2591fn wrapper_binop(fn_name: &str, inputs: &ProofLowerInputs) -> Option<crate::ast::BinOp> {
2597 use crate::ast::{BinOp, Expr};
2598
2599 let fd = inputs.find_fn_def_by_call_name(fn_name)?;
2600 if fd.params.len() != 2 || fd.return_type != "Int" {
2601 return None;
2602 }
2603 let (p1, t1) = &fd.params[0];
2604 let (p2, t2) = &fd.params[1];
2605 if t1 != "Int" || t2 != "Int" {
2606 return None;
2607 }
2608 let expr = body_terminal_expr(fd.body.as_ref())?;
2609 let Expr::BinOp(op, left, right) = &expr.node else {
2610 return None;
2611 };
2612 if !matches_ident_expr(left, p1) || !matches_ident_expr(right, p2) {
2613 return None;
2614 }
2615 matches!(op, BinOp::Add | BinOp::Mul | BinOp::Sub).then_some(*op)
2616}
2617
2618fn detect_wrapper_commutative(
2619 law: &crate::ast::VerifyLaw,
2620 fn_name: &str,
2621 _op: crate::ast::BinOp,
2622) -> bool {
2623 if law.givens.len() != 2 || law.givens.iter().any(|g| g.type_name != "Int") {
2624 return false;
2625 }
2626 let a = &law.givens[0].name;
2627 let b = &law.givens[1].name;
2628 matches_binary_call(&law.lhs, fn_name, a, b) && matches_binary_call(&law.rhs, fn_name, b, a)
2629 || matches_binary_call(&law.lhs, fn_name, b, a)
2630 && matches_binary_call(&law.rhs, fn_name, a, b)
2631}
2632
2633fn detect_wrapper_associative(
2634 law: &crate::ast::VerifyLaw,
2635 fn_name: &str,
2636 _op: crate::ast::BinOp,
2637) -> bool {
2638 if law.givens.len() != 3 || law.givens.iter().any(|g| g.type_name != "Int") {
2639 return false;
2640 }
2641 let a = &law.givens[0].name;
2642 let b = &law.givens[1].name;
2643 let c = &law.givens[2].name;
2644 let nested = |side| matches_assoc_nested(side, fn_name, a, b, c);
2645 let flat = |side| matches_assoc_flat(side, fn_name, a, b, c);
2646 (nested(&law.lhs) && flat(&law.rhs)) || (nested(&law.rhs) && flat(&law.lhs))
2647}
2648
2649fn detect_wrapper_unary_equivalence(
2655 law: &crate::ast::VerifyLaw,
2656 fn_name: &str,
2657 inputs: &ProofLowerInputs,
2658) -> Option<String> {
2659 if law.givens.len() != 1 || law.givens[0].type_name != "Int" {
2660 return None;
2661 }
2662 let unary = unary_int_wrapper(fn_name, inputs)?;
2663 let g = &law.givens[0].name;
2664
2665 let try_side = |call_side: &Spanned<crate::ast::Expr>,
2666 other_side: &Spanned<crate::ast::Expr>|
2667 -> Option<String> {
2668 if !matches_unary_call(call_side, fn_name, g) {
2669 return None;
2670 }
2671 let (callee_name, var_first, lit) = binary_call_var_const(other_side, g)?;
2672 if lit != unary.constant || var_first != unary.var_first {
2673 return None;
2674 }
2675 let inner_op = wrapper_binop(&callee_name, inputs)?;
2676 if inner_op != unary.op {
2677 return None;
2678 }
2679 Some(callee_name)
2680 };
2681 try_side(&law.lhs, &law.rhs).or_else(|| try_side(&law.rhs, &law.lhs))
2682}
2683
2684#[derive(Debug, Clone, Copy)]
2685struct UnaryIntWrapper {
2686 op: crate::ast::BinOp,
2687 constant: i64,
2688 var_first: bool,
2689}
2690
2691fn unary_int_wrapper(fn_name: &str, inputs: &ProofLowerInputs) -> Option<UnaryIntWrapper> {
2694 use crate::ast::{Expr, Literal};
2695
2696 let fd = inputs.find_fn_def_by_call_name(fn_name)?;
2697 if fd.params.len() != 1 || fd.return_type != "Int" {
2698 return None;
2699 }
2700 let (param, param_ty) = &fd.params[0];
2701 if param_ty != "Int" {
2702 return None;
2703 }
2704 let expr = body_terminal_expr(fd.body.as_ref())?;
2705 let Expr::BinOp(op, left, right) = &expr.node else {
2706 return None;
2707 };
2708 let lit_of = |e: &Spanned<Expr>| -> Option<i64> {
2709 match &e.node {
2710 Expr::Literal(Literal::Int(n)) => Some(*n),
2711 _ => None,
2712 }
2713 };
2714 if matches_ident_expr(left, param) {
2715 let n = lit_of(right)?;
2716 return Some(UnaryIntWrapper {
2717 op: *op,
2718 constant: n,
2719 var_first: true,
2720 });
2721 }
2722 if matches_ident_expr(right, param) {
2723 let n = lit_of(left)?;
2724 return Some(UnaryIntWrapper {
2725 op: *op,
2726 constant: n,
2727 var_first: false,
2728 });
2729 }
2730 None
2731}
2732
2733fn matches_unary_call(expr: &Spanned<crate::ast::Expr>, fn_name: &str, arg: &str) -> bool {
2734 use crate::ast::Expr;
2735 let Expr::FnCall(callee, args) = &expr.node else {
2736 return false;
2737 };
2738 args.len() == 1 && callee_matches_name(callee, fn_name) && matches_ident_expr(&args[0], arg)
2739}
2740
2741fn binary_call_var_const(
2751 expr: &Spanned<crate::ast::Expr>,
2752 var_name: &str,
2753) -> Option<(String, bool, i64)> {
2754 use crate::ast::{Expr, Literal};
2755 let Expr::FnCall(callee, args) = &expr.node else {
2756 return None;
2757 };
2758 if args.len() != 2 {
2759 return None;
2760 }
2761 let callee_name = expr_to_dotted_name(&callee.node)?;
2762 match (&args[0].node, &args[1].node) {
2763 (Expr::Ident(v) | Expr::Resolved { name: v, .. }, Expr::Literal(Literal::Int(n)))
2764 if v == var_name =>
2765 {
2766 Some((callee_name, true, *n))
2767 }
2768 (Expr::Literal(Literal::Int(n)), Expr::Ident(v) | Expr::Resolved { name: v, .. })
2769 if v == var_name =>
2770 {
2771 Some((callee_name, false, *n))
2772 }
2773 _ => None,
2774 }
2775}
2776
2777fn detect_wrapper_sub_right_identity(law: &crate::ast::VerifyLaw, fn_name: &str) -> bool {
2778 if law.givens.len() != 1 || law.givens[0].type_name != "Int" {
2779 return false;
2780 }
2781 let g = &law.givens[0].name;
2782 matches_sub_right_identity_side(&law.lhs, &law.rhs, fn_name, g)
2783 || matches_sub_right_identity_side(&law.rhs, &law.lhs, fn_name, g)
2784}
2785
2786fn detect_wrapper_sub_anti_commutative(law: &crate::ast::VerifyLaw, fn_name: &str) -> Option<bool> {
2791 if law.givens.len() != 2 || law.givens.iter().any(|g| g.type_name != "Int") {
2792 return None;
2793 }
2794 let a = &law.givens[0].name;
2795 let b = &law.givens[1].name;
2796 if matches_binary_call(&law.lhs, fn_name, a, b)
2797 && matches_neg_binary_call(&law.rhs, fn_name, b, a)
2798 {
2799 return Some(true);
2800 }
2801 if matches_binary_call(&law.rhs, fn_name, a, b)
2802 && matches_neg_binary_call(&law.lhs, fn_name, b, a)
2803 {
2804 return Some(false);
2805 }
2806 None
2807}
2808
2809fn detect_wrapper_identity(
2810 law: &crate::ast::VerifyLaw,
2811 fn_name: &str,
2812 op: crate::ast::BinOp,
2813) -> bool {
2814 if law.givens.len() != 1 || law.givens[0].type_name != "Int" {
2815 return false;
2816 }
2817 let identity = match op {
2818 crate::ast::BinOp::Add => 0,
2819 crate::ast::BinOp::Mul => 1,
2820 _ => return false,
2821 };
2822 let g = &law.givens[0].name;
2823 matches_identity_side(&law.lhs, &law.rhs, fn_name, g, identity)
2824 || matches_identity_side(&law.rhs, &law.lhs, fn_name, g, identity)
2825}
2826
2827fn body_terminal_expr(body: &crate::ast::FnBody) -> Option<&Spanned<crate::ast::Expr>> {
2836 use crate::ast::Stmt;
2837 match body.stmts() {
2838 [Stmt::Expr(expr)] => Some(expr),
2839 _ => None,
2840 }
2841}
2842
2843fn simplify_identity_expr(expr: &Spanned<crate::ast::Expr>) -> Spanned<crate::ast::Expr> {
2851 use crate::ast::{BinOp, Expr, Literal};
2852 let line = expr.line;
2853 let int_lit = |e: &Expr| -> Option<i64> {
2854 match e {
2855 Expr::Literal(Literal::Int(n)) => Some(*n),
2856 _ => None,
2857 }
2858 };
2859 let new_node = match &expr.node {
2860 Expr::BinOp(op, left, right) => {
2861 let left = simplify_identity_expr(left);
2862 let right = simplify_identity_expr(right);
2863 match op {
2864 BinOp::Add => {
2865 if int_lit(&left.node) == Some(0) {
2866 return right;
2867 } else if int_lit(&right.node) == Some(0) {
2868 return left;
2869 } else {
2870 Expr::BinOp(*op, Box::new(left), Box::new(right))
2871 }
2872 }
2873 BinOp::Sub => {
2874 if int_lit(&right.node) == Some(0) {
2875 return left;
2876 } else {
2877 Expr::BinOp(*op, Box::new(left), Box::new(right))
2878 }
2879 }
2880 BinOp::Mul => {
2881 if int_lit(&left.node) == Some(0) || int_lit(&right.node) == Some(0) {
2882 Expr::Literal(Literal::Int(0))
2883 } else if int_lit(&left.node) == Some(1) {
2884 return right;
2885 } else if int_lit(&right.node) == Some(1) {
2886 return left;
2887 } else {
2888 Expr::BinOp(*op, Box::new(left), Box::new(right))
2889 }
2890 }
2891 _ => Expr::BinOp(*op, Box::new(left), Box::new(right)),
2892 }
2893 }
2894 Expr::Neg(inner) => Expr::Neg(Box::new(simplify_identity_expr(inner))),
2895 Expr::Attr(base, field) => {
2896 Expr::Attr(Box::new(simplify_identity_expr(base)), field.clone())
2897 }
2898 Expr::FnCall(callee, args) => Expr::FnCall(
2899 Box::new(simplify_identity_expr(callee)),
2900 args.iter().map(simplify_identity_expr).collect(),
2901 ),
2902 Expr::Match { subject, arms } => Expr::Match {
2903 subject: Box::new(simplify_identity_expr(subject)),
2904 arms: arms
2905 .iter()
2906 .map(|arm| crate::ast::MatchArm {
2907 pattern: arm.pattern.clone(),
2908 body: Box::new(simplify_identity_expr(&arm.body)),
2909 binding_slots: arm.binding_slots.clone(),
2910 })
2911 .collect(),
2912 },
2913 other => other.clone(),
2914 };
2915 Spanned::new(new_node, line)
2916}
2917
2918fn matches_ident_expr(expr: &Spanned<crate::ast::Expr>, name: &str) -> bool {
2924 use crate::ast::Expr;
2925 matches!(&expr.node, Expr::Ident(n) | Expr::Resolved { name: n, .. } if n == name)
2926}
2927
2928fn callee_matches_name(expr: &Spanned<crate::ast::Expr>, target: &str) -> bool {
2937 let Some(name) = expr_to_dotted_name(&expr.node) else {
2938 return false;
2939 };
2940 name == target
2941}
2942
2943fn call2_args<'a>(
2944 expr: &'a Spanned<crate::ast::Expr>,
2945 fn_name: &str,
2946) -> Option<(&'a Spanned<crate::ast::Expr>, &'a Spanned<crate::ast::Expr>)> {
2947 use crate::ast::Expr;
2948 let Expr::FnCall(callee, args) = &expr.node else {
2949 return None;
2950 };
2951 if args.len() != 2 || !callee_matches_name(callee, fn_name) {
2952 return None;
2953 }
2954 Some((&args[0], &args[1]))
2955}
2956
2957fn matches_binary_call(expr: &Spanned<crate::ast::Expr>, fn_name: &str, a: &str, b: &str) -> bool {
2958 let Some((x, y)) = call2_args(expr, fn_name) else {
2959 return false;
2960 };
2961 matches_ident_expr(x, a) && matches_ident_expr(y, b)
2962}
2963
2964fn matches_assoc_nested(
2965 expr: &Spanned<crate::ast::Expr>,
2966 fn_name: &str,
2967 a: &str,
2968 b: &str,
2969 c: &str,
2970) -> bool {
2971 let Some((ab, z)) = call2_args(expr, fn_name) else {
2972 return false;
2973 };
2974 let Some((x, y)) = call2_args(ab, fn_name) else {
2975 return false;
2976 };
2977 matches_ident_expr(x, a) && matches_ident_expr(y, b) && matches_ident_expr(z, c)
2978}
2979
2980fn matches_assoc_flat(
2981 expr: &Spanned<crate::ast::Expr>,
2982 fn_name: &str,
2983 a: &str,
2984 b: &str,
2985 c: &str,
2986) -> bool {
2987 let Some((x, bc)) = call2_args(expr, fn_name) else {
2988 return false;
2989 };
2990 let Some((y, z)) = call2_args(bc, fn_name) else {
2991 return false;
2992 };
2993 matches_ident_expr(x, a) && matches_ident_expr(y, b) && matches_ident_expr(z, c)
2994}
2995
2996fn matches_sub_right_identity_side(
2997 call_side: &Spanned<crate::ast::Expr>,
2998 ident_side: &Spanned<crate::ast::Expr>,
2999 fn_name: &str,
3000 given_name: &str,
3001) -> bool {
3002 use crate::ast::{Expr, Literal};
3003 if !matches_ident_expr(ident_side, given_name) {
3004 return false;
3005 }
3006 let Some((x, y)) = call2_args(call_side, fn_name) else {
3007 return false;
3008 };
3009 matches_ident_expr(x, given_name)
3010 && matches!(&y.node, Expr::Literal(Literal::Int(n)) if *n == 0)
3011}
3012
3013fn matches_neg_binary_call(
3014 expr: &Spanned<crate::ast::Expr>,
3015 fn_name: &str,
3016 a: &str,
3017 b: &str,
3018) -> bool {
3019 use crate::ast::Expr;
3020 match &expr.node {
3021 Expr::Neg(inner) => matches_binary_call(inner, fn_name, a, b),
3022 _ => false,
3023 }
3024}
3025
3026fn matches_identity_side(
3027 call_side: &Spanned<crate::ast::Expr>,
3028 ident_side: &Spanned<crate::ast::Expr>,
3029 fn_name: &str,
3030 given_name: &str,
3031 identity: i64,
3032) -> bool {
3033 use crate::ast::{Expr, Literal};
3034 if !matches_ident_expr(ident_side, given_name) {
3035 return false;
3036 }
3037 let Some((x, y)) = call2_args(call_side, fn_name) else {
3038 return false;
3039 };
3040 let is_int_lit = |e: &Spanned<Expr>, n: i64| -> bool {
3041 matches!(&e.node, Expr::Literal(Literal::Int(m)) if *m == n)
3042 };
3043 (matches_ident_expr(x, given_name) && is_int_lit(y, identity))
3044 || (is_int_lit(x, identity) && matches_ident_expr(y, given_name))
3045}
3046
3047fn pick_witness(
3055 type_name: &str,
3056 type_id: crate::ir::TypeId,
3057 inputs: &ProofLowerInputs,
3058 predicate: &Spanned<Expr>,
3059 param_name: &str,
3060 scope: Option<&str>,
3061) -> Option<String> {
3062 let smart_ctor_name: Option<String> = match scope {
3070 None => inputs.entry_items.iter().find_map(|item| match item {
3071 TopLevel::FnDef(fd)
3072 if smart_ctor_matches(fd, type_id, type_name, inputs.symbol_table, scope) =>
3073 {
3074 Some(fd.name.clone())
3075 }
3076 _ => None,
3077 }),
3078 Some(prefix) => inputs
3079 .dep_modules
3080 .iter()
3081 .find(|m| m.prefix == prefix)
3082 .and_then(|m| {
3083 m.fn_defs
3084 .iter()
3085 .find(|fd| {
3086 smart_ctor_matches(fd, type_id, type_name, inputs.symbol_table, scope)
3087 })
3088 .map(|fd| fd.name.clone())
3089 }),
3090 };
3091 if let Some(smart_ctor_name) = smart_ctor_name {
3092 if scope.is_none() {
3100 for item in inputs.entry_items {
3101 let TopLevel::Verify(vb) = item else {
3102 continue;
3103 };
3104 if vb.fn_name != smart_ctor_name {
3105 continue;
3106 }
3107 for (lhs, rhs) in &vb.cases {
3108 if !is_result_ok(&rhs.node) {
3109 continue;
3110 }
3111 let Expr::FnCall(_, args) = &lhs.node else {
3112 continue;
3113 };
3114 if args.len() != 1 {
3115 continue;
3116 }
3117 if let Some(lit) = literal_int_value(&args[0]) {
3118 return Some(lit);
3119 }
3120 }
3121 }
3122 }
3123 }
3124 let mut tried = std::collections::HashSet::<i64>::new();
3135 let mut candidates: Vec<i64> = Vec::new();
3136 let mut from_ast: Vec<i64> = Vec::new();
3137 collect_int_literals(predicate, &mut from_ast);
3138 for k in from_ast {
3139 for delta in &[0_i64, 1, -1] {
3140 if let Some(c) = k.checked_add(*delta) {
3141 candidates.push(c);
3142 }
3143 }
3144 }
3145 candidates.extend_from_slice(&[
3146 0, 1, -1, 2, -2, 10, -10, 100, 1_000, 10_000, 100_000, 1_000_000,
3147 ]);
3148 for candidate in candidates {
3149 if !tried.insert(candidate) {
3150 continue;
3151 }
3152 if eval_int_bool_predicate(predicate, param_name, candidate) == Some(true) {
3153 return Some(candidate.to_string());
3154 }
3155 }
3156 None
3157}
3158
3159fn collect_int_literals(expr: &Spanned<Expr>, out: &mut Vec<i64>) {
3160 match &expr.node {
3161 Expr::Literal(Literal::Int(n)) => out.push(*n),
3162 Expr::Neg(inner) => {
3163 if let Expr::Literal(Literal::Int(n)) = &inner.node {
3164 out.push(-n);
3165 } else {
3166 collect_int_literals(inner, out);
3167 }
3168 }
3169 Expr::BinOp(_, l, r) => {
3170 collect_int_literals(l, out);
3171 collect_int_literals(r, out);
3172 }
3173 Expr::FnCall(callee, args) => {
3174 collect_int_literals(callee, out);
3175 for a in args {
3176 collect_int_literals(a, out);
3177 }
3178 }
3179 Expr::Match { subject, arms } => {
3180 collect_int_literals(subject, out);
3181 for arm in arms {
3182 collect_int_literals(&arm.body, out);
3183 }
3184 }
3185 Expr::Attr(o, _) | Expr::ErrorProp(o) => collect_int_literals(o, out),
3186 _ => {}
3187 }
3188}
3189
3190fn smart_ctor_matches(
3208 fd: &FnDef,
3209 type_id: crate::ir::TypeId,
3210 type_name: &str,
3211 symbols: &crate::ir::SymbolTable,
3212 scope: Option<&str>,
3213) -> bool {
3214 if fd.params.len() != 1 {
3215 return false;
3216 }
3217 let parsed = crate::types::parse_type_str(&fd.return_type);
3218 let crate::types::Type::Result(ok, _) = parsed else {
3219 return false;
3220 };
3221 let crate::types::Type::Named { name: n, .. } = &*ok else {
3227 return false;
3228 };
3229 let name_is_qualified = n.contains('.');
3234 let resolved_id = if name_is_qualified {
3235 n.rsplit_once('.').and_then(|(prefix, bare)| {
3236 symbols.type_id_of(&crate::ir::TypeKey::in_module(prefix.to_string(), bare))
3237 })
3238 } else if let Some(prefix) = scope {
3239 symbols
3240 .type_id_of(&crate::ir::TypeKey::in_module(
3241 prefix.to_string(),
3242 n.clone(),
3243 ))
3244 .or_else(|| symbols.type_id_of(&crate::ir::TypeKey::entry(n.clone())))
3245 } else {
3246 symbols.type_id_of(&crate::ir::TypeKey::entry(n.clone()))
3247 };
3248 match resolved_id {
3249 Some(id) => id == type_id,
3250 None => n == type_name,
3251 }
3252}
3253
3254fn is_result_ok(expr: &Expr) -> bool {
3255 match expr {
3256 Expr::Constructor(name, _) => name == "Result.Ok",
3257 Expr::FnCall(callee, _) => matches!(
3258 &callee.node,
3259 Expr::Attr(obj, field)
3260 if field == "Ok" && matches!(&obj.node, Expr::Ident(n) if n == "Result")
3261 ),
3262 _ => false,
3263 }
3264}
3265
3266fn literal_int_value(expr: &Spanned<Expr>) -> Option<String> {
3267 match &expr.node {
3268 Expr::Literal(Literal::Int(n)) => Some(n.to_string()),
3269 Expr::Neg(inner) => {
3270 let inner_str = literal_int_value(inner)?;
3271 Some(format!("-{inner_str}"))
3272 }
3273 _ => None,
3274 }
3275}
3276
3277fn eval_int_bool_predicate(expr: &Spanned<Expr>, param_name: &str, value: i64) -> Option<bool> {
3278 match &expr.node {
3279 Expr::Literal(Literal::Bool(b)) => Some(*b),
3280 Expr::BinOp(op, l, r) => {
3281 use crate::ast::BinOp::*;
3282 let li = eval_int_arith(l, param_name, value)?;
3283 let ri = eval_int_arith(r, param_name, value)?;
3284 Some(match op {
3285 Lt => li < ri,
3286 Gt => li > ri,
3287 Lte => li <= ri,
3288 Gte => li >= ri,
3289 Eq => li == ri,
3290 Neq => li != ri,
3291 _ => return None,
3292 })
3293 }
3294 Expr::FnCall(callee, args) if args.len() == 2 => {
3295 let name = expr_to_dotted_name(&callee.node)?;
3296 match name.as_str() {
3297 "Bool.and" => Some(
3298 eval_int_bool_predicate(&args[0], param_name, value)?
3299 && eval_int_bool_predicate(&args[1], param_name, value)?,
3300 ),
3301 "Bool.or" => Some(
3302 eval_int_bool_predicate(&args[0], param_name, value)?
3303 || eval_int_bool_predicate(&args[1], param_name, value)?,
3304 ),
3305 _ => None,
3306 }
3307 }
3308 _ => None,
3309 }
3310}
3311
3312fn eval_int_arith(expr: &Spanned<Expr>, param_name: &str, value: i64) -> Option<i64> {
3313 match &expr.node {
3314 Expr::Literal(Literal::Int(n)) => Some(*n),
3315 Expr::Ident(name) | Expr::Resolved { name, .. } if name == param_name => Some(value),
3316 Expr::BinOp(op, l, r) => {
3317 use crate::ast::BinOp::*;
3318 let li = eval_int_arith(l, param_name, value)?;
3319 let ri = eval_int_arith(r, param_name, value)?;
3320 match op {
3321 Add => Some(li.checked_add(ri)?),
3322 Sub => Some(li.checked_sub(ri)?),
3323 Mul => Some(li.checked_mul(ri)?),
3324 Div => Some(li.checked_div(ri)?),
3325 _ => None,
3326 }
3327 }
3328 Expr::Neg(inner) => Some(-eval_int_arith(inner, param_name, value)?),
3329 _ => None,
3330 }
3331}