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 predicate: &'a Spanned<Expr>,
40}
41
42pub fn refinement_info_for<'a>(
53 type_name: &str,
54 inputs: &crate::codegen::proof_lower::ProofLowerInputs<'a>,
55) -> Option<RefinementInfo<'a>> {
56 refinement_info_for_walk(type_name, inputs, None)
57}
58
59pub fn refinement_info_for_in_scope<'a>(
72 type_name: &str,
73 inputs: &crate::codegen::proof_lower::ProofLowerInputs<'a>,
74 scope: Option<&str>,
75) -> Option<RefinementInfo<'a>> {
76 refinement_info_for_walk(type_name, inputs, Some(scope))
77}
78
79fn refinement_info_for_walk<'a>(
80 type_name: &str,
81 inputs: &crate::codegen::proof_lower::ProofLowerInputs<'a>,
82 scope_filter: Option<Option<&str>>,
86) -> Option<RefinementInfo<'a>> {
87 if let Some(shape) = inputs.program_shape
94 && let Some(info) = refinement_info_from_shape(shape, inputs, type_name, scope_filter)
95 {
96 return Some(info);
97 }
98 refinement_info_for_walk_legacy(type_name, inputs, scope_filter)
99}
100
101fn refinement_info_from_shape<'a>(
108 shape: &crate::analysis::shape::ProgramShape,
109 inputs: &crate::codegen::proof_lower::ProofLowerInputs<'a>,
110 type_name: &str,
111 scope_filter: Option<Option<&str>>,
112) -> Option<RefinementInfo<'a>> {
113 use crate::analysis::shape::ModulePattern;
114
115 let pattern_match = shape.patterns.iter().find_map(|p| {
116 let ModulePattern::RefinementSmartConstructor {
117 scope,
118 type_name: ptn,
119 constructor_fn,
120 ..
121 } = p
122 else {
123 return None;
124 };
125 if ptn != type_name {
126 return None;
127 }
128 let scope_ok = match (scope_filter, scope.as_deref()) {
129 (None, _) => true,
130 (Some(None), None) => true,
131 (Some(None), Some(_)) => false,
132 (Some(Some(want)), Some(have)) => want == have,
133 (Some(Some(_)), None) => false,
134 };
135 if !scope_ok {
136 return None;
137 }
138 Some((scope.clone(), constructor_fn.clone()))
139 })?;
140
141 let (scope, constructor_fn) = pattern_match;
142
143 let (carrier_field, carrier_type) = match scope.as_deref() {
144 None => inputs.entry_items.iter().find_map(|i| match i {
145 TopLevel::TypeDef(TypeDef::Product { name, fields, .. })
146 if name == type_name && fields.len() == 1 =>
147 {
148 let (fname, ftype) = &fields[0];
149 Some((fname.as_str(), ftype.as_str()))
150 }
151 _ => None,
152 }),
153 Some(prefix) => inputs
154 .dep_modules
155 .iter()
156 .find(|m| m.prefix == prefix)
157 .and_then(|m| {
158 m.type_defs.iter().find_map(|td| match td {
159 TypeDef::Product { name, fields, .. }
160 if name == type_name && fields.len() == 1 =>
161 {
162 let (fname, ftype) = &fields[0];
163 Some((fname.as_str(), ftype.as_str()))
164 }
165 _ => None,
166 })
167 }),
168 }?;
169
170 let constructor_fd: &'a FnDef = match scope.as_deref() {
171 None => inputs.entry_items.iter().find_map(|i| match i {
172 TopLevel::FnDef(fd) if fd.name == constructor_fn => Some(fd),
173 _ => None,
174 }),
175 Some(prefix) => inputs
176 .dep_modules
177 .iter()
178 .find(|m| m.prefix == prefix)
179 .and_then(|m| m.fn_defs.iter().find(|fd| fd.name == constructor_fn)),
180 }?;
181
182 let (param_name, _) = constructor_fd.params.first()?;
183 let stmts = constructor_fd.body.stmts();
184 let Stmt::Expr(body_expr) = stmts.first()? else {
185 return None;
186 };
187 let Expr::Match { subject, .. } = &body_expr.node else {
188 return None;
189 };
190
191 Some(RefinementInfo {
192 carrier_type,
193 carrier_field,
194 param_name,
195 predicate: subject,
196 })
197}
198
199fn refinement_info_for_walk_legacy<'a>(
200 type_name: &str,
201 inputs: &crate::codegen::proof_lower::ProofLowerInputs<'a>,
202 scope_filter: Option<Option<&str>>,
203) -> Option<RefinementInfo<'a>> {
204 let entry_only = matches!(scope_filter, Some(None));
205 let module_only_prefix = match scope_filter {
206 Some(Some(p)) => Some(p),
207 _ => None,
208 };
209 let allow_entry = scope_filter.is_none() || entry_only;
210 let entry_typedefs: Vec<&'a TypeDef> = if allow_entry {
211 inputs
212 .entry_items
213 .iter()
214 .filter_map(|item| match item {
215 TopLevel::TypeDef(td) => Some(td),
216 _ => None,
217 })
218 .collect()
219 } else {
220 Vec::new()
221 };
222 let module_typedefs: Vec<&'a TypeDef> = inputs
223 .dep_modules
224 .iter()
225 .filter(|m| match (scope_filter, module_only_prefix) {
226 (None, _) => true,
227 (Some(None), _) => false,
228 (Some(Some(_)), Some(p)) => m.prefix == p,
229 _ => false,
230 })
231 .flat_map(|m| m.type_defs.iter())
232 .collect();
233 let (carrier_field, carrier_type) = entry_typedefs
234 .into_iter()
235 .chain(module_typedefs)
236 .find_map(|td| match td {
237 TypeDef::Product { name, fields, .. } if name == type_name && fields.len() == 1 => {
238 let (fname, ftype) = &fields[0];
239 Some((fname.as_str(), ftype.as_str()))
240 }
241 _ => None,
242 })?;
243
244 let entry_fns: Vec<&'a FnDef> = if allow_entry {
245 inputs
246 .entry_items
247 .iter()
248 .filter_map(|item| match item {
249 TopLevel::FnDef(fd) => Some(fd),
250 _ => None,
251 })
252 .collect()
253 } else {
254 Vec::new()
255 };
256 let module_fns: Vec<&'a FnDef> = inputs
257 .dep_modules
258 .iter()
259 .filter(|m| match (scope_filter, module_only_prefix) {
260 (None, _) => true,
261 (Some(None), _) => false,
262 (Some(Some(_)), Some(p)) => m.prefix == p,
263 _ => false,
264 })
265 .flat_map(|m| m.fn_defs.iter())
266 .collect();
267 for fd in entry_fns.into_iter().chain(module_fns) {
268 if !fd.return_type.starts_with("Result<") {
269 continue;
270 }
271 if !fd.return_type[7..].starts_with(type_name) {
272 continue;
273 }
274 if fd.params.len() != 1 {
275 continue;
276 }
277 let (param_name, _) = &fd.params[0];
278 let stmts = fd.body.stmts();
279 if stmts.len() != 1 {
280 continue;
281 }
282 let Stmt::Expr(body_expr) = &stmts[0] else {
283 continue;
284 };
285 let Expr::Match { subject, arms } = &body_expr.node else {
286 continue;
287 };
288 if !is_bool_ok_err_match(arms, type_name, carrier_field, param_name) {
289 continue;
290 }
291 return Some(RefinementInfo {
292 carrier_type,
293 carrier_field,
294 param_name,
295 predicate: subject,
296 });
297 }
298 None
299}
300
301pub fn is_refinement_bool_ok_err_match(
307 arms: &[MatchArm],
308 type_name: &str,
309 carrier_field: &str,
310 param_name: &str,
311) -> bool {
312 is_bool_ok_err_match(arms, type_name, carrier_field, param_name)
313}
314
315fn is_bool_ok_err_match(
316 arms: &[MatchArm],
317 type_name: &str,
318 carrier_field: &str,
319 param_name: &str,
320) -> bool {
321 if arms.len() != 2 {
322 return false;
323 }
324 let mut true_ok = false;
325 let mut false_err = false;
326 for arm in arms {
327 match &arm.pattern {
328 Pattern::Literal(Literal::Bool(true)) => {
329 if is_ok_constructor_with_identity(&arm.body, type_name, carrier_field, param_name)
330 {
331 true_ok = true;
332 }
333 }
334 Pattern::Literal(Literal::Bool(false)) => {
335 if is_err_constructor(&arm.body) {
336 false_err = true;
337 }
338 }
339 _ => return false,
340 }
341 }
342 true_ok && false_err
343}
344
345fn is_ok_constructor_with_identity(
346 expr: &Spanned<Expr>,
347 type_name: &str,
348 carrier_field: &str,
349 param_name: &str,
350) -> bool {
351 let (ctor_name, ctor_arg_node) = match &expr.node {
353 Expr::Constructor(name, Some(arg)) => (name.clone(), &arg.node),
354 Expr::FnCall(callee, args) if args.len() == 1 => {
355 let Some(name) = expr_to_dotted_name(&callee.node) else {
356 return false;
357 };
358 (name, &args[0].node)
359 }
360 _ => return false,
361 };
362 if ctor_name != "Result.Ok" {
363 return false;
364 }
365 let (t, fields) = match ctor_arg_node {
366 Expr::RecordCreate {
367 type_name: t,
368 fields,
369 } => (t.as_str(), fields),
370 _ => return false,
371 };
372 if t != type_name || fields.len() != 1 {
373 return false;
374 }
375 let (fname, fvalue) = &fields[0];
376 if fname != carrier_field {
377 return false;
378 }
379 match &fvalue.node {
384 Expr::Ident(name) | Expr::Resolved { name, .. } => name == param_name,
385 _ => false,
386 }
387}
388
389pub fn refinement_lift_for_given(
399 given_name: &str,
400 given_type: &str,
401 lhs: &Spanned<Expr>,
402 rhs: &Spanned<Expr>,
403 ctx: &CodegenContext,
404) -> Option<String> {
405 if given_type == "Float" {
413 return None;
414 }
415 let mut result: Option<String> = None;
423 search_refinement_wrapper(lhs, given_name, given_type, ctx, &mut result);
424 search_refinement_wrapper(rhs, given_name, given_type, ctx, &mut result);
425 result
426}
427
428fn search_refinement_wrapper(
429 expr: &Spanned<Expr>,
430 given_name: &str,
431 given_type: &str,
432 ctx: &CodegenContext,
433 result: &mut Option<String>,
434) {
435 if result.is_some() {
436 return;
437 }
438 match &expr.node {
439 Expr::RecordCreate { type_name, fields } if fields.len() == 1 => {
440 let (_, fvalue) = &fields[0];
441 let matches_var = matches!(
442 &fvalue.node,
443 Expr::Ident(n) | Expr::Resolved { name: n, .. } if n == given_name
444 );
445 if matches_var
446 && let Some((canonical_key, decl)) = find_refined_type_with_key(ctx, type_name)
447 && decl.carrier_type == given_type
448 {
449 *result = Some(canonical_key);
450 return;
451 }
452 for (_, v) in fields {
453 search_refinement_wrapper(v, given_name, given_type, ctx, result);
454 }
455 }
456 Expr::FnCall(callee, args) => {
457 search_refinement_wrapper(callee, given_name, given_type, ctx, result);
458 for a in args {
459 search_refinement_wrapper(a, given_name, given_type, ctx, result);
460 }
461 }
462 Expr::BinOp(_, l, r) => {
463 search_refinement_wrapper(l, given_name, given_type, ctx, result);
464 search_refinement_wrapper(r, given_name, given_type, ctx, result);
465 }
466 Expr::Attr(o, _) => search_refinement_wrapper(o, given_name, given_type, ctx, result),
467 Expr::Neg(i) | Expr::ErrorProp(i) => {
468 search_refinement_wrapper(i, given_name, given_type, ctx, result);
469 }
470 Expr::Match { subject, arms } => {
471 search_refinement_wrapper(subject, given_name, given_type, ctx, result);
472 for arm in arms {
473 search_refinement_wrapper(&arm.body, given_name, given_type, ctx, result);
474 }
475 }
476 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
477 for it in items {
478 search_refinement_wrapper(it, given_name, given_type, ctx, result);
479 }
480 }
481 Expr::Constructor(_, Some(arg)) => {
482 search_refinement_wrapper(arg, given_name, given_type, ctx, result);
483 }
484 _ => {}
485 }
486}
487
488pub fn project_lifted_idents_to_val(
515 expr: &Spanned<Expr>,
516 lifted_vars: &std::collections::HashMap<String, String>,
517) -> Spanned<Expr> {
518 if lifted_vars.is_empty() {
519 return expr.clone();
520 }
521 let new_node = match &expr.node {
522 Expr::BinOp(op, l, r) if is_comparator_binop(*op) => {
523 let l_proj = project_lifted_ident_leaf(l, lifted_vars);
524 let r_proj = project_lifted_ident_leaf(r, lifted_vars);
525 Expr::BinOp(*op, Box::new(l_proj), Box::new(r_proj))
526 }
527 Expr::FnCall(callee, args) => {
528 let name = expr_to_dotted_name(&callee.node);
529 if matches!(name.as_deref(), Some("Bool.and") | Some("Bool.or")) {
530 Expr::FnCall(
531 callee.clone(),
532 args.iter()
533 .map(|a| project_lifted_idents_to_val(a, lifted_vars))
534 .collect(),
535 )
536 } else {
537 return expr.clone();
538 }
539 }
540 _ => return expr.clone(),
541 };
542 Spanned::new(new_node, expr.line)
543}
544
545fn is_comparator_binop(op: crate::ast::BinOp) -> bool {
546 use crate::ast::BinOp::*;
547 matches!(op, Lt | Gt | Lte | Gte | Eq | Neq)
548}
549
550fn project_lifted_ident_leaf(
551 expr: &Spanned<Expr>,
552 lifted_vars: &std::collections::HashMap<String, String>,
553) -> Spanned<Expr> {
554 let target_name = match &expr.node {
555 Expr::Ident(n) | Expr::Resolved { name: n, .. } => n,
556 _ => return expr.clone(),
557 };
558 if lifted_vars.contains_key(target_name) {
559 Spanned::new(
560 Expr::Attr(
561 Box::new(Spanned::new(Expr::Ident(target_name.clone()), expr.line)),
562 "val".to_string(),
563 ),
564 expr.line,
565 )
566 } else {
567 expr.clone()
568 }
569}
570
571pub fn strip_refinement_wrappers(
572 expr: &Spanned<Expr>,
573 lifted_vars: &std::collections::HashMap<String, String>,
574 ctx: &CodegenContext,
575) -> Spanned<Expr> {
576 let new_node = match &expr.node {
577 Expr::RecordCreate { type_name, fields } if fields.len() == 1 => {
578 let (_, fvalue) = &fields[0];
579 let var_name = match &fvalue.node {
580 Expr::Ident(n) | Expr::Resolved { name: n, .. } => Some(n.clone()),
581 _ => None,
582 };
583 let canonical_for_ast = find_refined_type_with_key(ctx, type_name).map(|(k, _)| k);
590 if let Some(name) = var_name
591 && let Some(refined) = lifted_vars.get(&name)
592 && canonical_for_ast.as_deref() == Some(refined.as_str())
593 {
594 return Spanned::new(Expr::Ident(name), expr.line);
595 }
596 let new_fields: Vec<(String, Spanned<Expr>)> = fields
597 .iter()
598 .map(|(n, v)| (n.clone(), strip_refinement_wrappers(v, lifted_vars, ctx)))
599 .collect();
600 Expr::RecordCreate {
601 type_name: type_name.clone(),
602 fields: new_fields,
603 }
604 }
605 Expr::FnCall(callee, args) => Expr::FnCall(
606 Box::new(strip_refinement_wrappers(callee, lifted_vars, ctx)),
607 args.iter()
608 .map(|a| strip_refinement_wrappers(a, lifted_vars, ctx))
609 .collect(),
610 ),
611 Expr::BinOp(op, l, r) => Expr::BinOp(
612 *op,
613 Box::new(strip_refinement_wrappers(l, lifted_vars, ctx)),
614 Box::new(strip_refinement_wrappers(r, lifted_vars, ctx)),
615 ),
616 Expr::Attr(o, f) => Expr::Attr(
617 Box::new(strip_refinement_wrappers(o, lifted_vars, ctx)),
618 f.clone(),
619 ),
620 Expr::Neg(i) => Expr::Neg(Box::new(strip_refinement_wrappers(i, lifted_vars, ctx))),
621 Expr::ErrorProp(i) => {
622 Expr::ErrorProp(Box::new(strip_refinement_wrappers(i, lifted_vars, ctx)))
623 }
624 _ => expr.node.clone(),
625 };
626 Spanned::new(new_node, expr.line)
627}
628
629pub fn swap_comparison_operands_op(op: &crate::ast::BinOp) -> Option<crate::ast::BinOp> {
635 use crate::ast::BinOp::*;
636 match op {
637 Lt => Some(Gt),
638 Gt => Some(Lt),
639 Lte => Some(Gte),
640 Gte => Some(Lte),
641 Eq => Some(Eq),
642 Neq => Some(Neq),
643 _ => None,
644 }
645}
646
647pub fn predicate_syntactic_eq(a: &Spanned<Expr>, b: &Spanned<Expr>) -> bool {
658 match (&a.node, &b.node) {
659 (Expr::BinOp(op_a, la, ra), Expr::BinOp(op_b, lb, rb)) => {
660 if op_a == op_b && predicate_syntactic_eq(la, lb) && predicate_syntactic_eq(ra, rb) {
661 return true;
662 }
663 if let Some(swapped) = swap_comparison_operands_op(op_a)
664 && &swapped == op_b
665 && predicate_syntactic_eq(la, rb)
666 && predicate_syntactic_eq(ra, lb)
667 {
668 return true;
669 }
670 false
671 }
672 _ => a.node == b.node,
673 }
674}
675
676pub fn flatten_bool_and_conjuncts(expr: &Spanned<Expr>) -> Vec<Spanned<Expr>> {
683 if let Expr::FnCall(callee, args) = &expr.node
684 && args.len() == 2
685 && let Some(name) = expr_to_dotted_name(&callee.node)
686 && name == "Bool.and"
687 {
688 let mut out = flatten_bool_and_conjuncts(&args[0]);
689 out.extend(flatten_bool_and_conjuncts(&args[1]));
690 return out;
691 }
692 vec![expr.clone()]
693}
694
695pub fn flatten_bool_and_conjuncts_resolved(
700 expr: &Spanned<crate::ir::hir::ResolvedExpr>,
701) -> Vec<Spanned<crate::ir::hir::ResolvedExpr>> {
702 use crate::ir::hir::{ResolvedCallee, ResolvedExpr};
703 if let ResolvedExpr::Call(ResolvedCallee::Builtin(name), args) = &expr.node
704 && name == "Bool.and"
705 && args.len() == 2
706 {
707 let mut out = flatten_bool_and_conjuncts_resolved(&args[0]);
708 out.extend(flatten_bool_and_conjuncts_resolved(&args[1]));
709 return out;
710 }
711 vec![expr.clone()]
712}
713
714pub fn predicate_syntactic_eq_resolved(
716 a: &Spanned<crate::ir::hir::ResolvedExpr>,
717 b: &Spanned<crate::ir::hir::ResolvedExpr>,
718) -> bool {
719 use crate::ir::hir::ResolvedExpr;
720 match (&a.node, &b.node) {
721 (ResolvedExpr::BinOp(op_a, la, ra), ResolvedExpr::BinOp(op_b, lb, rb)) => {
722 if op_a == op_b
723 && predicate_syntactic_eq_resolved(la, lb)
724 && predicate_syntactic_eq_resolved(ra, rb)
725 {
726 return true;
727 }
728 if let Some(swapped) = swap_comparison_operands_op(op_a)
729 && &swapped == op_b
730 && predicate_syntactic_eq_resolved(la, rb)
731 && predicate_syntactic_eq_resolved(ra, lb)
732 {
733 return true;
734 }
735 false
736 }
737 _ => a.node == b.node,
738 }
739}
740
741pub fn substitute_ident_in_resolved_expr(
749 expr: &Spanned<crate::ir::hir::ResolvedExpr>,
750 from: &str,
751 to: &str,
752) -> Spanned<crate::ir::hir::ResolvedExpr> {
753 use crate::ir::hir::{ResolvedExpr, ResolvedMatchArm, ResolvedStrPart};
754 let line = expr.line;
755 let rec = |e: &Spanned<ResolvedExpr>| substitute_ident_in_resolved_expr(e, from, to);
756 let new_node = match &expr.node {
757 ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. } if name == from => {
758 ResolvedExpr::Ident(to.to_string())
759 }
760 ResolvedExpr::Literal(_) | ResolvedExpr::Ident(_) | ResolvedExpr::Resolved { .. } => {
761 return expr.clone();
762 }
763 ResolvedExpr::Attr(obj, field) => ResolvedExpr::Attr(Box::new(rec(obj)), field.clone()),
764 ResolvedExpr::Call(callee, args) => {
765 ResolvedExpr::Call(callee.clone(), args.iter().map(&rec).collect())
766 }
767 ResolvedExpr::BinOp(op, left, right) => {
768 ResolvedExpr::BinOp(*op, Box::new(rec(left)), Box::new(rec(right)))
769 }
770 ResolvedExpr::Neg(inner) => ResolvedExpr::Neg(Box::new(rec(inner))),
771 ResolvedExpr::Match { subject, arms } => ResolvedExpr::Match {
772 subject: Box::new(rec(subject)),
773 arms: arms
774 .iter()
775 .map(|arm| ResolvedMatchArm {
776 pattern: arm.pattern.clone(),
777 body: Box::new(rec(&arm.body)),
778 binding_slots: std::sync::OnceLock::new(),
779 })
780 .collect(),
781 },
782 ResolvedExpr::Ctor(ctor, args) => {
783 ResolvedExpr::Ctor(ctor.clone(), args.iter().map(&rec).collect())
784 }
785 ResolvedExpr::ErrorProp(inner) => ResolvedExpr::ErrorProp(Box::new(rec(inner))),
786 ResolvedExpr::InterpolatedStr(parts) => ResolvedExpr::InterpolatedStr(
787 parts
788 .iter()
789 .map(|p| match p {
790 ResolvedStrPart::Literal(_) => p.clone(),
791 ResolvedStrPart::Parsed(inner) => ResolvedStrPart::Parsed(Box::new(rec(inner))),
792 })
793 .collect(),
794 ),
795 ResolvedExpr::List(items) => ResolvedExpr::List(items.iter().map(&rec).collect()),
796 ResolvedExpr::Tuple(items) => ResolvedExpr::Tuple(items.iter().map(&rec).collect()),
797 ResolvedExpr::IndependentProduct(items, flag) => {
798 ResolvedExpr::IndependentProduct(items.iter().map(&rec).collect(), *flag)
799 }
800 ResolvedExpr::MapLiteral(entries) => {
801 ResolvedExpr::MapLiteral(entries.iter().map(|(k, v)| (rec(k), rec(v))).collect())
802 }
803 ResolvedExpr::RecordCreate {
804 type_id,
805 type_name,
806 fields,
807 } => ResolvedExpr::RecordCreate {
808 type_id: *type_id,
809 type_name: type_name.clone(),
810 fields: fields.iter().map(|(n, v)| (n.clone(), rec(v))).collect(),
811 },
812 ResolvedExpr::RecordUpdate {
813 type_id,
814 type_name,
815 base,
816 updates,
817 } => ResolvedExpr::RecordUpdate {
818 type_id: *type_id,
819 type_name: type_name.clone(),
820 base: Box::new(rec(base)),
821 updates: updates.iter().map(|(n, v)| (n.clone(), rec(v))).collect(),
822 },
823 ResolvedExpr::TailCall { target, args } => ResolvedExpr::TailCall {
824 target: *target,
825 args: args.iter().map(&rec).collect(),
826 },
827 };
828 Spanned::new(new_node, line)
829}
830
831pub fn substitute_ident_in_expr(expr: &Spanned<Expr>, from: &str, to: &str) -> Spanned<Expr> {
840 use crate::ast::{MatchArm, StrPart, TailCallData};
841 let line = expr.line;
842 let new_node = match &expr.node {
843 Expr::Ident(name) | Expr::Resolved { name, .. } if name == from => {
844 Expr::Ident(to.to_string())
845 }
846 Expr::Literal(_) | Expr::Ident(_) | Expr::Resolved { .. } => return expr.clone(),
847 Expr::Attr(obj, field) => Expr::Attr(
848 Box::new(substitute_ident_in_expr(obj, from, to)),
849 field.clone(),
850 ),
851 Expr::FnCall(callee, args) => Expr::FnCall(
852 Box::new(substitute_ident_in_expr(callee, from, to)),
853 args.iter()
854 .map(|a| substitute_ident_in_expr(a, from, to))
855 .collect(),
856 ),
857 Expr::BinOp(op, left, right) => Expr::BinOp(
858 *op,
859 Box::new(substitute_ident_in_expr(left, from, to)),
860 Box::new(substitute_ident_in_expr(right, from, to)),
861 ),
862 Expr::Neg(inner) => Expr::Neg(Box::new(substitute_ident_in_expr(inner, from, to))),
863 Expr::Match { subject, arms } => Expr::Match {
864 subject: Box::new(substitute_ident_in_expr(subject, from, to)),
865 arms: arms
866 .iter()
867 .map(|arm| MatchArm {
868 pattern: arm.pattern.clone(),
869 body: Box::new(substitute_ident_in_expr(&arm.body, from, to)),
870 binding_slots: std::sync::OnceLock::new(),
871 })
872 .collect(),
873 },
874 Expr::Constructor(name, arg) => Expr::Constructor(
875 name.clone(),
876 arg.as_ref()
877 .map(|inner| Box::new(substitute_ident_in_expr(inner, from, to))),
878 ),
879 Expr::ErrorProp(inner) => {
880 Expr::ErrorProp(Box::new(substitute_ident_in_expr(inner, from, to)))
881 }
882 Expr::InterpolatedStr(parts) => Expr::InterpolatedStr(
883 parts
884 .iter()
885 .map(|part| match part {
886 StrPart::Literal(_) => part.clone(),
887 StrPart::Parsed(inner) => {
888 StrPart::Parsed(Box::new(substitute_ident_in_expr(inner, from, to)))
889 }
890 })
891 .collect(),
892 ),
893 Expr::List(items) => Expr::List(
894 items
895 .iter()
896 .map(|item| substitute_ident_in_expr(item, from, to))
897 .collect(),
898 ),
899 Expr::Tuple(items) => Expr::Tuple(
900 items
901 .iter()
902 .map(|item| substitute_ident_in_expr(item, from, to))
903 .collect(),
904 ),
905 Expr::IndependentProduct(items, flag) => Expr::IndependentProduct(
906 items
907 .iter()
908 .map(|item| substitute_ident_in_expr(item, from, to))
909 .collect(),
910 *flag,
911 ),
912 Expr::MapLiteral(entries) => Expr::MapLiteral(
913 entries
914 .iter()
915 .map(|(k, v)| {
916 (
917 substitute_ident_in_expr(k, from, to),
918 substitute_ident_in_expr(v, from, to),
919 )
920 })
921 .collect(),
922 ),
923 Expr::RecordCreate { type_name, fields } => Expr::RecordCreate {
924 type_name: type_name.clone(),
925 fields: fields
926 .iter()
927 .map(|(n, v)| (n.clone(), substitute_ident_in_expr(v, from, to)))
928 .collect(),
929 },
930 Expr::RecordUpdate {
931 type_name,
932 base,
933 updates,
934 } => Expr::RecordUpdate {
935 type_name: type_name.clone(),
936 base: Box::new(substitute_ident_in_expr(base, from, to)),
937 updates: updates
938 .iter()
939 .map(|(n, v)| (n.clone(), substitute_ident_in_expr(v, from, to)))
940 .collect(),
941 },
942 Expr::TailCall(boxed) => Expr::TailCall(Box::new(TailCallData::new(
943 boxed.target.clone(),
944 boxed
945 .args
946 .iter()
947 .map(|a| substitute_ident_in_expr(a, from, to))
948 .collect(),
949 ))),
950 };
951 Spanned::new(new_node, line)
952}
953
954pub fn when_is_redundant_with_refinement_lifts(
970 when_expr: &Spanned<Expr>,
971 lifted_vars: &std::collections::HashMap<String, String>,
972 ctx: &CodegenContext,
973) -> bool {
974 if lifted_vars.is_empty() {
975 return false;
976 }
977 let resolved_when = ctx.resolve_expr(when_expr, ctx.active_module_scope().as_deref());
985 let when_conjuncts = flatten_bool_and_conjuncts_resolved(&resolved_when);
986 let mut lifted_predicates: Vec<Spanned<crate::ir::hir::ResolvedExpr>> = Vec::new();
1000 for (given_name, refined_type) in lifted_vars {
1001 let Some(decl) = find_refined_type(ctx, refined_type) else {
1002 return false;
1003 };
1004 let substituted = substitute_ident_in_resolved_expr(
1005 &decl.invariant.expr,
1006 decl.predicate_param.as_str(),
1007 given_name,
1008 );
1009 lifted_predicates.extend(flatten_bool_and_conjuncts_resolved(&substituted));
1010 }
1011 if when_conjuncts.len() != lifted_predicates.len() {
1012 return false;
1013 }
1014 let mut matched = vec![false; lifted_predicates.len()];
1015 for wc in &when_conjuncts {
1016 let Some(idx) = (0..lifted_predicates.len())
1017 .find(|&i| !matched[i] && predicate_syntactic_eq_resolved(wc, &lifted_predicates[i]))
1018 else {
1019 return false;
1020 };
1021 matched[idx] = true;
1022 }
1023 true
1024}
1025
1026fn is_err_constructor(expr: &Spanned<Expr>) -> bool {
1027 match &expr.node {
1028 Expr::Constructor(name, Some(_)) => name == "Result.Err",
1029 Expr::FnCall(callee, args) if args.len() == 1 => {
1030 matches!(
1031 expr_to_dotted_name(&callee.node),
1032 Some(name) if name == "Result.Err"
1033 )
1034 }
1035 _ => false,
1036 }
1037}
1038
1039pub fn is_pure_fn(fd: &FnDef) -> bool {
1045 fd.effects.is_empty() && fd.name != "main"
1046}
1047
1048pub fn find_refined_type<'a>(
1075 ctx: &'a CodegenContext,
1076 name: &str,
1077) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1078 find_refined_type_with_key_scoped(ctx, name, None).map(|(_, d)| d)
1079}
1080
1081pub fn backend_named_type_key(ctx: &CodegenContext, ty: &crate::types::Type) -> Option<String> {
1110 let crate::types::Type::Named { id, name } = ty else {
1111 return None;
1112 };
1113 if let Some(type_id) = id {
1114 return Some(ctx.symbol_table.type_entry(*type_id).key.canonical());
1115 }
1116 Some(name.clone())
1117}
1118
1119pub fn backend_type_def_key(ctx: &CodegenContext, td: &crate::ast::TypeDef) -> String {
1134 let key = type_key_for_decl(ctx, td);
1135 if ctx.symbol_table.type_id_of(&key).is_some() {
1136 key.canonical()
1137 } else {
1138 type_def_name(td).to_string()
1139 }
1140}
1141
1142pub fn find_refined_type_by_id(
1158 ctx: &CodegenContext,
1159 type_id: crate::ir::TypeId,
1160) -> Option<&crate::ir::proof_ir::RefinedTypeDecl> {
1161 ctx.proof_ir.refined_types.get(&type_id)
1162}
1163
1164pub fn find_refined_type_for_named<'a>(
1173 ctx: &'a CodegenContext,
1174 named: &crate::types::Type,
1175) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1176 let crate::types::Type::Named { id, name } = named else {
1177 return None;
1178 };
1179 match id {
1180 Some(type_id) => find_refined_type_by_id(ctx, *type_id),
1181 None => find_refined_type(ctx, name),
1182 }
1183}
1184
1185pub fn find_fn_contract_scoped<'a>(
1197 ctx: &'a CodegenContext,
1198 name: &str,
1199 scope: Option<&str>,
1200) -> Option<&'a crate::ir::proof_ir::FnContract> {
1201 let symbols = &ctx.symbol_table;
1209 let bare = name.rsplit('.').next().unwrap_or(name);
1210 let name_is_already_qualified = name.contains('.');
1211 let try_key = |key: crate::ir::FnKey| -> Option<&'a crate::ir::proof_ir::FnContract> {
1212 let id = symbols.fn_id_of(&key)?;
1213 ctx.proof_ir.fn_contracts.get(&id)
1214 };
1215 if let Some(prefix) = scope
1216 && !name_is_already_qualified
1217 && let Some(c) = try_key(crate::ir::FnKey::in_module(prefix.to_string(), bare))
1218 {
1219 return Some(c);
1220 }
1221 let direct_key =
1222 if name_is_already_qualified && let Some((prefix, bare_part)) = name.rsplit_once('.') {
1223 crate::ir::FnKey::in_module(prefix.to_string(), bare_part)
1224 } else {
1225 crate::ir::FnKey::entry(name)
1226 };
1227 if let Some(c) = try_key(direct_key) {
1228 return Some(c);
1229 }
1230 for m in &ctx.modules {
1231 for fd in &m.fn_defs {
1232 if fd.name == bare
1233 && let Some(c) = try_key(crate::ir::FnKey::in_module(m.prefix.clone(), bare))
1234 {
1235 return Some(c);
1236 }
1237 }
1238 }
1239 None
1240}
1241
1242pub fn fn_contract_exists_scoped(ctx: &CodegenContext, name: &str, scope: Option<&str>) -> bool {
1244 find_fn_contract_scoped(ctx, name, scope).is_some()
1245}
1246
1247pub fn fn_key_for_decl(ctx: &CodegenContext, fd: &FnDef) -> crate::ir::FnKey {
1255 match fn_owning_scope_for(ctx, fd) {
1256 Some(prefix) => crate::ir::FnKey::in_module(prefix.to_string(), fd.name.clone()),
1257 None => crate::ir::FnKey::entry(fd.name.clone()),
1258 }
1259}
1260
1261pub fn type_key_for_decl(ctx: &CodegenContext, td: &TypeDef) -> crate::ir::TypeKey {
1265 for m in &ctx.modules {
1266 for t in &m.type_defs {
1267 if std::ptr::eq(t, td) {
1268 return crate::ir::TypeKey::in_module(m.prefix.clone(), type_def_name(td));
1269 }
1270 }
1271 }
1272 crate::ir::TypeKey::entry(type_def_name(td))
1273}
1274
1275pub fn type_key_for_name(
1282 ctx: &CodegenContext,
1283 name: &str,
1284 scope: Option<&str>,
1285) -> crate::ir::TypeKey {
1286 let bare = name.rsplit('.').next().unwrap_or(name);
1287 let name_is_qualified = name.contains('.');
1288 if let Some(prefix) = scope
1289 && !name_is_qualified
1290 {
1291 for m in &ctx.modules {
1292 if m.prefix == prefix && m.type_defs.iter().any(|td| type_def_name(td) == bare) {
1293 return crate::ir::TypeKey::in_module(prefix.to_string(), bare);
1294 }
1295 }
1296 }
1297 if !name_is_qualified && ctx.type_defs.iter().any(|td| type_def_name(td) == bare) {
1300 return crate::ir::TypeKey::entry(bare);
1301 }
1302 if name_is_qualified
1303 && let Some((prefix, bare_part)) = name.rsplit_once('.')
1304 && ctx.modules.iter().any(|m| m.prefix == prefix)
1305 {
1306 return crate::ir::TypeKey::in_module(prefix.to_string(), bare_part);
1307 }
1308 for m in &ctx.modules {
1309 if m.type_defs.iter().any(|td| type_def_name(td) == bare) {
1310 return crate::ir::TypeKey::in_module(m.prefix.clone(), bare);
1311 }
1312 }
1313 crate::ir::TypeKey::entry(name)
1318}
1319
1320pub fn fn_owning_scope_for<'a>(ctx: &'a CodegenContext, fd: &FnDef) -> Option<&'a str> {
1330 for m in &ctx.modules {
1331 for f in &m.fn_defs {
1332 if std::ptr::eq(f, fd) {
1333 return Some(m.prefix.as_str());
1334 }
1335 }
1336 }
1337 None
1338}
1339
1340pub fn find_fn_contract_for_fn<'a>(
1346 ctx: &'a CodegenContext,
1347 fd: &FnDef,
1348) -> Option<&'a crate::ir::proof_ir::FnContract> {
1349 let symbols = &ctx.symbol_table;
1355 let fn_key = fn_key_for_decl(ctx, fd);
1356 let fn_id = symbols.fn_id_of(&fn_key)?;
1357 ctx.proof_ir.fn_contracts.get(&fn_id)
1358}
1359
1360pub fn fn_contract_exists_for_fn(ctx: &CodegenContext, fd: &FnDef) -> bool {
1362 find_fn_contract_for_fn(ctx, fd).is_some()
1363}
1364
1365pub fn fn_id_for_decl(ctx: &CodegenContext, fd: &FnDef) -> Option<crate::ir::FnId> {
1371 let fn_key = fn_key_for_decl(ctx, fd);
1372 ctx.symbol_table.fn_id_of(&fn_key)
1373}
1374
1375pub fn fn_id_for_dotted_name(ctx: &CodegenContext, dotted: &str) -> Option<crate::ir::FnId> {
1381 let key = if let Some((prefix, bare)) = dotted.rsplit_once('.') {
1382 crate::ir::FnKey::in_module(prefix.to_string(), bare)
1383 } else {
1384 crate::ir::FnKey::entry(dotted)
1385 };
1386 ctx.symbol_table.fn_id_of(&key)
1387}
1388
1389pub fn find_refined_type_with_key<'a>(
1394 ctx: &'a CodegenContext,
1395 name: &str,
1396) -> Option<(String, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1397 find_refined_type_with_key_scoped(ctx, name, None)
1398}
1399
1400pub fn find_refined_type_scoped<'a>(
1418 ctx: &'a CodegenContext,
1419 name: &str,
1420 scope: Option<&str>,
1421) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1422 find_refined_type_with_key_scoped(ctx, name, scope).map(|(_, d)| d)
1423}
1424
1425pub fn find_refined_type_with_key_scoped<'a>(
1440 ctx: &'a CodegenContext,
1441 name: &str,
1442 scope: Option<&str>,
1443) -> Option<(String, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1444 let symbols = &ctx.symbol_table;
1445 let bare = name.rsplit('.').next().unwrap_or(name);
1446 let name_is_already_qualified = name.contains('.');
1447 let try_key =
1448 |key: crate::ir::TypeKey| -> Option<(String, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1449 let id = symbols.type_id_of(&key)?;
1450 let decl = ctx.proof_ir.refined_types.get(&id)?;
1451 Some((key.canonical(), decl))
1452 };
1453 if let Some(prefix) = scope
1454 && !name_is_already_qualified
1455 && let Some(hit) = try_key(crate::ir::TypeKey::in_module(prefix.to_string(), bare))
1456 {
1457 return Some(hit);
1458 }
1459 let direct_key =
1460 if name_is_already_qualified && let Some((prefix, bare_part)) = name.rsplit_once('.') {
1461 crate::ir::TypeKey::in_module(prefix.to_string(), bare_part)
1462 } else {
1463 crate::ir::TypeKey::entry(name)
1464 };
1465 if let Some(hit) = try_key(direct_key) {
1466 return Some(hit);
1467 }
1468 for m in &ctx.modules {
1469 for td in &m.type_defs {
1470 if type_def_name(td) == bare
1471 && let Some(hit) = try_key(crate::ir::TypeKey::in_module(m.prefix.clone(), bare))
1472 {
1473 return Some(hit);
1474 }
1475 }
1476 }
1477 None
1478}
1479
1480pub fn resolve_refined_type_in<'a>(
1486 refined_types: &'a std::collections::HashMap<
1487 crate::ir::TypeId,
1488 crate::ir::proof_ir::RefinedTypeDecl,
1489 >,
1490 symbols: &crate::ir::SymbolTable,
1491 modules: &[crate::codegen::ModuleInfo],
1492 name: &str,
1493) -> Option<&'a crate::ir::proof_ir::RefinedTypeDecl> {
1494 resolve_refined_type_in_with_key(refined_types, symbols, modules, name).map(|(_, d)| d)
1495}
1496
1497pub fn resolve_refined_type_in_with_key<'a>(
1503 refined_types: &'a std::collections::HashMap<
1504 crate::ir::TypeId,
1505 crate::ir::proof_ir::RefinedTypeDecl,
1506 >,
1507 symbols: &crate::ir::SymbolTable,
1508 modules: &[crate::codegen::ModuleInfo],
1509 name: &str,
1510) -> Option<(crate::ir::TypeId, &'a crate::ir::proof_ir::RefinedTypeDecl)> {
1511 let bare = name.rsplit('.').next().unwrap_or(name);
1512 let name_is_already_qualified = name.contains('.');
1513 let direct_key =
1514 if name_is_already_qualified && let Some((prefix, bare_part)) = name.rsplit_once('.') {
1515 crate::ir::TypeKey::in_module(prefix.to_string(), bare_part)
1516 } else {
1517 crate::ir::TypeKey::entry(name)
1518 };
1519 if let Some(id) = symbols.type_id_of(&direct_key)
1520 && let Some(decl) = refined_types.get(&id)
1521 {
1522 return Some((id, decl));
1523 }
1524 for m in modules {
1525 for td in &m.type_defs {
1526 if type_def_name(td) == bare {
1527 let canonical = crate::ir::TypeKey::in_module(m.prefix.clone(), bare);
1528 if let Some(id) = symbols.type_id_of(&canonical)
1529 && let Some(decl) = refined_types.get(&id)
1530 {
1531 return Some((id, decl));
1532 }
1533 }
1534 }
1535 }
1536 None
1537}
1538
1539pub fn all_givens_are_singletons(law: &crate::ast::VerifyLaw) -> bool {
1548 !law.givens.is_empty()
1549 && law.givens.iter().all(|g| match &g.domain {
1550 VerifyGivenDomain::Explicit(values) => values.len() == 1,
1551 VerifyGivenDomain::IntRange { start, end } => start == end,
1552 })
1553}
1554
1555pub fn unclassified_fn_names(ctx: &CodegenContext) -> HashSet<String> {
1562 ctx.proof_ir
1563 .unclassified_fns
1564 .iter()
1565 .filter_map(|uf| {
1566 let s = &uf.message;
1567 let start = s.find('\'')?;
1568 let rest = &s[start + 1..];
1569 let end = rest.find('\'')?;
1570 Some(rest[..end].to_string())
1571 })
1572 .collect()
1573}
1574
1575pub fn law_calls_unclassified_fn(
1587 law: &crate::ast::VerifyLaw,
1588 unclassified: &HashSet<String>,
1589) -> bool {
1590 if unclassified.is_empty() {
1591 return false;
1592 }
1593 expr_calls_named(&law.lhs, unclassified) || expr_calls_named(&law.rhs, unclassified)
1594}
1595
1596fn expr_calls_named(expr: &Spanned<Expr>, names: &HashSet<String>) -> bool {
1597 match &expr.node {
1598 Expr::FnCall(callee, args) => {
1599 let direct = expr_to_dotted_name(&callee.node)
1610 .map(|n| {
1611 let bare = n.rsplit('.').next().unwrap_or(n.as_str());
1612 names.contains(n.as_str()) || names.contains(bare)
1613 })
1614 .unwrap_or(false);
1615 direct
1616 || expr_calls_named(callee, names)
1617 || args.iter().any(|a| expr_calls_named(a, names))
1618 }
1619 Expr::Attr(inner, _) | Expr::ErrorProp(inner) | Expr::Neg(inner) => {
1620 expr_calls_named(inner, names)
1621 }
1622 Expr::BinOp(_, l, r) => expr_calls_named(l, names) || expr_calls_named(r, names),
1623 Expr::Match { subject, arms } => {
1624 expr_calls_named(subject, names)
1625 || arms.iter().any(|a| expr_calls_named(&a.body, names))
1626 }
1627 Expr::Constructor(_, Some(arg)) => expr_calls_named(arg, names),
1628 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
1629 items.iter().any(|i| expr_calls_named(i, names))
1630 }
1631 Expr::MapLiteral(entries) => entries
1632 .iter()
1633 .any(|(k, v)| expr_calls_named(k, names) || expr_calls_named(v, names)),
1634 Expr::RecordCreate { fields, .. } => fields.iter().any(|(_, v)| expr_calls_named(v, names)),
1635 Expr::RecordUpdate { base, updates, .. } => {
1636 expr_calls_named(base, names) || updates.iter().any(|(_, v)| expr_calls_named(v, names))
1637 }
1638 Expr::InterpolatedStr(parts) => parts.iter().any(|p| match p {
1639 crate::ast::StrPart::Parsed(inner) => expr_calls_named(inner, names),
1640 crate::ast::StrPart::Literal(_) => false,
1641 }),
1642 Expr::TailCall(boxed) => {
1643 let crate::ast::TailCallData { target, args, .. } = boxed.as_ref();
1644 names.contains(target) || args.iter().any(|a| expr_calls_named(a, names))
1645 }
1646 _ => false,
1647 }
1648}
1649
1650pub fn law_rhs_is_independent_of_givens(law: &crate::ast::VerifyLaw) -> bool {
1660 let given_names: HashSet<&str> = law.givens.iter().map(|g| g.name.as_str()).collect();
1661 if given_names.is_empty() {
1662 return true;
1663 }
1664 !expr_references_any_ident(&law.rhs, &given_names)
1665}
1666
1667fn expr_references_any_ident(expr: &Spanned<Expr>, names: &HashSet<&str>) -> bool {
1668 match &expr.node {
1669 Expr::Ident(name) | Expr::Resolved { name, .. } => names.contains(name.as_str()),
1670 Expr::Attr(inner, _) | Expr::ErrorProp(inner) | Expr::Neg(inner) => {
1671 expr_references_any_ident(inner, names)
1672 }
1673 Expr::FnCall(callee, args) => {
1674 expr_references_any_ident(callee, names)
1675 || args.iter().any(|a| expr_references_any_ident(a, names))
1676 }
1677 Expr::BinOp(_, l, r) => {
1678 expr_references_any_ident(l, names) || expr_references_any_ident(r, names)
1679 }
1680 Expr::Match { subject, arms } => {
1681 expr_references_any_ident(subject, names)
1682 || arms
1683 .iter()
1684 .any(|a| expr_references_any_ident(&a.body, names))
1685 }
1686 Expr::Constructor(_, Some(arg)) => expr_references_any_ident(arg, names),
1687 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
1688 items.iter().any(|i| expr_references_any_ident(i, names))
1689 }
1690 Expr::MapLiteral(entries) => entries.iter().any(|(k, v)| {
1691 expr_references_any_ident(k, names) || expr_references_any_ident(v, names)
1692 }),
1693 Expr::RecordCreate { fields, .. } => fields
1694 .iter()
1695 .any(|(_, v)| expr_references_any_ident(v, names)),
1696 Expr::RecordUpdate { base, updates, .. } => {
1697 expr_references_any_ident(base, names)
1698 || updates
1699 .iter()
1700 .any(|(_, v)| expr_references_any_ident(v, names))
1701 }
1702 Expr::InterpolatedStr(parts) => parts.iter().any(|p| match p {
1703 crate::ast::StrPart::Parsed(inner) => expr_references_any_ident(inner, names),
1704 crate::ast::StrPart::Literal(_) => false,
1705 }),
1706 Expr::TailCall(boxed) => {
1707 let crate::ast::TailCallData { args, .. } = boxed.as_ref();
1708 args.iter().any(|a| expr_references_any_ident(a, names))
1709 }
1710 _ => false,
1711 }
1712}
1713
1714pub fn law_lhs_has_trace_projection(expr: &Spanned<Expr>) -> bool {
1735 expr_has_trace_field(expr) && expr_has_trace_api_call(expr)
1736}
1737
1738fn expr_has_trace_field(expr: &Spanned<Expr>) -> bool {
1739 match &expr.node {
1740 Expr::Attr(inner, field) => field == "trace" || expr_has_trace_field(inner),
1741 Expr::FnCall(callee, args) => {
1742 expr_has_trace_field(callee) || args.iter().any(expr_has_trace_field)
1743 }
1744 Expr::BinOp(_, l, r) => expr_has_trace_field(l) || expr_has_trace_field(r),
1745 Expr::Match { subject, arms } => {
1746 expr_has_trace_field(subject) || arms.iter().any(|a| expr_has_trace_field(&a.body))
1747 }
1748 Expr::ErrorProp(inner) => expr_has_trace_field(inner),
1749 Expr::Constructor(_, Some(arg)) => expr_has_trace_field(arg),
1750 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
1751 items.iter().any(expr_has_trace_field)
1752 }
1753 _ => false,
1754 }
1755}
1756
1757const TRACE_API_METHODS: &[&str] = &["event", "group", "branch", "length", "contains", "count"];
1762
1763fn expr_has_trace_api_call(expr: &Spanned<Expr>) -> bool {
1764 match &expr.node {
1765 Expr::FnCall(callee, args) => {
1766 let direct = matches!(
1767 &callee.node,
1768 Expr::Attr(_, method) if TRACE_API_METHODS.contains(&method.as_str())
1769 );
1770 direct || expr_has_trace_api_call(callee) || args.iter().any(expr_has_trace_api_call)
1771 }
1772 Expr::Attr(inner, _) | Expr::ErrorProp(inner) => expr_has_trace_api_call(inner),
1773 Expr::BinOp(_, l, r) => expr_has_trace_api_call(l) || expr_has_trace_api_call(r),
1774 Expr::Match { subject, arms } => {
1775 expr_has_trace_api_call(subject)
1776 || arms.iter().any(|a| expr_has_trace_api_call(&a.body))
1777 }
1778 Expr::Constructor(_, Some(arg)) => expr_has_trace_api_call(arg),
1779 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
1780 items.iter().any(expr_has_trace_api_call)
1781 }
1782 _ => false,
1783 }
1784}
1785
1786pub fn is_recursive_type_def(td: &TypeDef) -> bool {
1789 match td {
1790 TypeDef::Sum { name, variants, .. } => is_recursive_sum(name, variants),
1791 TypeDef::Product { name, fields, .. } => is_recursive_product(name, fields),
1792 }
1793}
1794
1795pub fn type_def_name(td: &TypeDef) -> &str {
1797 match td {
1798 TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
1799 }
1800}
1801
1802pub fn is_recursive_sum(name: &str, variants: &[TypeVariant]) -> bool {
1806 variants
1807 .iter()
1808 .any(|v| v.fields.iter().any(|f| type_ref_contains(f, name)))
1809}
1810
1811pub fn is_recursive_product(name: &str, fields: &[(String, String)]) -> bool {
1813 fields.iter().any(|(_, ty)| type_ref_contains(ty, name))
1814}
1815
1816fn type_ref_contains(annotation: &str, type_name: &str) -> bool {
1817 annotation == type_name
1820 || annotation.contains(&format!("<{}", type_name))
1821 || annotation.contains(&format!("{}>", type_name))
1822 || annotation.contains(&format!(", {}", type_name))
1823 || annotation.contains(&format!("{},", type_name))
1824}
1825
1826pub(crate) fn is_user_type(name: &str, ctx: &CodegenContext) -> bool {
1828 let check_td = |td: &TypeDef| match td {
1829 TypeDef::Sum { name: n, .. } => n == name,
1830 TypeDef::Product { name: n, .. } => n == name,
1831 };
1832 ctx.type_defs.iter().any(check_td)
1833 || ctx.modules.iter().any(|m| m.type_defs.iter().any(check_td))
1834}
1835
1836pub(crate) fn resolve_module_call<'a>(
1839 dotted_name: &'a str,
1840 ctx: &'a CodegenContext,
1841) -> Option<(&'a str, &'a str)> {
1842 let mut best: Option<&str> = None;
1843 for prefix in &ctx.module_prefixes {
1844 let dotted_prefix = format!("{}.", prefix);
1845 if dotted_name.starts_with(&dotted_prefix) && best.is_none_or(|b| prefix.len() > b.len()) {
1846 best = Some(prefix.as_str());
1847 }
1848 }
1849 best.map(|prefix| (prefix, &dotted_name[prefix.len() + 1..]))
1850}
1851
1852pub(crate) fn module_prefix_to_rust_segments(prefix: &str) -> Vec<String> {
1853 prefix.split('.').map(module_segment_to_rust).collect()
1854}
1855
1856pub(crate) fn module_prefix_to_filename(prefix: &str) -> String {
1861 prefix.replace('.', "/")
1862}
1863
1864pub(crate) struct DeclaredEffects {
1876 pub bare_namespaces: HashSet<String>,
1877 pub methods: HashSet<String>,
1878}
1879
1880impl DeclaredEffects {
1881 pub fn includes(&self, c_method: &str) -> bool {
1884 if self.methods.contains(c_method) {
1885 return true;
1886 }
1887 if let Some((ns, _)) = c_method.split_once('.') {
1888 return self.bare_namespaces.contains(ns);
1889 }
1890 false
1891 }
1892}
1893
1894pub(crate) fn collect_declared_effects(ctx: &CodegenContext) -> DeclaredEffects {
1898 let mut bare_namespaces: HashSet<String> = HashSet::new();
1899 let mut methods: HashSet<String> = HashSet::new();
1900 let mut record = |effect: &str| {
1901 if effect.contains('.') {
1902 methods.insert(effect.to_string());
1903 } else {
1904 bare_namespaces.insert(effect.to_string());
1905 }
1906 };
1907 for item in &ctx.items {
1908 if let TopLevel::FnDef(fd) = item {
1909 for eff in &fd.effects {
1910 record(&eff.node);
1911 }
1912 }
1913 }
1914 for module in &ctx.modules {
1915 for fd in &module.fn_defs {
1916 for eff in &fd.effects {
1917 record(&eff.node);
1918 }
1919 }
1920 }
1921 DeclaredEffects {
1922 bare_namespaces,
1923 methods,
1924 }
1925}
1926
1927pub fn entry_basename(ctx: &CodegenContext) -> String {
1935 ctx.items
1936 .iter()
1937 .find_map(|item| match item {
1938 TopLevel::Module(m) => Some(m.name.clone()),
1939 _ => None,
1940 })
1941 .unwrap_or_else(|| {
1942 let mut chars = ctx.project_name.chars();
1943 match chars.next() {
1944 None => String::new(),
1945 Some(c) => c.to_uppercase().chain(chars).collect(),
1946 }
1947 })
1948}
1949
1950pub fn verify_block_counter_key(vb: &crate::ast::VerifyBlock) -> String {
1961 match &vb.kind {
1962 crate::ast::VerifyKind::Cases => format!("fn:{}", vb.fn_name),
1963 crate::ast::VerifyKind::Law(law) => format!("law:{}::{}", vb.fn_name, law.name),
1964 }
1965}
1966
1967pub(crate) fn module_prefix_to_rust_path(prefix: &str) -> String {
1977 format!(
1978 "crate::aver_generated::{}",
1979 module_prefix_to_rust_segments(prefix).join("::")
1980 )
1981}
1982
1983fn module_segment_to_rust(segment: &str) -> String {
1984 let chars = segment.chars().collect::<Vec<_>>();
1985 let mut out = String::new();
1986
1987 for (idx, ch) in chars.iter().enumerate() {
1988 if ch.is_ascii_alphanumeric() {
1989 if ch.is_ascii_uppercase() {
1990 let prev_is_lower_or_digit = idx > 0
1991 && (chars[idx - 1].is_ascii_lowercase() || chars[idx - 1].is_ascii_digit());
1992 let next_is_lower = chars
1993 .get(idx + 1)
1994 .is_some_and(|next| next.is_ascii_lowercase());
1995 if idx > 0 && (prev_is_lower_or_digit || next_is_lower) && !out.ends_with('_') {
1996 out.push('_');
1997 }
1998 out.push(ch.to_ascii_lowercase());
1999 } else {
2000 out.push(ch.to_ascii_lowercase());
2001 }
2002 } else if !out.ends_with('_') {
2003 out.push('_');
2004 }
2005 }
2006
2007 let trimmed = out.trim_matches('_');
2008 let mut normalized = if trimmed.is_empty() {
2009 "module".to_string()
2010 } else {
2011 trimmed.to_string()
2012 };
2013
2014 if matches!(
2015 normalized.as_str(),
2016 "as" | "break"
2017 | "const"
2018 | "continue"
2019 | "crate"
2020 | "else"
2021 | "enum"
2022 | "extern"
2023 | "false"
2024 | "fn"
2025 | "for"
2026 | "if"
2027 | "impl"
2028 | "in"
2029 | "let"
2030 | "loop"
2031 | "match"
2032 | "mod"
2033 | "move"
2034 | "mut"
2035 | "pub"
2036 | "ref"
2037 | "return"
2038 | "self"
2039 | "Self"
2040 | "static"
2041 | "struct"
2042 | "super"
2043 | "trait"
2044 | "true"
2045 | "type"
2046 | "unsafe"
2047 | "use"
2048 | "where"
2049 | "while"
2050 ) {
2051 normalized.push_str("_mod");
2052 }
2053
2054 normalized
2055}
2056
2057pub(crate) fn split_type_params(s: &str, delim: char) -> Vec<String> {
2062 let mut parts = Vec::new();
2063 let mut depth = 0usize;
2064 let mut current = String::new();
2065 for ch in s.chars() {
2066 match ch {
2067 '<' | '(' => {
2068 depth += 1;
2069 current.push(ch);
2070 }
2071 '>' | ')' => {
2072 depth = depth.saturating_sub(1);
2073 current.push(ch);
2074 }
2075 _ if ch == delim && depth == 0 => {
2076 parts.push(current.trim().to_string());
2077 current.clear();
2078 }
2079 _ => current.push(ch),
2080 }
2081 }
2082 let rest = current.trim().to_string();
2083 if !rest.is_empty() {
2084 parts.push(rest);
2085 }
2086 parts
2087}
2088
2089pub(crate) fn escape_string_literal_ext(s: &str, unicode_escapes: bool) -> String {
2096 let mut out = String::with_capacity(s.len());
2097 for ch in s.chars() {
2098 match ch {
2099 '\\' => out.push_str("\\\\"),
2100 '"' => out.push_str("\\\""),
2101 '\n' => out.push_str("\\n"),
2102 '\r' => out.push_str("\\r"),
2103 '\t' => out.push_str("\\t"),
2104 '\0' => out.push_str("\\0"),
2105 c if c.is_control() => {
2106 if unicode_escapes {
2107 out.push_str(&format!("\\U{{{:06x}}}", c as u32));
2109 } else {
2110 out.push_str(&format!("\\x{:02x}", c as u32));
2111 }
2112 }
2113 c => out.push(c),
2114 }
2115 }
2116 out
2117}
2118
2119pub(crate) fn escape_string_literal(s: &str) -> String {
2121 escape_string_literal_ext(s, false)
2122}
2123
2124pub(crate) fn escape_string_literal_unicode(s: &str) -> String {
2126 escape_string_literal_ext(s, true)
2127}
2128
2129pub(crate) fn parse_type_annotation(ann: &str) -> Type {
2133 crate::types::parse_type_str(ann)
2134}
2135
2136pub(crate) fn is_set_type(ty: &Type) -> bool {
2142 matches!(ty, Type::Map(_, v) if matches!(v.as_ref(), Type::Unit))
2143}
2144
2145pub(crate) fn is_set_annotation(ann: &str) -> bool {
2147 is_set_type(&parse_type_annotation(ann))
2148}
2149
2150pub(crate) fn is_unit_expr_resolved(expr: &crate::ir::hir::ResolvedExpr) -> bool {
2153 matches!(
2154 expr,
2155 crate::ir::hir::ResolvedExpr::Literal(crate::ast::Literal::Unit)
2156 )
2157}
2158
2159pub(crate) fn escape_reserved_word(name: &str, reserved: &[&str], suffix: &str) -> String {
2164 if reserved.contains(&name) {
2165 format!("{}{}", name, suffix)
2166 } else {
2167 name.to_string()
2168 }
2169}
2170
2171pub(crate) fn escape_reserved_word_prefix(name: &str, reserved: &[&str], prefix: &str) -> String {
2174 if reserved.contains(&name) {
2175 format!("{}{}", prefix, name)
2176 } else {
2177 name.to_string()
2178 }
2179}
2180
2181pub(crate) fn to_lower_first(s: &str) -> String {
2185 let mut chars = s.chars();
2186 match chars.next() {
2187 None => String::new(),
2188 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
2189 }
2190}
2191
2192pub(crate) fn expr_to_dotted_name(expr: &Expr) -> Option<String> {
2195 crate::ir::expr_to_dotted_name(expr)
2196}
2197
2198#[derive(Debug, Clone)]
2212pub(crate) enum OracleInjectionMode<'a> {
2213 LemmaBinding,
2214 LemmaBindingProjected,
2224 #[allow(dead_code)]
2225 SampleValue,
2226 SampleCaseBinding(&'a [(String, crate::ast::Spanned<Expr>)]),
2227}
2228
2229pub(crate) fn rewrite_effectful_calls_in_law<'fd, F>(
2238 expr: &crate::ast::Spanned<Expr>,
2239 law: &crate::ast::VerifyLaw,
2240 find_fn_def: F,
2241 mode: OracleInjectionMode,
2242) -> crate::ast::Spanned<Expr>
2243where
2244 F: Fn(&str) -> Option<&'fd crate::ast::FnDef> + Copy,
2245{
2246 use crate::ast::{Spanned, VerifyGivenDomain};
2247
2248 let injection_by_effect: std::collections::HashMap<String, Spanned<Expr>> = law
2249 .givens
2250 .iter()
2251 .filter_map(|g| {
2252 let arg_expr = match &mode {
2253 OracleInjectionMode::LemmaBinding => {
2254 Spanned::new(Expr::Ident(g.name.clone()), expr.line)
2255 }
2256 OracleInjectionMode::LemmaBindingProjected => {
2257 Spanned::new(Expr::Ident(g.name.clone()), expr.line)
2265 }
2266 OracleInjectionMode::SampleValue => match &g.domain {
2267 VerifyGivenDomain::Explicit(vals) => vals.first().cloned()?,
2268 _ => return None,
2269 },
2270 OracleInjectionMode::SampleCaseBinding(case_bindings) => case_bindings
2271 .iter()
2272 .find(|(name, _)| name == &g.name)
2273 .map(|(_, v)| v.clone())?,
2274 };
2275 Some((g.type_name.clone(), arg_expr))
2276 })
2277 .collect();
2278 let rewritten = rewrite_effectful_call(expr, &injection_by_effect, find_fn_def);
2279
2280 if matches!(mode, OracleInjectionMode::LemmaBindingProjected) {
2289 let oracle_names: std::collections::HashSet<String> = law
2299 .givens
2300 .iter()
2301 .filter(|g| crate::types::checker::oracle_subtypes::has_bounded_subtype(&g.type_name))
2302 .map(|g| g.name.clone())
2303 .collect();
2304 if !oracle_names.is_empty() {
2305 return project_oracle_direct_calls(&rewritten, &oracle_names);
2306 }
2307 }
2308 rewritten
2309}
2310
2311fn project_oracle_direct_calls(
2324 expr: &crate::ast::Spanned<Expr>,
2325 oracle_names: &std::collections::HashSet<String>,
2326) -> crate::ast::Spanned<Expr> {
2327 use crate::ast::Spanned;
2328 let line = expr.line;
2329 let project_ident = |name: &str, line: usize| -> Spanned<Expr> {
2330 Spanned::new(
2331 Expr::Attr(
2332 Box::new(Spanned::new(Expr::Ident(name.to_string()), line)),
2333 "val".to_string(),
2334 ),
2335 line,
2336 )
2337 };
2338 let new_node = match &expr.node {
2339 Expr::Ident(name) if oracle_names.contains(name) => {
2343 return project_ident(name, line);
2344 }
2345 Expr::FnCall(callee, args) => {
2346 let new_args: Vec<Spanned<Expr>> = args
2347 .iter()
2348 .map(|a| project_oracle_direct_calls(a, oracle_names))
2349 .collect();
2350 let new_callee = if let Expr::Ident(name) = &callee.node
2352 && oracle_names.contains(name)
2353 {
2354 project_ident(name, callee.line)
2355 } else {
2356 project_oracle_direct_calls(callee, oracle_names)
2357 };
2358 Expr::FnCall(Box::new(new_callee), new_args)
2359 }
2360 Expr::Constructor(name, Some(arg)) => Expr::Constructor(
2361 name.clone(),
2362 Some(Box::new(project_oracle_direct_calls(arg, oracle_names))),
2363 ),
2364 Expr::Attr(obj, field) => Expr::Attr(
2365 Box::new(project_oracle_direct_calls(obj, oracle_names)),
2366 field.clone(),
2367 ),
2368 Expr::BinOp(op, l, r) => Expr::BinOp(
2369 *op,
2370 Box::new(project_oracle_direct_calls(l, oracle_names)),
2371 Box::new(project_oracle_direct_calls(r, oracle_names)),
2372 ),
2373 other => other.clone(),
2374 };
2375 Spanned::new(new_node, line)
2376}
2377
2378fn rewrite_effectful_call<'fd, F>(
2379 expr: &crate::ast::Spanned<Expr>,
2380 injection_by_effect: &std::collections::HashMap<String, crate::ast::Spanned<Expr>>,
2381 find_fn_def: F,
2382) -> crate::ast::Spanned<Expr>
2383where
2384 F: Fn(&str) -> Option<&'fd crate::ast::FnDef> + Copy,
2385{
2386 use crate::ast::Spanned;
2387 use crate::types::checker::effect_classification::{EffectDimension, classify};
2388
2389 match &expr.node {
2390 Expr::FnCall(callee, args) => {
2391 let rewritten_args: Vec<Spanned<Expr>> = args
2392 .iter()
2393 .map(|a| rewrite_effectful_call(a, injection_by_effect, find_fn_def))
2394 .collect();
2395 let rewritten_callee = Box::new(rewrite_effectful_call(
2396 callee,
2397 injection_by_effect,
2398 find_fn_def,
2399 ));
2400
2401 let callee_name = match &callee.node {
2402 Expr::Ident(name) => Some(name.clone()),
2403 Expr::Resolved { name, .. } => Some(name.clone()),
2404 _ => None,
2405 };
2406
2407 if let Some(name) = callee_name
2408 && let Some(fd) = find_fn_def(&name)
2409 && !fd.effects.is_empty()
2410 && fd
2411 .effects
2412 .iter()
2413 .all(|e| crate::types::checker::effect_classification::is_classified(&e.node))
2414 {
2415 let mut injected: Vec<Spanned<Expr>> = Vec::new();
2416 let needs_path = fd.effects.iter().any(|e| {
2417 matches!(
2418 classify(&e.node).map(|c| c.dimension),
2419 Some(EffectDimension::Generative | EffectDimension::GenerativeOutput)
2420 )
2421 });
2422 if needs_path {
2423 injected.push(Spanned::new(
2424 Expr::Attr(
2428 Box::new(Spanned::new(
2429 Expr::Ident("BranchPath".to_string()),
2430 expr.line,
2431 )),
2432 "Root".to_string(),
2433 ),
2434 expr.line,
2435 ));
2436 }
2437 let mut seen = std::collections::HashSet::new();
2438 for e in &fd.effects {
2439 if !seen.insert(e.node.clone()) {
2440 continue;
2441 }
2442 let Some(c) = classify(&e.node) else { continue };
2443 if matches!(c.dimension, EffectDimension::Output) {
2444 continue;
2445 }
2446 if let Some(inj) = injection_by_effect.get(&e.node) {
2447 injected.push(inj.clone());
2448 }
2449 }
2450 injected.extend(rewritten_args);
2451 return Spanned::new(Expr::FnCall(rewritten_callee, injected), expr.line);
2452 }
2453
2454 Spanned::new(Expr::FnCall(rewritten_callee, rewritten_args), expr.line)
2455 }
2456 Expr::BinOp(op, l, r) => Spanned::new(
2457 Expr::BinOp(
2458 *op,
2459 Box::new(rewrite_effectful_call(l, injection_by_effect, find_fn_def)),
2460 Box::new(rewrite_effectful_call(r, injection_by_effect, find_fn_def)),
2461 ),
2462 expr.line,
2463 ),
2464 Expr::Tuple(items) => Spanned::new(
2465 Expr::Tuple(
2466 items
2467 .iter()
2468 .map(|i| rewrite_effectful_call(i, injection_by_effect, find_fn_def))
2469 .collect(),
2470 ),
2471 expr.line,
2472 ),
2473 _ => expr.clone(),
2474 }
2475}
2476
2477pub(crate) fn verify_reachable_fn_names(items: &[TopLevel]) -> HashSet<String> {
2487 let mut reachable: HashSet<String> = HashSet::new();
2488 for item in items {
2489 if let TopLevel::Verify(vb) = item {
2490 collect_verify_block_refs(vb, &mut reachable);
2491 }
2492 }
2493 loop {
2495 let mut changed = false;
2496 for item in items {
2497 if let TopLevel::FnDef(fd) = item
2498 && reachable.contains(&fd.name)
2499 {
2500 let mut called = HashSet::new();
2501 collect_called_idents_in_body(&fd.body, &mut called);
2502 for name in called {
2503 if reachable.insert(name) {
2504 changed = true;
2505 }
2506 }
2507 }
2508 }
2509 if !changed {
2510 break;
2511 }
2512 }
2513 reachable
2514}
2515
2516fn collect_verify_block_refs(vb: &VerifyBlock, out: &mut HashSet<String>) {
2517 out.insert(vb.fn_name.clone());
2518 for (lhs, rhs) in &vb.cases {
2519 collect_called_idents(lhs, out);
2520 collect_called_idents(rhs, out);
2521 }
2522 if let VerifyKind::Law(law) = &vb.kind {
2523 collect_called_idents(&law.lhs, out);
2524 collect_called_idents(&law.rhs, out);
2525 if let Some(when) = &law.when {
2526 collect_called_idents(when, out);
2527 }
2528 for given in &law.givens {
2529 if let VerifyGivenDomain::Explicit(values) = &given.domain {
2530 for v in values {
2531 collect_called_idents(v, out);
2532 }
2533 }
2534 }
2535 }
2536 for given in &vb.cases_givens {
2537 if let VerifyGivenDomain::Explicit(values) = &given.domain {
2538 for v in values {
2539 collect_called_idents(v, out);
2540 }
2541 }
2542 }
2543}
2544
2545fn collect_called_idents_in_body(body: &FnBody, out: &mut HashSet<String>) {
2546 for stmt in body.stmts() {
2547 match stmt {
2548 Stmt::Binding(_, _, e) | Stmt::Expr(e) => collect_called_idents(e, out),
2549 }
2550 }
2551}
2552
2553fn collect_called_idents(expr: &Spanned<Expr>, out: &mut HashSet<String>) {
2554 match &expr.node {
2555 Expr::FnCall(callee, args) => {
2556 if let Expr::Ident(name) | Expr::Resolved { name, .. } = &callee.node {
2557 out.insert(name.clone());
2558 } else {
2559 collect_called_idents(callee, out);
2560 }
2561 for a in args {
2562 collect_called_idents(a, out);
2563 }
2564 }
2565 Expr::TailCall(boxed) => {
2566 let TailCallData { target, args, .. } = boxed.as_ref();
2567 out.insert(target.clone());
2568 for a in args {
2569 collect_called_idents(a, out);
2570 }
2571 }
2572 Expr::Ident(name) | Expr::Resolved { name, .. } => {
2573 out.insert(name.clone());
2574 }
2575 Expr::BinOp(_, l, r) => {
2576 collect_called_idents(l, out);
2577 collect_called_idents(r, out);
2578 }
2579 Expr::Neg(inner) => collect_called_idents(inner, out),
2580 Expr::Match { subject, arms, .. } => {
2581 collect_called_idents(subject, out);
2582 for arm in arms {
2583 collect_called_idents(&arm.body, out);
2584 }
2585 }
2586 Expr::ErrorProp(inner) | Expr::Attr(inner, _) => {
2587 collect_called_idents(inner, out);
2588 }
2589 Expr::Constructor(_, Some(inner)) => {
2590 collect_called_idents(inner, out);
2591 }
2592 Expr::InterpolatedStr(parts) => {
2593 for part in parts {
2594 if let StrPart::Parsed(inner) = part {
2595 collect_called_idents(inner, out);
2596 }
2597 }
2598 }
2599 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
2600 for i in items {
2601 collect_called_idents(i, out);
2602 }
2603 }
2604 Expr::MapLiteral(entries) => {
2605 for (k, v) in entries {
2606 collect_called_idents(k, out);
2607 collect_called_idents(v, out);
2608 }
2609 }
2610 Expr::RecordCreate { fields, .. } => {
2611 for (_, v) in fields {
2612 collect_called_idents(v, out);
2613 }
2614 }
2615 Expr::RecordUpdate { base, updates, .. } => {
2616 collect_called_idents(base, out);
2617 for (_, v) in updates {
2618 collect_called_idents(v, out);
2619 }
2620 }
2621 Expr::Literal(_) | Expr::Constructor(_, None) => {}
2622 }
2623}
2624
2625pub(crate) struct PerScopeSections {
2629 pub by_scope: std::collections::HashMap<String, Vec<String>>,
2630}
2631
2632impl PerScopeSections {
2633 pub(crate) fn take(&mut self, scope: &str) -> Vec<String> {
2634 self.by_scope.remove(scope).unwrap_or_default()
2635 }
2636}
2637
2638pub(crate) fn route_pure_components_per_scope<F, G>(
2647 ctx: &CodegenContext,
2648 is_pure: F,
2649 mut emit: G,
2650) -> PerScopeSections
2651where
2652 F: Fn(&FnDef) -> bool,
2653 G: FnMut(&[&FnDef], &str) -> Vec<String>,
2654{
2655 let mut by_scope: std::collections::HashMap<String, Vec<String>> =
2656 std::collections::HashMap::new();
2657
2658 let mut process =
2659 |fns: Vec<&FnDef>,
2660 scope: String,
2661 by_scope: &mut std::collections::HashMap<String, Vec<String>>| {
2662 let comps = crate::call_graph::ordered_fn_components(&fns, &ctx.module_prefixes);
2663 let bucket = by_scope.entry(scope.clone()).or_default();
2664 for comp in comps {
2665 bucket.extend(emit(&comp, scope.as_str()));
2666 }
2667 };
2668
2669 for module in &ctx.modules {
2670 let pure: Vec<&FnDef> = module.fn_defs.iter().filter(|fd| is_pure(fd)).collect();
2671 process(pure, module.prefix.clone(), &mut by_scope);
2672 }
2673 let entry_pure: Vec<&FnDef> = ctx.fn_defs.iter().filter(|fd| is_pure(fd)).collect();
2674 process(entry_pure, String::new(), &mut by_scope);
2675
2676 PerScopeSections { by_scope }
2677}
2678
2679#[cfg(test)]
2680mod tests {
2681 use super::*;
2682 use crate::ast::{Literal, VerifyGiven, VerifyGivenDomain, VerifyLaw};
2683
2684 fn sb(node: Expr) -> Spanned<Expr> {
2685 Spanned::new(node, 1)
2686 }
2687
2688 fn bsb(node: Expr) -> Box<Spanned<Expr>> {
2689 Box::new(sb(node))
2690 }
2691
2692 fn law_with(lhs: Spanned<Expr>, rhs: Spanned<Expr>) -> VerifyLaw {
2693 VerifyLaw {
2694 name: "test".to_string(),
2695 givens: vec![VerifyGiven {
2696 name: "xs".to_string(),
2697 type_name: "List<String>".to_string(),
2698 domain: VerifyGivenDomain::Explicit(vec![sb(Expr::List(vec![]))]),
2699 }],
2700 when: None,
2701 lhs,
2702 rhs,
2703 sample_guards: Vec::new(),
2704 }
2705 }
2706
2707 #[test]
2708 fn law_calls_unclassified_fn_detects_dotted_callee() {
2709 let lhs = sb(Expr::FnCall(
2720 bsb(Expr::Attr(
2721 bsb(Expr::Ident("Dep".to_string())),
2722 "toSorted".to_string(),
2723 )),
2724 vec![sb(Expr::Ident("xs".to_string()))],
2725 ));
2726 let rhs = sb(Expr::Ident("xs".to_string()));
2727 let law = law_with(lhs, rhs);
2728
2729 let mut canonical = HashSet::new();
2732 canonical.insert("Dep.toSorted".to_string());
2733 assert!(law_calls_unclassified_fn(&law, &canonical));
2734
2735 let mut bare_only = HashSet::new();
2738 bare_only.insert("toSorted".to_string());
2739 assert!(
2740 law_calls_unclassified_fn(&law, &bare_only),
2741 "bare unclassified name must catch a dotted callsite via suffix match"
2742 );
2743
2744 let mut unrelated = HashSet::new();
2747 unrelated.insert("somethingElse".to_string());
2748 assert!(!law_calls_unclassified_fn(&law, &unrelated));
2749 }
2750
2751 #[test]
2752 fn law_lhs_has_trace_projection_skips_user_record_field() {
2753 let user_field_lhs = sb(Expr::Attr(
2761 bsb(Expr::Ident("log".to_string())),
2762 "trace".to_string(),
2763 ));
2764 assert!(
2765 !law_lhs_has_trace_projection(&user_field_lhs),
2766 "bare user-record `.trace` field must not trigger the gate"
2767 );
2768
2769 let runtime_trace_lhs = sb(Expr::FnCall(
2771 bsb(Expr::Attr(
2772 bsb(Expr::Attr(
2773 bsb(Expr::FnCall(bsb(Expr::Ident("fn".to_string())), vec![])),
2774 "trace".to_string(),
2775 )),
2776 "event".to_string(),
2777 )),
2778 vec![sb(Expr::Literal(Literal::Int(0)))],
2779 ));
2780 assert!(
2781 law_lhs_has_trace_projection(&runtime_trace_lhs),
2782 "Oracle `.trace.event(0)` projection must trigger the gate"
2783 );
2784 }
2785
2786 #[test]
2787 fn resolve_refined_type_disambiguates_cross_module_same_bare_name() {
2788 use crate::ast::{TypeDef, TypeVariant};
2797 use crate::codegen::ModuleInfo;
2798 use crate::ir::proof_ir::{Predicate, QuantifierType, RefinedTypeDecl};
2799 use std::collections::HashMap;
2800 let _ = TypeVariant {
2801 name: String::new(),
2802 fields: Vec::new(),
2803 }; let make_module = |prefix: &str| ModuleInfo {
2806 prefix: prefix.to_string(),
2807 depends: Vec::new(),
2808 type_defs: vec![TypeDef::Product {
2809 name: "Natural".to_string(),
2810 fields: vec![("value".to_string(), "Int".to_string())],
2811 line: 1,
2812 }],
2813 fn_defs: Vec::new(),
2814 analysis: None,
2815 };
2816 let modules = vec![make_module("A"), make_module("B")];
2817
2818 let make_decl = |predicate_param: &str, witness: i64| RefinedTypeDecl {
2819 name: "Natural".to_string(),
2820 carrier_type: "Int".to_string(),
2821 carrier_field: "value".to_string(),
2822 predicate_param: predicate_param.to_string(),
2823 invariant: Predicate {
2824 free_vars: vec![(
2825 predicate_param.to_string(),
2826 QuantifierType::Plain("Int".to_string()),
2827 )],
2828 expr: crate::ast::Spanned::bare(crate::ir::hir::ResolvedExpr::Literal(
2829 Literal::Bool(true),
2830 )),
2831 },
2832 witness: Some(witness.to_string()),
2833 };
2834 let symbols = crate::ir::SymbolTable::build(&[], &modules);
2835 let a_id = symbols
2836 .type_id_of(&crate::ir::TypeKey::in_module("A", "Natural"))
2837 .expect("A.Natural TypeId");
2838 let b_id = symbols
2839 .type_id_of(&crate::ir::TypeKey::in_module("B", "Natural"))
2840 .expect("B.Natural TypeId");
2841
2842 let mut refined_types: HashMap<crate::ir::TypeId, RefinedTypeDecl> = HashMap::new();
2843 refined_types.insert(a_id, make_decl("a", 0));
2844 refined_types.insert(b_id, make_decl("b", 10));
2845
2846 let a = resolve_refined_type_in(&refined_types, &symbols, &modules, "A.Natural")
2848 .expect("A.Natural canonical lookup");
2849 assert_eq!(a.predicate_param, "a");
2850 assert_eq!(a.witness.as_deref(), Some("0"));
2851 let b = resolve_refined_type_in(&refined_types, &symbols, &modules, "B.Natural")
2852 .expect("B.Natural canonical lookup");
2853 assert_eq!(b.predicate_param, "b");
2854 assert_eq!(b.witness.as_deref(), Some("10"));
2855
2856 let bare = resolve_refined_type_in(&refined_types, &symbols, &modules, "Natural")
2860 .expect("bare Natural resolves via module walk");
2861 assert!(
2862 bare.predicate_param == "a" || bare.predicate_param == "b",
2863 "bare Natural must resolve to one of the canonical decls"
2864 );
2865
2866 assert!(resolve_refined_type_in(&refined_types, &symbols, &modules, "Unrelated").is_none());
2869 }
2870
2871 #[test]
2872 fn find_refined_type_scoped_prefers_current_module_over_entry_collision() {
2873 use crate::ast::{TopLevel, TypeDef};
2881 use crate::codegen::{CodegenContext, ModuleInfo};
2882 use crate::ir::proof_ir::{Predicate, QuantifierType, RefinedTypeDecl};
2883 use std::collections::{HashMap, HashSet};
2884
2885 let entry_natural = TypeDef::Product {
2886 name: "Natural".to_string(),
2887 fields: vec![("value".to_string(), "Int".to_string())],
2888 line: 1,
2889 };
2890 let module = ModuleInfo {
2891 prefix: "Mod".to_string(),
2892 depends: Vec::new(),
2893 type_defs: vec![TypeDef::Product {
2894 name: "Natural".to_string(),
2895 fields: vec![("value".to_string(), "Int".to_string())],
2896 line: 1,
2897 }],
2898 fn_defs: Vec::new(),
2899 analysis: None,
2900 };
2901
2902 let make_decl = |param: &str, witness: &str| RefinedTypeDecl {
2903 name: "Natural".to_string(),
2904 carrier_type: "Int".to_string(),
2905 carrier_field: "value".to_string(),
2906 predicate_param: param.to_string(),
2907 invariant: Predicate {
2908 free_vars: vec![(param.to_string(), QuantifierType::Plain("Int".to_string()))],
2909 expr: crate::ast::Spanned::bare(crate::ir::hir::ResolvedExpr::Literal(
2910 Literal::Bool(true),
2911 )),
2912 },
2913 witness: Some(witness.to_string()),
2914 };
2915
2916 let items = vec![TopLevel::TypeDef(entry_natural)];
2917 let modules = vec![module];
2918 let symbol_table = crate::ir::SymbolTable::build(&items, &modules);
2919 let entry_id = symbol_table
2920 .type_id_of(&crate::ir::TypeKey::entry("Natural"))
2921 .expect("entry Natural id");
2922 let mod_id = symbol_table
2923 .type_id_of(&crate::ir::TypeKey::in_module("Mod", "Natural"))
2924 .expect("Mod.Natural id");
2925
2926 let mut ctx = CodegenContext {
2927 items,
2928 type_defs: Vec::new(),
2929 fn_defs: Vec::new(),
2930 project_name: "scope-test".to_string(),
2931 modules,
2932 module_prefixes: HashSet::new(),
2933 #[cfg(feature = "runtime")]
2934 policy: None,
2935 emit_replay_runtime: false,
2936 runtime_policy_from_env: false,
2937 guest_entry: None,
2938 emit_self_host_support: false,
2939 extra_fn_defs: Vec::new(),
2940 mutual_tco_members: HashSet::new(),
2941 recursive_fns: HashSet::new(),
2942 buffer_build_sinks: HashMap::new(),
2943 buffer_fusion_sites: Vec::new(),
2944 synthesized_buffered_fns: Vec::new(),
2945 proof_ir: crate::ir::ProofIR::default(),
2946 symbol_table,
2947 resolved_fn_defs: Vec::new(),
2948 resolved_module_fn_defs: Vec::new(),
2949 current_module_scope: std::cell::RefCell::new(None),
2950 resolved_program: crate::codegen::program_view::ResolvedProgramView::default(),
2951 program_shape: None,
2952 mir_program: None,
2953 discovered_lemmas: Vec::new(),
2954 sample_expected: std::collections::HashMap::new(),
2955 };
2956 ctx.proof_ir
2957 .refined_types
2958 .insert(entry_id, make_decl("entry_n", "0"));
2959 ctx.proof_ir
2960 .refined_types
2961 .insert(mod_id, make_decl("mod_n", "10"));
2962
2963 let from_module = find_refined_type_scoped(&ctx, "Natural", Some("Mod"))
2964 .expect("Mod-scoped Natural lookup");
2965 assert_eq!(
2966 from_module.predicate_param, "mod_n",
2967 "scope=Some(\"Mod\") + bare `Natural` must resolve to Mod.Natural, \
2968 not entry's bare-keyed slot"
2969 );
2970
2971 let from_entry =
2973 find_refined_type_scoped(&ctx, "Natural", None).expect("entry Natural lookup");
2974 assert_eq!(from_entry.predicate_param, "entry_n");
2975 }
2976}