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 canonicalize_comparison(e: &Spanned<Expr>) -> Spanned<Expr> {
666 if let Expr::BinOp(op, l, r) = &e.node
667 && matches!(op, crate::ast::BinOp::Gt | crate::ast::BinOp::Gte)
668 && let Some(swapped) = swap_comparison_operands_op(op)
669 {
670 return Spanned::new(Expr::BinOp(swapped, r.clone(), l.clone()), e.line);
671 }
672 e.clone()
673}
674
675pub fn predicate_syntactic_eq(a: &Spanned<Expr>, b: &Spanned<Expr>) -> bool {
686 match (&a.node, &b.node) {
687 (Expr::BinOp(op_a, la, ra), Expr::BinOp(op_b, lb, rb)) => {
688 if op_a == op_b && predicate_syntactic_eq(la, lb) && predicate_syntactic_eq(ra, rb) {
689 return true;
690 }
691 if let Some(swapped) = swap_comparison_operands_op(op_a)
692 && &swapped == op_b
693 && predicate_syntactic_eq(la, rb)
694 && predicate_syntactic_eq(ra, lb)
695 {
696 return true;
697 }
698 false
699 }
700 _ => a.node == b.node,
701 }
702}
703
704pub fn flatten_bool_and_conjuncts(expr: &Spanned<Expr>) -> Vec<Spanned<Expr>> {
711 if let Expr::FnCall(callee, args) = &expr.node
712 && args.len() == 2
713 && let Some(name) = expr_to_dotted_name(&callee.node)
714 && name == "Bool.and"
715 {
716 let mut out = flatten_bool_and_conjuncts(&args[0]);
717 out.extend(flatten_bool_and_conjuncts(&args[1]));
718 return out;
719 }
720 vec![expr.clone()]
721}
722
723pub fn flatten_bool_and_conjuncts_resolved(
728 expr: &Spanned<crate::ir::hir::ResolvedExpr>,
729) -> Vec<Spanned<crate::ir::hir::ResolvedExpr>> {
730 use crate::ir::hir::{ResolvedCallee, ResolvedExpr};
731 if let ResolvedExpr::Call(ResolvedCallee::Builtin(name), args) = &expr.node
732 && name == "Bool.and"
733 && args.len() == 2
734 {
735 let mut out = flatten_bool_and_conjuncts_resolved(&args[0]);
736 out.extend(flatten_bool_and_conjuncts_resolved(&args[1]));
737 return out;
738 }
739 vec![expr.clone()]
740}
741
742pub fn predicate_syntactic_eq_resolved(
744 a: &Spanned<crate::ir::hir::ResolvedExpr>,
745 b: &Spanned<crate::ir::hir::ResolvedExpr>,
746) -> bool {
747 use crate::ir::hir::ResolvedExpr;
748 match (&a.node, &b.node) {
749 (ResolvedExpr::BinOp(op_a, la, ra), ResolvedExpr::BinOp(op_b, lb, rb)) => {
750 if op_a == op_b
751 && predicate_syntactic_eq_resolved(la, lb)
752 && predicate_syntactic_eq_resolved(ra, rb)
753 {
754 return true;
755 }
756 if let Some(swapped) = swap_comparison_operands_op(op_a)
757 && &swapped == op_b
758 && predicate_syntactic_eq_resolved(la, rb)
759 && predicate_syntactic_eq_resolved(ra, lb)
760 {
761 return true;
762 }
763 false
764 }
765 _ => a.node == b.node,
766 }
767}
768
769pub fn substitute_ident_in_resolved_expr(
777 expr: &Spanned<crate::ir::hir::ResolvedExpr>,
778 from: &str,
779 to: &str,
780) -> Spanned<crate::ir::hir::ResolvedExpr> {
781 use crate::ir::hir::{ResolvedExpr, ResolvedMatchArm, ResolvedStrPart};
782 let line = expr.line;
783 let rec = |e: &Spanned<ResolvedExpr>| substitute_ident_in_resolved_expr(e, from, to);
784 let new_node = match &expr.node {
785 ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. } if name == from => {
786 ResolvedExpr::Ident(to.to_string())
787 }
788 ResolvedExpr::Literal(_) | ResolvedExpr::Ident(_) | ResolvedExpr::Resolved { .. } => {
789 return expr.clone();
790 }
791 ResolvedExpr::Attr(obj, field) => ResolvedExpr::Attr(Box::new(rec(obj)), field.clone()),
792 ResolvedExpr::Call(callee, args) => {
793 ResolvedExpr::Call(callee.clone(), args.iter().map(&rec).collect())
794 }
795 ResolvedExpr::BinOp(op, left, right) => {
796 ResolvedExpr::BinOp(*op, Box::new(rec(left)), Box::new(rec(right)))
797 }
798 ResolvedExpr::Neg(inner) => ResolvedExpr::Neg(Box::new(rec(inner))),
799 ResolvedExpr::Match { subject, arms } => ResolvedExpr::Match {
800 subject: Box::new(rec(subject)),
801 arms: arms
802 .iter()
803 .map(|arm| ResolvedMatchArm {
804 pattern: arm.pattern.clone(),
805 body: Box::new(rec(&arm.body)),
806 binding_slots: std::sync::OnceLock::new(),
807 })
808 .collect(),
809 },
810 ResolvedExpr::Ctor(ctor, args) => {
811 ResolvedExpr::Ctor(ctor.clone(), args.iter().map(&rec).collect())
812 }
813 ResolvedExpr::ErrorProp(inner) => ResolvedExpr::ErrorProp(Box::new(rec(inner))),
814 ResolvedExpr::InterpolatedStr(parts) => ResolvedExpr::InterpolatedStr(
815 parts
816 .iter()
817 .map(|p| match p {
818 ResolvedStrPart::Literal(_) => p.clone(),
819 ResolvedStrPart::Parsed(inner) => ResolvedStrPart::Parsed(Box::new(rec(inner))),
820 })
821 .collect(),
822 ),
823 ResolvedExpr::List(items) => ResolvedExpr::List(items.iter().map(&rec).collect()),
824 ResolvedExpr::Tuple(items) => ResolvedExpr::Tuple(items.iter().map(&rec).collect()),
825 ResolvedExpr::IndependentProduct(items, flag) => {
826 ResolvedExpr::IndependentProduct(items.iter().map(&rec).collect(), *flag)
827 }
828 ResolvedExpr::MapLiteral(entries) => {
829 ResolvedExpr::MapLiteral(entries.iter().map(|(k, v)| (rec(k), rec(v))).collect())
830 }
831 ResolvedExpr::RecordCreate {
832 type_id,
833 type_name,
834 fields,
835 } => ResolvedExpr::RecordCreate {
836 type_id: *type_id,
837 type_name: type_name.clone(),
838 fields: fields.iter().map(|(n, v)| (n.clone(), rec(v))).collect(),
839 },
840 ResolvedExpr::RecordUpdate {
841 type_id,
842 type_name,
843 base,
844 updates,
845 } => ResolvedExpr::RecordUpdate {
846 type_id: *type_id,
847 type_name: type_name.clone(),
848 base: Box::new(rec(base)),
849 updates: updates.iter().map(|(n, v)| (n.clone(), rec(v))).collect(),
850 },
851 ResolvedExpr::TailCall { target, args } => ResolvedExpr::TailCall {
852 target: *target,
853 args: args.iter().map(&rec).collect(),
854 },
855 };
856 Spanned::new(new_node, line)
857}
858
859pub fn substitute_ident_in_expr(expr: &Spanned<Expr>, from: &str, to: &str) -> Spanned<Expr> {
868 use crate::ast::{MatchArm, StrPart, TailCallData};
869 let line = expr.line;
870 let new_node = match &expr.node {
871 Expr::Ident(name) | Expr::Resolved { name, .. } if name == from => {
872 Expr::Ident(to.to_string())
873 }
874 Expr::Literal(_) | Expr::Ident(_) | Expr::Resolved { .. } => return expr.clone(),
875 Expr::Attr(obj, field) => Expr::Attr(
876 Box::new(substitute_ident_in_expr(obj, from, to)),
877 field.clone(),
878 ),
879 Expr::FnCall(callee, args) => Expr::FnCall(
880 Box::new(substitute_ident_in_expr(callee, from, to)),
881 args.iter()
882 .map(|a| substitute_ident_in_expr(a, from, to))
883 .collect(),
884 ),
885 Expr::BinOp(op, left, right) => Expr::BinOp(
886 *op,
887 Box::new(substitute_ident_in_expr(left, from, to)),
888 Box::new(substitute_ident_in_expr(right, from, to)),
889 ),
890 Expr::Neg(inner) => Expr::Neg(Box::new(substitute_ident_in_expr(inner, from, to))),
891 Expr::Match { subject, arms } => Expr::Match {
892 subject: Box::new(substitute_ident_in_expr(subject, from, to)),
893 arms: arms
894 .iter()
895 .map(|arm| MatchArm {
896 pattern: arm.pattern.clone(),
897 body: Box::new(substitute_ident_in_expr(&arm.body, from, to)),
898 binding_slots: std::sync::OnceLock::new(),
899 })
900 .collect(),
901 },
902 Expr::Constructor(name, arg) => Expr::Constructor(
903 name.clone(),
904 arg.as_ref()
905 .map(|inner| Box::new(substitute_ident_in_expr(inner, from, to))),
906 ),
907 Expr::ErrorProp(inner) => {
908 Expr::ErrorProp(Box::new(substitute_ident_in_expr(inner, from, to)))
909 }
910 Expr::InterpolatedStr(parts) => Expr::InterpolatedStr(
911 parts
912 .iter()
913 .map(|part| match part {
914 StrPart::Literal(_) => part.clone(),
915 StrPart::Parsed(inner) => {
916 StrPart::Parsed(Box::new(substitute_ident_in_expr(inner, from, to)))
917 }
918 })
919 .collect(),
920 ),
921 Expr::List(items) => Expr::List(
922 items
923 .iter()
924 .map(|item| substitute_ident_in_expr(item, from, to))
925 .collect(),
926 ),
927 Expr::Tuple(items) => Expr::Tuple(
928 items
929 .iter()
930 .map(|item| substitute_ident_in_expr(item, from, to))
931 .collect(),
932 ),
933 Expr::IndependentProduct(items, flag) => Expr::IndependentProduct(
934 items
935 .iter()
936 .map(|item| substitute_ident_in_expr(item, from, to))
937 .collect(),
938 *flag,
939 ),
940 Expr::MapLiteral(entries) => Expr::MapLiteral(
941 entries
942 .iter()
943 .map(|(k, v)| {
944 (
945 substitute_ident_in_expr(k, from, to),
946 substitute_ident_in_expr(v, from, to),
947 )
948 })
949 .collect(),
950 ),
951 Expr::RecordCreate { type_name, fields } => Expr::RecordCreate {
952 type_name: type_name.clone(),
953 fields: fields
954 .iter()
955 .map(|(n, v)| (n.clone(), substitute_ident_in_expr(v, from, to)))
956 .collect(),
957 },
958 Expr::RecordUpdate {
959 type_name,
960 base,
961 updates,
962 } => Expr::RecordUpdate {
963 type_name: type_name.clone(),
964 base: Box::new(substitute_ident_in_expr(base, from, to)),
965 updates: updates
966 .iter()
967 .map(|(n, v)| (n.clone(), substitute_ident_in_expr(v, from, to)))
968 .collect(),
969 },
970 Expr::TailCall(boxed) => Expr::TailCall(Box::new(TailCallData::new(
971 boxed.target.clone(),
972 boxed
973 .args
974 .iter()
975 .map(|a| substitute_ident_in_expr(a, from, to))
976 .collect(),
977 ))),
978 };
979 Spanned::new(new_node, line)
980}
981
982pub fn when_is_redundant_with_refinement_lifts(
998 when_expr: &Spanned<Expr>,
999 lifted_vars: &std::collections::HashMap<String, String>,
1000 ctx: &CodegenContext,
1001) -> bool {
1002 if lifted_vars.is_empty() {
1003 return false;
1004 }
1005 let resolved_when = ctx.resolve_expr(when_expr, ctx.active_module_scope().as_deref());
1013 let when_conjuncts = flatten_bool_and_conjuncts_resolved(&resolved_when);
1014 let mut lifted_predicates: Vec<Spanned<crate::ir::hir::ResolvedExpr>> = Vec::new();
1028 for (given_name, refined_type) in lifted_vars {
1029 let Some(decl) = find_refined_type(ctx, refined_type) else {
1030 return false;
1031 };
1032 let substituted = substitute_ident_in_resolved_expr(
1033 &decl.invariant.expr,
1034 decl.predicate_param.as_str(),
1035 given_name,
1036 );
1037 lifted_predicates.extend(flatten_bool_and_conjuncts_resolved(&substituted));
1038 }
1039 if when_conjuncts.len() != lifted_predicates.len() {
1040 return false;
1041 }
1042 let mut matched = vec![false; lifted_predicates.len()];
1043 for wc in &when_conjuncts {
1044 let Some(idx) = (0..lifted_predicates.len())
1045 .find(|&i| !matched[i] && predicate_syntactic_eq_resolved(wc, &lifted_predicates[i]))
1046 else {
1047 return false;
1048 };
1049 matched[idx] = true;
1050 }
1051 true
1052}
1053
1054fn is_err_constructor(expr: &Spanned<Expr>) -> bool {
1055 match &expr.node {
1056 Expr::Constructor(name, Some(_)) => name == "Result.Err",
1057 Expr::FnCall(callee, args) if args.len() == 1 => {
1058 matches!(
1059 expr_to_dotted_name(&callee.node),
1060 Some(name) if name == "Result.Err"
1061 )
1062 }
1063 _ => false,
1064 }
1065}
1066
1067pub fn is_pure_fn(fd: &FnDef) -> bool {
1073 fd.effects.is_empty() && fd.name != "main"
1074}
1075
1076pub fn find_refined_type<'a>(
1103 ctx: &'a CodegenContext,
1104 name: &str,
1105) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1106 find_refined_type_with_key_scoped(ctx, name, None).map(|(_, d)| d)
1107}
1108
1109pub fn backend_named_type_key(ctx: &CodegenContext, ty: &crate::types::Type) -> Option<String> {
1138 let crate::types::Type::Named { id, name } = ty else {
1139 return None;
1140 };
1141 if let Some(type_id) = id {
1142 return Some(ctx.symbol_table.type_entry(*type_id).key.canonical());
1143 }
1144 Some(name.clone())
1145}
1146
1147pub fn backend_type_def_key(ctx: &CodegenContext, td: &crate::ast::TypeDef) -> String {
1162 let key = type_key_for_decl(ctx, td);
1163 if ctx.symbol_table.type_id_of(&key).is_some() {
1164 key.canonical()
1165 } else {
1166 type_def_name(td).to_string()
1167 }
1168}
1169
1170pub fn find_refined_type_by_id(
1186 ctx: &CodegenContext,
1187 type_id: crate::ir::TypeId,
1188) -> Option<&crate::ir::proof_ir::RefinedTypeDecl> {
1189 ctx.proof_ir.refined_types.get(&type_id)
1190}
1191
1192pub fn find_refined_type_for_named<'a>(
1201 ctx: &'a CodegenContext,
1202 named: &crate::types::Type,
1203) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1204 let crate::types::Type::Named { id, name } = named else {
1205 return None;
1206 };
1207 match id {
1208 Some(type_id) => find_refined_type_by_id(ctx, *type_id),
1209 None => find_refined_type(ctx, name),
1210 }
1211}
1212
1213pub fn find_fn_contract_scoped<'a>(
1225 ctx: &'a CodegenContext,
1226 name: &str,
1227 scope: Option<&str>,
1228) -> Option<&'a crate::ir::proof_ir::FnContract> {
1229 let symbols = &ctx.symbol_table;
1237 let bare = name.rsplit('.').next().unwrap_or(name);
1238 let name_is_already_qualified = name.contains('.');
1239 let try_key = |key: crate::ir::FnKey| -> Option<&'a crate::ir::proof_ir::FnContract> {
1240 let id = symbols.fn_id_of(&key)?;
1241 ctx.proof_ir.fn_contracts.get(&id)
1242 };
1243 if let Some(prefix) = scope
1244 && !name_is_already_qualified
1245 && let Some(c) = try_key(crate::ir::FnKey::in_module(prefix.to_string(), bare))
1246 {
1247 return Some(c);
1248 }
1249 let direct_key =
1250 if name_is_already_qualified && let Some((prefix, bare_part)) = name.rsplit_once('.') {
1251 crate::ir::FnKey::in_module(prefix.to_string(), bare_part)
1252 } else {
1253 crate::ir::FnKey::entry(name)
1254 };
1255 if let Some(c) = try_key(direct_key) {
1256 return Some(c);
1257 }
1258 for m in &ctx.modules {
1259 for fd in &m.fn_defs {
1260 if fd.name == bare
1261 && let Some(c) = try_key(crate::ir::FnKey::in_module(m.prefix.clone(), bare))
1262 {
1263 return Some(c);
1264 }
1265 }
1266 }
1267 None
1268}
1269
1270pub fn fn_contract_exists_scoped(ctx: &CodegenContext, name: &str, scope: Option<&str>) -> bool {
1272 find_fn_contract_scoped(ctx, name, scope).is_some()
1273}
1274
1275pub fn fn_key_for_decl(ctx: &CodegenContext, fd: &FnDef) -> crate::ir::FnKey {
1283 match fn_owning_scope_for(ctx, fd) {
1284 Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), fd.name.clone()),
1285 None => crate::ir::FnKey::entry(fd.name.clone()),
1286 }
1287}
1288
1289pub fn type_key_for_decl(ctx: &CodegenContext, td: &TypeDef) -> crate::ir::TypeKey {
1293 for m in &ctx.modules {
1294 for t in &m.type_defs {
1295 if std::ptr::eq(t, td) {
1296 return crate::ir::TypeKey::in_module(m.prefix.clone(), type_def_name(td));
1297 }
1298 }
1299 }
1300 crate::ir::TypeKey::entry(type_def_name(td))
1301}
1302
1303pub fn type_key_for_name(
1310 ctx: &CodegenContext,
1311 name: &str,
1312 scope: Option<&str>,
1313) -> crate::ir::TypeKey {
1314 let bare = name.rsplit('.').next().unwrap_or(name);
1315 let name_is_qualified = name.contains('.');
1316 if let Some(prefix) = scope
1317 && !name_is_qualified
1318 {
1319 for m in &ctx.modules {
1320 if m.prefix == prefix && m.type_defs.iter().any(|td| type_def_name(td) == bare) {
1321 return crate::ir::TypeKey::in_module(prefix.to_string(), bare);
1322 }
1323 }
1324 }
1325 if !name_is_qualified && ctx.type_defs.iter().any(|td| type_def_name(td) == bare) {
1328 return crate::ir::TypeKey::entry(bare);
1329 }
1330 if name_is_qualified
1331 && let Some((prefix, bare_part)) = name.rsplit_once('.')
1332 && ctx.modules.iter().any(|m| m.prefix == prefix)
1333 {
1334 return crate::ir::TypeKey::in_module(prefix.to_string(), bare_part);
1335 }
1336 for m in &ctx.modules {
1337 if m.type_defs.iter().any(|td| type_def_name(td) == bare) {
1338 return crate::ir::TypeKey::in_module(m.prefix.clone(), bare);
1339 }
1340 }
1341 crate::ir::TypeKey::entry(name)
1346}
1347
1348pub fn fn_owning_scope_for<'a>(ctx: &'a CodegenContext, fd: &FnDef) -> Option<&'a str> {
1358 for m in &ctx.modules {
1359 for f in &m.fn_defs {
1360 if std::ptr::eq(f, fd) {
1361 return Some(m.prefix.as_str());
1362 }
1363 }
1364 }
1365 None
1366}
1367
1368pub fn find_fn_contract_for_fn<'a>(
1374 ctx: &'a CodegenContext,
1375 fd: &FnDef,
1376) -> Option<&'a crate::ir::proof_ir::FnContract> {
1377 let symbols = &ctx.symbol_table;
1383 let fn_key = fn_key_for_decl(ctx, fd);
1384 let fn_id = symbols.fn_id_of(&fn_key)?;
1385 ctx.proof_ir.fn_contracts.get(&fn_id)
1386}
1387
1388pub fn fn_contract_exists_for_fn(ctx: &CodegenContext, fd: &FnDef) -> bool {
1390 find_fn_contract_for_fn(ctx, fd).is_some()
1391}
1392
1393pub fn fn_id_for_decl(ctx: &CodegenContext, fd: &FnDef) -> Option<crate::ir::FnId> {
1399 let fn_key = fn_key_for_decl(ctx, fd);
1400 ctx.symbol_table.fn_id_of(&fn_key)
1401}
1402
1403pub fn fn_id_for_dotted_name(ctx: &CodegenContext, dotted: &str) -> Option<crate::ir::FnId> {
1409 let key = if let Some((prefix, bare)) = dotted.rsplit_once('.') {
1410 crate::ir::FnKey::in_module(prefix.to_string(), bare)
1411 } else {
1412 crate::ir::FnKey::entry(dotted)
1413 };
1414 ctx.symbol_table.fn_id_of(&key)
1415}
1416
1417pub fn find_refined_type_with_key<'a>(
1422 ctx: &'a CodegenContext,
1423 name: &str,
1424) -> Option<(String, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1425 find_refined_type_with_key_scoped(ctx, name, None)
1426}
1427
1428pub fn find_refined_type_scoped<'a>(
1446 ctx: &'a CodegenContext,
1447 name: &str,
1448 scope: Option<&str>,
1449) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1450 find_refined_type_with_key_scoped(ctx, name, scope).map(|(_, d)| d)
1451}
1452
1453pub fn find_refined_type_with_key_scoped<'a>(
1468 ctx: &'a CodegenContext,
1469 name: &str,
1470 scope: Option<&str>,
1471) -> Option<(String, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1472 let symbols = &ctx.symbol_table;
1473 let bare = name.rsplit('.').next().unwrap_or(name);
1474 let name_is_already_qualified = name.contains('.');
1475 let try_key =
1476 |key: crate::ir::TypeKey| -> Option<(String, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1477 let id = symbols.type_id_of(&key)?;
1478 let decl = ctx.proof_ir.refined_types.get(&id)?;
1479 Some((key.canonical(), decl))
1480 };
1481 if let Some(prefix) = scope
1482 && !name_is_already_qualified
1483 && let Some(hit) = try_key(crate::ir::TypeKey::in_module(prefix.to_string(), bare))
1484 {
1485 return Some(hit);
1486 }
1487 let direct_key =
1488 if name_is_already_qualified && let Some((prefix, bare_part)) = name.rsplit_once('.') {
1489 crate::ir::TypeKey::in_module(prefix.to_string(), bare_part)
1490 } else {
1491 crate::ir::TypeKey::entry(name)
1492 };
1493 if let Some(hit) = try_key(direct_key) {
1494 return Some(hit);
1495 }
1496 for m in &ctx.modules {
1497 for td in &m.type_defs {
1498 if type_def_name(td) == bare
1499 && let Some(hit) = try_key(crate::ir::TypeKey::in_module(m.prefix.clone(), bare))
1500 {
1501 return Some(hit);
1502 }
1503 }
1504 }
1505 None
1506}
1507
1508pub fn resolve_refined_type_in<'a>(
1514 refined_types: &'a std::collections::HashMap<
1515 crate::ir::TypeId,
1516 crate::ir::proof_ir::RefinedTypeDecl,
1517 >,
1518 symbols: &crate::ir::SymbolTable,
1519 modules: &[crate::codegen::ModuleInfo],
1520 name: &str,
1521) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1522 resolve_refined_type_in_with_key(refined_types, symbols, modules, name).map(|(_, d)| d)
1523}
1524
1525pub fn resolve_refined_type_in_with_key<'a>(
1531 refined_types: &'a std::collections::HashMap<
1532 crate::ir::TypeId,
1533 crate::ir::proof_ir::RefinedTypeDecl,
1534 >,
1535 symbols: &crate::ir::SymbolTable,
1536 modules: &[crate::codegen::ModuleInfo],
1537 name: &str,
1538) -> Option<(crate::ir::TypeId, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1539 let bare = name.rsplit('.').next().unwrap_or(name);
1540 let name_is_already_qualified = name.contains('.');
1541 let direct_key =
1542 if name_is_already_qualified && let Some((prefix, bare_part)) = name.rsplit_once('.') {
1543 crate::ir::TypeKey::in_module(prefix.to_string(), bare_part)
1544 } else {
1545 crate::ir::TypeKey::entry(name)
1546 };
1547 if let Some(id) = symbols.type_id_of(&direct_key)
1548 && let Some(decl) = refined_types.get(&id)
1549 {
1550 return Some((id, decl));
1551 }
1552 for m in modules {
1553 for td in &m.type_defs {
1554 if type_def_name(td) == bare {
1555 let canonical = crate::ir::TypeKey::in_module(m.prefix.clone(), bare);
1556 if let Some(id) = symbols.type_id_of(&canonical)
1557 && let Some(decl) = refined_types.get(&id)
1558 {
1559 return Some((id, decl));
1560 }
1561 }
1562 }
1563 }
1564 None
1565}
1566
1567pub fn all_givens_are_singletons(law: &crate::ast::VerifyLaw) -> bool {
1576 !law.givens.is_empty()
1577 && law.givens.iter().all(|g| match &g.domain {
1578 VerifyGivenDomain::Explicit(values) => values.len() == 1,
1579 VerifyGivenDomain::IntRange { start, end } => start == end,
1580 })
1581}
1582
1583pub fn unclassified_fn_names(ctx: &CodegenContext) -> HashSet<String> {
1590 ctx.proof_ir
1591 .unclassified_fns
1592 .iter()
1593 .filter_map(|uf| {
1594 let s = &uf.message;
1595 let start = s.find('\'')?;
1596 let rest = &s[start + 1..];
1597 let end = rest.find('\'')?;
1598 Some(rest[..end].to_string())
1599 })
1600 .collect()
1601}
1602
1603pub fn law_calls_unclassified_fn(
1615 law: &crate::ast::VerifyLaw,
1616 unclassified: &HashSet<String>,
1617) -> bool {
1618 if unclassified.is_empty() {
1619 return false;
1620 }
1621 expr_calls_named(&law.lhs, unclassified) || expr_calls_named(&law.rhs, unclassified)
1622}
1623
1624fn expr_calls_named(expr: &Spanned<Expr>, names: &HashSet<String>) -> bool {
1625 match &expr.node {
1626 Expr::FnCall(callee, args) => {
1627 let direct = expr_to_dotted_name(&callee.node)
1638 .map(|n| {
1639 let bare = n.rsplit('.').next().unwrap_or(n.as_str());
1640 names.contains(n.as_str()) || names.contains(bare)
1641 })
1642 .unwrap_or(false);
1643 direct
1644 || expr_calls_named(callee, names)
1645 || args.iter().any(|a| expr_calls_named(a, names))
1646 }
1647 Expr::Attr(inner, _) | Expr::ErrorProp(inner) | Expr::Neg(inner) => {
1648 expr_calls_named(inner, names)
1649 }
1650 Expr::BinOp(_, l, r) => expr_calls_named(l, names) || expr_calls_named(r, names),
1651 Expr::Match { subject, arms } => {
1652 expr_calls_named(subject, names)
1653 || arms.iter().any(|a| expr_calls_named(&a.body, names))
1654 }
1655 Expr::Constructor(_, Some(arg)) => expr_calls_named(arg, names),
1656 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
1657 items.iter().any(|i| expr_calls_named(i, names))
1658 }
1659 Expr::MapLiteral(entries) => entries
1660 .iter()
1661 .any(|(k, v)| expr_calls_named(k, names) || expr_calls_named(v, names)),
1662 Expr::RecordCreate { fields, .. } => fields.iter().any(|(_, v)| expr_calls_named(v, names)),
1663 Expr::RecordUpdate { base, updates, .. } => {
1664 expr_calls_named(base, names) || updates.iter().any(|(_, v)| expr_calls_named(v, names))
1665 }
1666 Expr::InterpolatedStr(parts) => parts.iter().any(|p| match p {
1667 crate::ast::StrPart::Parsed(inner) => expr_calls_named(inner, names),
1668 crate::ast::StrPart::Literal(_) => false,
1669 }),
1670 Expr::TailCall(boxed) => {
1671 let crate::ast::TailCallData { target, args, .. } = boxed.as_ref();
1672 names.contains(target) || args.iter().any(|a| expr_calls_named(a, names))
1673 }
1674 _ => false,
1675 }
1676}
1677
1678pub fn accumulator_fold_fn_names(ctx: &CodegenContext) -> HashSet<String> {
1695 use crate::codegen::recursion::detect::{
1696 param_decremented_in_recursion, param_threaded_in_recursion,
1697 single_list_structural_param_index,
1698 };
1699 let threads_over_recursive_adt = |fd: &FnDef| -> bool {
1708 single_list_structural_param_index(fd).is_none()
1709 && (0..fd.params.len()).any(|i| param_threaded_in_recursion(fd, i))
1710 && fd.params.iter().enumerate().any(|(i, (_, ty))| {
1711 ctx_type_is_recursive_sum(ctx, ty.trim()) && param_decremented_in_recursion(fd, i)
1712 })
1713 };
1714 ctx.fn_defs
1715 .iter()
1716 .chain(ctx.modules.iter().flat_map(|m| m.fn_defs.iter()))
1717 .filter(|fd| threads_over_recursive_adt(fd))
1718 .map(|fd| fd.name.clone())
1719 .collect()
1720}
1721
1722fn ctx_type_is_recursive_sum(ctx: &CodegenContext, type_name: &str) -> bool {
1727 use crate::ast::TypeDef;
1728 ctx.type_defs
1729 .iter()
1730 .chain(ctx.modules.iter().flat_map(|m| m.type_defs.iter()))
1731 .any(|td| match td {
1732 TypeDef::Sum { name, variants, .. } if name == type_name => variants.iter().any(|v| {
1733 v.fields.iter().any(|f| {
1734 let f = f.trim();
1735 f == type_name
1736 || f.contains(&format!("<{type_name}"))
1737 || f.contains(&format!("{type_name}>"))
1738 || f.contains(&format!(", {type_name}"))
1739 || f.contains(&format!("{type_name},"))
1740 })
1741 }),
1742 _ => false,
1743 })
1744}
1745
1746fn accfold_combine_fn(fd: &FnDef) -> Option<String> {
1752 use crate::ast::Expr;
1753 use crate::codegen::recursion::detect::{
1754 call_matches, collect_calls_from_body, param_threaded_in_recursion,
1755 };
1756 let acc_idx = (0..fd.params.len()).find(|&i| param_threaded_in_recursion(fd, i))?;
1757 let calls: Vec<Vec<&Spanned<Expr>>> = collect_calls_from_body(fd.body.as_ref())
1758 .into_iter()
1759 .filter(|(name, _)| call_matches(name, &fd.name))
1760 .map(|(_, args)| args)
1761 .collect();
1762 let acc_arg = calls.first()?.get(acc_idx).copied()?;
1763 let Expr::FnCall(callee, _) = &acc_arg.node else {
1764 return None;
1765 };
1766 expr_to_dotted_name(&callee.node)
1767}
1768
1769fn call_args_are_idents(e: &Spanned<Expr>, fn_name: &str, arg_names: &[&str]) -> bool {
1771 let Expr::FnCall(callee, args) = &e.node else {
1772 return false;
1773 };
1774 expr_to_dotted_name(&callee.node).as_deref() == Some(fn_name)
1775 && args.len() == arg_names.len()
1776 && args.iter().zip(arg_names).all(
1777 |(a, n)| matches!(&a.node, Expr::Ident(x) | Expr::Resolved { name: x, .. } if x == n),
1778 )
1779}
1780
1781fn law_is_commutativity(vb: &VerifyBlock, combine: &str) -> bool {
1784 let crate::ast::VerifyKind::Law(law) = &vb.kind else {
1785 return false;
1786 };
1787 if law.givens.len() != 2 || law.when.is_some() {
1788 return false;
1789 }
1790 let a = law.givens[0].name.as_str();
1791 let b = law.givens[1].name.as_str();
1792 (call_args_are_idents(&law.lhs, combine, &[a, b])
1793 && call_args_are_idents(&law.rhs, combine, &[b, a]))
1794 || (call_args_are_idents(&law.lhs, combine, &[b, a])
1795 && call_args_are_idents(&law.rhs, combine, &[a, b]))
1796}
1797
1798fn law_is_associativity(vb: &VerifyBlock, combine: &str) -> bool {
1802 let crate::ast::VerifyKind::Law(law) = &vb.kind else {
1803 return false;
1804 };
1805 if law.givens.len() != 3 || law.when.is_some() {
1806 return false;
1807 }
1808 let a = law.givens[0].name.as_str();
1809 let b = law.givens[1].name.as_str();
1810 let c = law.givens[2].name.as_str();
1811 let nested = |e: &Spanned<Expr>| -> bool {
1813 let Expr::FnCall(callee, args) = &e.node else {
1814 return false;
1815 };
1816 expr_to_dotted_name(&callee.node).as_deref() == Some(combine)
1817 && args.len() == 2
1818 && call_args_are_idents(&args[0], combine, &[a, b])
1819 && matches!(&args[1].node, Expr::Ident(x) | Expr::Resolved { name: x, .. } if x == c)
1820 };
1821 let flat = |e: &Spanned<Expr>| -> bool {
1823 let Expr::FnCall(callee, args) = &e.node else {
1824 return false;
1825 };
1826 expr_to_dotted_name(&callee.node).as_deref() == Some(combine)
1827 && args.len() == 2
1828 && matches!(&args[0].node, Expr::Ident(x) | Expr::Resolved { name: x, .. } if x == a)
1829 && call_args_are_idents(&args[1], combine, &[b, c])
1830 };
1831 (nested(&law.lhs) && flat(&law.rhs)) || (nested(&law.rhs) && flat(&law.lhs))
1832}
1833
1834pub fn nat_accfold_self_closeable(
1845 ctx: &CodegenContext,
1846 verified_fn: &str,
1847 accgen_law_name: &str,
1848) -> bool {
1849 if !accumulator_fold_fn_names(ctx).contains(verified_fn) {
1850 return false;
1851 }
1852 let Some(fd) = ctx.fn_def_by_name(verified_fn, ctx.active_module_scope().as_deref()) else {
1853 return false;
1854 };
1855 let Some(combine) = accfold_combine_fn(fd) else {
1856 return false;
1857 };
1858 if !ctx
1865 .fn_def_by_name(&combine, ctx.active_module_scope().as_deref())
1866 .is_some_and(combine_is_additive_monoid)
1867 {
1868 return false;
1869 }
1870 let citable: Vec<&VerifyBlock> = ctx
1877 .items
1878 .iter()
1879 .filter_map(|i| match i {
1880 TopLevel::Verify(vb) => Some(vb),
1881 _ => None,
1882 })
1883 .take_while(|vb| {
1884 !matches!(&vb.kind, VerifyKind::Law(l)
1885 if vb.fn_name == verified_fn && l.name == accgen_law_name)
1886 })
1887 .collect();
1888 let has_comm = citable.iter().any(|vb| law_is_commutativity(vb, &combine));
1889 let has_assoc = citable.iter().any(|vb| law_is_associativity(vb, &combine));
1890 has_comm && has_assoc
1891}
1892
1893fn combine_is_additive_monoid(fd: &FnDef) -> bool {
1898 use crate::ast::{Expr, Pattern, Stmt};
1899 if fd.params.len() != 2 {
1900 return false;
1901 }
1902 let second = fd.params[1].0.as_str();
1903 let [Stmt::Expr(body)] = fd.body.stmts() else {
1904 return false;
1905 };
1906 let Expr::Match { arms, .. } = &body.node else {
1907 return false;
1908 };
1909 arms.iter().any(|arm| {
1910 matches!(&arm.pattern, Pattern::Constructor(_, binders) if binders.is_empty())
1911 && matches!(&arm.body.node, Expr::Ident(n) | Expr::Resolved { name: n, .. } if n == second)
1912 })
1913}
1914
1915pub fn law_calls_foreign_accumulator_fold(
1926 ctx: &CodegenContext,
1927 law: &crate::ast::VerifyLaw,
1928 verified_fn: &str,
1929) -> bool {
1930 let foreign: HashSet<String> = accumulator_fold_fn_names(ctx)
1931 .into_iter()
1932 .filter(|n| n != verified_fn)
1933 .collect();
1934 law_calls_unclassified_fn(law, &foreign)
1935}
1936
1937pub fn dafny_should_bound_accumulator_fold(
1951 ctx: &CodegenContext,
1952 law: &crate::ast::VerifyLaw,
1953 verified_fn: &str,
1954) -> bool {
1955 law_calls_foreign_accumulator_fold(ctx, law, verified_fn)
1956 || (accumulator_fold_fn_names(ctx).contains(verified_fn)
1957 && !nat_accfold_self_closeable(ctx, verified_fn, &law.name))
1958}
1959
1960pub fn law_rhs_is_independent_of_givens(law: &crate::ast::VerifyLaw) -> bool {
1970 let given_names: HashSet<&str> = law.givens.iter().map(|g| g.name.as_str()).collect();
1971 if given_names.is_empty() {
1972 return true;
1973 }
1974 !expr_references_any_ident(&law.rhs, &given_names)
1975}
1976
1977fn expr_references_any_ident(expr: &Spanned<Expr>, names: &HashSet<&str>) -> bool {
1978 match &expr.node {
1979 Expr::Ident(name) | Expr::Resolved { name, .. } => names.contains(name.as_str()),
1980 Expr::Attr(inner, _) | Expr::ErrorProp(inner) | Expr::Neg(inner) => {
1981 expr_references_any_ident(inner, names)
1982 }
1983 Expr::FnCall(callee, args) => {
1984 expr_references_any_ident(callee, names)
1985 || args.iter().any(|a| expr_references_any_ident(a, names))
1986 }
1987 Expr::BinOp(_, l, r) => {
1988 expr_references_any_ident(l, names) || expr_references_any_ident(r, names)
1989 }
1990 Expr::Match { subject, arms } => {
1991 expr_references_any_ident(subject, names)
1992 || arms
1993 .iter()
1994 .any(|a| expr_references_any_ident(&a.body, names))
1995 }
1996 Expr::Constructor(_, Some(arg)) => expr_references_any_ident(arg, names),
1997 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
1998 items.iter().any(|i| expr_references_any_ident(i, names))
1999 }
2000 Expr::MapLiteral(entries) => entries.iter().any(|(k, v)| {
2001 expr_references_any_ident(k, names) || expr_references_any_ident(v, names)
2002 }),
2003 Expr::RecordCreate { fields, .. } => fields
2004 .iter()
2005 .any(|(_, v)| expr_references_any_ident(v, names)),
2006 Expr::RecordUpdate { base, updates, .. } => {
2007 expr_references_any_ident(base, names)
2008 || updates
2009 .iter()
2010 .any(|(_, v)| expr_references_any_ident(v, names))
2011 }
2012 Expr::InterpolatedStr(parts) => parts.iter().any(|p| match p {
2013 crate::ast::StrPart::Parsed(inner) => expr_references_any_ident(inner, names),
2014 crate::ast::StrPart::Literal(_) => false,
2015 }),
2016 Expr::TailCall(boxed) => {
2017 let crate::ast::TailCallData { args, .. } = boxed.as_ref();
2018 args.iter().any(|a| expr_references_any_ident(a, names))
2019 }
2020 _ => false,
2021 }
2022}
2023
2024pub fn law_lhs_has_trace_projection(expr: &Spanned<Expr>) -> bool {
2045 expr_has_trace_field(expr) && expr_has_trace_api_call(expr)
2046}
2047
2048fn expr_has_trace_field(expr: &Spanned<Expr>) -> bool {
2049 match &expr.node {
2050 Expr::Attr(inner, field) => field == "trace" || expr_has_trace_field(inner),
2051 Expr::FnCall(callee, args) => {
2052 expr_has_trace_field(callee) || args.iter().any(expr_has_trace_field)
2053 }
2054 Expr::BinOp(_, l, r) => expr_has_trace_field(l) || expr_has_trace_field(r),
2055 Expr::Match { subject, arms } => {
2056 expr_has_trace_field(subject) || arms.iter().any(|a| expr_has_trace_field(&a.body))
2057 }
2058 Expr::ErrorProp(inner) => expr_has_trace_field(inner),
2059 Expr::Constructor(_, Some(arg)) => expr_has_trace_field(arg),
2060 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
2061 items.iter().any(expr_has_trace_field)
2062 }
2063 _ => false,
2064 }
2065}
2066
2067const TRACE_API_METHODS: &[&str] = &["event", "group", "branch", "length", "contains", "count"];
2072
2073fn expr_has_trace_api_call(expr: &Spanned<Expr>) -> bool {
2074 match &expr.node {
2075 Expr::FnCall(callee, args) => {
2076 let direct = matches!(
2077 &callee.node,
2078 Expr::Attr(_, method) if TRACE_API_METHODS.contains(&method.as_str())
2079 );
2080 direct || expr_has_trace_api_call(callee) || args.iter().any(expr_has_trace_api_call)
2081 }
2082 Expr::Attr(inner, _) | Expr::ErrorProp(inner) => expr_has_trace_api_call(inner),
2083 Expr::BinOp(_, l, r) => expr_has_trace_api_call(l) || expr_has_trace_api_call(r),
2084 Expr::Match { subject, arms } => {
2085 expr_has_trace_api_call(subject)
2086 || arms.iter().any(|a| expr_has_trace_api_call(&a.body))
2087 }
2088 Expr::Constructor(_, Some(arg)) => expr_has_trace_api_call(arg),
2089 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
2090 items.iter().any(expr_has_trace_api_call)
2091 }
2092 _ => false,
2093 }
2094}
2095
2096pub fn is_recursive_type_def(td: &TypeDef) -> bool {
2099 match td {
2100 TypeDef::Sum { name, variants, .. } => is_recursive_sum(name, variants),
2101 TypeDef::Product { name, fields, .. } => is_recursive_product(name, fields),
2102 }
2103}
2104
2105pub fn type_def_name(td: &TypeDef) -> &str {
2107 match td {
2108 TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
2109 }
2110}
2111
2112pub fn is_recursive_sum(name: &str, variants: &[TypeVariant]) -> bool {
2116 variants
2117 .iter()
2118 .any(|v| v.fields.iter().any(|f| type_ref_contains(f, name)))
2119}
2120
2121pub fn is_recursive_product(name: &str, fields: &[(String, String)]) -> bool {
2123 fields.iter().any(|(_, ty)| type_ref_contains(ty, name))
2124}
2125
2126fn type_ref_contains(annotation: &str, type_name: &str) -> bool {
2127 annotation == type_name
2130 || annotation.contains(&format!("<{}", type_name))
2131 || annotation.contains(&format!("{}>", type_name))
2132 || annotation.contains(&format!(", {}", type_name))
2133 || annotation.contains(&format!("{},", type_name))
2134}
2135
2136pub(crate) fn is_user_type(name: &str, ctx: &CodegenContext) -> bool {
2138 let check_td = |td: &TypeDef| match td {
2139 TypeDef::Sum { name: n, .. } => n == name,
2140 TypeDef::Product { name: n, .. } => n == name,
2141 };
2142 ctx.type_defs.iter().any(check_td)
2143 || ctx.modules.iter().any(|m| m.type_defs.iter().any(check_td))
2144}
2145
2146pub(crate) fn resolve_module_call<'a>(
2149 dotted_name: &'a str,
2150 ctx: &'a CodegenContext,
2151) -> Option<(&'a str, &'a str)> {
2152 let mut best: Option<&str> = None;
2153 for prefix in &ctx.module_prefixes {
2154 let dotted_prefix = format!("{}.", prefix);
2155 if dotted_name.starts_with(&dotted_prefix) && best.is_none_or(|b| prefix.len() > b.len()) {
2156 best = Some(prefix.as_str());
2157 }
2158 }
2159 best.map(|prefix| (prefix, &dotted_name[prefix.len() + 1..]))
2160}
2161
2162pub(crate) fn module_prefix_to_rust_segments(prefix: &str) -> Vec<String> {
2163 prefix.split('.').map(module_segment_to_rust).collect()
2164}
2165
2166pub(crate) fn module_prefix_to_filename(prefix: &str) -> String {
2171 prefix.replace('.', "/")
2172}
2173
2174pub(crate) struct DeclaredEffects {
2186 pub bare_namespaces: HashSet<String>,
2187 pub methods: HashSet<String>,
2188}
2189
2190impl DeclaredEffects {
2191 pub fn includes(&self, c_method: &str) -> bool {
2194 if self.methods.contains(c_method) {
2195 return true;
2196 }
2197 if let Some((ns, _)) = c_method.split_once('.') {
2198 return self.bare_namespaces.contains(ns);
2199 }
2200 false
2201 }
2202}
2203
2204pub(crate) fn collect_declared_effects(ctx: &CodegenContext) -> DeclaredEffects {
2208 let mut bare_namespaces: HashSet<String> = HashSet::new();
2209 let mut methods: HashSet<String> = HashSet::new();
2210 let mut record = |effect: &str| {
2211 if effect.contains('.') {
2212 methods.insert(effect.to_string());
2213 } else {
2214 bare_namespaces.insert(effect.to_string());
2215 }
2216 };
2217 for item in &ctx.items {
2218 if let TopLevel::FnDef(fd) = item {
2219 for eff in &fd.effects {
2220 record(&eff.node);
2221 }
2222 }
2223 }
2224 for module in &ctx.modules {
2225 for fd in &module.fn_defs {
2226 for eff in &fd.effects {
2227 record(&eff.node);
2228 }
2229 }
2230 }
2231 DeclaredEffects {
2232 bare_namespaces,
2233 methods,
2234 }
2235}
2236
2237pub fn entry_basename(ctx: &CodegenContext) -> String {
2245 ctx.items
2246 .iter()
2247 .find_map(|item| match item {
2248 TopLevel::Module(m) => Some(m.name.clone()),
2249 _ => None,
2250 })
2251 .unwrap_or_else(|| {
2252 let mut chars = ctx.project_name.chars();
2253 match chars.next() {
2254 None => String::new(),
2255 Some(c) => c.to_uppercase().chain(chars).collect(),
2256 }
2257 })
2258}
2259
2260pub fn verify_block_counter_key(vb: &crate::ast::VerifyBlock) -> String {
2271 match &vb.kind {
2272 crate::ast::VerifyKind::Cases => format!("fn:{}", vb.fn_name),
2273 crate::ast::VerifyKind::Law(law) => format!("law:{}::{}", vb.fn_name, law.name),
2274 }
2275}
2276
2277pub(crate) fn module_prefix_to_rust_path(prefix: &str) -> String {
2287 format!(
2288 "crate::aver_generated::{}",
2289 module_prefix_to_rust_segments(prefix).join("::")
2290 )
2291}
2292
2293fn module_segment_to_rust(segment: &str) -> String {
2294 let chars = segment.chars().collect::<Vec<_>>();
2295 let mut out = String::new();
2296
2297 for (idx, ch) in chars.iter().enumerate() {
2298 if ch.is_ascii_alphanumeric() {
2299 if ch.is_ascii_uppercase() {
2300 let prev_is_lower_or_digit = idx > 0
2301 && (chars[idx - 1].is_ascii_lowercase() || chars[idx - 1].is_ascii_digit());
2302 let next_is_lower = chars
2303 .get(idx + 1)
2304 .is_some_and(|next| next.is_ascii_lowercase());
2305 if idx > 0 && (prev_is_lower_or_digit || next_is_lower) && !out.ends_with('_') {
2306 out.push('_');
2307 }
2308 out.push(ch.to_ascii_lowercase());
2309 } else {
2310 out.push(ch.to_ascii_lowercase());
2311 }
2312 } else if !out.ends_with('_') {
2313 out.push('_');
2314 }
2315 }
2316
2317 let trimmed = out.trim_matches('_');
2318 let mut normalized = if trimmed.is_empty() {
2319 "module".to_string()
2320 } else {
2321 trimmed.to_string()
2322 };
2323
2324 if matches!(
2325 normalized.as_str(),
2326 "as" | "break"
2327 | "const"
2328 | "continue"
2329 | "crate"
2330 | "else"
2331 | "enum"
2332 | "extern"
2333 | "false"
2334 | "fn"
2335 | "for"
2336 | "if"
2337 | "impl"
2338 | "in"
2339 | "let"
2340 | "loop"
2341 | "match"
2342 | "mod"
2343 | "move"
2344 | "mut"
2345 | "pub"
2346 | "ref"
2347 | "return"
2348 | "self"
2349 | "Self"
2350 | "static"
2351 | "struct"
2352 | "super"
2353 | "trait"
2354 | "true"
2355 | "type"
2356 | "unsafe"
2357 | "use"
2358 | "where"
2359 | "while"
2360 ) {
2361 normalized.push_str("_mod");
2362 }
2363
2364 normalized
2365}
2366
2367pub(crate) fn split_type_params(s: &str, delim: char) -> Vec<String> {
2372 let mut parts = Vec::new();
2373 let mut depth = 0usize;
2374 let mut current = String::new();
2375 for ch in s.chars() {
2376 match ch {
2377 '<' | '(' => {
2378 depth += 1;
2379 current.push(ch);
2380 }
2381 '>' | ')' => {
2382 depth = depth.saturating_sub(1);
2383 current.push(ch);
2384 }
2385 _ if ch == delim && depth == 0 => {
2386 parts.push(current.trim().to_string());
2387 current.clear();
2388 }
2389 _ => current.push(ch),
2390 }
2391 }
2392 let rest = current.trim().to_string();
2393 if !rest.is_empty() {
2394 parts.push(rest);
2395 }
2396 parts
2397}
2398
2399pub(crate) fn escape_string_literal_ext(s: &str, unicode_escapes: bool) -> String {
2406 let mut out = String::with_capacity(s.len());
2407 for ch in s.chars() {
2408 match ch {
2409 '\\' => out.push_str("\\\\"),
2410 '"' => out.push_str("\\\""),
2411 '\n' => out.push_str("\\n"),
2412 '\r' => out.push_str("\\r"),
2413 '\t' => out.push_str("\\t"),
2414 '\0' => out.push_str("\\0"),
2415 c if c.is_control() => {
2416 if unicode_escapes {
2417 out.push_str(&format!("\\U{{{:06x}}}", c as u32));
2419 } else {
2420 out.push_str(&format!("\\x{:02x}", c as u32));
2421 }
2422 }
2423 c => out.push(c),
2424 }
2425 }
2426 out
2427}
2428
2429pub(crate) fn escape_string_literal(s: &str) -> String {
2431 escape_string_literal_ext(s, false)
2432}
2433
2434pub(crate) fn escape_string_literal_unicode(s: &str) -> String {
2436 escape_string_literal_ext(s, true)
2437}
2438
2439pub(crate) fn parse_type_annotation(ann: &str) -> Type {
2443 crate::types::parse_type_str(ann)
2444}
2445
2446pub(crate) fn is_set_type(ty: &Type) -> bool {
2452 matches!(ty, Type::Map(_, v) if matches!(v.as_ref(), Type::Unit))
2453}
2454
2455pub(crate) fn is_set_annotation(ann: &str) -> bool {
2457 is_set_type(&parse_type_annotation(ann))
2458}
2459
2460pub(crate) fn is_unit_expr_resolved(expr: &crate::ir::hir::ResolvedExpr) -> bool {
2463 matches!(
2464 expr,
2465 crate::ir::hir::ResolvedExpr::Literal(crate::ast::Literal::Unit)
2466 )
2467}
2468
2469pub(crate) fn escape_reserved_word(name: &str, reserved: &[&str], suffix: &str) -> String {
2474 if reserved.contains(&name) {
2475 format!("{}{}", name, suffix)
2476 } else {
2477 name.to_string()
2478 }
2479}
2480
2481pub(crate) fn escape_reserved_word_prefix(name: &str, reserved: &[&str], prefix: &str) -> String {
2484 if reserved.contains(&name) {
2485 format!("{}{}", prefix, name)
2486 } else {
2487 name.to_string()
2488 }
2489}
2490
2491pub(crate) fn to_lower_first(s: &str) -> String {
2495 let mut chars = s.chars();
2496 match chars.next() {
2497 None => String::new(),
2498 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
2499 }
2500}
2501
2502pub(crate) fn expr_to_dotted_name(expr: &Expr) -> Option<String> {
2505 crate::ir::expr_to_dotted_name(expr)
2506}
2507
2508#[derive(Debug, Clone)]
2522pub(crate) enum OracleInjectionMode<'a> {
2523 LemmaBinding,
2524 LemmaBindingProjected,
2534 #[allow(dead_code)]
2535 SampleValue,
2536 SampleCaseBinding(&'a [(String, crate::ast::Spanned<Expr>)]),
2537}
2538
2539pub(crate) fn rewrite_effectful_calls_in_law<'fd, F>(
2548 expr: &crate::ast::Spanned<Expr>,
2549 law: &crate::ast::VerifyLaw,
2550 find_fn_def: F,
2551 mode: OracleInjectionMode,
2552) -> crate::ast::Spanned<Expr>
2553where
2554 F: Fn(&str) -> Option<&'fd crate::ast::FnDef> + Copy,
2555{
2556 use crate::ast::{Spanned, VerifyGivenDomain};
2557
2558 let injection_by_effect: std::collections::HashMap<String, Spanned<Expr>> = law
2559 .givens
2560 .iter()
2561 .filter_map(|g| {
2562 let arg_expr = match &mode {
2563 OracleInjectionMode::LemmaBinding => {
2564 Spanned::new(Expr::Ident(g.name.clone()), expr.line)
2565 }
2566 OracleInjectionMode::LemmaBindingProjected => {
2567 Spanned::new(Expr::Ident(g.name.clone()), expr.line)
2575 }
2576 OracleInjectionMode::SampleValue => match &g.domain {
2577 VerifyGivenDomain::Explicit(vals) => vals.first().cloned()?,
2578 _ => return None,
2579 },
2580 OracleInjectionMode::SampleCaseBinding(case_bindings) => case_bindings
2581 .iter()
2582 .find(|(name, _)| name == &g.name)
2583 .map(|(_, v)| v.clone())?,
2584 };
2585 Some((g.type_name.clone(), arg_expr))
2586 })
2587 .collect();
2588 let rewritten = rewrite_effectful_call(expr, &injection_by_effect, find_fn_def);
2589
2590 if matches!(mode, OracleInjectionMode::LemmaBindingProjected) {
2599 let oracle_names: std::collections::HashSet<String> = law
2609 .givens
2610 .iter()
2611 .filter(|g| crate::types::checker::oracle_subtypes::has_bounded_subtype(&g.type_name))
2612 .map(|g| g.name.clone())
2613 .collect();
2614 if !oracle_names.is_empty() {
2615 return project_oracle_direct_calls(&rewritten, &oracle_names);
2616 }
2617 }
2618 rewritten
2619}
2620
2621fn project_oracle_direct_calls(
2634 expr: &crate::ast::Spanned<Expr>,
2635 oracle_names: &std::collections::HashSet<String>,
2636) -> crate::ast::Spanned<Expr> {
2637 use crate::ast::Spanned;
2638 let line = expr.line;
2639 let project_ident = |name: &str, line: usize| -> Spanned<Expr> {
2640 Spanned::new(
2641 Expr::Attr(
2642 Box::new(Spanned::new(Expr::Ident(name.to_string()), line)),
2643 "val".to_string(),
2644 ),
2645 line,
2646 )
2647 };
2648 let new_node = match &expr.node {
2649 Expr::Ident(name) if oracle_names.contains(name) => {
2653 return project_ident(name, line);
2654 }
2655 Expr::FnCall(callee, args) => {
2656 let new_args: Vec<Spanned<Expr>> = args
2657 .iter()
2658 .map(|a| project_oracle_direct_calls(a, oracle_names))
2659 .collect();
2660 let new_callee = if let Expr::Ident(name) = &callee.node
2662 && oracle_names.contains(name)
2663 {
2664 project_ident(name, callee.line)
2665 } else {
2666 project_oracle_direct_calls(callee, oracle_names)
2667 };
2668 Expr::FnCall(Box::new(new_callee), new_args)
2669 }
2670 Expr::Constructor(name, Some(arg)) => Expr::Constructor(
2671 name.clone(),
2672 Some(Box::new(project_oracle_direct_calls(arg, oracle_names))),
2673 ),
2674 Expr::Attr(obj, field) => Expr::Attr(
2675 Box::new(project_oracle_direct_calls(obj, oracle_names)),
2676 field.clone(),
2677 ),
2678 Expr::BinOp(op, l, r) => Expr::BinOp(
2679 *op,
2680 Box::new(project_oracle_direct_calls(l, oracle_names)),
2681 Box::new(project_oracle_direct_calls(r, oracle_names)),
2682 ),
2683 other => other.clone(),
2684 };
2685 Spanned::new(new_node, line)
2686}
2687
2688fn rewrite_effectful_call<'fd, F>(
2689 expr: &crate::ast::Spanned<Expr>,
2690 injection_by_effect: &std::collections::HashMap<String, crate::ast::Spanned<Expr>>,
2691 find_fn_def: F,
2692) -> crate::ast::Spanned<Expr>
2693where
2694 F: Fn(&str) -> Option<&'fd crate::ast::FnDef> + Copy,
2695{
2696 use crate::ast::Spanned;
2697 use crate::types::checker::effect_classification::{EffectDimension, classify};
2698
2699 match &expr.node {
2700 Expr::FnCall(callee, args) => {
2701 let rewritten_args: Vec<Spanned<Expr>> = args
2702 .iter()
2703 .map(|a| rewrite_effectful_call(a, injection_by_effect, find_fn_def))
2704 .collect();
2705 let rewritten_callee = Box::new(rewrite_effectful_call(
2706 callee,
2707 injection_by_effect,
2708 find_fn_def,
2709 ));
2710
2711 let callee_name = match &callee.node {
2712 Expr::Ident(name) => Some(name.clone()),
2713 Expr::Resolved { name, .. } => Some(name.clone()),
2714 _ => None,
2715 };
2716
2717 if let Some(name) = callee_name
2718 && let Some(fd) = find_fn_def(&name)
2719 && !fd.effects.is_empty()
2720 && fd
2721 .effects
2722 .iter()
2723 .all(|e| crate::types::checker::effect_classification::is_classified(&e.node))
2724 {
2725 let mut injected: Vec<Spanned<Expr>> = Vec::new();
2726 let needs_path = fd.effects.iter().any(|e| {
2727 matches!(
2728 classify(&e.node).map(|c| c.dimension),
2729 Some(EffectDimension::Generative | EffectDimension::GenerativeOutput)
2730 )
2731 });
2732 if needs_path {
2733 injected.push(Spanned::new(
2734 Expr::Attr(
2738 Box::new(Spanned::new(
2739 Expr::Ident("BranchPath".to_string()),
2740 expr.line,
2741 )),
2742 "Root".to_string(),
2743 ),
2744 expr.line,
2745 ));
2746 }
2747 let mut seen = std::collections::HashSet::new();
2748 for e in &fd.effects {
2749 if !seen.insert(e.node.clone()) {
2750 continue;
2751 }
2752 let Some(c) = classify(&e.node) else { continue };
2753 if matches!(c.dimension, EffectDimension::Output) {
2754 continue;
2755 }
2756 if let Some(inj) = injection_by_effect.get(&e.node) {
2757 injected.push(inj.clone());
2758 }
2759 }
2760 injected.extend(rewritten_args);
2761 return Spanned::new(Expr::FnCall(rewritten_callee, injected), expr.line);
2762 }
2763
2764 Spanned::new(Expr::FnCall(rewritten_callee, rewritten_args), expr.line)
2765 }
2766 Expr::BinOp(op, l, r) => Spanned::new(
2767 Expr::BinOp(
2768 *op,
2769 Box::new(rewrite_effectful_call(l, injection_by_effect, find_fn_def)),
2770 Box::new(rewrite_effectful_call(r, injection_by_effect, find_fn_def)),
2771 ),
2772 expr.line,
2773 ),
2774 Expr::Tuple(items) => Spanned::new(
2775 Expr::Tuple(
2776 items
2777 .iter()
2778 .map(|i| rewrite_effectful_call(i, injection_by_effect, find_fn_def))
2779 .collect(),
2780 ),
2781 expr.line,
2782 ),
2783 _ => expr.clone(),
2784 }
2785}
2786
2787pub(crate) fn verify_reachable_fn_names(items: &[TopLevel]) -> HashSet<String> {
2797 let mut reachable: HashSet<String> = HashSet::new();
2798 for item in items {
2799 if let TopLevel::Verify(vb) = item {
2800 collect_verify_block_refs(vb, &mut reachable);
2801 }
2802 }
2803 loop {
2805 let mut changed = false;
2806 for item in items {
2807 if let TopLevel::FnDef(fd) = item
2808 && reachable.contains(&fd.name)
2809 {
2810 let mut called = HashSet::new();
2811 collect_called_idents_in_body(&fd.body, &mut called);
2812 for name in called {
2813 if reachable.insert(name) {
2814 changed = true;
2815 }
2816 }
2817 }
2818 }
2819 if !changed {
2820 break;
2821 }
2822 }
2823 reachable
2824}
2825
2826fn collect_verify_block_refs(vb: &VerifyBlock, out: &mut HashSet<String>) {
2827 out.insert(vb.fn_name.clone());
2828 for (lhs, rhs) in &vb.cases {
2829 collect_called_idents(lhs, out);
2830 collect_called_idents(rhs, out);
2831 }
2832 if let VerifyKind::Law(law) = &vb.kind {
2833 collect_called_idents(&law.lhs, out);
2834 collect_called_idents(&law.rhs, out);
2835 if let Some(when) = &law.when {
2836 collect_called_idents(when, out);
2837 }
2838 for given in &law.givens {
2839 if let VerifyGivenDomain::Explicit(values) = &given.domain {
2840 for v in values {
2841 collect_called_idents(v, out);
2842 }
2843 }
2844 }
2845 }
2846 for given in &vb.cases_givens {
2847 if let VerifyGivenDomain::Explicit(values) = &given.domain {
2848 for v in values {
2849 collect_called_idents(v, out);
2850 }
2851 }
2852 }
2853}
2854
2855fn collect_called_idents_in_body(body: &FnBody, out: &mut HashSet<String>) {
2856 for stmt in body.stmts() {
2857 match stmt {
2858 Stmt::Binding(_, _, e) | Stmt::Expr(e) => collect_called_idents(e, out),
2859 }
2860 }
2861}
2862
2863fn collect_called_idents(expr: &Spanned<Expr>, out: &mut HashSet<String>) {
2864 match &expr.node {
2865 Expr::FnCall(callee, args) => {
2866 if let Expr::Ident(name) | Expr::Resolved { name, .. } = &callee.node {
2867 out.insert(name.clone());
2868 } else {
2869 collect_called_idents(callee, out);
2870 }
2871 for a in args {
2872 collect_called_idents(a, out);
2873 }
2874 }
2875 Expr::TailCall(boxed) => {
2876 let TailCallData { target, args, .. } = boxed.as_ref();
2877 out.insert(target.clone());
2878 for a in args {
2879 collect_called_idents(a, out);
2880 }
2881 }
2882 Expr::Ident(name) | Expr::Resolved { name, .. } => {
2883 out.insert(name.clone());
2884 }
2885 Expr::BinOp(_, l, r) => {
2886 collect_called_idents(l, out);
2887 collect_called_idents(r, out);
2888 }
2889 Expr::Neg(inner) => collect_called_idents(inner, out),
2890 Expr::Match { subject, arms, .. } => {
2891 collect_called_idents(subject, out);
2892 for arm in arms {
2893 collect_called_idents(&arm.body, out);
2894 }
2895 }
2896 Expr::ErrorProp(inner) | Expr::Attr(inner, _) => {
2897 collect_called_idents(inner, out);
2898 }
2899 Expr::Constructor(_, Some(inner)) => {
2900 collect_called_idents(inner, out);
2901 }
2902 Expr::InterpolatedStr(parts) => {
2903 for part in parts {
2904 if let StrPart::Parsed(inner) = part {
2905 collect_called_idents(inner, out);
2906 }
2907 }
2908 }
2909 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
2910 for i in items {
2911 collect_called_idents(i, out);
2912 }
2913 }
2914 Expr::MapLiteral(entries) => {
2915 for (k, v) in entries {
2916 collect_called_idents(k, out);
2917 collect_called_idents(v, out);
2918 }
2919 }
2920 Expr::RecordCreate { fields, .. } => {
2921 for (_, v) in fields {
2922 collect_called_idents(v, out);
2923 }
2924 }
2925 Expr::RecordUpdate { base, updates, .. } => {
2926 collect_called_idents(base, out);
2927 for (_, v) in updates {
2928 collect_called_idents(v, out);
2929 }
2930 }
2931 Expr::Literal(_) | Expr::Constructor(_, None) => {}
2932 }
2933}
2934
2935pub(crate) struct PerScopeSections {
2939 pub by_scope: std::collections::HashMap<String, Vec<String>>,
2940}
2941
2942impl PerScopeSections {
2943 pub(crate) fn take(&mut self, scope: &str) -> Vec<String> {
2944 self.by_scope.remove(scope).unwrap_or_default()
2945 }
2946}
2947
2948pub(crate) fn route_pure_components_per_scope<F, G>(
2957 ctx: &CodegenContext,
2958 is_pure: F,
2959 mut emit: G,
2960) -> PerScopeSections
2961where
2962 F: Fn(&FnDef) -> bool,
2963 G: FnMut(&[&FnDef], &str) -> Vec<String>,
2964{
2965 let mut by_scope: std::collections::HashMap<String, Vec<String>> =
2966 std::collections::HashMap::new();
2967
2968 let mut process =
2969 |fns: Vec<&FnDef>,
2970 scope: String,
2971 by_scope: &mut std::collections::HashMap<String, Vec<String>>| {
2972 let comps = crate::call_graph::ordered_fn_components(&fns, &ctx.module_prefixes);
2973 let bucket = by_scope.entry(scope.clone()).or_default();
2974 for comp in comps {
2975 bucket.extend(emit(&comp, scope.as_str()));
2976 }
2977 };
2978
2979 for module in &ctx.modules {
2980 let pure: Vec<&FnDef> = module.fn_defs.iter().filter(|fd| is_pure(fd)).collect();
2981 process(pure, module.prefix.clone(), &mut by_scope);
2982 }
2983 let entry_pure: Vec<&FnDef> = ctx.fn_defs.iter().filter(|fd| is_pure(fd)).collect();
2984 process(entry_pure, String::new(), &mut by_scope);
2985
2986 PerScopeSections { by_scope }
2987}
2988
2989#[cfg(test)]
2990mod tests {
2991 use super::*;
2992 use crate::ast::{Literal, VerifyGiven, VerifyGivenDomain, VerifyLaw};
2993
2994 fn sb(node: Expr) -> Spanned<Expr> {
2995 Spanned::new(node, 1)
2996 }
2997
2998 fn bsb(node: Expr) -> Box<Spanned<Expr>> {
2999 Box::new(sb(node))
3000 }
3001
3002 fn law_with(lhs: Spanned<Expr>, rhs: Spanned<Expr>) -> VerifyLaw {
3003 VerifyLaw {
3004 name: "test".to_string(),
3005 givens: vec![VerifyGiven {
3006 name: "xs".to_string(),
3007 type_name: "List<String>".to_string(),
3008 domain: VerifyGivenDomain::Explicit(vec![sb(Expr::List(vec![]))]),
3009 }],
3010 when: None,
3011 lhs,
3012 rhs,
3013 sample_guards: Vec::new(),
3014 }
3015 }
3016
3017 #[test]
3018 fn canonicalize_comparison_swaps_operands_never_negates() {
3019 use crate::ast::BinOp;
3020 let a = || bsb(Expr::Ident("a".to_string()));
3021 let b = || bsb(Expr::Ident("b".to_string()));
3022 let binop = |op, l: Box<Spanned<Expr>>, r: Box<Spanned<Expr>>| sb(Expr::BinOp(op, l, r));
3023
3024 let canon_gt = canonicalize_comparison(&binop(BinOp::Gt, a(), b()));
3026 assert_eq!(canon_gt.node, binop(BinOp::Lt, b(), a()).node);
3027 let canon_gte = canonicalize_comparison(&binop(BinOp::Gte, a(), b()));
3029 assert_eq!(canon_gte.node, binop(BinOp::Lte, b(), a()).node);
3030
3031 assert!(!matches!(&canon_gt.node, Expr::BinOp(BinOp::Lte, ..)));
3035 assert!(!matches!(&canon_gte.node, Expr::BinOp(BinOp::Lt, ..)));
3036
3037 for op in [BinOp::Lt, BinOp::Lte, BinOp::Eq, BinOp::Neq] {
3040 let clause = binop(op, a(), b());
3041 assert_eq!(canonicalize_comparison(&clause).node, clause.node);
3042 }
3043 let call = sb(Expr::FnCall(a(), vec![sb(Expr::Ident("x".to_string()))]));
3044 assert_eq!(canonicalize_comparison(&call).node, call.node);
3045 }
3046
3047 #[test]
3048 fn law_calls_unclassified_fn_detects_dotted_callee() {
3049 let lhs = sb(Expr::FnCall(
3060 bsb(Expr::Attr(
3061 bsb(Expr::Ident("Dep".to_string())),
3062 "toSorted".to_string(),
3063 )),
3064 vec![sb(Expr::Ident("xs".to_string()))],
3065 ));
3066 let rhs = sb(Expr::Ident("xs".to_string()));
3067 let law = law_with(lhs, rhs);
3068
3069 let mut canonical = HashSet::new();
3072 canonical.insert("Dep.toSorted".to_string());
3073 assert!(law_calls_unclassified_fn(&law, &canonical));
3074
3075 let mut bare_only = HashSet::new();
3078 bare_only.insert("toSorted".to_string());
3079 assert!(
3080 law_calls_unclassified_fn(&law, &bare_only),
3081 "bare unclassified name must catch a dotted callsite via suffix match"
3082 );
3083
3084 let mut unrelated = HashSet::new();
3087 unrelated.insert("somethingElse".to_string());
3088 assert!(!law_calls_unclassified_fn(&law, &unrelated));
3089 }
3090
3091 #[test]
3092 fn law_lhs_has_trace_projection_skips_user_record_field() {
3093 let user_field_lhs = sb(Expr::Attr(
3101 bsb(Expr::Ident("log".to_string())),
3102 "trace".to_string(),
3103 ));
3104 assert!(
3105 !law_lhs_has_trace_projection(&user_field_lhs),
3106 "bare user-record `.trace` field must not trigger the gate"
3107 );
3108
3109 let runtime_trace_lhs = sb(Expr::FnCall(
3111 bsb(Expr::Attr(
3112 bsb(Expr::Attr(
3113 bsb(Expr::FnCall(bsb(Expr::Ident("fn".to_string())), vec![])),
3114 "trace".to_string(),
3115 )),
3116 "event".to_string(),
3117 )),
3118 vec![sb(Expr::Literal(Literal::Int(0)))],
3119 ));
3120 assert!(
3121 law_lhs_has_trace_projection(&runtime_trace_lhs),
3122 "Oracle `.trace.event(0)` projection must trigger the gate"
3123 );
3124 }
3125
3126 #[test]
3127 fn resolve_refined_type_disambiguates_cross_module_same_bare_name() {
3128 use crate::ast::{TypeDef, TypeVariant};
3137 use crate::codegen::ModuleInfo;
3138 use crate::ir::proof_ir::{Predicate, QuantifierType, RefinedTypeDecl};
3139 use std::collections::HashMap;
3140 let _ = TypeVariant {
3141 name: String::new(),
3142 fields: Vec::new(),
3143 }; let make_module = |prefix: &str| ModuleInfo {
3146 prefix: prefix.to_string(),
3147 depends: Vec::new(),
3148 type_defs: vec![TypeDef::Product {
3149 name: "Natural".to_string(),
3150 fields: vec![("value".to_string(), "Int".to_string())],
3151 line: 1,
3152 }],
3153 fn_defs: Vec::new(),
3154 verify_laws: Vec::new(),
3155 analysis: None,
3156 };
3157 let modules = vec![make_module("A"), make_module("B")];
3158
3159 let make_decl = |predicate_param: &str, witness: i64| RefinedTypeDecl {
3160 name: "Natural".to_string(),
3161 carrier_type: "Int".to_string(),
3162 carrier_field: "value".to_string(),
3163 predicate_param: predicate_param.to_string(),
3164 invariant: Predicate {
3165 free_vars: vec![(
3166 predicate_param.to_string(),
3167 QuantifierType::Plain("Int".to_string()),
3168 )],
3169 expr: crate::ast::Spanned::bare(crate::ir::hir::ResolvedExpr::Literal(
3170 Literal::Bool(true),
3171 )),
3172 },
3173 witness: Some(witness.to_string()),
3174 interval: None,
3175 op_classes: Vec::new(),
3176 };
3177 let symbols = crate::ir::SymbolTable::build(&[], &modules);
3178 let a_id = symbols
3179 .type_id_of(&crate::ir::TypeKey::in_module("A", "Natural"))
3180 .expect("A.Natural TypeId");
3181 let b_id = symbols
3182 .type_id_of(&crate::ir::TypeKey::in_module("B", "Natural"))
3183 .expect("B.Natural TypeId");
3184
3185 let mut refined_types: HashMap<crate::ir::TypeId, RefinedTypeDecl> = HashMap::new();
3186 refined_types.insert(a_id, make_decl("a", 0));
3187 refined_types.insert(b_id, make_decl("b", 10));
3188
3189 let a = resolve_refined_type_in(&refined_types, &symbols, &modules, "A.Natural")
3191 .expect("A.Natural canonical lookup");
3192 assert_eq!(a.predicate_param, "a");
3193 assert_eq!(a.witness.as_deref(), Some("0"));
3194 let b = resolve_refined_type_in(&refined_types, &symbols, &modules, "B.Natural")
3195 .expect("B.Natural canonical lookup");
3196 assert_eq!(b.predicate_param, "b");
3197 assert_eq!(b.witness.as_deref(), Some("10"));
3198
3199 let bare = resolve_refined_type_in(&refined_types, &symbols, &modules, "Natural")
3203 .expect("bare Natural resolves via module walk");
3204 assert!(
3205 bare.predicate_param == "a" || bare.predicate_param == "b",
3206 "bare Natural must resolve to one of the canonical decls"
3207 );
3208
3209 assert!(resolve_refined_type_in(&refined_types, &symbols, &modules, "Unrelated").is_none());
3212 }
3213
3214 #[test]
3215 fn find_refined_type_scoped_prefers_current_module_over_entry_collision() {
3216 use crate::ast::{TopLevel, TypeDef};
3224 use crate::codegen::{CodegenContext, ModuleInfo};
3225 use crate::ir::proof_ir::{Predicate, QuantifierType, RefinedTypeDecl};
3226 use std::collections::{HashMap, HashSet};
3227
3228 let entry_natural = TypeDef::Product {
3229 name: "Natural".to_string(),
3230 fields: vec![("value".to_string(), "Int".to_string())],
3231 line: 1,
3232 };
3233 let module = ModuleInfo {
3234 prefix: "Mod".to_string(),
3235 depends: Vec::new(),
3236 type_defs: vec![TypeDef::Product {
3237 name: "Natural".to_string(),
3238 fields: vec![("value".to_string(), "Int".to_string())],
3239 line: 1,
3240 }],
3241 fn_defs: Vec::new(),
3242 verify_laws: Vec::new(),
3243 analysis: None,
3244 };
3245
3246 let make_decl = |param: &str, witness: &str| RefinedTypeDecl {
3247 name: "Natural".to_string(),
3248 carrier_type: "Int".to_string(),
3249 carrier_field: "value".to_string(),
3250 predicate_param: param.to_string(),
3251 invariant: Predicate {
3252 free_vars: vec![(param.to_string(), QuantifierType::Plain("Int".to_string()))],
3253 expr: crate::ast::Spanned::bare(crate::ir::hir::ResolvedExpr::Literal(
3254 Literal::Bool(true),
3255 )),
3256 },
3257 witness: Some(witness.to_string()),
3258 interval: None,
3259 op_classes: Vec::new(),
3260 };
3261
3262 let items = vec![TopLevel::TypeDef(entry_natural)];
3263 let modules = vec![module];
3264 let symbol_table = crate::ir::SymbolTable::build(&items, &modules);
3265 let entry_id = symbol_table
3266 .type_id_of(&crate::ir::TypeKey::entry("Natural"))
3267 .expect("entry Natural id");
3268 let mod_id = symbol_table
3269 .type_id_of(&crate::ir::TypeKey::in_module("Mod", "Natural"))
3270 .expect("Mod.Natural id");
3271
3272 let mut ctx = CodegenContext {
3273 items,
3274 type_defs: Vec::new(),
3275 fn_defs: Vec::new(),
3276 project_name: "scope-test".to_string(),
3277 modules,
3278 module_prefixes: HashSet::new(),
3279 #[cfg(feature = "runtime")]
3280 policy: None,
3281 emit_replay_runtime: false,
3282 runtime_policy_from_env: false,
3283 guest_entry: None,
3284 emit_self_host_support: false,
3285 extra_fn_defs: Vec::new(),
3286 mutual_tco_members: HashSet::new(),
3287 recursive_fns: HashSet::new(),
3288 buffer_build_sinks: HashMap::new(),
3289 buffer_fusion_sites: Vec::new(),
3290 synthesized_buffered_fns: Vec::new(),
3291 proof_ir: crate::ir::ProofIR::default(),
3292 symbol_table,
3293 resolved_fn_defs: Vec::new(),
3294 resolved_module_fn_defs: Vec::new(),
3295 current_module_scope: std::cell::RefCell::new(None),
3296 resolved_program: crate::codegen::program_view::ResolvedProgramView::default(),
3297 program_shape: None,
3298 mir_program: None,
3299 bare_i64: Default::default(),
3300 discovered_lemmas: Vec::new(),
3301 sample_expected: std::collections::HashMap::new(),
3302 allow_mathlib: false,
3303 hand_proofs: Default::default(),
3304 };
3305 ctx.proof_ir
3306 .refined_types
3307 .insert(entry_id, make_decl("entry_n", "0"));
3308 ctx.proof_ir
3309 .refined_types
3310 .insert(mod_id, make_decl("mod_n", "10"));
3311
3312 let from_module = find_refined_type_scoped(&ctx, "Natural", Some("Mod"))
3313 .expect("Mod-scoped Natural lookup");
3314 assert_eq!(
3315 from_module.predicate_param, "mod_n",
3316 "scope=Some(\"Mod\") + bare `Natural` must resolve to Mod.Natural, \
3317 not entry's bare-keyed slot"
3318 );
3319
3320 let from_entry =
3322 find_refined_type_scoped(&ctx, "Natural", None).expect("entry Natural lookup");
3323 assert_eq!(from_entry.predicate_param, "entry_n");
3324 }
3325}