1use std::collections::HashSet;
2
3use crate::ast::{
4 Expr, FnBody, FnDef, Literal, MatchArm, Pattern, Spanned, Stmt, StrPart, TailCallData,
5 TopLevel, TypeDef, TypeVariant, VerifyBlock, VerifyGivenDomain, VerifyKind,
6};
7use crate::codegen::CodegenContext;
8use crate::types::Type;
9
10#[derive(Debug, Clone)]
24pub struct RefinementInfo<'a> {
25 pub carrier_type: &'a str,
29 pub carrier_field: &'a str,
33 pub param_name: &'a str,
37 pub constructor_fn: &'a str,
42 pub predicate: &'a Spanned<Expr>,
45}
46
47pub fn refinement_info_for<'a>(
58 type_name: &str,
59 inputs: &crate::codegen::proof_lower::ProofLowerInputs<'a>,
60) -> Option<RefinementInfo<'a>> {
61 refinement_info_for_walk(type_name, inputs, None)
62}
63
64pub fn refinement_info_for_in_scope<'a>(
77 type_name: &str,
78 inputs: &crate::codegen::proof_lower::ProofLowerInputs<'a>,
79 scope: Option<&str>,
80) -> Option<RefinementInfo<'a>> {
81 refinement_info_for_walk(type_name, inputs, Some(scope))
82}
83
84fn refinement_info_for_walk<'a>(
85 type_name: &str,
86 inputs: &crate::codegen::proof_lower::ProofLowerInputs<'a>,
87 scope_filter: Option<Option<&str>>,
91) -> Option<RefinementInfo<'a>> {
92 if let Some(shape) = inputs.program_shape
99 && let Some(info) = refinement_info_from_shape(shape, inputs, type_name, scope_filter)
100 {
101 return Some(info);
102 }
103 refinement_info_for_walk_legacy(type_name, inputs, scope_filter)
104}
105
106fn refinement_info_from_shape<'a>(
113 shape: &crate::analysis::shape::ProgramShape,
114 inputs: &crate::codegen::proof_lower::ProofLowerInputs<'a>,
115 type_name: &str,
116 scope_filter: Option<Option<&str>>,
117) -> Option<RefinementInfo<'a>> {
118 use crate::analysis::shape::ModulePattern;
119
120 let pattern_match = shape.patterns.iter().find_map(|p| {
121 let ModulePattern::RefinementSmartConstructor {
122 scope,
123 type_name: ptn,
124 constructor_fn,
125 ..
126 } = p
127 else {
128 return None;
129 };
130 if ptn != type_name {
131 return None;
132 }
133 let scope_ok = match (scope_filter, scope.as_deref()) {
134 (None, _) => true,
135 (Some(None), None) => true,
136 (Some(None), Some(_)) => false,
137 (Some(Some(want)), Some(have)) => want == have,
138 (Some(Some(_)), None) => false,
139 };
140 if !scope_ok {
141 return None;
142 }
143 Some((scope.clone(), constructor_fn.clone()))
144 })?;
145
146 let (scope, constructor_fn) = pattern_match;
147
148 let (carrier_field, carrier_type) = match scope.as_deref() {
149 None => inputs.entry_items.iter().find_map(|i| match i {
150 TopLevel::TypeDef(TypeDef::Product { name, fields, .. })
151 if name == type_name && fields.len() == 1 =>
152 {
153 let (fname, ftype) = &fields[0];
154 Some((fname.as_str(), ftype.as_str()))
155 }
156 _ => None,
157 }),
158 Some(prefix) => inputs
159 .dep_modules
160 .iter()
161 .find(|m| m.prefix == prefix)
162 .and_then(|m| {
163 m.type_defs.iter().find_map(|td| match td {
164 TypeDef::Product { name, fields, .. }
165 if name == type_name && fields.len() == 1 =>
166 {
167 let (fname, ftype) = &fields[0];
168 Some((fname.as_str(), ftype.as_str()))
169 }
170 _ => None,
171 })
172 }),
173 }?;
174
175 let constructor_fd: &'a FnDef = match scope.as_deref() {
176 None => inputs.entry_items.iter().find_map(|i| match i {
177 TopLevel::FnDef(fd) if fd.name == constructor_fn => Some(fd),
178 _ => None,
179 }),
180 Some(prefix) => inputs
181 .dep_modules
182 .iter()
183 .find(|m| m.prefix == prefix)
184 .and_then(|m| m.fn_defs.iter().find(|fd| fd.name == constructor_fn)),
185 }?;
186
187 let (param_name, _) = constructor_fd.params.first()?;
188 let stmts = constructor_fd.body.stmts();
189 let Stmt::Expr(body_expr) = stmts.first()? else {
190 return None;
191 };
192 let Expr::Match { subject, .. } = &body_expr.node else {
193 return None;
194 };
195
196 Some(RefinementInfo {
197 carrier_type,
198 carrier_field,
199 param_name,
200 constructor_fn: constructor_fd.name.as_str(),
201 predicate: subject,
202 })
203}
204
205fn refinement_info_for_walk_legacy<'a>(
206 type_name: &str,
207 inputs: &crate::codegen::proof_lower::ProofLowerInputs<'a>,
208 scope_filter: Option<Option<&str>>,
209) -> Option<RefinementInfo<'a>> {
210 let entry_only = matches!(scope_filter, Some(None));
211 let module_only_prefix = match scope_filter {
212 Some(Some(p)) => Some(p),
213 _ => None,
214 };
215 let allow_entry = scope_filter.is_none() || entry_only;
216 let entry_typedefs: Vec<&'a TypeDef> = if allow_entry {
217 inputs
218 .entry_items
219 .iter()
220 .filter_map(|item| match item {
221 TopLevel::TypeDef(td) => Some(td),
222 _ => None,
223 })
224 .collect()
225 } else {
226 Vec::new()
227 };
228 let module_typedefs: Vec<&'a TypeDef> = inputs
229 .dep_modules
230 .iter()
231 .filter(|m| match (scope_filter, module_only_prefix) {
232 (None, _) => true,
233 (Some(None), _) => false,
234 (Some(Some(_)), Some(p)) => m.prefix == p,
235 _ => false,
236 })
237 .flat_map(|m| m.type_defs.iter())
238 .collect();
239 let (carrier_field, carrier_type) = entry_typedefs
240 .into_iter()
241 .chain(module_typedefs)
242 .find_map(|td| match td {
243 TypeDef::Product { name, fields, .. } if name == type_name && fields.len() == 1 => {
244 let (fname, ftype) = &fields[0];
245 Some((fname.as_str(), ftype.as_str()))
246 }
247 _ => None,
248 })?;
249
250 let entry_fns: Vec<&'a FnDef> = if allow_entry {
251 inputs
252 .entry_items
253 .iter()
254 .filter_map(|item| match item {
255 TopLevel::FnDef(fd) => Some(fd),
256 _ => None,
257 })
258 .collect()
259 } else {
260 Vec::new()
261 };
262 let module_fns: Vec<&'a FnDef> = inputs
263 .dep_modules
264 .iter()
265 .filter(|m| match (scope_filter, module_only_prefix) {
266 (None, _) => true,
267 (Some(None), _) => false,
268 (Some(Some(_)), Some(p)) => m.prefix == p,
269 _ => false,
270 })
271 .flat_map(|m| m.fn_defs.iter())
272 .collect();
273 for fd in entry_fns.into_iter().chain(module_fns) {
274 if !fd.return_type.starts_with("Result<") {
275 continue;
276 }
277 if !fd.return_type[7..].starts_with(type_name) {
278 continue;
279 }
280 if fd.params.len() != 1 {
281 continue;
282 }
283 let (param_name, _) = &fd.params[0];
284 let stmts = fd.body.stmts();
285 if stmts.len() != 1 {
286 continue;
287 }
288 let Stmt::Expr(body_expr) = &stmts[0] else {
289 continue;
290 };
291 let Expr::Match { subject, arms } = &body_expr.node else {
292 continue;
293 };
294 if !is_bool_ok_err_match(arms, type_name, carrier_field, param_name) {
295 continue;
296 }
297 return Some(RefinementInfo {
298 carrier_type,
299 carrier_field,
300 param_name,
301 constructor_fn: fd.name.as_str(),
302 predicate: subject,
303 });
304 }
305 None
306}
307
308pub fn is_refinement_bool_ok_err_match(
314 arms: &[MatchArm],
315 type_name: &str,
316 carrier_field: &str,
317 param_name: &str,
318) -> bool {
319 is_bool_ok_err_match(arms, type_name, carrier_field, param_name)
320}
321
322fn is_bool_ok_err_match(
323 arms: &[MatchArm],
324 type_name: &str,
325 carrier_field: &str,
326 param_name: &str,
327) -> bool {
328 if arms.len() != 2 {
329 return false;
330 }
331 let mut true_ok = false;
332 let mut false_err = false;
333 for arm in arms {
334 match &arm.pattern {
335 Pattern::Literal(Literal::Bool(true)) => {
336 if is_ok_constructor_with_identity(&arm.body, type_name, carrier_field, param_name)
337 {
338 true_ok = true;
339 }
340 }
341 Pattern::Literal(Literal::Bool(false)) => {
342 if is_err_constructor(&arm.body) {
343 false_err = true;
344 }
345 }
346 _ => return false,
347 }
348 }
349 true_ok && false_err
350}
351
352fn is_ok_constructor_with_identity(
353 expr: &Spanned<Expr>,
354 type_name: &str,
355 carrier_field: &str,
356 param_name: &str,
357) -> bool {
358 let (ctor_name, ctor_arg_node) = match &expr.node {
360 Expr::Constructor(name, Some(arg)) => (name.clone(), &arg.node),
361 Expr::FnCall(callee, args) if args.len() == 1 => {
362 let Some(name) = expr_to_dotted_name(&callee.node) else {
363 return false;
364 };
365 (name, &args[0].node)
366 }
367 _ => return false,
368 };
369 if ctor_name != "Result.Ok" {
370 return false;
371 }
372 let (t, fields) = match ctor_arg_node {
373 Expr::RecordCreate {
374 type_name: t,
375 fields,
376 } => (t.as_str(), fields),
377 _ => return false,
378 };
379 if t != type_name || fields.len() != 1 {
380 return false;
381 }
382 let (fname, fvalue) = &fields[0];
383 if fname != carrier_field {
384 return false;
385 }
386 match &fvalue.node {
391 Expr::Ident(name) | Expr::Resolved { name, .. } => name == param_name,
392 _ => false,
393 }
394}
395
396pub fn refinement_lift_for_given(
406 given_name: &str,
407 given_type: &str,
408 lhs: &Spanned<Expr>,
409 rhs: &Spanned<Expr>,
410 ctx: &CodegenContext,
411) -> Option<String> {
412 if given_type == "Float" {
420 return None;
421 }
422 let mut result: Option<String> = None;
430 search_refinement_wrapper(lhs, given_name, given_type, ctx, &mut result);
431 search_refinement_wrapper(rhs, given_name, given_type, ctx, &mut result);
432 result
433}
434
435fn search_refinement_wrapper(
436 expr: &Spanned<Expr>,
437 given_name: &str,
438 given_type: &str,
439 ctx: &CodegenContext,
440 result: &mut Option<String>,
441) {
442 if result.is_some() {
443 return;
444 }
445 match &expr.node {
446 Expr::RecordCreate { type_name, fields } if fields.len() == 1 => {
447 let (_, fvalue) = &fields[0];
448 let matches_var = matches!(
449 &fvalue.node,
450 Expr::Ident(n) | Expr::Resolved { name: n, .. } if n == given_name
451 );
452 if matches_var
453 && let Some((canonical_key, decl)) = find_refined_type_with_key(ctx, type_name)
454 && decl.carrier_type == given_type
455 {
456 *result = Some(canonical_key);
457 return;
458 }
459 for (_, v) in fields {
460 search_refinement_wrapper(v, given_name, given_type, ctx, result);
461 }
462 }
463 Expr::FnCall(callee, args) => {
464 search_refinement_wrapper(callee, given_name, given_type, ctx, result);
465 for a in args {
466 search_refinement_wrapper(a, given_name, given_type, ctx, result);
467 }
468 }
469 Expr::BinOp(_, l, r) => {
470 search_refinement_wrapper(l, given_name, given_type, ctx, result);
471 search_refinement_wrapper(r, given_name, given_type, ctx, result);
472 }
473 Expr::Attr(o, _) => search_refinement_wrapper(o, given_name, given_type, ctx, result),
474 Expr::Neg(i) | Expr::ErrorProp(i) => {
475 search_refinement_wrapper(i, given_name, given_type, ctx, result);
476 }
477 Expr::Match { subject, arms } => {
478 search_refinement_wrapper(subject, given_name, given_type, ctx, result);
479 for arm in arms {
480 search_refinement_wrapper(&arm.body, given_name, given_type, ctx, result);
481 }
482 }
483 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
484 for it in items {
485 search_refinement_wrapper(it, given_name, given_type, ctx, result);
486 }
487 }
488 Expr::Constructor(_, Some(arg)) => {
489 search_refinement_wrapper(arg, given_name, given_type, ctx, result);
490 }
491 _ => {}
492 }
493}
494
495pub fn project_lifted_idents_to_val(
522 expr: &Spanned<Expr>,
523 lifted_vars: &std::collections::HashMap<String, String>,
524) -> Spanned<Expr> {
525 if lifted_vars.is_empty() {
526 return expr.clone();
527 }
528 let new_node = match &expr.node {
529 Expr::BinOp(op, l, r) if is_comparator_binop(*op) => {
530 let l_proj = project_lifted_ident_leaf(l, lifted_vars);
531 let r_proj = project_lifted_ident_leaf(r, lifted_vars);
532 Expr::BinOp(*op, Box::new(l_proj), Box::new(r_proj))
533 }
534 Expr::FnCall(callee, args) => {
535 let name = expr_to_dotted_name(&callee.node);
536 if matches!(name.as_deref(), Some("Bool.and") | Some("Bool.or")) {
537 Expr::FnCall(
538 callee.clone(),
539 args.iter()
540 .map(|a| project_lifted_idents_to_val(a, lifted_vars))
541 .collect(),
542 )
543 } else {
544 return expr.clone();
545 }
546 }
547 _ => return expr.clone(),
548 };
549 Spanned::new(new_node, expr.line)
550}
551
552fn is_comparator_binop(op: crate::ast::BinOp) -> bool {
553 use crate::ast::BinOp::*;
554 matches!(op, Lt | Gt | Lte | Gte | Eq | Neq)
555}
556
557fn project_lifted_ident_leaf(
558 expr: &Spanned<Expr>,
559 lifted_vars: &std::collections::HashMap<String, String>,
560) -> Spanned<Expr> {
561 let target_name = match &expr.node {
562 Expr::Ident(n) | Expr::Resolved { name: n, .. } => n,
563 _ => return expr.clone(),
564 };
565 if lifted_vars.contains_key(target_name) {
566 Spanned::new(
567 Expr::Attr(
568 Box::new(Spanned::new(Expr::Ident(target_name.clone()), expr.line)),
569 "val".to_string(),
570 ),
571 expr.line,
572 )
573 } else {
574 expr.clone()
575 }
576}
577
578pub fn strip_refinement_wrappers(
579 expr: &Spanned<Expr>,
580 lifted_vars: &std::collections::HashMap<String, String>,
581 ctx: &CodegenContext,
582) -> Spanned<Expr> {
583 let new_node = match &expr.node {
584 Expr::RecordCreate { type_name, fields } if fields.len() == 1 => {
585 let (_, fvalue) = &fields[0];
586 let var_name = match &fvalue.node {
587 Expr::Ident(n) | Expr::Resolved { name: n, .. } => Some(n.clone()),
588 _ => None,
589 };
590 let canonical_for_ast = find_refined_type_with_key(ctx, type_name).map(|(k, _)| k);
597 if let Some(name) = var_name
598 && let Some(refined) = lifted_vars.get(&name)
599 && canonical_for_ast.as_deref() == Some(refined.as_str())
600 {
601 return Spanned::new(Expr::Ident(name), expr.line);
602 }
603 let new_fields: Vec<(String, Spanned<Expr>)> = fields
604 .iter()
605 .map(|(n, v)| (n.clone(), strip_refinement_wrappers(v, lifted_vars, ctx)))
606 .collect();
607 Expr::RecordCreate {
608 type_name: type_name.clone(),
609 fields: new_fields,
610 }
611 }
612 Expr::FnCall(callee, args) => Expr::FnCall(
613 Box::new(strip_refinement_wrappers(callee, lifted_vars, ctx)),
614 args.iter()
615 .map(|a| strip_refinement_wrappers(a, lifted_vars, ctx))
616 .collect(),
617 ),
618 Expr::BinOp(op, l, r) => Expr::BinOp(
619 *op,
620 Box::new(strip_refinement_wrappers(l, lifted_vars, ctx)),
621 Box::new(strip_refinement_wrappers(r, lifted_vars, ctx)),
622 ),
623 Expr::Attr(o, f) => Expr::Attr(
624 Box::new(strip_refinement_wrappers(o, lifted_vars, ctx)),
625 f.clone(),
626 ),
627 Expr::Neg(i) => Expr::Neg(Box::new(strip_refinement_wrappers(i, lifted_vars, ctx))),
628 Expr::ErrorProp(i) => {
629 Expr::ErrorProp(Box::new(strip_refinement_wrappers(i, lifted_vars, ctx)))
630 }
631 _ => expr.node.clone(),
632 };
633 Spanned::new(new_node, expr.line)
634}
635
636pub fn swap_comparison_operands_op(op: &crate::ast::BinOp) -> Option<crate::ast::BinOp> {
642 use crate::ast::BinOp::*;
643 match op {
644 Lt => Some(Gt),
645 Gt => Some(Lt),
646 Lte => Some(Gte),
647 Gte => Some(Lte),
648 Eq => Some(Eq),
649 Neq => Some(Neq),
650 _ => None,
651 }
652}
653
654pub fn predicate_syntactic_eq(a: &Spanned<Expr>, b: &Spanned<Expr>) -> bool {
665 match (&a.node, &b.node) {
666 (Expr::BinOp(op_a, la, ra), Expr::BinOp(op_b, lb, rb)) => {
667 if op_a == op_b && predicate_syntactic_eq(la, lb) && predicate_syntactic_eq(ra, rb) {
668 return true;
669 }
670 if let Some(swapped) = swap_comparison_operands_op(op_a)
671 && &swapped == op_b
672 && predicate_syntactic_eq(la, rb)
673 && predicate_syntactic_eq(ra, lb)
674 {
675 return true;
676 }
677 false
678 }
679 _ => a.node == b.node,
680 }
681}
682
683pub fn flatten_bool_and_conjuncts(expr: &Spanned<Expr>) -> Vec<Spanned<Expr>> {
690 if let Expr::FnCall(callee, args) = &expr.node
691 && args.len() == 2
692 && let Some(name) = expr_to_dotted_name(&callee.node)
693 && name == "Bool.and"
694 {
695 let mut out = flatten_bool_and_conjuncts(&args[0]);
696 out.extend(flatten_bool_and_conjuncts(&args[1]));
697 return out;
698 }
699 vec![expr.clone()]
700}
701
702pub fn flatten_bool_and_conjuncts_resolved(
707 expr: &Spanned<crate::ir::hir::ResolvedExpr>,
708) -> Vec<Spanned<crate::ir::hir::ResolvedExpr>> {
709 use crate::ir::hir::{ResolvedCallee, ResolvedExpr};
710 if let ResolvedExpr::Call(ResolvedCallee::Builtin(name), args) = &expr.node
711 && name == "Bool.and"
712 && args.len() == 2
713 {
714 let mut out = flatten_bool_and_conjuncts_resolved(&args[0]);
715 out.extend(flatten_bool_and_conjuncts_resolved(&args[1]));
716 return out;
717 }
718 vec![expr.clone()]
719}
720
721pub fn predicate_syntactic_eq_resolved(
723 a: &Spanned<crate::ir::hir::ResolvedExpr>,
724 b: &Spanned<crate::ir::hir::ResolvedExpr>,
725) -> bool {
726 use crate::ir::hir::ResolvedExpr;
727 match (&a.node, &b.node) {
728 (ResolvedExpr::BinOp(op_a, la, ra), ResolvedExpr::BinOp(op_b, lb, rb)) => {
729 if op_a == op_b
730 && predicate_syntactic_eq_resolved(la, lb)
731 && predicate_syntactic_eq_resolved(ra, rb)
732 {
733 return true;
734 }
735 if let Some(swapped) = swap_comparison_operands_op(op_a)
736 && &swapped == op_b
737 && predicate_syntactic_eq_resolved(la, rb)
738 && predicate_syntactic_eq_resolved(ra, lb)
739 {
740 return true;
741 }
742 false
743 }
744 _ => a.node == b.node,
745 }
746}
747
748pub fn substitute_ident_in_resolved_expr(
756 expr: &Spanned<crate::ir::hir::ResolvedExpr>,
757 from: &str,
758 to: &str,
759) -> Spanned<crate::ir::hir::ResolvedExpr> {
760 use crate::ir::hir::{ResolvedExpr, ResolvedMatchArm, ResolvedStrPart};
761 let line = expr.line;
762 let rec = |e: &Spanned<ResolvedExpr>| substitute_ident_in_resolved_expr(e, from, to);
763 let new_node = match &expr.node {
764 ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. } if name == from => {
765 ResolvedExpr::Ident(to.to_string())
766 }
767 ResolvedExpr::Literal(_) | ResolvedExpr::Ident(_) | ResolvedExpr::Resolved { .. } => {
768 return expr.clone();
769 }
770 ResolvedExpr::Attr(obj, field) => ResolvedExpr::Attr(Box::new(rec(obj)), field.clone()),
771 ResolvedExpr::Call(callee, args) => {
772 ResolvedExpr::Call(callee.clone(), args.iter().map(&rec).collect())
773 }
774 ResolvedExpr::BinOp(op, left, right) => {
775 ResolvedExpr::BinOp(*op, Box::new(rec(left)), Box::new(rec(right)))
776 }
777 ResolvedExpr::Neg(inner) => ResolvedExpr::Neg(Box::new(rec(inner))),
778 ResolvedExpr::Match { subject, arms } => ResolvedExpr::Match {
779 subject: Box::new(rec(subject)),
780 arms: arms
781 .iter()
782 .map(|arm| ResolvedMatchArm {
783 pattern: arm.pattern.clone(),
784 body: Box::new(rec(&arm.body)),
785 binding_slots: std::sync::OnceLock::new(),
786 })
787 .collect(),
788 },
789 ResolvedExpr::Ctor(ctor, args) => {
790 ResolvedExpr::Ctor(ctor.clone(), args.iter().map(&rec).collect())
791 }
792 ResolvedExpr::ErrorProp(inner) => ResolvedExpr::ErrorProp(Box::new(rec(inner))),
793 ResolvedExpr::InterpolatedStr(parts) => ResolvedExpr::InterpolatedStr(
794 parts
795 .iter()
796 .map(|p| match p {
797 ResolvedStrPart::Literal(_) => p.clone(),
798 ResolvedStrPart::Parsed(inner) => ResolvedStrPart::Parsed(Box::new(rec(inner))),
799 })
800 .collect(),
801 ),
802 ResolvedExpr::List(items) => ResolvedExpr::List(items.iter().map(&rec).collect()),
803 ResolvedExpr::Tuple(items) => ResolvedExpr::Tuple(items.iter().map(&rec).collect()),
804 ResolvedExpr::IndependentProduct(items, flag) => {
805 ResolvedExpr::IndependentProduct(items.iter().map(&rec).collect(), *flag)
806 }
807 ResolvedExpr::MapLiteral(entries) => {
808 ResolvedExpr::MapLiteral(entries.iter().map(|(k, v)| (rec(k), rec(v))).collect())
809 }
810 ResolvedExpr::RecordCreate {
811 type_id,
812 type_name,
813 fields,
814 } => ResolvedExpr::RecordCreate {
815 type_id: *type_id,
816 type_name: type_name.clone(),
817 fields: fields.iter().map(|(n, v)| (n.clone(), rec(v))).collect(),
818 },
819 ResolvedExpr::RecordUpdate {
820 type_id,
821 type_name,
822 base,
823 updates,
824 } => ResolvedExpr::RecordUpdate {
825 type_id: *type_id,
826 type_name: type_name.clone(),
827 base: Box::new(rec(base)),
828 updates: updates.iter().map(|(n, v)| (n.clone(), rec(v))).collect(),
829 },
830 ResolvedExpr::TailCall { target, args } => ResolvedExpr::TailCall {
831 target: *target,
832 args: args.iter().map(&rec).collect(),
833 },
834 };
835 Spanned::new(new_node, line)
836}
837
838pub fn substitute_ident_in_expr(expr: &Spanned<Expr>, from: &str, to: &str) -> Spanned<Expr> {
847 use crate::ast::{MatchArm, StrPart, TailCallData};
848 let line = expr.line;
849 let new_node = match &expr.node {
850 Expr::Ident(name) | Expr::Resolved { name, .. } if name == from => {
851 Expr::Ident(to.to_string())
852 }
853 Expr::Literal(_) | Expr::Ident(_) | Expr::Resolved { .. } => return expr.clone(),
854 Expr::Attr(obj, field) => Expr::Attr(
855 Box::new(substitute_ident_in_expr(obj, from, to)),
856 field.clone(),
857 ),
858 Expr::FnCall(callee, args) => Expr::FnCall(
859 Box::new(substitute_ident_in_expr(callee, from, to)),
860 args.iter()
861 .map(|a| substitute_ident_in_expr(a, from, to))
862 .collect(),
863 ),
864 Expr::BinOp(op, left, right) => Expr::BinOp(
865 *op,
866 Box::new(substitute_ident_in_expr(left, from, to)),
867 Box::new(substitute_ident_in_expr(right, from, to)),
868 ),
869 Expr::Neg(inner) => Expr::Neg(Box::new(substitute_ident_in_expr(inner, from, to))),
870 Expr::Match { subject, arms } => Expr::Match {
871 subject: Box::new(substitute_ident_in_expr(subject, from, to)),
872 arms: arms
873 .iter()
874 .map(|arm| MatchArm {
875 pattern: arm.pattern.clone(),
876 body: Box::new(substitute_ident_in_expr(&arm.body, from, to)),
877 binding_slots: std::sync::OnceLock::new(),
878 })
879 .collect(),
880 },
881 Expr::Constructor(name, arg) => Expr::Constructor(
882 name.clone(),
883 arg.as_ref()
884 .map(|inner| Box::new(substitute_ident_in_expr(inner, from, to))),
885 ),
886 Expr::ErrorProp(inner) => {
887 Expr::ErrorProp(Box::new(substitute_ident_in_expr(inner, from, to)))
888 }
889 Expr::InterpolatedStr(parts) => Expr::InterpolatedStr(
890 parts
891 .iter()
892 .map(|part| match part {
893 StrPart::Literal(_) => part.clone(),
894 StrPart::Parsed(inner) => {
895 StrPart::Parsed(Box::new(substitute_ident_in_expr(inner, from, to)))
896 }
897 })
898 .collect(),
899 ),
900 Expr::List(items) => Expr::List(
901 items
902 .iter()
903 .map(|item| substitute_ident_in_expr(item, from, to))
904 .collect(),
905 ),
906 Expr::Tuple(items) => Expr::Tuple(
907 items
908 .iter()
909 .map(|item| substitute_ident_in_expr(item, from, to))
910 .collect(),
911 ),
912 Expr::IndependentProduct(items, flag) => Expr::IndependentProduct(
913 items
914 .iter()
915 .map(|item| substitute_ident_in_expr(item, from, to))
916 .collect(),
917 *flag,
918 ),
919 Expr::MapLiteral(entries) => Expr::MapLiteral(
920 entries
921 .iter()
922 .map(|(k, v)| {
923 (
924 substitute_ident_in_expr(k, from, to),
925 substitute_ident_in_expr(v, from, to),
926 )
927 })
928 .collect(),
929 ),
930 Expr::RecordCreate { type_name, fields } => Expr::RecordCreate {
931 type_name: type_name.clone(),
932 fields: fields
933 .iter()
934 .map(|(n, v)| (n.clone(), substitute_ident_in_expr(v, from, to)))
935 .collect(),
936 },
937 Expr::RecordUpdate {
938 type_name,
939 base,
940 updates,
941 } => Expr::RecordUpdate {
942 type_name: type_name.clone(),
943 base: Box::new(substitute_ident_in_expr(base, from, to)),
944 updates: updates
945 .iter()
946 .map(|(n, v)| (n.clone(), substitute_ident_in_expr(v, from, to)))
947 .collect(),
948 },
949 Expr::TailCall(boxed) => Expr::TailCall(Box::new(TailCallData::new(
950 boxed.target.clone(),
951 boxed
952 .args
953 .iter()
954 .map(|a| substitute_ident_in_expr(a, from, to))
955 .collect(),
956 ))),
957 };
958 Spanned::new(new_node, line)
959}
960
961pub fn when_is_redundant_with_refinement_lifts(
977 when_expr: &Spanned<Expr>,
978 lifted_vars: &std::collections::HashMap<String, String>,
979 ctx: &CodegenContext,
980) -> bool {
981 if lifted_vars.is_empty() {
982 return false;
983 }
984 let resolved_when = ctx.resolve_expr(when_expr, ctx.active_module_scope().as_deref());
992 let when_conjuncts = flatten_bool_and_conjuncts_resolved(&resolved_when);
993 let mut lifted_predicates: Vec<Spanned<crate::ir::hir::ResolvedExpr>> = Vec::new();
1007 for (given_name, refined_type) in lifted_vars {
1008 let Some(decl) = find_refined_type(ctx, refined_type) else {
1009 return false;
1010 };
1011 let substituted = substitute_ident_in_resolved_expr(
1012 &decl.invariant.expr,
1013 decl.predicate_param.as_str(),
1014 given_name,
1015 );
1016 lifted_predicates.extend(flatten_bool_and_conjuncts_resolved(&substituted));
1017 }
1018 if when_conjuncts.len() != lifted_predicates.len() {
1019 return false;
1020 }
1021 let mut matched = vec![false; lifted_predicates.len()];
1022 for wc in &when_conjuncts {
1023 let Some(idx) = (0..lifted_predicates.len())
1024 .find(|&i| !matched[i] && predicate_syntactic_eq_resolved(wc, &lifted_predicates[i]))
1025 else {
1026 return false;
1027 };
1028 matched[idx] = true;
1029 }
1030 true
1031}
1032
1033fn is_err_constructor(expr: &Spanned<Expr>) -> bool {
1034 match &expr.node {
1035 Expr::Constructor(name, Some(_)) => name == "Result.Err",
1036 Expr::FnCall(callee, args) if args.len() == 1 => {
1037 matches!(
1038 expr_to_dotted_name(&callee.node),
1039 Some(name) if name == "Result.Err"
1040 )
1041 }
1042 _ => false,
1043 }
1044}
1045
1046pub fn is_pure_fn(fd: &FnDef) -> bool {
1052 fd.effects.is_empty() && fd.name != "main"
1053}
1054
1055pub fn find_refined_type<'a>(
1082 ctx: &'a CodegenContext,
1083 name: &str,
1084) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1085 find_refined_type_with_key_scoped(ctx, name, None).map(|(_, d)| d)
1086}
1087
1088pub fn backend_named_type_key(ctx: &CodegenContext, ty: &crate::types::Type) -> Option<String> {
1117 let crate::types::Type::Named { id, name } = ty else {
1118 return None;
1119 };
1120 if let Some(type_id) = id {
1121 return Some(ctx.symbol_table.type_entry(*type_id).key.canonical());
1122 }
1123 Some(name.clone())
1124}
1125
1126pub fn backend_type_def_key(ctx: &CodegenContext, td: &crate::ast::TypeDef) -> String {
1141 let key = type_key_for_decl(ctx, td);
1142 if ctx.symbol_table.type_id_of(&key).is_some() {
1143 key.canonical()
1144 } else {
1145 type_def_name(td).to_string()
1146 }
1147}
1148
1149pub fn find_refined_type_by_id(
1165 ctx: &CodegenContext,
1166 type_id: crate::ir::TypeId,
1167) -> Option<&crate::ir::proof_ir::RefinedTypeDecl> {
1168 ctx.proof_ir.refined_types.get(&type_id)
1169}
1170
1171pub fn find_refined_type_for_named<'a>(
1180 ctx: &'a CodegenContext,
1181 named: &crate::types::Type,
1182) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1183 let crate::types::Type::Named { id, name } = named else {
1184 return None;
1185 };
1186 match id {
1187 Some(type_id) => find_refined_type_by_id(ctx, *type_id),
1188 None => find_refined_type(ctx, name),
1189 }
1190}
1191
1192pub fn find_fn_contract_scoped<'a>(
1204 ctx: &'a CodegenContext,
1205 name: &str,
1206 scope: Option<&str>,
1207) -> Option<&'a crate::ir::proof_ir::FnContract> {
1208 let symbols = &ctx.symbol_table;
1216 let bare = name.rsplit('.').next().unwrap_or(name);
1217 let name_is_already_qualified = name.contains('.');
1218 let try_key = |key: crate::ir::FnKey| -> Option<&'a crate::ir::proof_ir::FnContract> {
1219 let id = symbols.fn_id_of(&key)?;
1220 ctx.proof_ir.fn_contracts.get(&id)
1221 };
1222 if let Some(prefix) = scope
1223 && !name_is_already_qualified
1224 && let Some(c) = try_key(crate::ir::FnKey::in_module(prefix.to_string(), bare))
1225 {
1226 return Some(c);
1227 }
1228 let direct_key =
1229 if name_is_already_qualified && let Some((prefix, bare_part)) = name.rsplit_once('.') {
1230 crate::ir::FnKey::in_module(prefix.to_string(), bare_part)
1231 } else {
1232 crate::ir::FnKey::entry(name)
1233 };
1234 if let Some(c) = try_key(direct_key) {
1235 return Some(c);
1236 }
1237 for m in &ctx.modules {
1238 for fd in &m.fn_defs {
1239 if fd.name == bare
1240 && let Some(c) = try_key(crate::ir::FnKey::in_module(m.prefix.clone(), bare))
1241 {
1242 return Some(c);
1243 }
1244 }
1245 }
1246 None
1247}
1248
1249pub fn fn_contract_exists_scoped(ctx: &CodegenContext, name: &str, scope: Option<&str>) -> bool {
1251 find_fn_contract_scoped(ctx, name, scope).is_some()
1252}
1253
1254pub fn fn_key_for_decl(ctx: &CodegenContext, fd: &FnDef) -> crate::ir::FnKey {
1262 match fn_owning_scope_for(ctx, fd) {
1263 Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), fd.name.clone()),
1264 None => crate::ir::FnKey::entry(fd.name.clone()),
1265 }
1266}
1267
1268pub fn type_key_for_decl(ctx: &CodegenContext, td: &TypeDef) -> crate::ir::TypeKey {
1272 for m in &ctx.modules {
1273 for t in &m.type_defs {
1274 if std::ptr::eq(t, td) {
1275 return crate::ir::TypeKey::in_module(m.prefix.clone(), type_def_name(td));
1276 }
1277 }
1278 }
1279 crate::ir::TypeKey::entry(type_def_name(td))
1280}
1281
1282pub fn type_key_for_name(
1289 ctx: &CodegenContext,
1290 name: &str,
1291 scope: Option<&str>,
1292) -> crate::ir::TypeKey {
1293 let bare = name.rsplit('.').next().unwrap_or(name);
1294 let name_is_qualified = name.contains('.');
1295 if let Some(prefix) = scope
1296 && !name_is_qualified
1297 {
1298 for m in &ctx.modules {
1299 if m.prefix == prefix && m.type_defs.iter().any(|td| type_def_name(td) == bare) {
1300 return crate::ir::TypeKey::in_module(prefix.to_string(), bare);
1301 }
1302 }
1303 }
1304 if !name_is_qualified && ctx.type_defs.iter().any(|td| type_def_name(td) == bare) {
1307 return crate::ir::TypeKey::entry(bare);
1308 }
1309 if name_is_qualified
1310 && let Some((prefix, bare_part)) = name.rsplit_once('.')
1311 && ctx.modules.iter().any(|m| m.prefix == prefix)
1312 {
1313 return crate::ir::TypeKey::in_module(prefix.to_string(), bare_part);
1314 }
1315 for m in &ctx.modules {
1316 if m.type_defs.iter().any(|td| type_def_name(td) == bare) {
1317 return crate::ir::TypeKey::in_module(m.prefix.clone(), bare);
1318 }
1319 }
1320 crate::ir::TypeKey::entry(name)
1325}
1326
1327pub fn fn_owning_scope_for<'a>(ctx: &'a CodegenContext, fd: &FnDef) -> Option<&'a str> {
1337 for m in &ctx.modules {
1338 for f in &m.fn_defs {
1339 if std::ptr::eq(f, fd) {
1340 return Some(m.prefix.as_str());
1341 }
1342 }
1343 }
1344 None
1345}
1346
1347pub fn find_fn_contract_for_fn<'a>(
1353 ctx: &'a CodegenContext,
1354 fd: &FnDef,
1355) -> Option<&'a crate::ir::proof_ir::FnContract> {
1356 let symbols = &ctx.symbol_table;
1362 let fn_key = fn_key_for_decl(ctx, fd);
1363 let fn_id = symbols.fn_id_of(&fn_key)?;
1364 ctx.proof_ir.fn_contracts.get(&fn_id)
1365}
1366
1367pub fn fn_contract_exists_for_fn(ctx: &CodegenContext, fd: &FnDef) -> bool {
1369 find_fn_contract_for_fn(ctx, fd).is_some()
1370}
1371
1372pub fn fn_id_for_decl(ctx: &CodegenContext, fd: &FnDef) -> Option<crate::ir::FnId> {
1378 let fn_key = fn_key_for_decl(ctx, fd);
1379 ctx.symbol_table.fn_id_of(&fn_key)
1380}
1381
1382pub fn fn_id_for_dotted_name(ctx: &CodegenContext, dotted: &str) -> Option<crate::ir::FnId> {
1388 let key = if let Some((prefix, bare)) = dotted.rsplit_once('.') {
1389 crate::ir::FnKey::in_module(prefix.to_string(), bare)
1390 } else {
1391 crate::ir::FnKey::entry(dotted)
1392 };
1393 ctx.symbol_table.fn_id_of(&key)
1394}
1395
1396pub fn find_refined_type_with_key<'a>(
1401 ctx: &'a CodegenContext,
1402 name: &str,
1403) -> Option<(String, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1404 find_refined_type_with_key_scoped(ctx, name, None)
1405}
1406
1407pub fn find_refined_type_scoped<'a>(
1425 ctx: &'a CodegenContext,
1426 name: &str,
1427 scope: Option<&str>,
1428) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1429 find_refined_type_with_key_scoped(ctx, name, scope).map(|(_, d)| d)
1430}
1431
1432pub fn find_refined_type_with_key_scoped<'a>(
1447 ctx: &'a CodegenContext,
1448 name: &str,
1449 scope: Option<&str>,
1450) -> Option<(String, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1451 let symbols = &ctx.symbol_table;
1452 let bare = name.rsplit('.').next().unwrap_or(name);
1453 let name_is_already_qualified = name.contains('.');
1454 let try_key =
1455 |key: crate::ir::TypeKey| -> Option<(String, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1456 let id = symbols.type_id_of(&key)?;
1457 let decl = ctx.proof_ir.refined_types.get(&id)?;
1458 Some((key.canonical(), decl))
1459 };
1460 if let Some(prefix) = scope
1461 && !name_is_already_qualified
1462 && let Some(hit) = try_key(crate::ir::TypeKey::in_module(prefix.to_string(), bare))
1463 {
1464 return Some(hit);
1465 }
1466 let direct_key =
1467 if name_is_already_qualified && let Some((prefix, bare_part)) = name.rsplit_once('.') {
1468 crate::ir::TypeKey::in_module(prefix.to_string(), bare_part)
1469 } else {
1470 crate::ir::TypeKey::entry(name)
1471 };
1472 if let Some(hit) = try_key(direct_key) {
1473 return Some(hit);
1474 }
1475 for m in &ctx.modules {
1476 for td in &m.type_defs {
1477 if type_def_name(td) == bare
1478 && let Some(hit) = try_key(crate::ir::TypeKey::in_module(m.prefix.clone(), bare))
1479 {
1480 return Some(hit);
1481 }
1482 }
1483 }
1484 None
1485}
1486
1487pub fn resolve_refined_type_in<'a>(
1493 refined_types: &'a std::collections::HashMap<
1494 crate::ir::TypeId,
1495 crate::ir::proof_ir::RefinedTypeDecl,
1496 >,
1497 symbols: &crate::ir::SymbolTable,
1498 modules: &[crate::codegen::ModuleInfo],
1499 name: &str,
1500) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1501 resolve_refined_type_in_with_key(refined_types, symbols, modules, name).map(|(_, d)| d)
1502}
1503
1504pub fn resolve_refined_type_in_with_key<'a>(
1510 refined_types: &'a std::collections::HashMap<
1511 crate::ir::TypeId,
1512 crate::ir::proof_ir::RefinedTypeDecl,
1513 >,
1514 symbols: &crate::ir::SymbolTable,
1515 modules: &[crate::codegen::ModuleInfo],
1516 name: &str,
1517) -> Option<(crate::ir::TypeId, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1518 let bare = name.rsplit('.').next().unwrap_or(name);
1519 let name_is_already_qualified = name.contains('.');
1520 let direct_key =
1521 if name_is_already_qualified && let Some((prefix, bare_part)) = name.rsplit_once('.') {
1522 crate::ir::TypeKey::in_module(prefix.to_string(), bare_part)
1523 } else {
1524 crate::ir::TypeKey::entry(name)
1525 };
1526 if let Some(id) = symbols.type_id_of(&direct_key)
1527 && let Some(decl) = refined_types.get(&id)
1528 {
1529 return Some((id, decl));
1530 }
1531 for m in modules {
1532 for td in &m.type_defs {
1533 if type_def_name(td) == bare {
1534 let canonical = crate::ir::TypeKey::in_module(m.prefix.clone(), bare);
1535 if let Some(id) = symbols.type_id_of(&canonical)
1536 && let Some(decl) = refined_types.get(&id)
1537 {
1538 return Some((id, decl));
1539 }
1540 }
1541 }
1542 }
1543 None
1544}
1545
1546pub fn all_givens_are_singletons(law: &crate::ast::VerifyLaw) -> bool {
1555 !law.givens.is_empty()
1556 && law.givens.iter().all(|g| match &g.domain {
1557 VerifyGivenDomain::Explicit(values) => values.len() == 1,
1558 VerifyGivenDomain::IntRange { start, end } => start == end,
1559 })
1560}
1561
1562pub fn unclassified_fn_names(ctx: &CodegenContext) -> HashSet<String> {
1569 ctx.proof_ir
1570 .unclassified_fns
1571 .iter()
1572 .filter_map(|uf| {
1573 let s = &uf.message;
1574 let start = s.find('\'')?;
1575 let rest = &s[start + 1..];
1576 let end = rest.find('\'')?;
1577 Some(rest[..end].to_string())
1578 })
1579 .collect()
1580}
1581
1582pub fn law_calls_unclassified_fn(
1594 law: &crate::ast::VerifyLaw,
1595 unclassified: &HashSet<String>,
1596) -> bool {
1597 if unclassified.is_empty() {
1598 return false;
1599 }
1600 expr_calls_named(&law.lhs, unclassified) || expr_calls_named(&law.rhs, unclassified)
1601}
1602
1603fn expr_calls_named(expr: &Spanned<Expr>, names: &HashSet<String>) -> bool {
1604 match &expr.node {
1605 Expr::FnCall(callee, args) => {
1606 let direct = expr_to_dotted_name(&callee.node)
1617 .map(|n| {
1618 let bare = n.rsplit('.').next().unwrap_or(n.as_str());
1619 names.contains(n.as_str()) || names.contains(bare)
1620 })
1621 .unwrap_or(false);
1622 direct
1623 || expr_calls_named(callee, names)
1624 || args.iter().any(|a| expr_calls_named(a, names))
1625 }
1626 Expr::Attr(inner, _) | Expr::ErrorProp(inner) | Expr::Neg(inner) => {
1627 expr_calls_named(inner, names)
1628 }
1629 Expr::BinOp(_, l, r) => expr_calls_named(l, names) || expr_calls_named(r, names),
1630 Expr::Match { subject, arms } => {
1631 expr_calls_named(subject, names)
1632 || arms.iter().any(|a| expr_calls_named(&a.body, names))
1633 }
1634 Expr::Constructor(_, Some(arg)) => expr_calls_named(arg, names),
1635 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
1636 items.iter().any(|i| expr_calls_named(i, names))
1637 }
1638 Expr::MapLiteral(entries) => entries
1639 .iter()
1640 .any(|(k, v)| expr_calls_named(k, names) || expr_calls_named(v, names)),
1641 Expr::RecordCreate { fields, .. } => fields.iter().any(|(_, v)| expr_calls_named(v, names)),
1642 Expr::RecordUpdate { base, updates, .. } => {
1643 expr_calls_named(base, names) || updates.iter().any(|(_, v)| expr_calls_named(v, names))
1644 }
1645 Expr::InterpolatedStr(parts) => parts.iter().any(|p| match p {
1646 crate::ast::StrPart::Parsed(inner) => expr_calls_named(inner, names),
1647 crate::ast::StrPart::Literal(_) => false,
1648 }),
1649 Expr::TailCall(boxed) => {
1650 let crate::ast::TailCallData { target, args, .. } = boxed.as_ref();
1651 names.contains(target) || args.iter().any(|a| expr_calls_named(a, names))
1652 }
1653 _ => false,
1654 }
1655}
1656
1657pub fn accumulator_fold_fn_names(ctx: &CodegenContext) -> HashSet<String> {
1674 use crate::codegen::recursion::detect::{
1675 param_decremented_in_recursion, param_threaded_in_recursion,
1676 single_list_structural_param_index,
1677 };
1678 let threads_over_recursive_adt = |fd: &FnDef| -> bool {
1687 single_list_structural_param_index(fd).is_none()
1688 && (0..fd.params.len()).any(|i| param_threaded_in_recursion(fd, i))
1689 && fd.params.iter().enumerate().any(|(i, (_, ty))| {
1690 ctx_type_is_recursive_sum(ctx, ty.trim()) && param_decremented_in_recursion(fd, i)
1691 })
1692 };
1693 ctx.fn_defs
1694 .iter()
1695 .chain(ctx.modules.iter().flat_map(|m| m.fn_defs.iter()))
1696 .filter(|fd| threads_over_recursive_adt(fd))
1697 .map(|fd| fd.name.clone())
1698 .collect()
1699}
1700
1701fn ctx_type_is_recursive_sum(ctx: &CodegenContext, type_name: &str) -> bool {
1706 use crate::ast::TypeDef;
1707 ctx.type_defs
1708 .iter()
1709 .chain(ctx.modules.iter().flat_map(|m| m.type_defs.iter()))
1710 .any(|td| match td {
1711 TypeDef::Sum { name, variants, .. } if name == type_name => variants.iter().any(|v| {
1712 v.fields.iter().any(|f| {
1713 let f = f.trim();
1714 f == type_name
1715 || f.contains(&format!("<{type_name}"))
1716 || f.contains(&format!("{type_name}>"))
1717 || f.contains(&format!(", {type_name}"))
1718 || f.contains(&format!("{type_name},"))
1719 })
1720 }),
1721 _ => false,
1722 })
1723}
1724
1725fn accfold_combine_fn(fd: &FnDef) -> Option<String> {
1731 use crate::ast::Expr;
1732 use crate::codegen::recursion::detect::{
1733 call_matches, collect_calls_from_body, param_threaded_in_recursion,
1734 };
1735 let acc_idx = (0..fd.params.len()).find(|&i| param_threaded_in_recursion(fd, i))?;
1736 let calls: Vec<Vec<&Spanned<Expr>>> = collect_calls_from_body(fd.body.as_ref())
1737 .into_iter()
1738 .filter(|(name, _)| call_matches(name, &fd.name))
1739 .map(|(_, args)| args)
1740 .collect();
1741 let acc_arg = calls.first()?.get(acc_idx).copied()?;
1742 let Expr::FnCall(callee, _) = &acc_arg.node else {
1743 return None;
1744 };
1745 expr_to_dotted_name(&callee.node)
1746}
1747
1748fn call_args_are_idents(e: &Spanned<Expr>, fn_name: &str, arg_names: &[&str]) -> bool {
1750 let Expr::FnCall(callee, args) = &e.node else {
1751 return false;
1752 };
1753 expr_to_dotted_name(&callee.node).as_deref() == Some(fn_name)
1754 && args.len() == arg_names.len()
1755 && args.iter().zip(arg_names).all(
1756 |(a, n)| matches!(&a.node, Expr::Ident(x) | Expr::Resolved { name: x, .. } if x == n),
1757 )
1758}
1759
1760fn law_is_commutativity(vb: &VerifyBlock, combine: &str) -> bool {
1763 let crate::ast::VerifyKind::Law(law) = &vb.kind else {
1764 return false;
1765 };
1766 if law.givens.len() != 2 || law.when.is_some() {
1767 return false;
1768 }
1769 let a = law.givens[0].name.as_str();
1770 let b = law.givens[1].name.as_str();
1771 (call_args_are_idents(&law.lhs, combine, &[a, b])
1772 && call_args_are_idents(&law.rhs, combine, &[b, a]))
1773 || (call_args_are_idents(&law.lhs, combine, &[b, a])
1774 && call_args_are_idents(&law.rhs, combine, &[a, b]))
1775}
1776
1777fn law_is_associativity(vb: &VerifyBlock, combine: &str) -> bool {
1781 let crate::ast::VerifyKind::Law(law) = &vb.kind else {
1782 return false;
1783 };
1784 if law.givens.len() != 3 || law.when.is_some() {
1785 return false;
1786 }
1787 let a = law.givens[0].name.as_str();
1788 let b = law.givens[1].name.as_str();
1789 let c = law.givens[2].name.as_str();
1790 let nested = |e: &Spanned<Expr>| -> bool {
1792 let Expr::FnCall(callee, args) = &e.node else {
1793 return false;
1794 };
1795 expr_to_dotted_name(&callee.node).as_deref() == Some(combine)
1796 && args.len() == 2
1797 && call_args_are_idents(&args[0], combine, &[a, b])
1798 && matches!(&args[1].node, Expr::Ident(x) | Expr::Resolved { name: x, .. } if x == c)
1799 };
1800 let flat = |e: &Spanned<Expr>| -> bool {
1802 let Expr::FnCall(callee, args) = &e.node else {
1803 return false;
1804 };
1805 expr_to_dotted_name(&callee.node).as_deref() == Some(combine)
1806 && args.len() == 2
1807 && matches!(&args[0].node, Expr::Ident(x) | Expr::Resolved { name: x, .. } if x == a)
1808 && call_args_are_idents(&args[1], combine, &[b, c])
1809 };
1810 (nested(&law.lhs) && flat(&law.rhs)) || (nested(&law.rhs) && flat(&law.lhs))
1811}
1812
1813pub fn nat_accfold_self_closeable(
1824 ctx: &CodegenContext,
1825 verified_fn: &str,
1826 accgen_law_name: &str,
1827) -> bool {
1828 if !accumulator_fold_fn_names(ctx).contains(verified_fn) {
1829 return false;
1830 }
1831 let Some(fd) = ctx.fn_def_by_name(verified_fn, ctx.active_module_scope().as_deref()) else {
1832 return false;
1833 };
1834 let Some(combine) = accfold_combine_fn(fd) else {
1835 return false;
1836 };
1837 if !ctx
1844 .fn_def_by_name(&combine, ctx.active_module_scope().as_deref())
1845 .is_some_and(combine_is_additive_monoid)
1846 {
1847 return false;
1848 }
1849 let citable: Vec<&VerifyBlock> = ctx
1856 .items
1857 .iter()
1858 .filter_map(|i| match i {
1859 TopLevel::Verify(vb) => Some(vb),
1860 _ => None,
1861 })
1862 .take_while(|vb| {
1863 !matches!(&vb.kind, VerifyKind::Law(l)
1864 if vb.fn_name == verified_fn && l.name == accgen_law_name)
1865 })
1866 .collect();
1867 let has_comm = citable.iter().any(|vb| law_is_commutativity(vb, &combine));
1868 let has_assoc = citable.iter().any(|vb| law_is_associativity(vb, &combine));
1869 has_comm && has_assoc
1870}
1871
1872fn combine_is_additive_monoid(fd: &FnDef) -> bool {
1877 use crate::ast::{Expr, Pattern, Stmt};
1878 if fd.params.len() != 2 {
1879 return false;
1880 }
1881 let second = fd.params[1].0.as_str();
1882 let [Stmt::Expr(body)] = fd.body.stmts() else {
1883 return false;
1884 };
1885 let Expr::Match { arms, .. } = &body.node else {
1886 return false;
1887 };
1888 arms.iter().any(|arm| {
1889 matches!(&arm.pattern, Pattern::Constructor(_, binders) if binders.is_empty())
1890 && matches!(&arm.body.node, Expr::Ident(n) | Expr::Resolved { name: n, .. } if n == second)
1891 })
1892}
1893
1894pub fn law_calls_foreign_accumulator_fold(
1905 ctx: &CodegenContext,
1906 law: &crate::ast::VerifyLaw,
1907 verified_fn: &str,
1908) -> bool {
1909 let foreign: HashSet<String> = accumulator_fold_fn_names(ctx)
1910 .into_iter()
1911 .filter(|n| n != verified_fn)
1912 .collect();
1913 law_calls_unclassified_fn(law, &foreign)
1914}
1915
1916pub fn dafny_should_bound_accumulator_fold(
1930 ctx: &CodegenContext,
1931 law: &crate::ast::VerifyLaw,
1932 verified_fn: &str,
1933) -> bool {
1934 law_calls_foreign_accumulator_fold(ctx, law, verified_fn)
1935 || (accumulator_fold_fn_names(ctx).contains(verified_fn)
1936 && !nat_accfold_self_closeable(ctx, verified_fn, &law.name))
1937}
1938
1939pub fn law_rhs_is_independent_of_givens(law: &crate::ast::VerifyLaw) -> bool {
1949 let given_names: HashSet<&str> = law.givens.iter().map(|g| g.name.as_str()).collect();
1950 if given_names.is_empty() {
1951 return true;
1952 }
1953 !expr_references_any_ident(&law.rhs, &given_names)
1954}
1955
1956fn expr_references_any_ident(expr: &Spanned<Expr>, names: &HashSet<&str>) -> bool {
1957 match &expr.node {
1958 Expr::Ident(name) | Expr::Resolved { name, .. } => names.contains(name.as_str()),
1959 Expr::Attr(inner, _) | Expr::ErrorProp(inner) | Expr::Neg(inner) => {
1960 expr_references_any_ident(inner, names)
1961 }
1962 Expr::FnCall(callee, args) => {
1963 expr_references_any_ident(callee, names)
1964 || args.iter().any(|a| expr_references_any_ident(a, names))
1965 }
1966 Expr::BinOp(_, l, r) => {
1967 expr_references_any_ident(l, names) || expr_references_any_ident(r, names)
1968 }
1969 Expr::Match { subject, arms } => {
1970 expr_references_any_ident(subject, names)
1971 || arms
1972 .iter()
1973 .any(|a| expr_references_any_ident(&a.body, names))
1974 }
1975 Expr::Constructor(_, Some(arg)) => expr_references_any_ident(arg, names),
1976 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
1977 items.iter().any(|i| expr_references_any_ident(i, names))
1978 }
1979 Expr::MapLiteral(entries) => entries.iter().any(|(k, v)| {
1980 expr_references_any_ident(k, names) || expr_references_any_ident(v, names)
1981 }),
1982 Expr::RecordCreate { fields, .. } => fields
1983 .iter()
1984 .any(|(_, v)| expr_references_any_ident(v, names)),
1985 Expr::RecordUpdate { base, updates, .. } => {
1986 expr_references_any_ident(base, names)
1987 || updates
1988 .iter()
1989 .any(|(_, v)| expr_references_any_ident(v, names))
1990 }
1991 Expr::InterpolatedStr(parts) => parts.iter().any(|p| match p {
1992 crate::ast::StrPart::Parsed(inner) => expr_references_any_ident(inner, names),
1993 crate::ast::StrPart::Literal(_) => false,
1994 }),
1995 Expr::TailCall(boxed) => {
1996 let crate::ast::TailCallData { args, .. } = boxed.as_ref();
1997 args.iter().any(|a| expr_references_any_ident(a, names))
1998 }
1999 _ => false,
2000 }
2001}
2002
2003pub fn law_lhs_has_trace_projection(expr: &Spanned<Expr>) -> bool {
2024 expr_has_trace_field(expr) && expr_has_trace_api_call(expr)
2025}
2026
2027fn expr_has_trace_field(expr: &Spanned<Expr>) -> bool {
2028 match &expr.node {
2029 Expr::Attr(inner, field) => field == "trace" || expr_has_trace_field(inner),
2030 Expr::FnCall(callee, args) => {
2031 expr_has_trace_field(callee) || args.iter().any(expr_has_trace_field)
2032 }
2033 Expr::BinOp(_, l, r) => expr_has_trace_field(l) || expr_has_trace_field(r),
2034 Expr::Match { subject, arms } => {
2035 expr_has_trace_field(subject) || arms.iter().any(|a| expr_has_trace_field(&a.body))
2036 }
2037 Expr::ErrorProp(inner) => expr_has_trace_field(inner),
2038 Expr::Constructor(_, Some(arg)) => expr_has_trace_field(arg),
2039 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
2040 items.iter().any(expr_has_trace_field)
2041 }
2042 _ => false,
2043 }
2044}
2045
2046const TRACE_API_METHODS: &[&str] = &["event", "group", "branch", "length", "contains", "count"];
2051
2052fn expr_has_trace_api_call(expr: &Spanned<Expr>) -> bool {
2053 match &expr.node {
2054 Expr::FnCall(callee, args) => {
2055 let direct = matches!(
2056 &callee.node,
2057 Expr::Attr(_, method) if TRACE_API_METHODS.contains(&method.as_str())
2058 );
2059 direct || expr_has_trace_api_call(callee) || args.iter().any(expr_has_trace_api_call)
2060 }
2061 Expr::Attr(inner, _) | Expr::ErrorProp(inner) => expr_has_trace_api_call(inner),
2062 Expr::BinOp(_, l, r) => expr_has_trace_api_call(l) || expr_has_trace_api_call(r),
2063 Expr::Match { subject, arms } => {
2064 expr_has_trace_api_call(subject)
2065 || arms.iter().any(|a| expr_has_trace_api_call(&a.body))
2066 }
2067 Expr::Constructor(_, Some(arg)) => expr_has_trace_api_call(arg),
2068 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
2069 items.iter().any(expr_has_trace_api_call)
2070 }
2071 _ => false,
2072 }
2073}
2074
2075pub fn is_recursive_type_def(td: &TypeDef) -> bool {
2078 match td {
2079 TypeDef::Sum { name, variants, .. } => is_recursive_sum(name, variants),
2080 TypeDef::Product { name, fields, .. } => is_recursive_product(name, fields),
2081 }
2082}
2083
2084pub fn type_def_name(td: &TypeDef) -> &str {
2086 match td {
2087 TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
2088 }
2089}
2090
2091pub fn is_recursive_sum(name: &str, variants: &[TypeVariant]) -> bool {
2095 variants
2096 .iter()
2097 .any(|v| v.fields.iter().any(|f| type_ref_contains(f, name)))
2098}
2099
2100pub fn is_recursive_product(name: &str, fields: &[(String, String)]) -> bool {
2102 fields.iter().any(|(_, ty)| type_ref_contains(ty, name))
2103}
2104
2105fn type_ref_contains(annotation: &str, type_name: &str) -> bool {
2106 annotation == type_name
2109 || annotation.contains(&format!("<{}", type_name))
2110 || annotation.contains(&format!("{}>", type_name))
2111 || annotation.contains(&format!(", {}", type_name))
2112 || annotation.contains(&format!("{},", type_name))
2113}
2114
2115pub(crate) fn is_user_type(name: &str, ctx: &CodegenContext) -> bool {
2117 let check_td = |td: &TypeDef| match td {
2118 TypeDef::Sum { name: n, .. } => n == name,
2119 TypeDef::Product { name: n, .. } => n == name,
2120 };
2121 ctx.type_defs.iter().any(check_td)
2122 || ctx.modules.iter().any(|m| m.type_defs.iter().any(check_td))
2123}
2124
2125pub(crate) fn resolve_module_call<'a>(
2128 dotted_name: &'a str,
2129 ctx: &'a CodegenContext,
2130) -> Option<(&'a str, &'a str)> {
2131 let mut best: Option<&str> = None;
2132 for prefix in &ctx.module_prefixes {
2133 let dotted_prefix = format!("{}.", prefix);
2134 if dotted_name.starts_with(&dotted_prefix) && best.is_none_or(|b| prefix.len() > b.len()) {
2135 best = Some(prefix.as_str());
2136 }
2137 }
2138 best.map(|prefix| (prefix, &dotted_name[prefix.len() + 1..]))
2139}
2140
2141pub(crate) fn module_prefix_to_rust_segments(prefix: &str) -> Vec<String> {
2142 prefix.split('.').map(module_segment_to_rust).collect()
2143}
2144
2145pub(crate) fn module_prefix_to_filename(prefix: &str) -> String {
2150 prefix.replace('.', "/")
2151}
2152
2153pub(crate) struct DeclaredEffects {
2165 pub bare_namespaces: HashSet<String>,
2166 pub methods: HashSet<String>,
2167}
2168
2169impl DeclaredEffects {
2170 pub fn includes(&self, c_method: &str) -> bool {
2173 if self.methods.contains(c_method) {
2174 return true;
2175 }
2176 if let Some((ns, _)) = c_method.split_once('.') {
2177 return self.bare_namespaces.contains(ns);
2178 }
2179 false
2180 }
2181}
2182
2183pub(crate) fn collect_declared_effects(ctx: &CodegenContext) -> DeclaredEffects {
2187 let mut bare_namespaces: HashSet<String> = HashSet::new();
2188 let mut methods: HashSet<String> = HashSet::new();
2189 let mut record = |effect: &str| {
2190 if effect.contains('.') {
2191 methods.insert(effect.to_string());
2192 } else {
2193 bare_namespaces.insert(effect.to_string());
2194 }
2195 };
2196 for item in &ctx.items {
2197 if let TopLevel::FnDef(fd) = item {
2198 for eff in &fd.effects {
2199 record(&eff.node);
2200 }
2201 }
2202 }
2203 for module in &ctx.modules {
2204 for fd in &module.fn_defs {
2205 for eff in &fd.effects {
2206 record(&eff.node);
2207 }
2208 }
2209 }
2210 DeclaredEffects {
2211 bare_namespaces,
2212 methods,
2213 }
2214}
2215
2216pub fn entry_basename(ctx: &CodegenContext) -> String {
2224 ctx.items
2225 .iter()
2226 .find_map(|item| match item {
2227 TopLevel::Module(m) => Some(m.name.clone()),
2228 _ => None,
2229 })
2230 .unwrap_or_else(|| {
2231 let mut chars = ctx.project_name.chars();
2232 match chars.next() {
2233 None => String::new(),
2234 Some(c) => c.to_uppercase().chain(chars).collect(),
2235 }
2236 })
2237}
2238
2239pub fn verify_block_counter_key(vb: &crate::ast::VerifyBlock) -> String {
2250 match &vb.kind {
2251 crate::ast::VerifyKind::Cases => format!("fn:{}", vb.fn_name),
2252 crate::ast::VerifyKind::Law(law) => format!("law:{}::{}", vb.fn_name, law.name),
2253 }
2254}
2255
2256pub(crate) fn module_prefix_to_rust_path(prefix: &str) -> String {
2266 format!(
2267 "crate::aver_generated::{}",
2268 module_prefix_to_rust_segments(prefix).join("::")
2269 )
2270}
2271
2272fn module_segment_to_rust(segment: &str) -> String {
2273 let chars = segment.chars().collect::<Vec<_>>();
2274 let mut out = String::new();
2275
2276 for (idx, ch) in chars.iter().enumerate() {
2277 if ch.is_ascii_alphanumeric() {
2278 if ch.is_ascii_uppercase() {
2279 let prev_is_lower_or_digit = idx > 0
2280 && (chars[idx - 1].is_ascii_lowercase() || chars[idx - 1].is_ascii_digit());
2281 let next_is_lower = chars
2282 .get(idx + 1)
2283 .is_some_and(|next| next.is_ascii_lowercase());
2284 if idx > 0 && (prev_is_lower_or_digit || next_is_lower) && !out.ends_with('_') {
2285 out.push('_');
2286 }
2287 out.push(ch.to_ascii_lowercase());
2288 } else {
2289 out.push(ch.to_ascii_lowercase());
2290 }
2291 } else if !out.ends_with('_') {
2292 out.push('_');
2293 }
2294 }
2295
2296 let trimmed = out.trim_matches('_');
2297 let mut normalized = if trimmed.is_empty() {
2298 "module".to_string()
2299 } else {
2300 trimmed.to_string()
2301 };
2302
2303 if matches!(
2304 normalized.as_str(),
2305 "as" | "break"
2306 | "const"
2307 | "continue"
2308 | "crate"
2309 | "else"
2310 | "enum"
2311 | "extern"
2312 | "false"
2313 | "fn"
2314 | "for"
2315 | "if"
2316 | "impl"
2317 | "in"
2318 | "let"
2319 | "loop"
2320 | "match"
2321 | "mod"
2322 | "move"
2323 | "mut"
2324 | "pub"
2325 | "ref"
2326 | "return"
2327 | "self"
2328 | "Self"
2329 | "static"
2330 | "struct"
2331 | "super"
2332 | "trait"
2333 | "true"
2334 | "type"
2335 | "unsafe"
2336 | "use"
2337 | "where"
2338 | "while"
2339 ) {
2340 normalized.push_str("_mod");
2341 }
2342
2343 normalized
2344}
2345
2346pub(crate) fn split_type_params(s: &str, delim: char) -> Vec<String> {
2351 let mut parts = Vec::new();
2352 let mut depth = 0usize;
2353 let mut current = String::new();
2354 for ch in s.chars() {
2355 match ch {
2356 '<' | '(' => {
2357 depth += 1;
2358 current.push(ch);
2359 }
2360 '>' | ')' => {
2361 depth = depth.saturating_sub(1);
2362 current.push(ch);
2363 }
2364 _ if ch == delim && depth == 0 => {
2365 parts.push(current.trim().to_string());
2366 current.clear();
2367 }
2368 _ => current.push(ch),
2369 }
2370 }
2371 let rest = current.trim().to_string();
2372 if !rest.is_empty() {
2373 parts.push(rest);
2374 }
2375 parts
2376}
2377
2378pub(crate) fn escape_string_literal_ext(s: &str, unicode_escapes: bool) -> String {
2385 let mut out = String::with_capacity(s.len());
2386 for ch in s.chars() {
2387 match ch {
2388 '\\' => out.push_str("\\\\"),
2389 '"' => out.push_str("\\\""),
2390 '\n' => out.push_str("\\n"),
2391 '\r' => out.push_str("\\r"),
2392 '\t' => out.push_str("\\t"),
2393 '\0' => out.push_str("\\0"),
2394 c if c.is_control() => {
2395 if unicode_escapes {
2396 out.push_str(&format!("\\U{{{:06x}}}", c as u32));
2398 } else {
2399 out.push_str(&format!("\\x{:02x}", c as u32));
2400 }
2401 }
2402 c => out.push(c),
2403 }
2404 }
2405 out
2406}
2407
2408pub(crate) fn escape_string_literal(s: &str) -> String {
2410 escape_string_literal_ext(s, false)
2411}
2412
2413pub(crate) fn escape_string_literal_unicode(s: &str) -> String {
2415 escape_string_literal_ext(s, true)
2416}
2417
2418pub(crate) fn parse_type_annotation(ann: &str) -> Type {
2422 crate::types::parse_type_str(ann)
2423}
2424
2425pub(crate) fn is_set_type(ty: &Type) -> bool {
2431 matches!(ty, Type::Map(_, v) if matches!(v.as_ref(), Type::Unit))
2432}
2433
2434pub(crate) fn is_set_annotation(ann: &str) -> bool {
2436 is_set_type(&parse_type_annotation(ann))
2437}
2438
2439pub(crate) fn is_unit_expr_resolved(expr: &crate::ir::hir::ResolvedExpr) -> bool {
2442 matches!(
2443 expr,
2444 crate::ir::hir::ResolvedExpr::Literal(crate::ast::Literal::Unit)
2445 )
2446}
2447
2448pub(crate) fn escape_reserved_word(name: &str, reserved: &[&str], suffix: &str) -> String {
2453 if reserved.contains(&name) {
2454 format!("{}{}", name, suffix)
2455 } else {
2456 name.to_string()
2457 }
2458}
2459
2460pub(crate) fn escape_reserved_word_prefix(name: &str, reserved: &[&str], prefix: &str) -> String {
2463 if reserved.contains(&name) {
2464 format!("{}{}", prefix, name)
2465 } else {
2466 name.to_string()
2467 }
2468}
2469
2470pub(crate) fn to_lower_first(s: &str) -> String {
2474 let mut chars = s.chars();
2475 match chars.next() {
2476 None => String::new(),
2477 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
2478 }
2479}
2480
2481pub(crate) fn expr_to_dotted_name(expr: &Expr) -> Option<String> {
2484 crate::ir::expr_to_dotted_name(expr)
2485}
2486
2487#[derive(Debug, Clone)]
2501pub(crate) enum OracleInjectionMode<'a> {
2502 LemmaBinding,
2503 LemmaBindingProjected,
2513 #[allow(dead_code)]
2514 SampleValue,
2515 SampleCaseBinding(&'a [(String, crate::ast::Spanned<Expr>)]),
2516}
2517
2518pub(crate) fn rewrite_effectful_calls_in_law<'fd, F>(
2527 expr: &crate::ast::Spanned<Expr>,
2528 law: &crate::ast::VerifyLaw,
2529 find_fn_def: F,
2530 mode: OracleInjectionMode,
2531) -> crate::ast::Spanned<Expr>
2532where
2533 F: Fn(&str) -> Option<&'fd crate::ast::FnDef> + Copy,
2534{
2535 use crate::ast::{Spanned, VerifyGivenDomain};
2536
2537 let injection_by_effect: std::collections::HashMap<String, Spanned<Expr>> = law
2538 .givens
2539 .iter()
2540 .filter_map(|g| {
2541 let arg_expr = match &mode {
2542 OracleInjectionMode::LemmaBinding => {
2543 Spanned::new(Expr::Ident(g.name.clone()), expr.line)
2544 }
2545 OracleInjectionMode::LemmaBindingProjected => {
2546 Spanned::new(Expr::Ident(g.name.clone()), expr.line)
2554 }
2555 OracleInjectionMode::SampleValue => match &g.domain {
2556 VerifyGivenDomain::Explicit(vals) => vals.first().cloned()?,
2557 _ => return None,
2558 },
2559 OracleInjectionMode::SampleCaseBinding(case_bindings) => case_bindings
2560 .iter()
2561 .find(|(name, _)| name == &g.name)
2562 .map(|(_, v)| v.clone())?,
2563 };
2564 Some((g.type_name.clone(), arg_expr))
2565 })
2566 .collect();
2567 let rewritten = rewrite_effectful_call(expr, &injection_by_effect, find_fn_def);
2568
2569 if matches!(mode, OracleInjectionMode::LemmaBindingProjected) {
2578 let oracle_names: std::collections::HashSet<String> = law
2588 .givens
2589 .iter()
2590 .filter(|g| crate::types::checker::oracle_subtypes::has_bounded_subtype(&g.type_name))
2591 .map(|g| g.name.clone())
2592 .collect();
2593 if !oracle_names.is_empty() {
2594 return project_oracle_direct_calls(&rewritten, &oracle_names);
2595 }
2596 }
2597 rewritten
2598}
2599
2600fn project_oracle_direct_calls(
2613 expr: &crate::ast::Spanned<Expr>,
2614 oracle_names: &std::collections::HashSet<String>,
2615) -> crate::ast::Spanned<Expr> {
2616 use crate::ast::Spanned;
2617 let line = expr.line;
2618 let project_ident = |name: &str, line: usize| -> Spanned<Expr> {
2619 Spanned::new(
2620 Expr::Attr(
2621 Box::new(Spanned::new(Expr::Ident(name.to_string()), line)),
2622 "val".to_string(),
2623 ),
2624 line,
2625 )
2626 };
2627 let new_node = match &expr.node {
2628 Expr::Ident(name) if oracle_names.contains(name) => {
2632 return project_ident(name, line);
2633 }
2634 Expr::FnCall(callee, args) => {
2635 let new_args: Vec<Spanned<Expr>> = args
2636 .iter()
2637 .map(|a| project_oracle_direct_calls(a, oracle_names))
2638 .collect();
2639 let new_callee = if let Expr::Ident(name) = &callee.node
2641 && oracle_names.contains(name)
2642 {
2643 project_ident(name, callee.line)
2644 } else {
2645 project_oracle_direct_calls(callee, oracle_names)
2646 };
2647 Expr::FnCall(Box::new(new_callee), new_args)
2648 }
2649 Expr::Constructor(name, Some(arg)) => Expr::Constructor(
2650 name.clone(),
2651 Some(Box::new(project_oracle_direct_calls(arg, oracle_names))),
2652 ),
2653 Expr::Attr(obj, field) => Expr::Attr(
2654 Box::new(project_oracle_direct_calls(obj, oracle_names)),
2655 field.clone(),
2656 ),
2657 Expr::BinOp(op, l, r) => Expr::BinOp(
2658 *op,
2659 Box::new(project_oracle_direct_calls(l, oracle_names)),
2660 Box::new(project_oracle_direct_calls(r, oracle_names)),
2661 ),
2662 other => other.clone(),
2663 };
2664 Spanned::new(new_node, line)
2665}
2666
2667fn rewrite_effectful_call<'fd, F>(
2668 expr: &crate::ast::Spanned<Expr>,
2669 injection_by_effect: &std::collections::HashMap<String, crate::ast::Spanned<Expr>>,
2670 find_fn_def: F,
2671) -> crate::ast::Spanned<Expr>
2672where
2673 F: Fn(&str) -> Option<&'fd crate::ast::FnDef> + Copy,
2674{
2675 use crate::ast::Spanned;
2676 use crate::types::checker::effect_classification::{EffectDimension, classify};
2677
2678 match &expr.node {
2679 Expr::FnCall(callee, args) => {
2680 let rewritten_args: Vec<Spanned<Expr>> = args
2681 .iter()
2682 .map(|a| rewrite_effectful_call(a, injection_by_effect, find_fn_def))
2683 .collect();
2684 let rewritten_callee = Box::new(rewrite_effectful_call(
2685 callee,
2686 injection_by_effect,
2687 find_fn_def,
2688 ));
2689
2690 let callee_name = match &callee.node {
2691 Expr::Ident(name) => Some(name.clone()),
2692 Expr::Resolved { name, .. } => Some(name.clone()),
2693 _ => None,
2694 };
2695
2696 if let Some(name) = callee_name
2697 && let Some(fd) = find_fn_def(&name)
2698 && !fd.effects.is_empty()
2699 && fd
2700 .effects
2701 .iter()
2702 .all(|e| crate::types::checker::effect_classification::is_classified(&e.node))
2703 {
2704 let mut injected: Vec<Spanned<Expr>> = Vec::new();
2705 let needs_path = fd.effects.iter().any(|e| {
2706 matches!(
2707 classify(&e.node).map(|c| c.dimension),
2708 Some(EffectDimension::Generative | EffectDimension::GenerativeOutput)
2709 )
2710 });
2711 if needs_path {
2712 injected.push(Spanned::new(
2713 Expr::Attr(
2717 Box::new(Spanned::new(
2718 Expr::Ident("BranchPath".to_string()),
2719 expr.line,
2720 )),
2721 "Root".to_string(),
2722 ),
2723 expr.line,
2724 ));
2725 }
2726 let mut seen = std::collections::HashSet::new();
2727 for e in &fd.effects {
2728 if !seen.insert(e.node.clone()) {
2729 continue;
2730 }
2731 let Some(c) = classify(&e.node) else { continue };
2732 if matches!(c.dimension, EffectDimension::Output) {
2733 continue;
2734 }
2735 if let Some(inj) = injection_by_effect.get(&e.node) {
2736 injected.push(inj.clone());
2737 }
2738 }
2739 injected.extend(rewritten_args);
2740 return Spanned::new(Expr::FnCall(rewritten_callee, injected), expr.line);
2741 }
2742
2743 Spanned::new(Expr::FnCall(rewritten_callee, rewritten_args), expr.line)
2744 }
2745 Expr::BinOp(op, l, r) => Spanned::new(
2746 Expr::BinOp(
2747 *op,
2748 Box::new(rewrite_effectful_call(l, injection_by_effect, find_fn_def)),
2749 Box::new(rewrite_effectful_call(r, injection_by_effect, find_fn_def)),
2750 ),
2751 expr.line,
2752 ),
2753 Expr::Tuple(items) => Spanned::new(
2754 Expr::Tuple(
2755 items
2756 .iter()
2757 .map(|i| rewrite_effectful_call(i, injection_by_effect, find_fn_def))
2758 .collect(),
2759 ),
2760 expr.line,
2761 ),
2762 _ => expr.clone(),
2763 }
2764}
2765
2766pub(crate) fn verify_reachable_fn_names(items: &[TopLevel]) -> HashSet<String> {
2776 let mut reachable: HashSet<String> = HashSet::new();
2777 for item in items {
2778 if let TopLevel::Verify(vb) = item {
2779 collect_verify_block_refs(vb, &mut reachable);
2780 }
2781 }
2782 loop {
2784 let mut changed = false;
2785 for item in items {
2786 if let TopLevel::FnDef(fd) = item
2787 && reachable.contains(&fd.name)
2788 {
2789 let mut called = HashSet::new();
2790 collect_called_idents_in_body(&fd.body, &mut called);
2791 for name in called {
2792 if reachable.insert(name) {
2793 changed = true;
2794 }
2795 }
2796 }
2797 }
2798 if !changed {
2799 break;
2800 }
2801 }
2802 reachable
2803}
2804
2805fn collect_verify_block_refs(vb: &VerifyBlock, out: &mut HashSet<String>) {
2806 out.insert(vb.fn_name.clone());
2807 for (lhs, rhs) in &vb.cases {
2808 collect_called_idents(lhs, out);
2809 collect_called_idents(rhs, out);
2810 }
2811 if let VerifyKind::Law(law) = &vb.kind {
2812 collect_called_idents(&law.lhs, out);
2813 collect_called_idents(&law.rhs, out);
2814 if let Some(when) = &law.when {
2815 collect_called_idents(when, out);
2816 }
2817 for given in &law.givens {
2818 if let VerifyGivenDomain::Explicit(values) = &given.domain {
2819 for v in values {
2820 collect_called_idents(v, out);
2821 }
2822 }
2823 }
2824 }
2825 for given in &vb.cases_givens {
2826 if let VerifyGivenDomain::Explicit(values) = &given.domain {
2827 for v in values {
2828 collect_called_idents(v, out);
2829 }
2830 }
2831 }
2832}
2833
2834fn collect_called_idents_in_body(body: &FnBody, out: &mut HashSet<String>) {
2835 for stmt in body.stmts() {
2836 match stmt {
2837 Stmt::Binding(_, _, e) | Stmt::Expr(e) => collect_called_idents(e, out),
2838 }
2839 }
2840}
2841
2842fn collect_called_idents(expr: &Spanned<Expr>, out: &mut HashSet<String>) {
2843 match &expr.node {
2844 Expr::FnCall(callee, args) => {
2845 if let Expr::Ident(name) | Expr::Resolved { name, .. } = &callee.node {
2846 out.insert(name.clone());
2847 } else {
2848 collect_called_idents(callee, out);
2849 }
2850 for a in args {
2851 collect_called_idents(a, out);
2852 }
2853 }
2854 Expr::TailCall(boxed) => {
2855 let TailCallData { target, args, .. } = boxed.as_ref();
2856 out.insert(target.clone());
2857 for a in args {
2858 collect_called_idents(a, out);
2859 }
2860 }
2861 Expr::Ident(name) | Expr::Resolved { name, .. } => {
2862 out.insert(name.clone());
2863 }
2864 Expr::BinOp(_, l, r) => {
2865 collect_called_idents(l, out);
2866 collect_called_idents(r, out);
2867 }
2868 Expr::Neg(inner) => collect_called_idents(inner, out),
2869 Expr::Match { subject, arms, .. } => {
2870 collect_called_idents(subject, out);
2871 for arm in arms {
2872 collect_called_idents(&arm.body, out);
2873 }
2874 }
2875 Expr::ErrorProp(inner) | Expr::Attr(inner, _) => {
2876 collect_called_idents(inner, out);
2877 }
2878 Expr::Constructor(_, Some(inner)) => {
2879 collect_called_idents(inner, out);
2880 }
2881 Expr::InterpolatedStr(parts) => {
2882 for part in parts {
2883 if let StrPart::Parsed(inner) = part {
2884 collect_called_idents(inner, out);
2885 }
2886 }
2887 }
2888 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
2889 for i in items {
2890 collect_called_idents(i, out);
2891 }
2892 }
2893 Expr::MapLiteral(entries) => {
2894 for (k, v) in entries {
2895 collect_called_idents(k, out);
2896 collect_called_idents(v, out);
2897 }
2898 }
2899 Expr::RecordCreate { fields, .. } => {
2900 for (_, v) in fields {
2901 collect_called_idents(v, out);
2902 }
2903 }
2904 Expr::RecordUpdate { base, updates, .. } => {
2905 collect_called_idents(base, out);
2906 for (_, v) in updates {
2907 collect_called_idents(v, out);
2908 }
2909 }
2910 Expr::Literal(_) | Expr::Constructor(_, None) => {}
2911 }
2912}
2913
2914pub(crate) struct PerScopeSections {
2918 pub by_scope: std::collections::HashMap<String, Vec<String>>,
2919}
2920
2921impl PerScopeSections {
2922 pub(crate) fn take(&mut self, scope: &str) -> Vec<String> {
2923 self.by_scope.remove(scope).unwrap_or_default()
2924 }
2925}
2926
2927pub(crate) fn route_pure_components_per_scope<F, G>(
2936 ctx: &CodegenContext,
2937 is_pure: F,
2938 mut emit: G,
2939) -> PerScopeSections
2940where
2941 F: Fn(&FnDef) -> bool,
2942 G: FnMut(&[&FnDef], &str) -> Vec<String>,
2943{
2944 let mut by_scope: std::collections::HashMap<String, Vec<String>> =
2945 std::collections::HashMap::new();
2946
2947 let mut process =
2948 |fns: Vec<&FnDef>,
2949 scope: String,
2950 by_scope: &mut std::collections::HashMap<String, Vec<String>>| {
2951 let comps = crate::call_graph::ordered_fn_components(&fns, &ctx.module_prefixes);
2952 let bucket = by_scope.entry(scope.clone()).or_default();
2953 for comp in comps {
2954 bucket.extend(emit(&comp, scope.as_str()));
2955 }
2956 };
2957
2958 for module in &ctx.modules {
2959 let pure: Vec<&FnDef> = module.fn_defs.iter().filter(|fd| is_pure(fd)).collect();
2960 process(pure, module.prefix.clone(), &mut by_scope);
2961 }
2962 let entry_pure: Vec<&FnDef> = ctx.fn_defs.iter().filter(|fd| is_pure(fd)).collect();
2963 process(entry_pure, String::new(), &mut by_scope);
2964
2965 PerScopeSections { by_scope }
2966}
2967
2968#[cfg(test)]
2969mod tests {
2970 use super::*;
2971 use crate::ast::{Literal, VerifyGiven, VerifyGivenDomain, VerifyLaw};
2972
2973 fn sb(node: Expr) -> Spanned<Expr> {
2974 Spanned::new(node, 1)
2975 }
2976
2977 fn bsb(node: Expr) -> Box<Spanned<Expr>> {
2978 Box::new(sb(node))
2979 }
2980
2981 fn law_with(lhs: Spanned<Expr>, rhs: Spanned<Expr>) -> VerifyLaw {
2982 VerifyLaw {
2983 name: "test".to_string(),
2984 givens: vec![VerifyGiven {
2985 name: "xs".to_string(),
2986 type_name: "List<String>".to_string(),
2987 domain: VerifyGivenDomain::Explicit(vec![sb(Expr::List(vec![]))]),
2988 }],
2989 when: None,
2990 lhs,
2991 rhs,
2992 sample_guards: Vec::new(),
2993 }
2994 }
2995
2996 #[test]
2997 fn law_calls_unclassified_fn_detects_dotted_callee() {
2998 let lhs = sb(Expr::FnCall(
3009 bsb(Expr::Attr(
3010 bsb(Expr::Ident("Dep".to_string())),
3011 "toSorted".to_string(),
3012 )),
3013 vec![sb(Expr::Ident("xs".to_string()))],
3014 ));
3015 let rhs = sb(Expr::Ident("xs".to_string()));
3016 let law = law_with(lhs, rhs);
3017
3018 let mut canonical = HashSet::new();
3021 canonical.insert("Dep.toSorted".to_string());
3022 assert!(law_calls_unclassified_fn(&law, &canonical));
3023
3024 let mut bare_only = HashSet::new();
3027 bare_only.insert("toSorted".to_string());
3028 assert!(
3029 law_calls_unclassified_fn(&law, &bare_only),
3030 "bare unclassified name must catch a dotted callsite via suffix match"
3031 );
3032
3033 let mut unrelated = HashSet::new();
3036 unrelated.insert("somethingElse".to_string());
3037 assert!(!law_calls_unclassified_fn(&law, &unrelated));
3038 }
3039
3040 #[test]
3041 fn law_lhs_has_trace_projection_skips_user_record_field() {
3042 let user_field_lhs = sb(Expr::Attr(
3050 bsb(Expr::Ident("log".to_string())),
3051 "trace".to_string(),
3052 ));
3053 assert!(
3054 !law_lhs_has_trace_projection(&user_field_lhs),
3055 "bare user-record `.trace` field must not trigger the gate"
3056 );
3057
3058 let runtime_trace_lhs = sb(Expr::FnCall(
3060 bsb(Expr::Attr(
3061 bsb(Expr::Attr(
3062 bsb(Expr::FnCall(bsb(Expr::Ident("fn".to_string())), vec![])),
3063 "trace".to_string(),
3064 )),
3065 "event".to_string(),
3066 )),
3067 vec![sb(Expr::Literal(Literal::Int(0)))],
3068 ));
3069 assert!(
3070 law_lhs_has_trace_projection(&runtime_trace_lhs),
3071 "Oracle `.trace.event(0)` projection must trigger the gate"
3072 );
3073 }
3074
3075 #[test]
3076 fn resolve_refined_type_disambiguates_cross_module_same_bare_name() {
3077 use crate::ast::{TypeDef, TypeVariant};
3086 use crate::codegen::ModuleInfo;
3087 use crate::ir::proof_ir::{Predicate, QuantifierType, RefinedTypeDecl};
3088 use std::collections::HashMap;
3089 let _ = TypeVariant {
3090 name: String::new(),
3091 fields: Vec::new(),
3092 }; let make_module = |prefix: &str| ModuleInfo {
3095 prefix: prefix.to_string(),
3096 depends: Vec::new(),
3097 type_defs: vec![TypeDef::Product {
3098 name: "Natural".to_string(),
3099 fields: vec![("value".to_string(), "Int".to_string())],
3100 line: 1,
3101 }],
3102 fn_defs: Vec::new(),
3103 verify_laws: Vec::new(),
3104 analysis: None,
3105 };
3106 let modules = vec![make_module("A"), make_module("B")];
3107
3108 let make_decl = |predicate_param: &str, witness: i64| RefinedTypeDecl {
3109 name: "Natural".to_string(),
3110 carrier_type: "Int".to_string(),
3111 carrier_field: "value".to_string(),
3112 predicate_param: predicate_param.to_string(),
3113 invariant: Predicate {
3114 free_vars: vec![(
3115 predicate_param.to_string(),
3116 QuantifierType::Plain("Int".to_string()),
3117 )],
3118 expr: crate::ast::Spanned::bare(crate::ir::hir::ResolvedExpr::Literal(
3119 Literal::Bool(true),
3120 )),
3121 },
3122 witness: Some(witness.to_string()),
3123 interval: None,
3124 op_classes: Vec::new(),
3125 };
3126 let symbols = crate::ir::SymbolTable::build(&[], &modules);
3127 let a_id = symbols
3128 .type_id_of(&crate::ir::TypeKey::in_module("A", "Natural"))
3129 .expect("A.Natural TypeId");
3130 let b_id = symbols
3131 .type_id_of(&crate::ir::TypeKey::in_module("B", "Natural"))
3132 .expect("B.Natural TypeId");
3133
3134 let mut refined_types: HashMap<crate::ir::TypeId, RefinedTypeDecl> = HashMap::new();
3135 refined_types.insert(a_id, make_decl("a", 0));
3136 refined_types.insert(b_id, make_decl("b", 10));
3137
3138 let a = resolve_refined_type_in(&refined_types, &symbols, &modules, "A.Natural")
3140 .expect("A.Natural canonical lookup");
3141 assert_eq!(a.predicate_param, "a");
3142 assert_eq!(a.witness.as_deref(), Some("0"));
3143 let b = resolve_refined_type_in(&refined_types, &symbols, &modules, "B.Natural")
3144 .expect("B.Natural canonical lookup");
3145 assert_eq!(b.predicate_param, "b");
3146 assert_eq!(b.witness.as_deref(), Some("10"));
3147
3148 let bare = resolve_refined_type_in(&refined_types, &symbols, &modules, "Natural")
3152 .expect("bare Natural resolves via module walk");
3153 assert!(
3154 bare.predicate_param == "a" || bare.predicate_param == "b",
3155 "bare Natural must resolve to one of the canonical decls"
3156 );
3157
3158 assert!(resolve_refined_type_in(&refined_types, &symbols, &modules, "Unrelated").is_none());
3161 }
3162
3163 #[test]
3164 fn find_refined_type_scoped_prefers_current_module_over_entry_collision() {
3165 use crate::ast::{TopLevel, TypeDef};
3173 use crate::codegen::{CodegenContext, ModuleInfo};
3174 use crate::ir::proof_ir::{Predicate, QuantifierType, RefinedTypeDecl};
3175 use std::collections::{HashMap, HashSet};
3176
3177 let entry_natural = TypeDef::Product {
3178 name: "Natural".to_string(),
3179 fields: vec![("value".to_string(), "Int".to_string())],
3180 line: 1,
3181 };
3182 let module = ModuleInfo {
3183 prefix: "Mod".to_string(),
3184 depends: Vec::new(),
3185 type_defs: vec![TypeDef::Product {
3186 name: "Natural".to_string(),
3187 fields: vec![("value".to_string(), "Int".to_string())],
3188 line: 1,
3189 }],
3190 fn_defs: Vec::new(),
3191 verify_laws: Vec::new(),
3192 analysis: None,
3193 };
3194
3195 let make_decl = |param: &str, witness: &str| RefinedTypeDecl {
3196 name: "Natural".to_string(),
3197 carrier_type: "Int".to_string(),
3198 carrier_field: "value".to_string(),
3199 predicate_param: param.to_string(),
3200 invariant: Predicate {
3201 free_vars: vec![(param.to_string(), QuantifierType::Plain("Int".to_string()))],
3202 expr: crate::ast::Spanned::bare(crate::ir::hir::ResolvedExpr::Literal(
3203 Literal::Bool(true),
3204 )),
3205 },
3206 witness: Some(witness.to_string()),
3207 interval: None,
3208 op_classes: Vec::new(),
3209 };
3210
3211 let items = vec![TopLevel::TypeDef(entry_natural)];
3212 let modules = vec![module];
3213 let symbol_table = crate::ir::SymbolTable::build(&items, &modules);
3214 let entry_id = symbol_table
3215 .type_id_of(&crate::ir::TypeKey::entry("Natural"))
3216 .expect("entry Natural id");
3217 let mod_id = symbol_table
3218 .type_id_of(&crate::ir::TypeKey::in_module("Mod", "Natural"))
3219 .expect("Mod.Natural id");
3220
3221 let mut ctx = CodegenContext {
3222 items,
3223 type_defs: Vec::new(),
3224 fn_defs: Vec::new(),
3225 project_name: "scope-test".to_string(),
3226 modules,
3227 module_prefixes: HashSet::new(),
3228 #[cfg(feature = "runtime")]
3229 policy: None,
3230 emit_replay_runtime: false,
3231 runtime_policy_from_env: false,
3232 guest_entry: None,
3233 emit_self_host_support: false,
3234 extra_fn_defs: Vec::new(),
3235 mutual_tco_members: HashSet::new(),
3236 recursive_fns: HashSet::new(),
3237 buffer_build_sinks: HashMap::new(),
3238 buffer_fusion_sites: Vec::new(),
3239 synthesized_buffered_fns: Vec::new(),
3240 proof_ir: crate::ir::ProofIR::default(),
3241 symbol_table,
3242 resolved_fn_defs: Vec::new(),
3243 resolved_module_fn_defs: Vec::new(),
3244 current_module_scope: std::cell::RefCell::new(None),
3245 resolved_program: crate::codegen::program_view::ResolvedProgramView::default(),
3246 program_shape: None,
3247 mir_program: None,
3248 bare_i64: Default::default(),
3249 discovered_lemmas: Vec::new(),
3250 sample_expected: std::collections::HashMap::new(),
3251 };
3252 ctx.proof_ir
3253 .refined_types
3254 .insert(entry_id, make_decl("entry_n", "0"));
3255 ctx.proof_ir
3256 .refined_types
3257 .insert(mod_id, make_decl("mod_n", "10"));
3258
3259 let from_module = find_refined_type_scoped(&ctx, "Natural", Some("Mod"))
3260 .expect("Mod-scoped Natural lookup");
3261 assert_eq!(
3262 from_module.predicate_param, "mod_n",
3263 "scope=Some(\"Mod\") + bare `Natural` must resolve to Mod.Natural, \
3264 not entry's bare-keyed slot"
3265 );
3266
3267 let from_entry =
3269 find_refined_type_scoped(&ctx, "Natural", None).expect("entry Natural lookup");
3270 assert_eq!(from_entry.predicate_param, "entry_n");
3271 }
3272}