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 pub program_shape: Option<&'a crate::analysis::shape::ProgramShape>,
126}
127
128impl<'a> ProofLowerInputs<'a> {
129 pub fn from_ctx(ctx: &'a CodegenContext) -> Self {
134 Self {
135 entry_items: &ctx.items,
136 dep_modules: &ctx.modules,
137 module_prefixes: &ctx.module_prefixes,
138 recursive_fns: &ctx.recursive_fns,
139 symbol_table: &ctx.symbol_table,
140 program_shape: ctx.program_shape.as_ref(),
141 }
142 }
143
144 pub fn pure_fns(&self) -> Vec<&'a FnDef> {
149 self.dep_modules
155 .iter()
156 .flat_map(|m| m.fn_defs.iter())
157 .chain(self.entry_items.iter().filter_map(|item| match item {
158 TopLevel::FnDef(fd) => Some(fd),
159 _ => None,
160 }))
161 .filter(|fd| crate::codegen::common::is_pure_fn(fd))
162 .collect()
163 }
164
165 pub fn recursive_pure_fn_names(&self) -> HashSet<String> {
170 let symbols = self.symbol_table;
171 let pure_ids: HashSet<crate::ir::FnId> = self
172 .pure_fns()
173 .into_iter()
174 .filter_map(|fd| {
175 let scope = self
176 .dep_modules
177 .iter()
178 .find(|m| m.fn_defs.iter().any(|d| std::ptr::eq(d, fd)))
179 .map(|m| m.prefix.as_str());
180 let key = match scope {
186 Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), &fd.name),
187 None => crate::ir::FnKey::entry(&fd.name),
188 };
189 symbols.fn_id_of(&key)
190 })
191 .collect();
192 self.recursive_fns
193 .intersection(&pure_ids)
194 .map(|id| symbols.fn_entry(*id).key.name.clone())
195 .collect()
196 }
197
198 pub fn pure_fns_in_scope(&self, scope: Option<&str>) -> Vec<&'a FnDef> {
206 match scope {
207 None => self
208 .entry_items
209 .iter()
210 .filter_map(|item| match item {
211 TopLevel::FnDef(fd) => Some(fd),
212 _ => None,
213 })
214 .filter(|fd| crate::codegen::common::is_pure_fn(fd))
215 .collect(),
216 Some(prefix) => self
217 .dep_modules
218 .iter()
219 .filter(|m| m.prefix == prefix)
220 .flat_map(|m| m.fn_defs.iter())
221 .filter(|fd| crate::codegen::common::is_pure_fn(fd))
222 .collect(),
223 }
224 }
225
226 pub fn recursive_pure_fn_names_in_scope(&self, scope: Option<&str>) -> HashSet<String> {
232 let symbols = self.symbol_table;
233 let pure_ids: HashSet<crate::ir::FnId> = self
234 .pure_fns_in_scope(scope)
235 .into_iter()
236 .filter_map(|fd| {
237 let key = match scope {
244 Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), &fd.name),
245 None => crate::ir::FnKey::entry(&fd.name),
246 };
247 symbols.fn_id_of(&key)
248 })
249 .collect();
250 self.recursive_fns
251 .intersection(&pure_ids)
252 .map(|id| symbols.fn_entry(*id).key.name.clone())
253 .collect()
254 }
255
256 pub fn scopes(&self) -> Vec<Option<String>> {
259 let mut out = vec![None];
260 for m in self.dep_modules {
261 out.push(Some(m.prefix.clone()));
262 }
263 out
264 }
265
266 pub fn fn_owning_scope(&self, fd: &FnDef) -> Option<&'a str> {
271 for m in self.dep_modules {
272 for f in &m.fn_defs {
273 if std::ptr::eq(f, fd) {
274 return Some(m.prefix.as_str());
275 }
276 }
277 }
278 None
279 }
280
281 pub fn resolve_expr(
290 &self,
291 expr: &crate::ast::Spanned<crate::ast::Expr>,
292 scope: Option<&str>,
293 ) -> crate::ast::Spanned<crate::ir::hir::ResolvedExpr> {
294 use crate::ir::hir::{ResolveCtx, ResolvedStmt};
295 let mut rctx = ResolveCtx::new(self.symbol_table);
296 rctx.current_module = scope.map(String::from);
297 let stmt = crate::ast::Stmt::Expr(expr.clone());
298 match crate::ir::hir::resolve::resolve_stmt_external(&rctx, &stmt) {
299 ResolvedStmt::Expr(s) => s,
300 ResolvedStmt::Binding { value, .. } => value,
301 }
302 }
303
304 pub fn recursive_type_names(&self) -> HashSet<String> {
306 self.entry_items
307 .iter()
308 .filter_map(|item| match item {
309 TopLevel::TypeDef(td) => Some(td),
310 _ => None,
311 })
312 .chain(self.dep_modules.iter().flat_map(|m| m.type_defs.iter()))
313 .filter(|td| crate::codegen::common::is_recursive_type_def(td))
314 .map(|td| crate::codegen::common::type_def_name(td).to_string())
315 .collect()
316 }
317
318 pub fn find_fn_def_by_call_name(&self, call_name: &str) -> Option<&'a FnDef> {
322 let find_exact = |name: &str| -> Option<&'a FnDef> {
323 self.dep_modules
324 .iter()
325 .flat_map(|m| m.fn_defs.iter())
326 .chain(self.entry_items.iter().filter_map(|item| match item {
327 TopLevel::FnDef(fd) => Some(fd),
328 _ => None,
329 }))
330 .find(|fd| fd.name == name)
331 };
332 find_exact(call_name).or_else(|| {
333 let short = call_name.rsplit('.').next()?;
334 find_exact(short)
335 })
336 }
337
338 pub fn find_type_def(&self, type_name: &str) -> Option<&'a TypeDef> {
341 self.entry_items
342 .iter()
343 .filter_map(|item| match item {
344 TopLevel::TypeDef(td) => Some(td),
345 _ => None,
346 })
347 .chain(self.dep_modules.iter().flat_map(|m| m.type_defs.iter()))
348 .find(|td| crate::codegen::common::type_def_name(td) == type_name)
349 }
350}
351
352pub fn lower(inputs: &ProofLowerInputs) -> ProofIR {
357 let mut ir = ProofIR::default();
358 populate_refined_types(inputs, &mut ir);
359 populate_fn_contracts(inputs, &mut ir);
360 populate_law_theorems(inputs, &mut ir);
361 ir
362}
363
364pub fn populate_refined_types(inputs: &ProofLowerInputs, ir: &mut ProofIR) {
370 let symbols = inputs.symbol_table;
386
387 let entry_typedefs = inputs.entry_items.iter().filter_map(|item| match item {
388 TopLevel::TypeDef(td) => Some((None::<&str>, td)),
389 _ => None,
390 });
391 let module_typedefs = inputs.dep_modules.iter().flat_map(|m| {
392 m.type_defs
393 .iter()
394 .map(move |td| (Some(m.prefix.as_str()), td))
395 });
396
397 for (module_prefix, td) in entry_typedefs.chain(module_typedefs) {
398 let TypeDef::Product { name, fields, .. } = td else {
399 continue;
400 };
401 if fields.len() != 1 {
402 continue;
403 }
404 let type_key = match module_prefix {
405 Some(prefix) => crate::ir::TypeKey::in_module(prefix.to_string(), name),
406 None => crate::ir::TypeKey::entry(name),
407 };
408 let Some(canonical_key) = symbols.type_id_of(&type_key) else {
409 continue;
414 };
415 if ir.refined_types.contains_key(&canonical_key) {
416 continue;
421 }
422 let Some(info) =
430 crate::codegen::common::refinement_info_for_in_scope(name, inputs, module_prefix)
431 else {
432 continue;
433 };
434 let invariant = Predicate {
435 free_vars: vec![(
436 info.param_name.to_string(),
437 crate::ir::proof_ir::QuantifierType::Plain(info.carrier_type.to_string()),
438 )],
439 expr: inputs.resolve_expr(info.predicate, module_prefix),
440 };
441 let witness = pick_witness(
442 name,
443 canonical_key,
444 inputs,
445 info.predicate,
446 info.param_name,
447 module_prefix,
448 );
449 let Some(witness) = witness else {
458 continue;
459 };
460 ir.refined_types.insert(
461 canonical_key,
462 RefinedTypeDecl {
463 name: name.clone(),
464 carrier_type: info.carrier_type.to_string(),
465 carrier_field: info.carrier_field.to_string(),
466 predicate_param: info.param_name.to_string(),
467 invariant,
468 witness: Some(witness),
469 },
470 );
471 }
472}
473
474pub fn populate_fn_contracts(inputs: &ProofLowerInputs, ir: &mut ProofIR) {
486 for scope in inputs.scopes() {
493 let (plans, issues) =
494 crate::codegen::recursion::analyze_plans_in_scope(inputs, scope.as_deref(), false);
495 ir.unclassified_fns
496 .extend(issues.into_iter().map(|issue| crate::ir::UnclassifiedFn {
497 line: issue.line,
498 message: issue.message,
499 }));
500 populate_fn_contracts_for_scope(inputs, ir, scope.as_deref(), &plans);
501 }
502}
503
504fn populate_fn_contracts_for_scope(
505 inputs: &ProofLowerInputs,
506 ir: &mut ProofIR,
507 scope: Option<&str>,
508 plans: &HashMap<String, RecursionPlan>,
509) {
510 let scoped_fns: Vec<&FnDef> = inputs.pure_fns_in_scope(scope);
511 let qualify = |bare: &str| -> crate::ir::FnKey {
512 match scope {
513 Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), bare),
514 None => crate::ir::FnKey::entry(bare),
515 }
516 };
517 let symbols = inputs.symbol_table;
521
522 for (fn_name, plan) in plans {
523 let Some(fd) = scoped_fns.iter().find(|fd| fd.name == *fn_name) else {
524 continue;
525 };
526 let fn_key = qualify(fn_name);
527 let Some(canonical_key) = symbols.fn_id_of(&fn_key) else {
528 continue;
529 };
530
531 if let RecursionPlan::IntCountdown { param_index } = plan {
537 if let Some((param_name, _)) = fd.params.get(*param_index) {
538 ir.fn_contracts.insert(
539 canonical_key,
540 FnContract {
541 source_name: fn_name.clone(),
542 recursion: Some(RecursionContract::Fuel {
543 fuel_metric: crate::ir::FuelMetric::NatAbsPlusOne {
544 param: param_name.clone(),
545 },
546 }),
547 },
548 );
549 }
550 continue;
551 }
552
553 if let RecursionPlan::IntAscending { param_index, bound } = plan {
558 if let Some((param_name, _)) = fd.params.get(*param_index) {
559 ir.fn_contracts.insert(
560 canonical_key,
561 FnContract {
562 source_name: fn_name.clone(),
563 recursion: Some(RecursionContract::Fuel {
564 fuel_metric: crate::ir::FuelMetric::BoundMinusParamNatAbsPlusOne {
565 param: param_name.clone(),
566 bound: inputs.resolve_expr(bound, scope),
567 },
568 }),
569 },
570 );
571 }
572 continue;
573 }
574
575 if let RecursionPlan::ListStructural { param_index } = plan {
582 if let Some((param_name, _)) = fd.params.get(*param_index) {
583 ir.fn_contracts.insert(
584 canonical_key,
585 FnContract {
586 source_name: fn_name.clone(),
587 recursion: Some(RecursionContract::Fuel {
588 fuel_metric: crate::ir::FuelMetric::SeqLenPlusOne {
589 param: param_name.clone(),
590 },
591 }),
592 },
593 );
594 }
595 continue;
596 }
597
598 if matches!(plan, RecursionPlan::SizeOfStructural) {
603 ir.fn_contracts.insert(
604 canonical_key,
605 FnContract {
606 source_name: fn_name.clone(),
607 recursion: Some(RecursionContract::Fuel {
608 fuel_metric: crate::ir::FuelMetric::SizeOfPlusOne,
609 }),
610 },
611 );
612 continue;
613 }
614
615 if matches!(plan, RecursionPlan::StringPosAdvance) {
619 if let (Some((string_param, _)), Some((pos_param, _))) =
620 (fd.params.first(), fd.params.get(1))
621 {
622 ir.fn_contracts.insert(
623 canonical_key,
624 FnContract {
625 source_name: fn_name.clone(),
626 recursion: Some(RecursionContract::Fuel {
627 fuel_metric: crate::ir::FuelMetric::StringLenMinusPos {
628 string_param: string_param.clone(),
629 pos_param: pos_param.clone(),
630 },
631 }),
632 },
633 );
634 }
635 continue;
636 }
637
638 match plan {
653 RecursionPlan::MutualIntCountdown => {
654 let params = fd
655 .params
656 .first()
657 .map(|(n, _)| vec![n.clone()])
658 .unwrap_or_default();
659 ir.fn_contracts.insert(
660 canonical_key,
661 FnContract {
662 source_name: fn_name.clone(),
663 recursion: Some(RecursionContract::Fuel {
664 fuel_metric: crate::ir::FuelMetric::Lex { params, rank: 0 },
665 }),
666 },
667 );
668 continue;
669 }
670 RecursionPlan::MutualStringPosAdvance { rank } => {
671 let params = fd.params.iter().take(2).map(|(n, _)| n.clone()).collect();
672 ir.fn_contracts.insert(
673 canonical_key,
674 FnContract {
675 source_name: fn_name.clone(),
676 recursion: Some(RecursionContract::Fuel {
677 fuel_metric: crate::ir::FuelMetric::Lex {
678 params,
679 rank: *rank,
680 },
681 }),
682 },
683 );
684 continue;
685 }
686 RecursionPlan::MutualSizeOfRanked { rank } => {
687 ir.fn_contracts.insert(
688 canonical_key,
689 FnContract {
690 source_name: fn_name.clone(),
691 recursion: Some(RecursionContract::Fuel {
692 fuel_metric: crate::ir::FuelMetric::Lex {
693 params: Vec::new(),
694 rank: *rank,
695 },
696 }),
697 },
698 );
699 continue;
700 }
701 RecursionPlan::LinearRecurrence2 => {
702 ir.fn_contracts.insert(
703 canonical_key,
704 FnContract {
705 source_name: fn_name.clone(),
706 recursion: Some(RecursionContract::LinearRecurrence2),
707 },
708 );
709 continue;
710 }
711 _ => {}
712 }
713
714 let RecursionPlan::IntCountdownGuarded {
715 param_index,
716 base_arm_literal,
717 base_arm_body,
718 wildcard_arm_body,
719 precondition,
720 } = plan
721 else {
722 continue;
723 };
724 let Some((countdown_param_name, _)) = fd.params.get(*param_index) else {
725 continue;
726 };
727
728 let precondition_predicates: Vec<Predicate> = precondition
729 .iter()
730 .map(|clause| Predicate {
731 free_vars: vec![(
732 countdown_param_name.clone(),
733 QuantifierType::Plain("Int".to_string()),
734 )],
735 expr: inputs.resolve_expr(clause, scope),
736 })
737 .collect();
738
739 ir.fn_contracts.insert(
740 canonical_key,
741 FnContract {
742 source_name: fn_name.clone(),
743 recursion: Some(RecursionContract::Native {
744 precondition: precondition_predicates,
745 measure: Measure::NatAbsInt {
746 param: countdown_param_name.clone(),
747 },
748 preservation: PreservationProof::IntCountdownLiteralZero,
749 decrease: DecreaseProof::NatAbsCountdown,
750 body: NativeIntCountdownBody {
751 base_arm_literal: *base_arm_literal,
752 base_arm_body: inputs.resolve_expr(base_arm_body, scope),
753 wildcard_arm_body: inputs.resolve_expr(wildcard_arm_body, scope),
754 },
755 }),
756 },
757 );
758 }
759}
760
761pub fn populate_law_theorems(inputs: &ProofLowerInputs, ir: &mut ProofIR) {
777 use crate::ast::{TopLevel, VerifyKind};
778 use crate::ir::{LawTheorem, Predicate, Quantifier, QuantifierType};
779
780 let symbols = inputs.symbol_table;
781
782 let entry_verifies = inputs.entry_items.iter().filter_map(|item| match item {
783 TopLevel::Verify(vb) => Some(vb),
784 _ => None,
785 });
786 for vb in entry_verifies {
790 let VerifyKind::Law(law) = &vb.kind else {
791 continue;
792 };
793
794 let quantifiers: Vec<Quantifier> = law
795 .givens
796 .iter()
797 .map(|g| Quantifier {
798 name: g.name.clone(),
799 binder_type: QuantifierType::Plain(g.type_name.clone()),
800 })
801 .collect();
802
803 let law_scope: Option<String> = symbols
811 .fn_id_of(&crate::ir::FnKey::entry(&vb.fn_name))
812 .or_else(|| {
813 inputs.dep_modules.iter().find_map(|m| {
814 symbols.fn_id_of(&crate::ir::FnKey::in_module(m.prefix.clone(), &vb.fn_name))
815 })
816 })
817 .and_then(|id| symbols.fn_entry(id).key.scope_str().map(|s| s.to_string()));
818 let law_scope_ref = law_scope.as_deref();
819
820 let premises: Vec<Predicate> = match &law.when {
821 Some(when_expr) => vec![Predicate {
822 free_vars: quantifiers
823 .iter()
824 .map(|q| (q.name.clone(), q.binder_type.clone()))
825 .collect(),
826 expr: inputs.resolve_expr(when_expr, law_scope_ref),
827 }],
828 None => Vec::new(),
829 };
830
831 let strategy =
832 classify_law_strategy(law, &vb.fn_name, inputs, &ir.refined_types, law_scope_ref);
833
834 let Some(fn_id) = symbols.fn_id_of(&crate::ir::FnKey::entry(&vb.fn_name)) else {
842 continue;
843 };
844 ir.law_theorems.push(LawTheorem {
845 fn_id,
846 law_name: law.name.clone(),
847 quantifiers,
848 premises,
849 claim_lhs: inputs.resolve_expr(&law.lhs, law_scope_ref),
850 claim_rhs: inputs.resolve_expr(&law.rhs, law_scope_ref),
851 strategy,
852 });
853 }
854}
855
856fn classify_law_strategy(
875 law: &crate::ast::VerifyLaw,
876 fn_name: &str,
877 inputs: &ProofLowerInputs,
878 refined_types: &std::collections::HashMap<crate::ir::TypeId, crate::ir::RefinedTypeDecl>,
879 scope: Option<&str>,
880) -> crate::ir::ProofStrategy {
881 use crate::ir::ProofStrategy;
882
883 if law.when.is_none()
888 && let Some(s) = detect_match_dispatcher_fold_equivalence(law, fn_name, inputs)
889 {
890 return s;
891 }
892 if law.when.is_none()
897 && let Some(s) = detect_result_pipeline_chain_equivalence(law, fn_name, inputs)
898 {
899 return s;
900 }
901 if law.when.is_none()
910 && let Some(s) = detect_wrapper_over_recursion(law, fn_name, inputs)
911 {
912 return s;
913 }
914 if law.when.is_none()
921 && let Some(param) = detect_induction_target(law, inputs)
922 {
923 return ProofStrategy::Induction { param };
924 }
925 if law.lhs == law.rhs {
926 return ProofStrategy::Reflexive;
927 }
928 if let Some(op) = wrapper_binop(fn_name, inputs) {
932 if detect_wrapper_commutative(law, fn_name, op) {
933 return ProofStrategy::Commutative { op };
934 }
935 if detect_wrapper_associative(law, fn_name, op) {
936 return ProofStrategy::Associative { op };
937 }
938 if detect_wrapper_identity(law, fn_name, op) {
939 return ProofStrategy::IdentityElement { op };
940 }
941 if matches!(op, crate::ast::BinOp::Sub) && detect_wrapper_sub_right_identity(law, fn_name) {
946 return ProofStrategy::IdentityElement { op };
947 }
948 if matches!(op, crate::ast::BinOp::Sub)
952 && let Some(neg_on_rhs) = detect_wrapper_sub_anti_commutative(law, fn_name)
953 {
954 return ProofStrategy::AntiCommutative { op, neg_on_rhs };
955 }
956 }
957 if let Some(inner_fn) = detect_wrapper_unary_equivalence(law, fn_name, inputs) {
960 return ProofStrategy::UnaryEqualsBinary { inner_fn };
961 }
962 if let Some((axiom, args)) = detect_map_set_axiom(law) {
965 let resolved_args: Vec<_> = args.iter().map(|a| inputs.resolve_expr(a, scope)).collect();
966 return ProofStrategy::LibraryAxiom {
967 axiom,
968 args: resolved_args,
969 };
970 }
971 if let Some(inc) = detect_map_key_tracked_increment(law, fn_name, inputs) {
975 return ProofStrategy::MapKeyTrackedIncrement {
976 outer_fn: inc.outer_fn,
977 map_arg: inputs.resolve_expr(&inc.map_arg, scope),
978 key_arg: inputs.resolve_expr(&inc.key_arg, scope),
979 };
980 }
981 if let Some(post) = detect_map_update_postcondition(law, fn_name, inputs) {
984 return ProofStrategy::MapUpdatePostcondition {
985 outer_fn: post.outer_fn,
986 kind: post.kind,
987 map_arg: inputs.resolve_expr(&post.map_arg, scope),
988 key_arg: inputs.resolve_expr(&post.key_arg, scope),
989 extra_unfolds: post.extra_unfolds,
990 };
991 }
992 if let Some(extra_unfolds) = detect_spec_equivalence(law, fn_name, inputs) {
995 return ProofStrategy::SpecEquivalence { extra_unfolds };
996 }
997 if let Some(extra_unfolds) = detect_simp_normalized_spec_equivalence(law, fn_name, inputs) {
1002 return ProofStrategy::SpecEquivalenceSimpNormalized { extra_unfolds };
1003 }
1004 if let Some((unfolded_impl, unfolded_spec)) =
1007 detect_linear_int_spec_equivalence(law, fn_name, inputs)
1008 {
1009 return ProofStrategy::LinearIntSpecEquivalence {
1010 unfolded_impl: inputs.resolve_expr(&unfolded_impl, scope),
1011 unfolded_spec: inputs.resolve_expr(&unfolded_spec, scope),
1012 };
1013 }
1014 if let Some(spec_fn) = detect_effectful_spec_equivalence(law, fn_name, inputs) {
1020 return ProofStrategy::EffectfulSpecEquivalence {
1021 impl_fn: fn_name.to_string(),
1022 spec_fn,
1023 };
1024 }
1025 if let Some((spec_fn, helper_fn)) =
1033 detect_linear_recurrence2_spec_equivalence(law, fn_name, inputs)
1034 {
1035 return ProofStrategy::LinearRecurrence2SpecEquivalence {
1036 impl_fn: fn_name.to_string(),
1037 spec_fn,
1038 helper_fn,
1039 };
1040 }
1041 if let Some(plan) = detect_simp_omega_unfold(law, fn_name, inputs, refined_types) {
1044 return ProofStrategy::LinearArithmetic {
1045 unfold_fns: plan.unfold_fns,
1046 wrapper_return: plan.wrapper_return,
1047 smart_guard: plan.smart_guard,
1048 lifted: plan.lifted,
1049 };
1050 }
1051 ProofStrategy::BackendDispatch
1052}
1053
1054struct SimpOmegaPlan {
1058 unfold_fns: Vec<String>,
1059 wrapper_return: bool,
1060 smart_guard: Option<crate::ir::SmartGuard>,
1061 lifted: bool,
1066}
1067
1068fn detect_simp_omega_unfold(
1069 law: &crate::ast::VerifyLaw,
1070 fn_name: &str,
1071 inputs: &ProofLowerInputs,
1072 refined_types: &std::collections::HashMap<crate::ir::TypeId, crate::ir::RefinedTypeDecl>,
1073) -> Option<SimpOmegaPlan> {
1074 use std::collections::BTreeSet;
1075
1076 let outer_fd = inputs.find_fn_def_by_call_name(fn_name)?;
1077 if law.givens.is_empty() || law.givens.iter().any(|g| g.type_name != "Int") {
1079 return None;
1080 }
1081 let symbols = inputs.symbol_table;
1088 let lifted = law.givens.iter().any(|g| {
1089 refinement_lift_for_given_ir(
1090 &g.name,
1091 &law.lhs,
1092 &law.rhs,
1093 refined_types,
1094 symbols,
1095 inputs.dep_modules,
1096 )
1097 .is_some()
1098 });
1099 if !lifted && outer_fd.params.iter().any(|(_, t)| t != "Int") {
1100 return None;
1101 }
1102
1103 let mut fn_names: BTreeSet<String> = BTreeSet::new();
1105 collect_fn_calls_expr(&law.lhs, &mut fn_names);
1106 collect_fn_calls_expr(&law.rhs, &mut fn_names);
1107 fn_names.insert(fn_name.to_string());
1108
1109 loop {
1116 let before = fn_names.len();
1117 let snapshot: Vec<String> = fn_names.iter().cloned().collect();
1118 for fd in iter_all_fn_defs(inputs) {
1119 if !snapshot.contains(&fd.name) {
1120 continue;
1121 }
1122 for stmt in fd.body.stmts() {
1123 match stmt {
1124 crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
1125 collect_fn_calls_expr(e, &mut fn_names);
1126 }
1127 }
1128 }
1129 }
1130 if fn_names.len() == before {
1131 break;
1132 }
1133 }
1134
1135 let mut wrapper_return = false;
1140 for fd in iter_all_fn_defs(inputs) {
1141 if !fn_names.contains(&fd.name) {
1142 continue;
1143 }
1144 let mut self_only: BTreeSet<String> = BTreeSet::new();
1145 self_only.insert(fd.name.clone());
1146 if body_calls_any_of_inputs(&fd.body, &self_only) {
1147 return None;
1148 }
1149 if fd.name == fn_name && !lifted && fd.params.iter().any(|(_, t)| t != "Int") {
1153 return None;
1154 }
1155 let ret = fd.return_type.as_str();
1156 if ret != "Int" && ret != "Float" {
1157 wrapper_return = true;
1158 }
1159 }
1160
1161 let mut ordered: Vec<String> = Vec::new();
1165 if fn_names.contains(fn_name) {
1166 ordered.push(fn_name.to_string());
1167 }
1168 for n in &fn_names {
1169 if n != fn_name {
1170 ordered.push(n.clone());
1171 }
1172 }
1173
1174 let smart_guard = extract_smart_constructor_guard(&fn_names, inputs);
1175
1176 Some(SimpOmegaPlan {
1177 unfold_fns: ordered,
1178 wrapper_return,
1179 smart_guard,
1180 lifted,
1181 })
1182}
1183
1184fn refinement_lift_for_given_ir(
1196 given_name: &str,
1197 lhs: &Spanned<crate::ast::Expr>,
1198 rhs: &Spanned<crate::ast::Expr>,
1199 refined_types: &std::collections::HashMap<crate::ir::TypeId, crate::ir::RefinedTypeDecl>,
1200 symbols: &crate::ir::SymbolTable,
1201 dep_modules: &[crate::codegen::ModuleInfo],
1202) -> Option<String> {
1203 let mut result: Option<String> = None;
1204 walk_for_refinement_carrier(
1205 lhs,
1206 given_name,
1207 refined_types,
1208 symbols,
1209 dep_modules,
1210 &mut result,
1211 );
1212 walk_for_refinement_carrier(
1213 rhs,
1214 given_name,
1215 refined_types,
1216 symbols,
1217 dep_modules,
1218 &mut result,
1219 );
1220 result
1221}
1222
1223fn walk_for_refinement_carrier(
1232 expr: &Spanned<crate::ast::Expr>,
1233 given_name: &str,
1234 refined_types: &std::collections::HashMap<crate::ir::TypeId, crate::ir::RefinedTypeDecl>,
1235 symbols: &crate::ir::SymbolTable,
1236 dep_modules: &[crate::codegen::ModuleInfo],
1237 result: &mut Option<String>,
1238) {
1239 use crate::ast::Expr;
1240 if result.is_some() {
1241 return;
1242 }
1243 match &expr.node {
1244 Expr::RecordCreate { type_name, fields } if fields.len() == 1 => {
1245 let (_, fvalue) = &fields[0];
1246 let matches_var = matches!(
1247 &fvalue.node,
1248 Expr::Ident(n) | Expr::Resolved { name: n, .. } if n == given_name
1249 );
1250 if matches_var
1251 && let Some((type_id, _decl)) =
1252 crate::codegen::common::resolve_refined_type_in_with_key(
1253 refined_types,
1254 symbols,
1255 dep_modules,
1256 type_name,
1257 )
1258 {
1259 *result = Some(symbols.type_entry(type_id).key.canonical());
1264 return;
1265 }
1266 for (_, v) in fields {
1269 walk_for_refinement_carrier(
1270 v,
1271 given_name,
1272 refined_types,
1273 symbols,
1274 dep_modules,
1275 result,
1276 );
1277 }
1278 }
1279 Expr::FnCall(callee, args) => {
1280 walk_for_refinement_carrier(
1281 callee,
1282 given_name,
1283 refined_types,
1284 symbols,
1285 dep_modules,
1286 result,
1287 );
1288 for a in args {
1289 walk_for_refinement_carrier(
1290 a,
1291 given_name,
1292 refined_types,
1293 symbols,
1294 dep_modules,
1295 result,
1296 );
1297 }
1298 }
1299 Expr::BinOp(_, l, r) => {
1300 walk_for_refinement_carrier(l, given_name, refined_types, symbols, dep_modules, result);
1301 walk_for_refinement_carrier(r, given_name, refined_types, symbols, dep_modules, result);
1302 }
1303 Expr::Match { subject, arms, .. } => {
1304 walk_for_refinement_carrier(
1305 subject,
1306 given_name,
1307 refined_types,
1308 symbols,
1309 dep_modules,
1310 result,
1311 );
1312 for arm in arms {
1313 walk_for_refinement_carrier(
1314 &arm.body,
1315 given_name,
1316 refined_types,
1317 symbols,
1318 dep_modules,
1319 result,
1320 );
1321 }
1322 }
1323 Expr::Attr(obj, _) => {
1324 walk_for_refinement_carrier(
1325 obj,
1326 given_name,
1327 refined_types,
1328 symbols,
1329 dep_modules,
1330 result,
1331 );
1332 }
1333 _ => {}
1334 }
1335}
1336
1337fn iter_all_fn_defs<'a>(inputs: &'a ProofLowerInputs<'a>) -> impl Iterator<Item = &'a FnDef> {
1338 inputs
1339 .entry_items
1340 .iter()
1341 .filter_map(|item| match item {
1342 TopLevel::FnDef(fd) => Some(fd),
1343 _ => None,
1344 })
1345 .chain(inputs.dep_modules.iter().flat_map(|m| m.fn_defs.iter()))
1346}
1347
1348fn body_calls_any_of_inputs(
1349 body: &crate::ast::FnBody,
1350 names: &std::collections::BTreeSet<String>,
1351) -> bool {
1352 let mut called = std::collections::BTreeSet::new();
1353 for stmt in body.stmts() {
1354 match stmt {
1355 crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
1356 collect_fn_calls_expr(e, &mut called);
1357 }
1358 }
1359 }
1360 called.iter().any(|c| names.contains(c))
1361}
1362
1363fn collect_fn_calls_expr(
1364 expr: &Spanned<crate::ast::Expr>,
1365 out: &mut std::collections::BTreeSet<String>,
1366) {
1367 use crate::ast::Expr;
1368 match &expr.node {
1369 Expr::FnCall(f, args) => {
1370 if let Some(name) = expr_to_dotted_name(&f.node) {
1371 let last = name.rsplit('.').next().unwrap_or(&name);
1378 if last.chars().next().is_some_and(|c| c.is_lowercase()) {
1379 out.insert(name);
1380 }
1381 }
1382 for arg in args {
1383 collect_fn_calls_expr(arg, out);
1384 }
1385 }
1386 Expr::BinOp(_, l, r) => {
1387 collect_fn_calls_expr(l, out);
1388 collect_fn_calls_expr(r, out);
1389 }
1390 Expr::Attr(obj, _) => collect_fn_calls_expr(obj, out),
1391 Expr::Match { subject, arms, .. } => {
1392 collect_fn_calls_expr(subject, out);
1393 for arm in arms {
1394 collect_fn_calls_expr(&arm.body, out);
1395 }
1396 }
1397 Expr::TailCall(boxed) => {
1398 out.insert(boxed.target.clone());
1399 for arg in &boxed.args {
1400 collect_fn_calls_expr(arg, out);
1401 }
1402 }
1403 _ => {}
1404 }
1405}
1406
1407fn extract_smart_constructor_guard(
1412 fn_names: &std::collections::BTreeSet<String>,
1413 inputs: &ProofLowerInputs,
1414) -> Option<crate::ir::SmartGuard> {
1415 use crate::ast::{Expr, MatchArm, Pattern, Stmt};
1416 for fd in iter_all_fn_defs(inputs) {
1417 if !fn_names.contains(&fd.name) {
1418 continue;
1419 }
1420 if !fd.return_type.starts_with("Result<") {
1421 continue;
1422 }
1423 if fd.params.len() != 1 {
1424 continue;
1425 }
1426 let (param_name, param_type) = &fd.params[0];
1427 if param_type != "Int" {
1428 continue;
1429 }
1430 let stmts = fd.body.stmts();
1431 if stmts.len() != 1 {
1432 continue;
1433 }
1434 let Stmt::Expr(body_expr) = &stmts[0] else {
1435 continue;
1436 };
1437 let Expr::Match { subject, arms } = &body_expr.node else {
1438 continue;
1439 };
1440 if !arms_match_bool_ok_err(arms) {
1441 continue;
1442 }
1443 let scope = inputs.fn_owning_scope(fd);
1444 return Some(crate::ir::SmartGuard {
1445 param: param_name.clone(),
1446 predicate: inputs.resolve_expr(subject, scope),
1447 });
1448 #[allow(unreachable_code)]
1450 {
1451 let _: Option<&MatchArm> = None;
1452 let _: Option<&Pattern> = None;
1453 }
1454 }
1455 None
1456}
1457
1458fn arms_match_bool_ok_err(arms: &[crate::ast::MatchArm]) -> bool {
1459 use crate::ast::{Expr, Literal, Pattern};
1460 if arms.len() != 2 {
1461 return false;
1462 }
1463 let starts_with_ctor = |expr: &Spanned<Expr>, name: &str| -> bool {
1464 match &expr.node {
1465 Expr::Constructor(n, _) => n == name,
1466 Expr::FnCall(callee, _) => {
1467 if let Expr::Attr(obj, field) = &callee.node
1468 && let Expr::Ident(ns) = &obj.node
1469 {
1470 format!("{ns}.{field}") == name
1471 } else {
1472 false
1473 }
1474 }
1475 _ => false,
1476 }
1477 };
1478 let mut saw_true_ok = false;
1479 let mut saw_false_err = false;
1480 for arm in arms {
1481 match &arm.pattern {
1482 Pattern::Literal(Literal::Bool(true)) => {
1483 if starts_with_ctor(&arm.body, "Result.Ok") {
1484 saw_true_ok = true;
1485 }
1486 }
1487 Pattern::Literal(Literal::Bool(false)) => {
1488 if starts_with_ctor(&arm.body, "Result.Err") {
1489 saw_false_err = true;
1490 }
1491 }
1492 _ => return false,
1493 }
1494 }
1495 saw_true_ok && saw_false_err
1496}
1497
1498fn detect_map_set_axiom(
1504 law: &crate::ast::VerifyLaw,
1505) -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1506 let has_side = |side: &Spanned<crate::ast::Expr>,
1508 other: &Spanned<crate::ast::Expr>|
1509 -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1510 let (m, k, v) = map_has_set_parts(side)?;
1511 if !is_bool_true(other) {
1512 return None;
1513 }
1514 Some((
1515 "Map.has_set_self".to_string(),
1516 vec![m.clone(), k.clone(), v.clone()],
1517 ))
1518 };
1519 if let Some(found) = has_side(&law.lhs, &law.rhs).or_else(|| has_side(&law.rhs, &law.lhs)) {
1520 return Some(found);
1521 }
1522
1523 let get_side = |side: &Spanned<crate::ast::Expr>,
1525 other: &Spanned<crate::ast::Expr>|
1526 -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1527 let (m, k, v) = map_get_set_parts(side)?;
1528 let some_v = option_some_arg(other)?;
1529 if some_v.node != v.node {
1530 return None;
1531 }
1532 Some((
1533 "Map.get_set_self".to_string(),
1534 vec![m.clone(), k.clone(), v.clone()],
1535 ))
1536 };
1537 get_side(&law.lhs, &law.rhs).or_else(|| get_side(&law.rhs, &law.lhs))
1538}
1539
1540struct MapUpdatePostconditionPlan {
1543 outer_fn: String,
1544 kind: crate::ir::MapUpdatePostconditionKind,
1545 map_arg: Spanned<crate::ast::Expr>,
1546 key_arg: Spanned<crate::ast::Expr>,
1547 extra_unfolds: Vec<String>,
1548}
1549
1550fn detect_map_update_postcondition(
1556 law: &crate::ast::VerifyLaw,
1557 fn_name: &str,
1558 inputs: &ProofLowerInputs,
1559) -> Option<MapUpdatePostconditionPlan> {
1560 use crate::ir::MapUpdatePostconditionKind;
1561
1562 outer_fn_map_update_shape(fn_name, inputs)?;
1563
1564 let has_side = |side: &Spanned<crate::ast::Expr>,
1565 other: &Spanned<crate::ast::Expr>|
1566 -> Option<MapUpdatePostconditionPlan> {
1567 if !is_bool_true(other) {
1568 return None;
1569 }
1570 let (map_arg, key_arg) = map_has_after_fn_call(side, fn_name)?;
1571 Some(MapUpdatePostconditionPlan {
1572 outer_fn: fn_name.to_string(),
1573 kind: MapUpdatePostconditionKind::HasAfter,
1574 map_arg: map_arg.clone(),
1575 key_arg: key_arg.clone(),
1576 extra_unfolds: Vec::new(),
1577 })
1578 };
1579 if let Some(plan) = has_side(&law.lhs, &law.rhs).or_else(|| has_side(&law.rhs, &law.lhs)) {
1580 return Some(plan);
1581 }
1582
1583 let get_side = |side: &Spanned<crate::ast::Expr>,
1584 other: &Spanned<crate::ast::Expr>|
1585 -> Option<MapUpdatePostconditionPlan> {
1586 option_some_arg(other)?;
1587 let (map_arg, key_arg) = map_get_after_fn_call(side, fn_name)?;
1588 let extra_unfolds = law_helper_unfolds(law, fn_name, inputs);
1589 Some(MapUpdatePostconditionPlan {
1590 outer_fn: fn_name.to_string(),
1591 kind: MapUpdatePostconditionKind::GetAfter,
1592 map_arg: map_arg.clone(),
1593 key_arg: key_arg.clone(),
1594 extra_unfolds,
1595 })
1596 };
1597 get_side(&law.lhs, &law.rhs).or_else(|| get_side(&law.rhs, &law.lhs))
1598}
1599
1600fn law_helper_unfolds(
1607 law: &crate::ast::VerifyLaw,
1608 outer_fn: &str,
1609 inputs: &ProofLowerInputs,
1610) -> Vec<String> {
1611 use std::collections::BTreeSet;
1612
1613 let resolve_user_fn = |name: &str| -> Option<&FnDef> {
1614 let fd = inputs.find_fn_def_by_call_name(name)?;
1615 if !fd.effects.is_empty() || fd.name == "main" {
1616 return None;
1617 }
1618 Some(fd)
1619 };
1620
1621 let mut raw: BTreeSet<String> = BTreeSet::new();
1623 collect_fn_calls_expr(&law.lhs, &mut raw);
1624 collect_fn_calls_expr(&law.rhs, &mut raw);
1625 if let Some(when_expr) = &law.when {
1626 collect_fn_calls_expr(when_expr, &mut raw);
1627 }
1628 let mut names: BTreeSet<String> = raw
1629 .into_iter()
1630 .filter_map(|n| resolve_user_fn(&n).map(|fd| fd.name.clone()))
1631 .collect();
1632
1633 loop {
1635 let before = names.len();
1636 let snapshot: Vec<String> = names.iter().cloned().collect();
1637 for name in snapshot {
1638 let Some(fd) = resolve_user_fn(&name) else {
1639 continue;
1640 };
1641 let mut called: BTreeSet<String> = BTreeSet::new();
1642 for stmt in fd.body.stmts() {
1643 match stmt {
1644 crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
1645 collect_fn_calls_expr(e, &mut called);
1646 }
1647 }
1648 }
1649 for c in called {
1650 if let Some(callee_fd) = resolve_user_fn(&c) {
1651 names.insert(callee_fd.name.clone());
1652 }
1653 }
1654 }
1655 if names.len() == before {
1656 break;
1657 }
1658 }
1659 names.remove(outer_fn);
1660 names.into_iter().collect()
1661}
1662
1663fn detect_spec_equivalence(
1674 law: &crate::ast::VerifyLaw,
1675 fn_name: &str,
1676 inputs: &ProofLowerInputs,
1677) -> Option<Vec<String>> {
1678 use crate::ast::Expr;
1679 use std::collections::BTreeSet;
1680
1681 let spec_fn_name = &law.name;
1682 if spec_fn_name == fn_name {
1683 return None;
1684 }
1685 let spec_fd = inputs.find_fn_def_by_call_name(spec_fn_name)?;
1686 if !spec_fd.effects.is_empty() || spec_fd.name == "main" {
1687 return None;
1688 }
1689 let impl_fd = inputs.find_fn_def_by_call_name(fn_name)?;
1690
1691 let direct_call =
1692 |expr: &Spanned<crate::ast::Expr>| -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1693 let Expr::FnCall(callee, args) = &expr.node else {
1694 return None;
1695 };
1696 let name = match &callee.node {
1697 Expr::Ident(n) | Expr::Resolved { name: n, .. } => n.clone(),
1698 _ => return None,
1699 };
1700 Some((name, args.clone()))
1701 };
1702 let canonical_shape =
1703 |lhs: &Spanned<crate::ast::Expr>, rhs: &Spanned<crate::ast::Expr>| -> bool {
1704 let Some((l_name, l_args)) = direct_call(lhs) else {
1705 return false;
1706 };
1707 let Some((r_name, r_args)) = direct_call(rhs) else {
1708 return false;
1709 };
1710 l_name == fn_name && r_name == *spec_fn_name && l_args == r_args
1711 };
1712 if !canonical_shape(&law.lhs, &law.rhs) && !canonical_shape(&law.rhs, &law.lhs) {
1713 return None;
1714 }
1715
1716 let impl_body = body_terminal_expr(impl_fd.body.as_ref())?;
1717 let spec_body = body_terminal_expr(spec_fd.body.as_ref())?;
1718 if impl_body.node != spec_body.node {
1719 return None;
1720 }
1721
1722 let resolve_user_fn = |name: &str| -> Option<&FnDef> {
1726 let fd = inputs.find_fn_def_by_call_name(name)?;
1727 if !fd.effects.is_empty() || fd.name == "main" {
1728 return None;
1729 }
1730 Some(fd)
1731 };
1732 let mut names: BTreeSet<String> = BTreeSet::new();
1733 names.insert(fn_name.to_string());
1734 names.insert(spec_fn_name.clone());
1735 let mut seed: BTreeSet<String> = BTreeSet::new();
1736 collect_fn_calls_expr(&law.lhs, &mut seed);
1737 collect_fn_calls_expr(&law.rhs, &mut seed);
1738 if let Some(when_expr) = &law.when {
1739 collect_fn_calls_expr(when_expr, &mut seed);
1740 }
1741 for n in seed {
1742 if let Some(fd) = resolve_user_fn(&n) {
1743 names.insert(fd.name.clone());
1744 }
1745 }
1746 loop {
1747 let before = names.len();
1748 let snapshot: Vec<String> = names.iter().cloned().collect();
1749 for name in snapshot {
1750 let Some(fd) = resolve_user_fn(&name) else {
1751 continue;
1752 };
1753 let mut called: BTreeSet<String> = BTreeSet::new();
1754 for stmt in fd.body.stmts() {
1755 match stmt {
1756 crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
1757 collect_fn_calls_expr(e, &mut called);
1758 }
1759 }
1760 }
1761 for c in called {
1762 if let Some(callee_fd) = resolve_user_fn(&c) {
1763 names.insert(callee_fd.name.clone());
1764 }
1765 }
1766 }
1767 if names.len() == before {
1768 break;
1769 }
1770 }
1771 Some(names.into_iter().collect())
1772}
1773
1774fn detect_simp_normalized_spec_equivalence(
1781 law: &crate::ast::VerifyLaw,
1782 fn_name: &str,
1783 inputs: &ProofLowerInputs,
1784) -> Option<Vec<String>> {
1785 use crate::ast::Expr;
1786 use std::collections::BTreeSet;
1787
1788 let spec_fn_name = &law.name;
1789 if spec_fn_name == fn_name {
1790 return None;
1791 }
1792 let spec_fd = inputs.find_fn_def_by_call_name(spec_fn_name)?;
1793 if !spec_fd.effects.is_empty() || spec_fd.name == "main" {
1794 return None;
1795 }
1796 let impl_fd = inputs.find_fn_def_by_call_name(fn_name)?;
1797
1798 let direct_call =
1799 |expr: &Spanned<crate::ast::Expr>| -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1800 let Expr::FnCall(callee, args) = &expr.node else {
1801 return None;
1802 };
1803 let name = match &callee.node {
1804 Expr::Ident(n) | Expr::Resolved { name: n, .. } => n.clone(),
1805 _ => return None,
1806 };
1807 Some((name, args.clone()))
1808 };
1809 let canonical_shape_args = |lhs: &Spanned<crate::ast::Expr>,
1810 rhs: &Spanned<crate::ast::Expr>|
1811 -> Option<Vec<Spanned<crate::ast::Expr>>> {
1812 let (l_name, l_args) = direct_call(lhs)?;
1813 let (r_name, r_args) = direct_call(rhs)?;
1814 if l_name != fn_name || r_name != *spec_fn_name || l_args != r_args {
1815 return None;
1816 }
1817 if l_args.len() != impl_fd.params.len() || r_args.len() != spec_fd.params.len() {
1818 return None;
1819 }
1820 Some(l_args)
1821 };
1822 let call_args = canonical_shape_args(&law.lhs, &law.rhs)
1823 .or_else(|| canonical_shape_args(&law.rhs, &law.lhs))?;
1824
1825 let impl_body = body_terminal_expr(impl_fd.body.as_ref())?;
1826 let spec_body = body_terminal_expr(spec_fd.body.as_ref())?;
1827 if impl_body.node == spec_body.node {
1830 return None;
1831 }
1832 let impl_subst: std::collections::HashMap<String, Spanned<crate::ast::Expr>> = impl_fd
1833 .params
1834 .iter()
1835 .zip(call_args.iter())
1836 .map(|((n, _), arg)| (n.clone(), arg.clone()))
1837 .collect();
1838 let spec_subst: std::collections::HashMap<String, Spanned<crate::ast::Expr>> = spec_fd
1839 .params
1840 .iter()
1841 .zip(call_args.iter())
1842 .map(|((n, _), arg)| (n.clone(), arg.clone()))
1843 .collect();
1844 let impl_normalised = simplify_identity_expr(&crate::ast_rewrite::rewrite_idents_scoped(
1845 impl_body,
1846 |name| impl_subst.get(name).cloned(),
1847 ));
1848 let spec_normalised = simplify_identity_expr(&crate::ast_rewrite::rewrite_idents_scoped(
1849 spec_body,
1850 |name| spec_subst.get(name).cloned(),
1851 ));
1852 if impl_normalised.node != spec_normalised.node {
1853 return None;
1854 }
1855
1856 let resolve_user_fn = |name: &str| -> Option<&FnDef> {
1858 let fd = inputs.find_fn_def_by_call_name(name)?;
1859 if !fd.effects.is_empty() || fd.name == "main" {
1860 return None;
1861 }
1862 Some(fd)
1863 };
1864 let mut names: BTreeSet<String> = BTreeSet::new();
1865 names.insert(fn_name.to_string());
1866 names.insert(spec_fn_name.clone());
1867 let mut seed: BTreeSet<String> = BTreeSet::new();
1868 collect_fn_calls_expr(&law.lhs, &mut seed);
1869 collect_fn_calls_expr(&law.rhs, &mut seed);
1870 if let Some(when_expr) = &law.when {
1871 collect_fn_calls_expr(when_expr, &mut seed);
1872 }
1873 for n in seed {
1874 if let Some(fd) = resolve_user_fn(&n) {
1875 names.insert(fd.name.clone());
1876 }
1877 }
1878 loop {
1879 let before = names.len();
1880 let snapshot: Vec<String> = names.iter().cloned().collect();
1881 for name in snapshot {
1882 let Some(fd) = resolve_user_fn(&name) else {
1883 continue;
1884 };
1885 let mut called: BTreeSet<String> = BTreeSet::new();
1886 for stmt in fd.body.stmts() {
1887 match stmt {
1888 crate::ast::Stmt::Binding(_, _, e) | crate::ast::Stmt::Expr(e) => {
1889 collect_fn_calls_expr(e, &mut called);
1890 }
1891 }
1892 }
1893 for c in called {
1894 if let Some(callee_fd) = resolve_user_fn(&c) {
1895 names.insert(callee_fd.name.clone());
1896 }
1897 }
1898 }
1899 if names.len() == before {
1900 break;
1901 }
1902 }
1903 Some(names.into_iter().collect())
1904}
1905
1906fn detect_linear_int_spec_equivalence(
1915 law: &crate::ast::VerifyLaw,
1916 fn_name: &str,
1917 inputs: &ProofLowerInputs,
1918) -> Option<(Spanned<crate::ast::Expr>, Spanned<crate::ast::Expr>)> {
1919 use crate::ast::Expr;
1920 use std::collections::HashSet;
1921
1922 if law.givens.is_empty() || !law.givens.iter().all(|g| g.type_name == "Int") {
1923 return None;
1924 }
1925 let spec_fn_name = &law.name;
1926 if spec_fn_name == fn_name {
1927 return None;
1928 }
1929 let spec_fd = inputs.find_fn_def_by_call_name(spec_fn_name)?;
1930 if !spec_fd.effects.is_empty() || spec_fd.name == "main" {
1931 return None;
1932 }
1933 let impl_fd = inputs.find_fn_def_by_call_name(fn_name)?;
1934 if impl_fd.return_type != "Int" || spec_fd.return_type != "Int" {
1935 return None;
1936 }
1937
1938 let direct_call =
1939 |expr: &Spanned<crate::ast::Expr>| -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
1940 let Expr::FnCall(callee, args) = &expr.node else {
1941 return None;
1942 };
1943 let name = match &callee.node {
1944 Expr::Ident(n) | Expr::Resolved { name: n, .. } => n.clone(),
1945 _ => return None,
1946 };
1947 Some((name, args.clone()))
1948 };
1949 let canonical_shape_args = |lhs: &Spanned<crate::ast::Expr>,
1950 rhs: &Spanned<crate::ast::Expr>|
1951 -> Option<Vec<Spanned<crate::ast::Expr>>> {
1952 let (l_name, l_args) = direct_call(lhs)?;
1953 let (r_name, r_args) = direct_call(rhs)?;
1954 if l_name != fn_name || r_name != *spec_fn_name || l_args != r_args {
1955 return None;
1956 }
1957 if l_args.len() != impl_fd.params.len() || r_args.len() != spec_fd.params.len() {
1958 return None;
1959 }
1960 Some(l_args)
1961 };
1962 let call_args = canonical_shape_args(&law.lhs, &law.rhs)
1963 .or_else(|| canonical_shape_args(&law.rhs, &law.lhs))?;
1964
1965 let impl_body = body_terminal_expr(impl_fd.body.as_ref())?;
1966 let spec_body = body_terminal_expr(spec_fd.body.as_ref())?;
1967 let impl_subst: std::collections::HashMap<String, Spanned<crate::ast::Expr>> = impl_fd
1968 .params
1969 .iter()
1970 .zip(call_args.iter())
1971 .map(|((n, _), arg)| (n.clone(), arg.clone()))
1972 .collect();
1973 let spec_subst: std::collections::HashMap<String, Spanned<crate::ast::Expr>> = spec_fd
1974 .params
1975 .iter()
1976 .zip(call_args.iter())
1977 .map(|((n, _), arg)| (n.clone(), arg.clone()))
1978 .collect();
1979 let unfolded_impl =
1980 crate::ast_rewrite::rewrite_idents_scoped(impl_body, |name| impl_subst.get(name).cloned());
1981 let unfolded_spec =
1982 crate::ast_rewrite::rewrite_idents_scoped(spec_body, |name| spec_subst.get(name).cloned());
1983
1984 let allowed_idents: HashSet<&str> = law.givens.iter().map(|g| g.name.as_str()).collect();
1985 if !is_linear_int_expr(&unfolded_impl, &allowed_idents)
1986 || !is_linear_int_expr(&unfolded_spec, &allowed_idents)
1987 {
1988 return None;
1989 }
1990 Some((unfolded_impl, unfolded_spec))
1991}
1992
1993fn is_linear_int_expr(
1997 expr: &Spanned<crate::ast::Expr>,
1998 allowed_idents: &std::collections::HashSet<&str>,
1999) -> bool {
2000 use crate::ast::{BinOp, Expr, Literal};
2001 match &expr.node {
2002 Expr::Literal(Literal::Int(_)) => true,
2003 Expr::Ident(name) | Expr::Resolved { name, .. } => allowed_idents.contains(name.as_str()),
2004 Expr::BinOp(BinOp::Add | BinOp::Sub, left, right) => {
2005 is_linear_int_expr(left, allowed_idents) && is_linear_int_expr(right, allowed_idents)
2006 }
2007 _ => false,
2008 }
2009}
2010
2011fn detect_effectful_spec_equivalence(
2023 law: &crate::ast::VerifyLaw,
2024 fn_name: &str,
2025 inputs: &ProofLowerInputs,
2026) -> Option<String> {
2027 use crate::ast::Expr;
2028
2029 let impl_fd = inputs.find_fn_def_by_call_name(fn_name)?;
2030 if impl_fd.effects.is_empty() {
2031 return None;
2032 }
2033 if !impl_fd
2034 .effects
2035 .iter()
2036 .all(|e| crate::types::checker::effect_classification::is_classified(&e.node))
2037 {
2038 return None;
2039 }
2040
2041 let find_fn = |name: &str| -> Option<&crate::ast::FnDef> {
2042 inputs
2043 .entry_items
2044 .iter()
2045 .filter_map(|item| match item {
2046 TopLevel::FnDef(fd) => Some(fd),
2047 _ => None,
2048 })
2049 .find(|fd| fd.name == name)
2050 };
2051 let rewritten_lhs = crate::codegen::common::rewrite_effectful_calls_in_law(
2052 &law.lhs,
2053 law,
2054 find_fn,
2055 crate::codegen::common::OracleInjectionMode::LemmaBindingProjected,
2056 );
2057 let rewritten_rhs = crate::codegen::common::rewrite_effectful_calls_in_law(
2058 &law.rhs,
2059 law,
2060 find_fn,
2061 crate::codegen::common::OracleInjectionMode::LemmaBindingProjected,
2062 );
2063
2064 let direct_call =
2065 |expr: &Spanned<crate::ast::Expr>| -> Option<(String, Vec<Spanned<crate::ast::Expr>>)> {
2066 let Expr::FnCall(callee, args) = &expr.node else {
2067 return None;
2068 };
2069 let name = match &callee.node {
2070 Expr::Ident(n) | Expr::Resolved { name: n, .. } => n.clone(),
2071 _ => return None,
2072 };
2073 Some((name, args.clone()))
2074 };
2075 let try_side = |impl_side: &Spanned<crate::ast::Expr>,
2076 spec_side: &Spanned<crate::ast::Expr>|
2077 -> Option<String> {
2078 let (l_name, l_args) = direct_call(impl_side)?;
2079 let (r_name, r_args) = direct_call(spec_side)?;
2080 if l_args != r_args || l_name == r_name || l_name != fn_name {
2081 return None;
2082 }
2083 Some(r_name)
2084 };
2085 try_side(&rewritten_lhs, &rewritten_rhs).or_else(|| try_side(&rewritten_rhs, &rewritten_lhs))
2086}
2087
2088fn detect_linear_recurrence2_spec_equivalence(
2098 law: &crate::ast::VerifyLaw,
2099 fn_name: &str,
2100 inputs: &ProofLowerInputs,
2101) -> Option<(String, String)> {
2102 use crate::codegen::lean::recurrence::{
2103 detect_second_order_int_linear_recurrence, detect_tailrec_int_linear_pair_worker,
2104 detect_tailrec_int_linear_pair_wrapper,
2105 };
2106
2107 let spec_fn_name = &law.name;
2108 if spec_fn_name == fn_name {
2109 return None;
2110 }
2111 if !law_references_fn(&law.lhs, spec_fn_name) && !law_references_fn(&law.rhs, spec_fn_name) {
2112 return None;
2113 }
2114
2115 let impl_fd = inputs.find_fn_def_by_call_name(fn_name)?;
2116 let spec_fd = inputs.find_fn_def_by_call_name(spec_fn_name)?;
2117 let impl_shape = detect_tailrec_int_linear_pair_wrapper(impl_fd)?;
2118 let spec_shape = detect_second_order_int_linear_recurrence(spec_fd)?;
2119
2120 if impl_shape.negative_branch.node != spec_shape.negative_branch.node
2123 || impl_shape.seed_prev.node != spec_shape.base0.node
2124 || impl_shape.seed_curr.node != spec_shape.base1.node
2125 {
2126 return None;
2127 }
2128
2129 let helper_fd = inputs.find_fn_def_by_call_name(&impl_shape.helper_fn_name)?;
2130 let helper_shape = detect_tailrec_int_linear_pair_worker(helper_fd)?;
2131 if helper_shape.recurrence != spec_shape.recurrence {
2132 return None;
2133 }
2134
2135 Some((spec_fn_name.clone(), impl_shape.helper_fn_name))
2136}
2137
2138fn law_references_fn(expr: &Spanned<crate::ast::Expr>, target: &str) -> bool {
2139 use crate::ast::Expr;
2140 match &expr.node {
2141 Expr::FnCall(callee, args) => {
2142 let name = match &callee.node {
2143 Expr::Ident(n) | Expr::Resolved { name: n, .. } => Some(n.as_str()),
2144 _ => None,
2145 };
2146 if name == Some(target) {
2147 return true;
2148 }
2149 args.iter().any(|a| law_references_fn(a, target))
2150 }
2151 Expr::BinOp(_, l, r) => law_references_fn(l, target) || law_references_fn(r, target),
2152 Expr::Attr(base, _) => law_references_fn(base, target),
2153 Expr::Match { subject, arms } => {
2154 law_references_fn(subject, target)
2155 || arms.iter().any(|arm| law_references_fn(&arm.body, target))
2156 }
2157 _ => false,
2158 }
2159}
2160
2161fn outer_fn_map_update_shape(fn_name: &str, inputs: &ProofLowerInputs) -> Option<()> {
2177 let fd = inputs.find_fn_def_by_call_name(fn_name)?;
2178 if fd.params.len() != 2 {
2179 return None;
2180 }
2181 let map_param = fd.params[0].0.as_str();
2182 let key_param = fd.params[1].0.as_str();
2183 map_update_body_matches(fd.body.stmts(), map_param, key_param).then_some(())
2184}
2185
2186fn map_update_body_matches(stmts: &[crate::ast::Stmt], map_param: &str, key_param: &str) -> bool {
2187 use crate::ast::Stmt;
2188 if stmts.len() < 2 {
2189 return matches!(stmts.first(), Some(Stmt::Expr(e)) if map_update_match_expr(e, map_param, key_param, None));
2192 }
2193 let Some(last) = stmts.last() else {
2194 return false;
2195 };
2196 let mut bound_name: Option<&str> = None;
2197 for stmt in &stmts[..stmts.len() - 1] {
2198 match stmt {
2199 Stmt::Binding(name, _, expr) => {
2200 if !is_map_get_of_params(expr, map_param, key_param) {
2201 return false;
2202 }
2203 bound_name = Some(name);
2204 }
2205 Stmt::Expr(_) => return false,
2206 }
2207 }
2208 match last {
2209 Stmt::Expr(expr) => map_update_match_expr(expr, map_param, key_param, bound_name),
2210 Stmt::Binding(_, _, _) => false,
2211 }
2212}
2213
2214fn map_update_match_expr(
2215 expr: &Spanned<crate::ast::Expr>,
2216 map_param: &str,
2217 key_param: &str,
2218 bound_name: Option<&str>,
2219) -> bool {
2220 use crate::ast::Expr;
2221 let Expr::Match { subject, arms } = &expr.node else {
2222 return false;
2223 };
2224 if arms.len() < 2 {
2225 return false;
2226 }
2227 let subject_ok = match bound_name {
2228 Some(name) => matches_ident_expr(subject, name),
2229 None => is_map_get_of_params(subject, map_param, key_param),
2230 };
2231 if !subject_ok {
2232 return false;
2233 }
2234 arms.iter()
2235 .all(|arm| is_map_set_of_params(&arm.body, map_param, key_param))
2236}
2237
2238fn is_map_get_of_params(
2239 expr: &Spanned<crate::ast::Expr>,
2240 map_param: &str,
2241 key_param: &str,
2242) -> bool {
2243 let Some(args) = call_named_args(expr, "Map.get") else {
2244 return false;
2245 };
2246 args.len() == 2
2247 && matches_ident_expr(&args[0], map_param)
2248 && matches_ident_expr(&args[1], key_param)
2249}
2250
2251fn is_map_set_of_params(
2252 expr: &Spanned<crate::ast::Expr>,
2253 map_param: &str,
2254 key_param: &str,
2255) -> bool {
2256 let Some(args) = call_named_args(expr, "Map.set") else {
2257 return false;
2258 };
2259 args.len() == 3
2260 && matches_ident_expr(&args[0], map_param)
2261 && matches_ident_expr(&args[1], key_param)
2262}
2263
2264struct MapKeyTrackedIncrementPlan {
2265 outer_fn: String,
2266 map_arg: Spanned<crate::ast::Expr>,
2267 key_arg: Spanned<crate::ast::Expr>,
2268}
2269
2270fn detect_map_key_tracked_increment(
2276 law: &crate::ast::VerifyLaw,
2277 fn_name: &str,
2278 inputs: &ProofLowerInputs,
2279) -> Option<MapKeyTrackedIncrementPlan> {
2280 use crate::ast::{BinOp, Expr};
2281
2282 outer_fn_map_increment_shape(fn_name, inputs)?;
2283
2284 let side = |after: &Spanned<crate::ast::Expr>,
2285 rhs: &Spanned<crate::ast::Expr>|
2286 -> Option<MapKeyTrackedIncrementPlan> {
2287 let (map_arg, key_arg, default_arg) = defaulted_map_get_after_fn_call(after, fn_name)?;
2288 if !is_int_lit(default_arg, 0) {
2289 return None;
2290 }
2291 let Expr::BinOp(BinOp::Add, base, one) = &rhs.node else {
2292 return None;
2293 };
2294 if !is_int_lit(one, 1) {
2295 return None;
2296 }
2297 let (base_map, base_key, base_default) = defaulted_map_get(base)?;
2298 if map_arg.node != base_map.node
2299 || key_arg.node != base_key.node
2300 || default_arg.node != base_default.node
2301 {
2302 return None;
2303 }
2304 Some(MapKeyTrackedIncrementPlan {
2305 outer_fn: fn_name.to_string(),
2306 map_arg: map_arg.clone(),
2307 key_arg: key_arg.clone(),
2308 })
2309 };
2310 side(&law.lhs, &law.rhs).or_else(|| side(&law.rhs, &law.lhs))
2311}
2312
2313fn outer_fn_map_increment_shape(fn_name: &str, inputs: &ProofLowerInputs) -> Option<()> {
2324 use crate::ast::{BinOp, Expr, Pattern, Stmt};
2325
2326 let fd = inputs.find_fn_def_by_call_name(fn_name)?;
2327 if fd.params.len() != 2 {
2328 return None;
2329 }
2330 let map_param = fd.params[0].0.as_str();
2331 let key_param = fd.params[1].0.as_str();
2332 let stmts = fd.body.stmts();
2333 if stmts.len() != 2 {
2334 return None;
2335 }
2336 let Stmt::Binding(current, _, bound_expr) = &stmts[0] else {
2337 return None;
2338 };
2339 if !is_map_get_of_params(bound_expr, map_param, key_param) {
2340 return None;
2341 }
2342 let Stmt::Expr(last_expr) = &stmts[1] else {
2343 return None;
2344 };
2345 let Expr::Match { subject, arms, .. } = &last_expr.node else {
2346 return None;
2347 };
2348 if !matches_ident_expr(subject, current) || arms.len() != 2 {
2349 return None;
2350 }
2351
2352 let some_arm = arms.iter().find_map(|arm| match &arm.pattern {
2353 Pattern::Constructor(name, vars) if name == "Option.Some" && vars.len() == 1 => {
2354 Some((vars[0].as_str(), arm.body.as_ref()))
2355 }
2356 _ => None,
2357 })?;
2358 let none_arm = arms.iter().find_map(|arm| match &arm.pattern {
2359 Pattern::Constructor(name, vars) if name == "Option.None" && vars.is_empty() => {
2360 Some(arm.body.as_ref())
2361 }
2362 _ => None,
2363 })?;
2364
2365 let (some_bound, some_body) = some_arm;
2366 let some_set = call_named_args(some_body, "Map.set")?;
2367 let none_set = call_named_args(none_arm, "Map.set")?;
2368 if some_set.len() != 3 || none_set.len() != 3 {
2369 return None;
2370 }
2371 if !matches_ident_expr(&some_set[0], map_param)
2372 || !matches_ident_expr(&some_set[1], key_param)
2373 || !matches_ident_expr(&none_set[0], map_param)
2374 || !matches_ident_expr(&none_set[1], key_param)
2375 {
2376 return None;
2377 }
2378 let Expr::BinOp(BinOp::Add, add_left, add_right) = &some_set[2].node else {
2379 return None;
2380 };
2381 if !matches_ident_expr(add_left, some_bound) || !is_int_lit(add_right, 1) {
2382 return None;
2383 }
2384 if !is_int_lit(&none_set[2], 1) {
2385 return None;
2386 }
2387 Some(())
2388}
2389
2390fn defaulted_map_get_after_fn_call<'a>(
2394 expr: &'a Spanned<crate::ast::Expr>,
2395 fn_name: &str,
2396) -> Option<(
2397 &'a Spanned<crate::ast::Expr>,
2398 &'a Spanned<crate::ast::Expr>,
2399 &'a Spanned<crate::ast::Expr>,
2400)> {
2401 let (inner, default) = option_with_default_args(expr)?;
2402 let (map_arg, key_arg) = map_get_after_fn_call(inner, fn_name)?;
2403 Some((map_arg, key_arg, default))
2404}
2405
2406fn defaulted_map_get(
2409 expr: &Spanned<crate::ast::Expr>,
2410) -> Option<(
2411 &Spanned<crate::ast::Expr>,
2412 &Spanned<crate::ast::Expr>,
2413 &Spanned<crate::ast::Expr>,
2414)> {
2415 let (inner, default) = option_with_default_args(expr)?;
2416 let get_args = call_named_args(inner, "Map.get")?;
2417 if get_args.len() != 2 {
2418 return None;
2419 }
2420 Some((&get_args[0], &get_args[1], default))
2421}
2422
2423fn option_with_default_args(
2424 expr: &Spanned<crate::ast::Expr>,
2425) -> Option<(&Spanned<crate::ast::Expr>, &Spanned<crate::ast::Expr>)> {
2426 let args = call_named_args(expr, "Option.withDefault")?;
2427 (args.len() == 2).then_some((&args[0], &args[1]))
2428}
2429
2430fn is_int_lit(expr: &Spanned<crate::ast::Expr>, n: i64) -> bool {
2431 use crate::ast::{Expr, Literal};
2432 matches!(&expr.node, Expr::Literal(Literal::Int(m)) if *m == n)
2433}
2434
2435fn map_has_after_fn_call<'a>(
2439 expr: &'a Spanned<crate::ast::Expr>,
2440 fn_name: &str,
2441) -> Option<(&'a Spanned<crate::ast::Expr>, &'a Spanned<crate::ast::Expr>)> {
2442 use crate::ast::Expr;
2443 let has_args = call_named_args(expr, "Map.has")?;
2444 if has_args.len() != 2 {
2445 return None;
2446 }
2447 let Expr::FnCall(callee, fn_args) = &has_args[0].node else {
2448 return None;
2449 };
2450 if fn_args.len() != 2
2451 || !callee_matches_name(callee, fn_name)
2452 || fn_args[1].node != has_args[1].node
2453 {
2454 return None;
2455 }
2456 Some((&fn_args[0], &fn_args[1]))
2457}
2458
2459fn map_get_after_fn_call<'a>(
2462 expr: &'a Spanned<crate::ast::Expr>,
2463 fn_name: &str,
2464) -> Option<(&'a Spanned<crate::ast::Expr>, &'a Spanned<crate::ast::Expr>)> {
2465 use crate::ast::Expr;
2466 let get_args = call_named_args(expr, "Map.get")?;
2467 if get_args.len() != 2 {
2468 return None;
2469 }
2470 let Expr::FnCall(callee, fn_args) = &get_args[0].node else {
2471 return None;
2472 };
2473 if fn_args.len() != 2
2474 || !callee_matches_name(callee, fn_name)
2475 || fn_args[1].node != get_args[1].node
2476 {
2477 return None;
2478 }
2479 Some((&fn_args[0], &fn_args[1]))
2480}
2481
2482fn map_has_set_parts(
2483 expr: &Spanned<crate::ast::Expr>,
2484) -> Option<(
2485 &Spanned<crate::ast::Expr>,
2486 &Spanned<crate::ast::Expr>,
2487 &Spanned<crate::ast::Expr>,
2488)> {
2489 let has_args = call_named_args(expr, "Map.has")?;
2490 if has_args.len() != 2 {
2491 return None;
2492 }
2493 let set_args = call_named_args(&has_args[0], "Map.set")?;
2494 if set_args.len() != 3 {
2495 return None;
2496 }
2497 if set_args[1].node != has_args[1].node {
2498 return None;
2499 }
2500 Some((&set_args[0], &set_args[1], &set_args[2]))
2501}
2502
2503fn map_get_set_parts(
2504 expr: &Spanned<crate::ast::Expr>,
2505) -> Option<(
2506 &Spanned<crate::ast::Expr>,
2507 &Spanned<crate::ast::Expr>,
2508 &Spanned<crate::ast::Expr>,
2509)> {
2510 let get_args = call_named_args(expr, "Map.get")?;
2511 if get_args.len() != 2 {
2512 return None;
2513 }
2514 let set_args = call_named_args(&get_args[0], "Map.set")?;
2515 if set_args.len() != 3 {
2516 return None;
2517 }
2518 if set_args[1].node != get_args[1].node {
2519 return None;
2520 }
2521 Some((&set_args[0], &set_args[1], &set_args[2]))
2522}
2523
2524fn option_some_arg(expr: &Spanned<crate::ast::Expr>) -> Option<&Spanned<crate::ast::Expr>> {
2525 let args = call_named_args(expr, "Option.Some")?;
2526 (args.len() == 1).then_some(&args[0])
2527}
2528
2529fn call_named_args<'a>(
2535 expr: &'a Spanned<crate::ast::Expr>,
2536 full_name: &str,
2537) -> Option<&'a [Spanned<crate::ast::Expr>]> {
2538 use crate::ast::Expr;
2539 let Expr::FnCall(callee, args) = &expr.node else {
2540 return None;
2541 };
2542 let callee_name = expr_to_dotted_name(&callee.node)?;
2543 if callee_name == full_name {
2544 Some(args.as_slice())
2545 } else {
2546 None
2547 }
2548}
2549
2550fn is_bool_true(expr: &Spanned<crate::ast::Expr>) -> bool {
2551 use crate::ast::{Expr, Literal};
2552 matches!(&expr.node, Expr::Literal(Literal::Bool(true)))
2553}
2554
2555fn detect_match_dispatcher_fold_equivalence(
2578 law: &crate::ast::VerifyLaw,
2579 fn_name: &str,
2580 inputs: &ProofLowerInputs,
2581) -> Option<crate::ir::ProofStrategy> {
2582 use crate::analysis::shape::ModulePattern;
2583 use crate::ast::Expr;
2584
2585 fn ident_name(e: &Spanned<Expr>) -> Option<&str> {
2586 match &e.node {
2587 Expr::Ident(n) => Some(n.as_str()),
2588 Expr::Resolved { name, .. } => Some(name.as_str()),
2589 _ => None,
2590 }
2591 }
2592
2593 let shape = inputs.program_shape?;
2594
2595 if law.givens.len() != 1 {
2596 return None;
2597 }
2598 let given_name = &law.givens[0].name;
2599
2600 let fold_fn_pinned = shape.patterns.iter().any(|p| {
2602 matches!(
2603 p,
2604 ModulePattern::MatchDispatcherFold { fn_name: n, .. } if n == fn_name
2605 )
2606 });
2607 if !fold_fn_pinned {
2608 return None;
2609 }
2610
2611 let extract = |expr: &Spanned<Expr>| -> Option<String> {
2613 let Expr::FnCall(callee, args) = &expr.node else {
2614 return None;
2615 };
2616 let name = ident_name(callee)?;
2617 if args.len() != 1 {
2618 return None;
2619 }
2620 if ident_name(&args[0])? != given_name {
2621 return None;
2622 }
2623 Some(name.to_string())
2624 };
2625 let lhs_call = extract(&law.lhs)?;
2626 let rhs_call = extract(&law.rhs)?;
2627 let (fold_fn, spec_fn) = if lhs_call == fn_name && rhs_call != fn_name {
2628 (lhs_call, rhs_call)
2629 } else if rhs_call == fn_name && lhs_call != fn_name {
2630 (rhs_call, lhs_call)
2631 } else {
2632 return None;
2633 };
2634
2635 let spec_pinned = shape.patterns.iter().any(|p| {
2639 matches!(
2640 p,
2641 ModulePattern::MatchDispatcherFold { fn_name: n, .. } if n == &spec_fn
2642 )
2643 });
2644 if !spec_pinned {
2645 return None;
2646 }
2647
2648 Some(crate::ir::ProofStrategy::MatchDispatcherFold { fold_fn, spec_fn })
2649}
2650
2651fn detect_result_pipeline_chain_equivalence(
2652 law: &crate::ast::VerifyLaw,
2653 fn_name: &str,
2654 inputs: &ProofLowerInputs,
2655) -> Option<crate::ir::ProofStrategy> {
2656 use crate::analysis::shape::ModulePattern;
2657 use crate::ast::{Expr, Pattern, Stmt};
2658
2659 fn ident_name(e: &Spanned<Expr>) -> Option<&str> {
2660 match &e.node {
2661 Expr::Ident(n) => Some(n.as_str()),
2662 Expr::Resolved { name, .. } => Some(name.as_str()),
2663 _ => None,
2664 }
2665 }
2666
2667 let shape = inputs.program_shape?;
2668
2669 if law.givens.len() != 1 {
2670 return None;
2671 }
2672 let given_name = &law.givens[0].name;
2673
2674 let (chain_qm_fn, step_fns) = shape.patterns.iter().find_map(|p| match p {
2680 ModulePattern::ResultPipelineChain {
2681 fn_name: n,
2682 step_fns,
2683 ..
2684 } if n == fn_name => Some((n.clone(), step_fns.clone())),
2685 _ => None,
2686 })?;
2687
2688 let extract = |expr: &Spanned<Expr>| -> Option<String> {
2690 let Expr::FnCall(callee, args) = &expr.node else {
2691 return None;
2692 };
2693 let name = ident_name(callee)?;
2694 if args.len() != 1 {
2695 return None;
2696 }
2697 if ident_name(&args[0])? != given_name {
2698 return None;
2699 }
2700 Some(name.to_string())
2701 };
2702 let lhs_call = extract(&law.lhs);
2703 let rhs_call = extract(&law.rhs);
2704 let chain_manual_fn = match (lhs_call, rhs_call) {
2705 (Some(l), Some(r)) if l == chain_qm_fn && r != chain_qm_fn => r,
2706 (Some(l), Some(r)) if r == chain_qm_fn && l != chain_qm_fn => l,
2707 _ => return None,
2708 };
2709
2710 if step_fns.len() < 2 {
2711 return None;
2712 }
2713
2714 let manual_fd = inputs.find_fn_def_by_call_name(&chain_manual_fn)?;
2719 let mut manual_steps: Vec<String> = Vec::new();
2720 fn walk_manual<'a>(
2721 expr: &'a Spanned<Expr>,
2722 steps: &mut Vec<String>,
2723 ident_name: &dyn Fn(&'a Spanned<Expr>) -> Option<&'a str>,
2724 ) {
2725 if let Expr::Match { subject, arms } = &expr.node
2726 && let Expr::FnCall(callee, _) = &subject.node
2727 && let Some(n) = ident_name(subject).or_else(|| ident_name(callee))
2728 {
2729 let has_err_pass = arms.iter().any(|a| {
2730 let pat_is_err = matches!(
2731 &a.pattern,
2732 Pattern::Constructor(c, _) if c == "Result.Err" || c.ends_with(".Err")
2733 );
2734 let body_is_err = match &a.body.node {
2739 Expr::Constructor(c, _) => c == "Result.Err" || c.ends_with(".Err"),
2740 Expr::FnCall(callee, _) => matches!(
2741 &callee.node,
2742 Expr::Attr(base, attr)
2743 if attr == "Err"
2744 && matches!(&base.node, Expr::Ident(b) if b == "Result")
2745 ),
2746 _ => false,
2747 };
2748 pat_is_err && body_is_err
2749 });
2750 if has_err_pass {
2751 steps.push(n.to_string());
2752 }
2753 for a in arms {
2754 walk_manual(&a.body, steps, ident_name);
2755 }
2756 }
2757 }
2758 let manual_stmts = manual_fd.body.stmts();
2759 if manual_stmts.len() != 1 {
2760 return None;
2761 }
2762 let Stmt::Expr(manual_root) = &manual_stmts[0] else {
2763 return None;
2764 };
2765 walk_manual(manual_root, &mut manual_steps, &ident_name);
2766 if manual_steps.len() < 2 {
2767 return None;
2768 }
2769
2770 Some(crate::ir::ProofStrategy::ResultPipelineChain {
2771 chain_qm_fn,
2772 chain_manual_fn,
2773 step_fns,
2774 })
2775}
2776
2777fn detect_wrapper_over_recursion(
2784 law: &crate::ast::VerifyLaw,
2785 fn_name: &str,
2786 inputs: &ProofLowerInputs,
2787) -> Option<crate::ir::ProofStrategy> {
2788 use crate::analysis::shape::ModulePattern;
2789 use crate::ast::{BinOp, Expr, Pattern, Stmt};
2790
2791 fn ident_name(e: &Spanned<Expr>) -> Option<&str> {
2795 match &e.node {
2796 Expr::Ident(n) => Some(n.as_str()),
2797 Expr::Resolved { name, .. } => Some(name.as_str()),
2798 _ => None,
2799 }
2800 }
2801
2802 let shape = inputs.program_shape?;
2803
2804 if law.givens.len() != 1 {
2807 return None;
2808 }
2809 let given_name = &law.givens[0].name;
2810
2811 let (wrapper_fn, inner_fn) = shape.patterns.iter().find_map(|p| match p {
2814 ModulePattern::WrapperOverRecursion {
2815 wrapper_fn,
2816 inner_fn,
2817 ..
2818 } if wrapper_fn == fn_name => Some((wrapper_fn.clone(), inner_fn.clone())),
2819 _ => None,
2820 })?;
2821
2822 let extract = |expr: &Spanned<crate::ast::Expr>| -> Option<String> {
2824 let Expr::FnCall(callee, args) = &expr.node else {
2825 return None;
2826 };
2827 let name = ident_name(callee)?;
2828 if args.len() != 1 {
2829 return None;
2830 }
2831 if ident_name(&args[0])? != given_name {
2832 return None;
2833 }
2834 Some(name.to_string())
2835 };
2836 let lhs_call = extract(&law.lhs);
2837 let rhs_call = extract(&law.rhs);
2838 let other_fn = match (lhs_call, rhs_call) {
2839 (Some(l), Some(r)) if l == wrapper_fn && r != wrapper_fn => r,
2840 (Some(l), Some(r)) if r == wrapper_fn && l != wrapper_fn => l,
2841 _ => return None,
2842 };
2843
2844 let inner_fd = inputs.find_fn_def_by_call_name(&inner_fn)?;
2848 if inner_fd.params.len() != 2 {
2849 return None;
2850 }
2851 let stmts = inner_fd.body.stmts();
2852 if stmts.len() != 1 {
2853 return None;
2854 }
2855 let Stmt::Expr(body) = &stmts[0] else {
2856 return None;
2857 };
2858 let Expr::Match { subject, arms } = &body.node else {
2859 return None;
2860 };
2861 if ident_name(subject)? != inner_fd.params[0].0 {
2862 return None;
2863 }
2864 if arms.len() != 2 {
2865 return None;
2866 }
2867 let mut nil_acc_ok = false;
2868 let mut cons_op: Option<BinOp> = None;
2869 let acc_name = &inner_fd.params[1].0;
2870 for arm in arms {
2871 match &arm.pattern {
2872 Pattern::EmptyList => {
2873 if ident_name(&arm.body) == Some(acc_name.as_str()) {
2874 nil_acc_ok = true;
2875 }
2876 }
2877 Pattern::Cons(head_name, tail_name) => {
2878 let (callee_name, args) = match &arm.body.node {
2881 Expr::FnCall(c, a) => (ident_name(c)?, a.clone()),
2882 Expr::TailCall(td) => (td.target.as_str(), td.args.clone()),
2883 _ => return None,
2884 };
2885 if callee_name != inner_fn {
2886 return None;
2887 }
2888 if args.len() != 2 {
2889 return None;
2890 }
2891 if ident_name(&args[0])? != tail_name.as_str() {
2892 return None;
2893 }
2894 let Expr::BinOp(op, l, r) = &args[1].node else {
2895 return None;
2896 };
2897 if !matches!(op, BinOp::Add | BinOp::Mul) {
2898 return None;
2899 }
2900 let l_n = ident_name(l);
2901 let r_n = ident_name(r);
2902 let l_is_acc = l_n == Some(acc_name.as_str());
2903 let r_is_head = r_n == Some(head_name.as_str());
2904 let l_is_head = l_n == Some(head_name.as_str());
2905 let r_is_acc = r_n == Some(acc_name.as_str());
2906 if !((l_is_acc && r_is_head) || (l_is_head && r_is_acc)) {
2907 return None;
2908 }
2909 cons_op = Some(*op);
2910 }
2911 _ => return None,
2912 }
2913 }
2914 if !nil_acc_ok {
2915 return None;
2916 }
2917 let combine_op = cons_op?;
2918
2919 let wrapper_fd = inputs.find_fn_def_by_call_name(&wrapper_fn)?;
2922 let wstmts = wrapper_fd.body.stmts();
2923 if wstmts.len() != 1 {
2924 return None;
2925 }
2926 let Stmt::Expr(wbody) = &wstmts[0] else {
2927 return None;
2928 };
2929 let (wcallee_name, wargs) = match &wbody.node {
2930 Expr::FnCall(c, a) => (ident_name(c)?, a.clone()),
2931 Expr::TailCall(td) => (td.target.as_str(), td.args.clone()),
2932 _ => return None,
2933 };
2934 if wcallee_name != inner_fn {
2935 return None;
2936 }
2937 if wargs.len() != 2 {
2938 return None;
2939 }
2940 let expected_neutral: i64 = match combine_op {
2941 BinOp::Add | BinOp::Sub => 0,
2942 BinOp::Mul => 1,
2943 _ => return None,
2944 };
2945 let neutral_matches = matches!(
2946 &wargs[1].node,
2947 Expr::Literal(crate::ast::Literal::Int(n)) if *n == expected_neutral
2948 );
2949 if !neutral_matches {
2950 return None;
2951 }
2952
2953 Some(crate::ir::ProofStrategy::WrapperOverRecursion {
2954 wrapper_fn,
2955 inner_fn,
2956 other_fn,
2957 combine_op,
2958 })
2959}
2960
2961fn detect_induction_target(
2962 law: &crate::ast::VerifyLaw,
2963 inputs: &ProofLowerInputs,
2964) -> Option<String> {
2965 if let Some(shape) = inputs.program_shape {
2973 for given in &law.givens {
2974 if shape.inductable_sum_types.contains(&given.type_name) {
2975 return Some(given.name.clone());
2976 }
2977 }
2978 if let Some(given) = list_induction_given(law) {
2984 return Some(given);
2985 }
2986 return None;
2987 }
2988 detect_induction_target_legacy(law, inputs)
2989}
2990
2991fn list_induction_given(law: &crate::ast::VerifyLaw) -> Option<String> {
2994 law.givens
2995 .iter()
2996 .find(|g| g.type_name.trim().starts_with("List<"))
2997 .map(|g| g.name.clone())
2998}
2999
3000fn detect_induction_target_legacy(
3001 law: &crate::ast::VerifyLaw,
3002 inputs: &ProofLowerInputs,
3003) -> Option<String> {
3004 use crate::ast::TypeDef;
3005 for given in &law.givens {
3006 let Some(TypeDef::Sum {
3007 name: type_name,
3008 variants,
3009 ..
3010 }) = inputs.find_type_def(&given.type_name)
3011 else {
3012 continue;
3013 };
3014 let direct_rec = variants.iter().any(|variant| {
3015 variant.fields.iter().any(|field| {
3016 let f = field.trim();
3017 f == type_name
3018 || f.contains(&format!("<{}", type_name))
3019 || f.contains(&format!("{}>", type_name))
3020 || f.contains(&format!(", {}", type_name))
3021 || f.contains(&format!("{},", type_name))
3022 })
3023 });
3024 if !direct_rec {
3025 continue;
3026 }
3027 if has_indirect_rec_variants(variants, type_name) {
3028 continue;
3029 }
3030 return Some(given.name.clone());
3031 }
3032 list_induction_given(law)
3033}
3034
3035fn has_indirect_rec_variants(variants: &[crate::ast::TypeVariant], type_name: &str) -> bool {
3042 for variant in variants {
3043 for field in &variant.fields {
3044 let f = field.trim();
3045 if f == type_name {
3047 continue;
3048 }
3049 let opens = f.matches('<').count();
3052 if opens > 1 && f.contains(type_name) {
3053 return true;
3054 }
3055 }
3056 }
3057 false
3058}
3059
3060fn wrapper_binop(fn_name: &str, inputs: &ProofLowerInputs) -> Option<crate::ast::BinOp> {
3066 use crate::ast::{BinOp, Expr};
3067
3068 let fd = inputs.find_fn_def_by_call_name(fn_name)?;
3069 if fd.params.len() != 2 || fd.return_type != "Int" {
3070 return None;
3071 }
3072 let (p1, t1) = &fd.params[0];
3073 let (p2, t2) = &fd.params[1];
3074 if t1 != "Int" || t2 != "Int" {
3075 return None;
3076 }
3077 let expr = body_terminal_expr(fd.body.as_ref())?;
3078 let Expr::BinOp(op, left, right) = &expr.node else {
3079 return None;
3080 };
3081 if !matches_ident_expr(left, p1) || !matches_ident_expr(right, p2) {
3082 return None;
3083 }
3084 matches!(op, BinOp::Add | BinOp::Mul | BinOp::Sub).then_some(*op)
3085}
3086
3087fn detect_wrapper_commutative(
3088 law: &crate::ast::VerifyLaw,
3089 fn_name: &str,
3090 _op: crate::ast::BinOp,
3091) -> bool {
3092 if law.givens.len() != 2 || law.givens.iter().any(|g| g.type_name != "Int") {
3093 return false;
3094 }
3095 let a = &law.givens[0].name;
3096 let b = &law.givens[1].name;
3097 matches_binary_call(&law.lhs, fn_name, a, b) && matches_binary_call(&law.rhs, fn_name, b, a)
3098 || matches_binary_call(&law.lhs, fn_name, b, a)
3099 && matches_binary_call(&law.rhs, fn_name, a, b)
3100}
3101
3102fn detect_wrapper_associative(
3103 law: &crate::ast::VerifyLaw,
3104 fn_name: &str,
3105 _op: crate::ast::BinOp,
3106) -> bool {
3107 if law.givens.len() != 3 || law.givens.iter().any(|g| g.type_name != "Int") {
3108 return false;
3109 }
3110 let a = &law.givens[0].name;
3111 let b = &law.givens[1].name;
3112 let c = &law.givens[2].name;
3113 let nested = |side| matches_assoc_nested(side, fn_name, a, b, c);
3114 let flat = |side| matches_assoc_flat(side, fn_name, a, b, c);
3115 (nested(&law.lhs) && flat(&law.rhs)) || (nested(&law.rhs) && flat(&law.lhs))
3116}
3117
3118fn detect_wrapper_unary_equivalence(
3124 law: &crate::ast::VerifyLaw,
3125 fn_name: &str,
3126 inputs: &ProofLowerInputs,
3127) -> Option<String> {
3128 if law.givens.len() != 1 || law.givens[0].type_name != "Int" {
3129 return None;
3130 }
3131 let unary = unary_int_wrapper(fn_name, inputs)?;
3132 let g = &law.givens[0].name;
3133
3134 let try_side = |call_side: &Spanned<crate::ast::Expr>,
3135 other_side: &Spanned<crate::ast::Expr>|
3136 -> Option<String> {
3137 if !matches_unary_call(call_side, fn_name, g) {
3138 return None;
3139 }
3140 let (callee_name, var_first, lit) = binary_call_var_const(other_side, g)?;
3141 if lit != unary.constant || var_first != unary.var_first {
3142 return None;
3143 }
3144 let inner_op = wrapper_binop(&callee_name, inputs)?;
3145 if inner_op != unary.op {
3146 return None;
3147 }
3148 Some(callee_name)
3149 };
3150 try_side(&law.lhs, &law.rhs).or_else(|| try_side(&law.rhs, &law.lhs))
3151}
3152
3153#[derive(Debug, Clone, Copy)]
3154struct UnaryIntWrapper {
3155 op: crate::ast::BinOp,
3156 constant: i64,
3157 var_first: bool,
3158}
3159
3160fn unary_int_wrapper(fn_name: &str, inputs: &ProofLowerInputs) -> Option<UnaryIntWrapper> {
3163 use crate::ast::{Expr, Literal};
3164
3165 let fd = inputs.find_fn_def_by_call_name(fn_name)?;
3166 if fd.params.len() != 1 || fd.return_type != "Int" {
3167 return None;
3168 }
3169 let (param, param_ty) = &fd.params[0];
3170 if param_ty != "Int" {
3171 return None;
3172 }
3173 let expr = body_terminal_expr(fd.body.as_ref())?;
3174 let Expr::BinOp(op, left, right) = &expr.node else {
3175 return None;
3176 };
3177 let lit_of = |e: &Spanned<Expr>| -> Option<i64> {
3178 match &e.node {
3179 Expr::Literal(Literal::Int(n)) => Some(*n),
3180 _ => None,
3181 }
3182 };
3183 if matches_ident_expr(left, param) {
3184 let n = lit_of(right)?;
3185 return Some(UnaryIntWrapper {
3186 op: *op,
3187 constant: n,
3188 var_first: true,
3189 });
3190 }
3191 if matches_ident_expr(right, param) {
3192 let n = lit_of(left)?;
3193 return Some(UnaryIntWrapper {
3194 op: *op,
3195 constant: n,
3196 var_first: false,
3197 });
3198 }
3199 None
3200}
3201
3202fn matches_unary_call(expr: &Spanned<crate::ast::Expr>, fn_name: &str, arg: &str) -> bool {
3203 use crate::ast::Expr;
3204 let Expr::FnCall(callee, args) = &expr.node else {
3205 return false;
3206 };
3207 args.len() == 1 && callee_matches_name(callee, fn_name) && matches_ident_expr(&args[0], arg)
3208}
3209
3210fn binary_call_var_const(
3220 expr: &Spanned<crate::ast::Expr>,
3221 var_name: &str,
3222) -> Option<(String, bool, i64)> {
3223 use crate::ast::{Expr, Literal};
3224 let Expr::FnCall(callee, args) = &expr.node else {
3225 return None;
3226 };
3227 if args.len() != 2 {
3228 return None;
3229 }
3230 let callee_name = expr_to_dotted_name(&callee.node)?;
3231 match (&args[0].node, &args[1].node) {
3232 (Expr::Ident(v) | Expr::Resolved { name: v, .. }, Expr::Literal(Literal::Int(n)))
3233 if v == var_name =>
3234 {
3235 Some((callee_name, true, *n))
3236 }
3237 (Expr::Literal(Literal::Int(n)), Expr::Ident(v) | Expr::Resolved { name: v, .. })
3238 if v == var_name =>
3239 {
3240 Some((callee_name, false, *n))
3241 }
3242 _ => None,
3243 }
3244}
3245
3246fn detect_wrapper_sub_right_identity(law: &crate::ast::VerifyLaw, fn_name: &str) -> bool {
3247 if law.givens.len() != 1 || law.givens[0].type_name != "Int" {
3248 return false;
3249 }
3250 let g = &law.givens[0].name;
3251 matches_sub_right_identity_side(&law.lhs, &law.rhs, fn_name, g)
3252 || matches_sub_right_identity_side(&law.rhs, &law.lhs, fn_name, g)
3253}
3254
3255fn detect_wrapper_sub_anti_commutative(law: &crate::ast::VerifyLaw, fn_name: &str) -> Option<bool> {
3260 if law.givens.len() != 2 || law.givens.iter().any(|g| g.type_name != "Int") {
3261 return None;
3262 }
3263 let a = &law.givens[0].name;
3264 let b = &law.givens[1].name;
3265 if matches_binary_call(&law.lhs, fn_name, a, b)
3266 && matches_neg_binary_call(&law.rhs, fn_name, b, a)
3267 {
3268 return Some(true);
3269 }
3270 if matches_binary_call(&law.rhs, fn_name, a, b)
3271 && matches_neg_binary_call(&law.lhs, fn_name, b, a)
3272 {
3273 return Some(false);
3274 }
3275 None
3276}
3277
3278fn detect_wrapper_identity(
3279 law: &crate::ast::VerifyLaw,
3280 fn_name: &str,
3281 op: crate::ast::BinOp,
3282) -> bool {
3283 if law.givens.len() != 1 || law.givens[0].type_name != "Int" {
3284 return false;
3285 }
3286 let identity = match op {
3287 crate::ast::BinOp::Add => 0,
3288 crate::ast::BinOp::Mul => 1,
3289 _ => return false,
3290 };
3291 let g = &law.givens[0].name;
3292 matches_identity_side(&law.lhs, &law.rhs, fn_name, g, identity)
3293 || matches_identity_side(&law.rhs, &law.lhs, fn_name, g, identity)
3294}
3295
3296fn body_terminal_expr(body: &crate::ast::FnBody) -> Option<&Spanned<crate::ast::Expr>> {
3305 use crate::ast::Stmt;
3306 match body.stmts() {
3307 [Stmt::Expr(expr)] => Some(expr),
3308 _ => None,
3309 }
3310}
3311
3312fn simplify_identity_expr(expr: &Spanned<crate::ast::Expr>) -> Spanned<crate::ast::Expr> {
3320 use crate::ast::{BinOp, Expr, Literal};
3321 let line = expr.line;
3322 let int_lit = |e: &Expr| -> Option<i64> {
3323 match e {
3324 Expr::Literal(Literal::Int(n)) => Some(*n),
3325 _ => None,
3326 }
3327 };
3328 let new_node = match &expr.node {
3329 Expr::BinOp(op, left, right) => {
3330 let left = simplify_identity_expr(left);
3331 let right = simplify_identity_expr(right);
3332 match op {
3333 BinOp::Add => {
3334 if int_lit(&left.node) == Some(0) {
3335 return right;
3336 } else if int_lit(&right.node) == Some(0) {
3337 return left;
3338 } else {
3339 Expr::BinOp(*op, Box::new(left), Box::new(right))
3340 }
3341 }
3342 BinOp::Sub => {
3343 if int_lit(&right.node) == Some(0) {
3344 return left;
3345 } else {
3346 Expr::BinOp(*op, Box::new(left), Box::new(right))
3347 }
3348 }
3349 BinOp::Mul => {
3350 if int_lit(&left.node) == Some(0) || int_lit(&right.node) == Some(0) {
3351 Expr::Literal(Literal::Int(0))
3352 } else if int_lit(&left.node) == Some(1) {
3353 return right;
3354 } else if int_lit(&right.node) == Some(1) {
3355 return left;
3356 } else {
3357 Expr::BinOp(*op, Box::new(left), Box::new(right))
3358 }
3359 }
3360 _ => Expr::BinOp(*op, Box::new(left), Box::new(right)),
3361 }
3362 }
3363 Expr::Neg(inner) => Expr::Neg(Box::new(simplify_identity_expr(inner))),
3364 Expr::Attr(base, field) => {
3365 Expr::Attr(Box::new(simplify_identity_expr(base)), field.clone())
3366 }
3367 Expr::FnCall(callee, args) => Expr::FnCall(
3368 Box::new(simplify_identity_expr(callee)),
3369 args.iter().map(simplify_identity_expr).collect(),
3370 ),
3371 Expr::Match { subject, arms } => Expr::Match {
3372 subject: Box::new(simplify_identity_expr(subject)),
3373 arms: arms
3374 .iter()
3375 .map(|arm| crate::ast::MatchArm {
3376 pattern: arm.pattern.clone(),
3377 body: Box::new(simplify_identity_expr(&arm.body)),
3378 binding_slots: arm.binding_slots.clone(),
3379 })
3380 .collect(),
3381 },
3382 other => other.clone(),
3383 };
3384 Spanned::new(new_node, line)
3385}
3386
3387fn matches_ident_expr(expr: &Spanned<crate::ast::Expr>, name: &str) -> bool {
3393 use crate::ast::Expr;
3394 matches!(&expr.node, Expr::Ident(n) | Expr::Resolved { name: n, .. } if n == name)
3395}
3396
3397fn callee_matches_name(expr: &Spanned<crate::ast::Expr>, target: &str) -> bool {
3406 let Some(name) = expr_to_dotted_name(&expr.node) else {
3407 return false;
3408 };
3409 name == target
3410}
3411
3412fn call2_args<'a>(
3413 expr: &'a Spanned<crate::ast::Expr>,
3414 fn_name: &str,
3415) -> Option<(&'a Spanned<crate::ast::Expr>, &'a Spanned<crate::ast::Expr>)> {
3416 use crate::ast::Expr;
3417 let Expr::FnCall(callee, args) = &expr.node else {
3418 return None;
3419 };
3420 if args.len() != 2 || !callee_matches_name(callee, fn_name) {
3421 return None;
3422 }
3423 Some((&args[0], &args[1]))
3424}
3425
3426fn matches_binary_call(expr: &Spanned<crate::ast::Expr>, fn_name: &str, a: &str, b: &str) -> bool {
3427 let Some((x, y)) = call2_args(expr, fn_name) else {
3428 return false;
3429 };
3430 matches_ident_expr(x, a) && matches_ident_expr(y, b)
3431}
3432
3433fn matches_assoc_nested(
3434 expr: &Spanned<crate::ast::Expr>,
3435 fn_name: &str,
3436 a: &str,
3437 b: &str,
3438 c: &str,
3439) -> bool {
3440 let Some((ab, z)) = call2_args(expr, fn_name) else {
3441 return false;
3442 };
3443 let Some((x, y)) = call2_args(ab, fn_name) else {
3444 return false;
3445 };
3446 matches_ident_expr(x, a) && matches_ident_expr(y, b) && matches_ident_expr(z, c)
3447}
3448
3449fn matches_assoc_flat(
3450 expr: &Spanned<crate::ast::Expr>,
3451 fn_name: &str,
3452 a: &str,
3453 b: &str,
3454 c: &str,
3455) -> bool {
3456 let Some((x, bc)) = call2_args(expr, fn_name) else {
3457 return false;
3458 };
3459 let Some((y, z)) = call2_args(bc, fn_name) else {
3460 return false;
3461 };
3462 matches_ident_expr(x, a) && matches_ident_expr(y, b) && matches_ident_expr(z, c)
3463}
3464
3465fn matches_sub_right_identity_side(
3466 call_side: &Spanned<crate::ast::Expr>,
3467 ident_side: &Spanned<crate::ast::Expr>,
3468 fn_name: &str,
3469 given_name: &str,
3470) -> bool {
3471 use crate::ast::{Expr, Literal};
3472 if !matches_ident_expr(ident_side, given_name) {
3473 return false;
3474 }
3475 let Some((x, y)) = call2_args(call_side, fn_name) else {
3476 return false;
3477 };
3478 matches_ident_expr(x, given_name)
3479 && matches!(&y.node, Expr::Literal(Literal::Int(n)) if *n == 0)
3480}
3481
3482fn matches_neg_binary_call(
3483 expr: &Spanned<crate::ast::Expr>,
3484 fn_name: &str,
3485 a: &str,
3486 b: &str,
3487) -> bool {
3488 use crate::ast::Expr;
3489 match &expr.node {
3490 Expr::Neg(inner) => matches_binary_call(inner, fn_name, a, b),
3491 _ => false,
3492 }
3493}
3494
3495fn matches_identity_side(
3496 call_side: &Spanned<crate::ast::Expr>,
3497 ident_side: &Spanned<crate::ast::Expr>,
3498 fn_name: &str,
3499 given_name: &str,
3500 identity: i64,
3501) -> bool {
3502 use crate::ast::{Expr, Literal};
3503 if !matches_ident_expr(ident_side, given_name) {
3504 return false;
3505 }
3506 let Some((x, y)) = call2_args(call_side, fn_name) else {
3507 return false;
3508 };
3509 let is_int_lit = |e: &Spanned<Expr>, n: i64| -> bool {
3510 matches!(&e.node, Expr::Literal(Literal::Int(m)) if *m == n)
3511 };
3512 (matches_ident_expr(x, given_name) && is_int_lit(y, identity))
3513 || (is_int_lit(x, identity) && matches_ident_expr(y, given_name))
3514}
3515
3516fn pick_witness(
3524 type_name: &str,
3525 type_id: crate::ir::TypeId,
3526 inputs: &ProofLowerInputs,
3527 predicate: &Spanned<Expr>,
3528 param_name: &str,
3529 scope: Option<&str>,
3530) -> Option<String> {
3531 let smart_ctor_name: Option<String> = match scope {
3539 None => inputs.entry_items.iter().find_map(|item| match item {
3540 TopLevel::FnDef(fd)
3541 if smart_ctor_matches(fd, type_id, type_name, inputs.symbol_table, scope) =>
3542 {
3543 Some(fd.name.clone())
3544 }
3545 _ => None,
3546 }),
3547 Some(prefix) => inputs
3548 .dep_modules
3549 .iter()
3550 .find(|m| m.prefix == prefix)
3551 .and_then(|m| {
3552 m.fn_defs
3553 .iter()
3554 .find(|fd| {
3555 smart_ctor_matches(fd, type_id, type_name, inputs.symbol_table, scope)
3556 })
3557 .map(|fd| fd.name.clone())
3558 }),
3559 };
3560 if let Some(smart_ctor_name) = smart_ctor_name {
3561 if scope.is_none() {
3569 for item in inputs.entry_items {
3570 let TopLevel::Verify(vb) = item else {
3571 continue;
3572 };
3573 if vb.fn_name != smart_ctor_name {
3574 continue;
3575 }
3576 for (lhs, rhs) in &vb.cases {
3577 if !is_result_ok(&rhs.node) {
3578 continue;
3579 }
3580 let Expr::FnCall(_, args) = &lhs.node else {
3581 continue;
3582 };
3583 if args.len() != 1 {
3584 continue;
3585 }
3586 if let Some(lit) = literal_int_value(&args[0]) {
3587 return Some(lit);
3588 }
3589 }
3590 }
3591 }
3592 }
3593 let mut tried = std::collections::HashSet::<i64>::new();
3604 let mut candidates: Vec<i64> = Vec::new();
3605 let mut from_ast: Vec<i64> = Vec::new();
3606 collect_int_literals(predicate, &mut from_ast);
3607 for k in from_ast {
3608 for delta in &[0_i64, 1, -1] {
3609 if let Some(c) = k.checked_add(*delta) {
3610 candidates.push(c);
3611 }
3612 }
3613 }
3614 candidates.extend_from_slice(&[
3615 0, 1, -1, 2, -2, 10, -10, 100, 1_000, 10_000, 100_000, 1_000_000,
3616 ]);
3617 for candidate in candidates {
3618 if !tried.insert(candidate) {
3619 continue;
3620 }
3621 if eval_int_bool_predicate(predicate, param_name, candidate) == Some(true) {
3622 return Some(candidate.to_string());
3623 }
3624 }
3625 None
3626}
3627
3628fn collect_int_literals(expr: &Spanned<Expr>, out: &mut Vec<i64>) {
3629 match &expr.node {
3630 Expr::Literal(Literal::Int(n)) => out.push(*n),
3631 Expr::Neg(inner) => {
3632 if let Expr::Literal(Literal::Int(n)) = &inner.node {
3633 out.push(-n);
3634 } else {
3635 collect_int_literals(inner, out);
3636 }
3637 }
3638 Expr::BinOp(_, l, r) => {
3639 collect_int_literals(l, out);
3640 collect_int_literals(r, out);
3641 }
3642 Expr::FnCall(callee, args) => {
3643 collect_int_literals(callee, out);
3644 for a in args {
3645 collect_int_literals(a, out);
3646 }
3647 }
3648 Expr::Match { subject, arms } => {
3649 collect_int_literals(subject, out);
3650 for arm in arms {
3651 collect_int_literals(&arm.body, out);
3652 }
3653 }
3654 Expr::Attr(o, _) | Expr::ErrorProp(o) => collect_int_literals(o, out),
3655 _ => {}
3656 }
3657}
3658
3659fn smart_ctor_matches(
3677 fd: &FnDef,
3678 type_id: crate::ir::TypeId,
3679 type_name: &str,
3680 symbols: &crate::ir::SymbolTable,
3681 scope: Option<&str>,
3682) -> bool {
3683 if fd.params.len() != 1 {
3684 return false;
3685 }
3686 let parsed = crate::types::parse_type_str(&fd.return_type);
3687 let crate::types::Type::Result(ok, _) = parsed else {
3688 return false;
3689 };
3690 let crate::types::Type::Named { name: n, .. } = &*ok else {
3696 return false;
3697 };
3698 let name_is_qualified = n.contains('.');
3703 let resolved_id = if name_is_qualified {
3704 n.rsplit_once('.').and_then(|(prefix, bare)| {
3705 symbols.type_id_of(&crate::ir::TypeKey::in_module(prefix.to_string(), bare))
3706 })
3707 } else if let Some(prefix) = scope {
3708 symbols
3709 .type_id_of(&crate::ir::TypeKey::in_module(
3710 prefix.to_string(),
3711 n.clone(),
3712 ))
3713 .or_else(|| symbols.type_id_of(&crate::ir::TypeKey::entry(n.clone())))
3714 } else {
3715 symbols.type_id_of(&crate::ir::TypeKey::entry(n.clone()))
3716 };
3717 match resolved_id {
3718 Some(id) => id == type_id,
3719 None => n == type_name,
3720 }
3721}
3722
3723fn is_result_ok(expr: &Expr) -> bool {
3724 match expr {
3725 Expr::Constructor(name, _) => name == "Result.Ok",
3726 Expr::FnCall(callee, _) => matches!(
3727 &callee.node,
3728 Expr::Attr(obj, field)
3729 if field == "Ok" && matches!(&obj.node, Expr::Ident(n) if n == "Result")
3730 ),
3731 _ => false,
3732 }
3733}
3734
3735fn literal_int_value(expr: &Spanned<Expr>) -> Option<String> {
3736 match &expr.node {
3737 Expr::Literal(Literal::Int(n)) => Some(n.to_string()),
3738 Expr::Neg(inner) => {
3739 let inner_str = literal_int_value(inner)?;
3740 Some(format!("-{inner_str}"))
3741 }
3742 _ => None,
3743 }
3744}
3745
3746fn eval_int_bool_predicate(expr: &Spanned<Expr>, param_name: &str, value: i64) -> Option<bool> {
3747 match &expr.node {
3748 Expr::Literal(Literal::Bool(b)) => Some(*b),
3749 Expr::BinOp(op, l, r) => {
3750 use crate::ast::BinOp::*;
3751 let li = eval_int_arith(l, param_name, value)?;
3752 let ri = eval_int_arith(r, param_name, value)?;
3753 Some(match op {
3754 Lt => li < ri,
3755 Gt => li > ri,
3756 Lte => li <= ri,
3757 Gte => li >= ri,
3758 Eq => li == ri,
3759 Neq => li != ri,
3760 _ => return None,
3761 })
3762 }
3763 Expr::FnCall(callee, args) if args.len() == 2 => {
3764 let name = expr_to_dotted_name(&callee.node)?;
3765 match name.as_str() {
3766 "Bool.and" => Some(
3767 eval_int_bool_predicate(&args[0], param_name, value)?
3768 && eval_int_bool_predicate(&args[1], param_name, value)?,
3769 ),
3770 "Bool.or" => Some(
3771 eval_int_bool_predicate(&args[0], param_name, value)?
3772 || eval_int_bool_predicate(&args[1], param_name, value)?,
3773 ),
3774 _ => None,
3775 }
3776 }
3777 _ => None,
3778 }
3779}
3780
3781fn eval_int_arith(expr: &Spanned<Expr>, param_name: &str, value: i64) -> Option<i64> {
3782 match &expr.node {
3783 Expr::Literal(Literal::Int(n)) => Some(*n),
3784 Expr::Ident(name) | Expr::Resolved { name, .. } if name == param_name => Some(value),
3785 Expr::BinOp(op, l, r) => {
3786 use crate::ast::BinOp::*;
3787 let li = eval_int_arith(l, param_name, value)?;
3788 let ri = eval_int_arith(r, param_name, value)?;
3789 match op {
3790 Add => Some(li.checked_add(ri)?),
3791 Sub => Some(li.checked_sub(ri)?),
3792 Mul => Some(li.checked_mul(ri)?),
3793 Div => Some(li.checked_div(ri)?),
3794 _ => None,
3795 }
3796 }
3797 Expr::Neg(inner) => Some(-eval_int_arith(inner, param_name, value)?),
3798 _ => None,
3799 }
3800}