1mod builtins;
5pub mod emit_ctx;
6mod expr;
7mod from_mir;
8pub use from_mir::{CoverageReport, MirEmitCtx, coverage_report, coverage_report_with_blockers};
9mod pattern;
10mod policy;
11mod project;
12mod replay;
13mod runtime;
14mod self_host;
15mod syntax;
16mod toplevel;
17mod types;
18
19use std::collections::{BTreeMap, BTreeSet, HashSet};
20
21use crate::ast::{FnDef, TopLevel, TypeDef};
22use crate::codegen::common::module_prefix_to_rust_segments;
23use crate::codegen::{CodegenContext, ProjectOutput};
24use crate::types::Type;
25
26fn synthesize_rust_module_cascade(
33 rel_dir: &str,
34 modules: &[(Vec<String>, String)],
35) -> Vec<(String, String)> {
36 let mut by_dir: BTreeMap<Vec<String>, (Option<String>, BTreeSet<String>)> = BTreeMap::new();
37 for (segments, content) in modules {
38 by_dir.entry(segments.clone()).or_default().0 = Some(content.clone());
39 for i in 0..segments.len() {
40 let parent: Vec<String> = segments[..i].to_vec();
41 by_dir
42 .entry(parent)
43 .or_default()
44 .1
45 .insert(segments[i].clone());
46 }
47 }
48 let mut files = Vec::new();
49 for (dir_segs, (content, children)) in by_dir {
50 let dir_path = if dir_segs.is_empty() {
51 rel_dir.to_string()
52 } else {
53 format!("{}/{}", rel_dir, dir_segs.join("/"))
54 };
55 let mut parts: Vec<String> = Vec::new();
56 if let Some(c) = content
57 && !c.trim().is_empty()
58 {
59 parts.push(c.trim_end().to_string());
60 }
61 for child in children {
62 parts.push(format!("pub mod {};", child));
63 }
64 let mut mod_rs = parts.join("\n\n");
65 if !mod_rs.is_empty() {
66 mod_rs.push('\n');
67 }
68 files.push((format!("{}/mod.rs", dir_path), mod_rs));
69 }
70 files
71}
72
73pub fn transpile(ctx: &mut CodegenContext) -> ProjectOutput {
75 if let Some(prog) = ctx.mir_program.take() {
84 let boxed = ctx.mutual_tco_members.clone();
89 let carrier = {
95 let inputs = crate::codegen::proof_lower::ProofLowerInputs::from_ctx(ctx);
96 crate::codegen::proof_lower::carrier_interval_table(&inputs)
97 };
98 ctx.mir_program = Some(
99 crate::ir::mir::optimize::bare_i64_rewrite::rewrite_for_rust(prog, &boxed, &carrier),
100 );
101 }
102 let has_embedded_policy = ctx.policy.is_some();
103 let has_runtime_policy = ctx.runtime_policy_from_env;
104 let embedded_independence_cancel = ctx
105 .policy
106 .as_ref()
107 .is_some_and(|config| config.independence_mode == crate::config::IndependenceMode::Cancel);
108 let used_services = detect_used_services(ctx);
109 let needs_http_types =
110 needs_named_type(ctx, "HttpResponse") || needs_named_type(ctx, "HttpRequest");
111 let needs_tcp_types = needs_named_type(ctx, "Tcp.Connection");
112 let needs_terminal_types = needs_named_type(ctx, "Terminal.Size");
113 let needs_branch_path = needs_named_type(ctx, "BranchPath");
117
118 let has_tcp_runtime = used_services.contains("Tcp");
119 let has_http_runtime = used_services.contains("Http");
120 let has_http_server_runtime = used_services.contains("HttpServer");
121 let has_terminal_runtime = used_services.contains("Terminal");
122
123 let has_tcp_types = has_tcp_runtime || needs_tcp_types;
124 let has_http_types = has_http_runtime || has_http_server_runtime || needs_http_types;
125 let has_http_server_types = has_http_server_runtime || needs_named_type(ctx, "HttpRequest");
126 let has_terminal_types = has_terminal_runtime || needs_terminal_types;
127
128 let main_fn = ctx.fn_defs.iter().find(|fd| fd.name == "main");
135 debug_assert_eq!(
136 main_fn.is_some(),
137 ctx.resolved_program
138 .entry_fns()
139 .any(|rfd| rfd.name == "main"),
140 "ctx.fn_defs and ctx.resolved_program.entry_fns() must agree on \
141 main fn presence (epic #170 Phase 1 invariant)"
142 );
143 let top_level_stmts: Vec<_> = ctx
144 .items
145 .iter()
146 .filter_map(|item| {
147 if let TopLevel::Stmt(stmt) = item {
148 Some(stmt)
149 } else {
150 None
151 }
152 })
153 .collect();
154 let verify_blocks: Vec<_> = ctx
155 .items
156 .iter()
157 .filter_map(|item| {
158 if let TopLevel::Verify(vb) = item {
159 Some(vb)
160 } else {
161 None
162 }
163 })
164 .collect();
165
166 let mut files = vec![
167 (
168 "Cargo.toml".to_string(),
169 project::generate_cargo_toml(
170 &ctx.project_name,
171 &used_services,
172 has_embedded_policy,
173 has_runtime_policy,
174 ctx.emit_replay_runtime,
175 ),
176 ),
177 (
178 "src/main.rs".to_string(),
179 render_root_main(
180 main_fn,
181 has_embedded_policy,
182 ctx.emit_replay_runtime,
183 ctx.guest_entry.as_deref(),
184 !verify_blocks.is_empty(),
185 ctx.emit_self_host_support,
186 ),
187 ),
188 (
189 "src/runtime_support.rs".to_string(),
190 render_runtime_support(
191 has_tcp_types,
192 has_http_types,
193 has_http_server_types,
194 has_terminal_types,
195 needs_branch_path,
196 ctx.emit_replay_runtime,
197 embedded_independence_cancel,
198 ),
199 ),
200 ];
201
202 if ctx.emit_self_host_support {
203 files.push((
204 "src/self_host_support.rs".to_string(),
205 self_host::generate_self_host_support(),
206 ));
207 }
208
209 if has_embedded_policy && let Some(config) = &ctx.policy {
210 files.push((
211 "src/policy_support.rs".to_string(),
212 format!("{}\n", policy::generate_policy_runtime(config)),
213 ));
214 }
215
216 if ctx.emit_replay_runtime {
217 files.push((
218 "src/replay_support.rs".to_string(),
219 replay::generate_replay_runtime(
220 has_embedded_policy,
221 has_runtime_policy,
222 has_terminal_types,
223 has_tcp_types,
224 has_http_types,
225 has_http_server_types,
226 embedded_independence_cancel,
227 ),
228 ));
229 }
230
231 if !verify_blocks.is_empty() {
232 files.push((
233 "src/verify.rs".to_string(),
234 render_verify_module(&verify_blocks, ctx),
235 ));
236 }
237
238 let mut rust_modules: Vec<(Vec<String>, String)> = Vec::new();
239 rust_modules.push((
240 vec!["entry".to_string()],
241 render_generated_module(
242 root_module_depends(&ctx.items),
243 entry_module_sections(ctx, main_fn, &top_level_stmts),
244 ),
245 ));
246
247 for i in 0..ctx.modules.len() {
248 ctx.extra_fn_defs = ctx.modules[i].fn_defs.clone();
251 let module = &ctx.modules[i];
252 let segments = module_prefix_to_rust_segments(&module.prefix);
253 rust_modules.push((
254 segments,
255 render_generated_module(module.depends.clone(), module_sections(module, ctx)),
256 ));
257 }
258 ctx.extra_fn_defs.clear();
259
260 files.extend(synthesize_rust_module_cascade(
261 "src/aver_generated",
262 &rust_modules,
263 ));
264 files.sort_by(|left, right| left.0.cmp(&right.0));
265
266 ProjectOutput { files }
267}
268
269fn render_root_main(
270 main_fn: Option<&FnDef>,
271 has_policy: bool,
272 has_replay: bool,
273 guest_entry: Option<&str>,
274 has_verify: bool,
275 has_self_host_support: bool,
276) -> String {
277 let mut sections = vec![
278 "#![allow(unused_variables, unused_mut, dead_code, unused_imports, unused_parens, non_snake_case, non_camel_case_types, unreachable_patterns, hidden_glob_reexports)]".to_string(),
279 "// Aver Rust emission".to_string(),
280 "#[macro_use] extern crate aver_rt;".to_string(),
281 "pub use ::aver_rt::AverMap as HashMap;".to_string(),
282 "pub use ::aver_rt::AverStr;".to_string(),
283 "pub use ::aver_rt::Buffer;".to_string(),
284 String::new(),
285 "mod runtime_support;".to_string(),
286 "pub use runtime_support::*;".to_string(),
287 ];
288
289 if has_policy {
290 sections.push(String::new());
291 sections.push("mod policy_support;".to_string());
292 sections.push("pub use policy_support::*;".to_string());
293 }
294
295 if has_replay {
296 sections.push(String::new());
297 sections.push("mod replay_support;".to_string());
298 sections.push("pub use replay_support::*;".to_string());
299 }
300
301 if has_self_host_support {
302 sections.push(String::new());
303 sections.push("mod self_host_support;".to_string());
304 }
305
306 sections.push(String::new());
307 sections.push("pub mod aver_generated;".to_string());
308
309 if has_verify {
310 sections.push(String::new());
311 sections.push("#[cfg(test)]".to_string());
312 sections.push("mod verify;".to_string());
313 }
314
315 sections.push(String::new());
317 let returns_result = main_fn.is_some_and(|fd| fd.return_type.starts_with("Result<"));
318 let result_unit_string =
319 main_fn.is_some_and(|fd| fd.return_type.replace(' ', "") == "Result<Unit,String>");
320 let bench_dispatch_lines = |has_replay: bool| -> Vec<String> {
329 let user_call = if has_replay {
330 "aver_replay::with_guest_scope(\"main\", serde_json::Value::Null, aver_generated::entry::main)"
331 } else {
332 "aver_generated::entry::main()"
333 };
334 vec![
335 " if let Ok(n_str) = std::env::var(\"AVER_BENCH_ITER\") {".to_string(),
336 " let n: usize = n_str.parse().unwrap_or(0);".to_string(),
337 " let warmup: usize = std::env::var(\"AVER_BENCH_WARMUP\").ok().and_then(|s| s.parse().ok()).unwrap_or(0);".to_string(),
338 " for _ in 0..warmup {".to_string(),
339 format!(" let _ = std::hint::black_box({});", user_call),
340 " }".to_string(),
341 " for _ in 0..n {".to_string(),
342 " let t = std::time::Instant::now();".to_string(),
343 format!(" let _ = std::hint::black_box({});", user_call),
344 " eprintln!(\"__bench_iter_ms__: {}\", t.elapsed().as_secs_f64() * 1000.0);".to_string(),
345 " }".to_string(),
346 " std::process::exit(0);".to_string(),
347 " }".to_string(),
348 ]
349 };
350 if returns_result {
351 if result_unit_string {
352 sections.push("fn main() {".to_string());
353 sections.extend(bench_dispatch_lines(has_replay && guest_entry.is_none()));
354 sections.push(" let child = std::thread::Builder::new()".to_string());
355 sections.push(" .stack_size(256 * 1024 * 1024)".to_string());
356 if has_replay && guest_entry.is_none() {
357 sections.push(" .spawn(|| {".to_string());
358 sections.push(" let __result = aver_replay::with_guest_scope(\"main\", serde_json::Value::Null, aver_generated::entry::main);".to_string());
359 sections.push(" __result.map_err(|e| e.to_string())".to_string());
360 sections.push(" })".to_string());
361 } else {
362 sections.push(" .spawn(|| {".to_string());
363 sections
364 .push(" let __result = aver_generated::entry::main();".to_string());
365 sections.push(" __result.map_err(|e| e.to_string())".to_string());
366 sections.push(" })".to_string());
367 }
368 sections.push(" .expect(\"thread spawn\");".to_string());
369 sections.push(" match child.join().expect(\"thread join\") {".to_string());
370 sections.push(" Ok(()) => {}".to_string());
371 sections.push(" Err(e) => {".to_string());
372 sections.push(" eprintln!(\"{}\", e);".to_string());
373 sections.push(" std::process::exit(1);".to_string());
374 sections.push(" }".to_string());
375 sections.push(" }".to_string());
376 } else {
377 let ret_type = types::type_annotation_to_rust(&main_fn.unwrap().return_type);
378 sections.push(format!("fn main() -> {} {{", ret_type));
379 sections.extend(bench_dispatch_lines(has_replay && guest_entry.is_none()));
380 if has_replay && guest_entry.is_none() {
381 sections.push(
382 " aver_replay::with_guest_scope(\"main\", serde_json::Value::Null, aver_generated::entry::main)"
383 .to_string(),
384 );
385 } else {
386 sections.push(" aver_generated::entry::main()".to_string());
387 }
388 }
389 } else {
390 sections.push("fn main() {".to_string());
391 if main_fn.is_some() {
392 sections.extend(bench_dispatch_lines(has_replay && guest_entry.is_none()));
393 sections.push(" let child = std::thread::Builder::new()".to_string());
394 sections.push(" .stack_size(256 * 1024 * 1024)".to_string());
395 if has_replay && guest_entry.is_none() {
396 sections.push(" .spawn(|| aver_replay::with_guest_scope(\"main\", serde_json::Value::Null, || aver_generated::entry::main()))".to_string());
397 } else {
398 sections.push(" .spawn(|| aver_generated::entry::main())".to_string());
399 }
400 sections.push(" .expect(\"thread spawn\");".to_string());
401 sections.push(" child.join().expect(\"thread join\");".to_string());
402 }
403 }
404 sections.push("}".to_string());
405 sections.push(String::new());
406
407 sections.join("\n")
408}
409
410fn render_runtime_support(
411 has_tcp_types: bool,
412 has_http_types: bool,
413 has_http_server_types: bool,
414 has_terminal_types: bool,
415 needs_branch_path: bool,
416 has_replay: bool,
417 embedded_independence_cancel: bool,
418) -> String {
419 let mut sections = vec![runtime::generate_runtime(
420 has_replay,
421 has_http_server_types,
422 embedded_independence_cancel,
423 )];
424 if has_tcp_types {
425 sections.push(runtime::generate_tcp_types());
426 }
427 if has_http_types {
428 sections.push(runtime::generate_http_types());
429 }
430 if has_http_server_types {
431 sections.push(runtime::generate_http_server_types());
432 }
433 if has_terminal_types {
434 sections.push(runtime::generate_terminal_types());
435 }
436 if needs_branch_path {
437 sections.push(runtime::generate_branch_path_types());
438 }
439 format!("{}\n", sections.join("\n\n"))
440}
441
442fn render_verify_module(
443 verify_blocks: &[&crate::ast::VerifyBlock],
444 ctx: &CodegenContext,
445) -> String {
446 [
447 "#[allow(unused_imports)]".to_string(),
448 "use crate::*;".to_string(),
449 "#[allow(unused_imports)]".to_string(),
450 "use crate::aver_generated::entry::*;".to_string(),
451 String::new(),
452 toplevel::emit_verify_blocks(verify_blocks, ctx),
453 String::new(),
454 ]
455 .join("\n")
456}
457
458fn render_generated_module(depends: Vec<String>, sections: Vec<String>) -> String {
459 if sections.is_empty() {
460 String::new()
461 } else {
462 let mut lines = vec![
463 "#[allow(unused_imports)]".to_string(),
464 "use crate::*;".to_string(),
465 ];
466 for dep in depends {
467 let path = module_prefix_to_rust_segments(&dep).join("::");
468 lines.push("#[allow(unused_imports)]".to_string());
469 lines.push(format!("use crate::aver_generated::{}::*;", path));
470 }
471 lines.push(String::new());
472 lines.push(sections.join("\n\n"));
473 lines.push(String::new());
474 lines.join("\n")
475 }
476}
477
478fn emit_mutual_tco_block_routed(
487 group_id: usize,
488 group_fns: &[&FnDef],
489 ctx: &CodegenContext,
490 scope: Option<&str>,
491 visibility: &str,
492) -> String {
493 let resolved: Option<Vec<(&crate::ir::mir::MirFn, &crate::ir::hir::ResolvedFnDef)>> =
495 ctx.mir_program.as_ref().and_then(|prog| {
496 group_fns
497 .iter()
498 .map(|fd| {
499 let fn_id = crate::codegen::common::fn_id_for_decl(ctx, fd)?;
500 let mir_fn = prog.fn_by_id(fn_id)?;
501 let resolved_fd = ctx.resolved_program.fn_by_id(fn_id)?;
502 Some((mir_fn, resolved_fd))
503 })
504 .collect()
505 });
506 let code = resolved.and_then(|pairs| {
507 let mir_fns: Vec<&crate::ir::mir::MirFn> = pairs.iter().map(|(m, _)| *m).collect();
508 let resolved_fns: Vec<&crate::ir::hir::ResolvedFnDef> =
509 pairs.iter().map(|(_, r)| *r).collect();
510 from_mir::emit_mir_mutual_tco_block(
511 group_id,
512 group_fns,
513 &mir_fns,
514 &resolved_fns,
515 ctx,
516 scope,
517 visibility,
518 )
519 });
520 code.unwrap_or_else(|| {
521 let names = group_fns
522 .iter()
523 .map(|fd| fd.name.as_str())
524 .collect::<Vec<_>>()
525 .join(", ");
526 format!(
527 "{}fn __mutual_tco_block_{}_render_error() {{ compile_error!(\"MIR walker could not render mutual-TCO block [{}]\"); }}",
528 visibility, group_id, names
529 )
530 })
531}
532
533fn entry_module_sections(
534 ctx: &CodegenContext,
535 main_fn: Option<&FnDef>,
536 top_level_stmts: &[&crate::ast::Stmt],
537) -> Vec<String> {
538 let mut sections = Vec::new();
539
540 for td in &ctx.type_defs {
541 if is_shared_runtime_type(td) {
542 continue;
543 }
544 sections.push(toplevel::emit_public_type_def(td, ctx));
545 if ctx.emit_replay_runtime {
546 sections.push(replay::emit_replay_value_impl(td));
547 }
548 }
549
550 let non_main_fns: Vec<&FnDef> = ctx.fn_defs.iter().filter(|fd| fd.name != "main").collect();
561 let mutual_groups = toplevel::find_mutual_tco_groups(&non_main_fns);
562
563 for (group_id, group_indices) in mutual_groups.iter().enumerate() {
564 let group_fns: Vec<&FnDef> = group_indices.iter().map(|&idx| non_main_fns[idx]).collect();
565 sections.push(emit_mutual_tco_block_routed(
566 group_id + 1,
567 &group_fns,
568 ctx,
569 None,
570 "pub ",
571 ));
572 }
573
574 for fd in &ctx.fn_defs {
582 let Some(fn_id) = crate::codegen::common::fn_id_for_decl(ctx, fd) else {
583 continue;
584 };
585 let is_mutual = ctx.mutual_tco_members.contains(&fn_id);
586 if fd.name == "main" || is_mutual {
587 continue;
588 }
589 let Some(resolved_fd) = ctx.resolved_program.fn_by_id(fn_id) else {
590 let resolved_owned = ctx.resolve_fn_def(fd, None);
595 sections.push(toplevel::emit_public_fn_def(
596 fd,
597 resolved_owned.as_ref(),
598 ctx,
599 None,
600 ));
601 continue;
602 };
603 sections.push(toplevel::emit_public_fn_def(fd, resolved_fd, ctx, None));
604 }
605
606 if main_fn.is_some() || !top_level_stmts.is_empty() {
607 let main_fn_id = main_fn.and_then(|fd| crate::codegen::common::fn_id_for_decl(ctx, fd));
613 sections.push(toplevel::emit_public_main(
614 main_fn,
615 top_level_stmts,
616 ctx,
617 main_fn_id,
618 ));
619 }
620
621 sections
622}
623
624fn module_sections(module: &crate::codegen::ModuleInfo, ctx: &CodegenContext) -> Vec<String> {
625 let mut sections = Vec::new();
626
627 for td in &module.type_defs {
628 if is_shared_runtime_type(td) {
629 continue;
630 }
631 sections.push(toplevel::emit_public_type_def(td, ctx));
632 if ctx.emit_replay_runtime {
633 sections.push(replay::emit_replay_value_impl(td));
634 }
635 }
636
637 let fn_refs: Vec<&FnDef> = module.fn_defs.iter().collect();
644 let mutual_groups = toplevel::find_mutual_tco_groups(&fn_refs);
645 let module_mutual_owned: HashSet<String> = match module.analysis.as_ref() {
646 Some(a) => a.mutual_tco_members.clone(),
647 None => ctx
648 .mutual_tco_members
649 .iter()
650 .filter_map(|id| {
651 let entry = ctx.symbol_table.fn_entry(*id);
652 entry
653 .key
654 .scope
655 .as_deref()
656 .filter(|s| *s == module.prefix)
657 .map(|_| entry.key.name.clone())
658 })
659 .collect(),
660 };
661 let module_mutual = &module_mutual_owned;
662
663 for (group_id, group_indices) in mutual_groups.iter().enumerate() {
664 let group_fns: Vec<&FnDef> = group_indices.iter().map(|&idx| fn_refs[idx]).collect();
665 sections.push(emit_mutual_tco_block_routed(
666 group_id + 1,
667 &group_fns,
668 ctx,
669 Some(&module.prefix),
670 "pub ",
671 ));
672 }
673
674 for fd in &module.fn_defs {
675 if module_mutual.contains(&fd.name) {
676 continue;
677 }
678 let resolved_fd = crate::codegen::common::fn_id_for_decl(ctx, fd)
683 .and_then(|id| ctx.resolved_program.fn_by_id(id));
684 let resolved_owned = if resolved_fd.is_some() {
685 None
686 } else {
687 Some(ctx.resolve_fn_def(fd, Some(&module.prefix)))
690 };
691 let resolved_ref: &crate::ir::hir::ResolvedFnDef =
692 resolved_fd.unwrap_or_else(|| resolved_owned.as_ref().unwrap().as_ref());
693 sections.push(toplevel::emit_public_fn_def(
694 fd,
695 resolved_ref,
696 ctx,
697 Some(&module.prefix),
698 ));
699 }
700
701 sections
702}
703
704fn root_module_depends(items: &[TopLevel]) -> Vec<String> {
705 items
706 .iter()
707 .find_map(|item| {
708 if let TopLevel::Module(module) = item {
709 Some(module.depends.clone())
710 } else {
711 None
712 }
713 })
714 .unwrap_or_default()
715}
716
717fn detect_used_services(ctx: &CodegenContext) -> HashSet<String> {
719 let mut services = HashSet::new();
720 for item in &ctx.items {
721 if let TopLevel::FnDef(fd) = item {
722 for eff in &fd.effects {
723 services.insert(eff.node.clone());
724 if let Some((service, _)) = eff.node.split_once('.') {
725 services.insert(service.to_string());
726 }
727 }
728 }
729 }
730 for module in &ctx.modules {
731 for fd in &module.fn_defs {
732 for eff in &fd.effects {
733 services.insert(eff.node.clone());
734 if let Some((service, _)) = eff.node.split_once('.') {
735 services.insert(service.to_string());
736 }
737 }
738 }
739 }
740 services
741}
742
743fn is_shared_runtime_type(td: &TypeDef) -> bool {
744 matches!(
745 td,
746 TypeDef::Product { name, .. }
747 if matches!(name.as_str(), "HttpResponse" | "HttpRequest")
748 )
749}
750
751fn needs_named_type(ctx: &CodegenContext, wanted: &str) -> bool {
752 let scan = |params: &[(String, Type)], ret: &Type| -> bool {
757 params.iter().any(|(_, p)| type_contains_named(p, wanted))
758 || type_contains_named(ret, wanted)
759 };
760 if ctx
761 .resolved_program
762 .entry_fns()
763 .any(|rfd| scan(&rfd.params, &rfd.return_type))
764 {
765 return true;
766 }
767 ctx.resolved_program.modules.iter().any(|m| {
768 m.fn_defs
769 .iter()
770 .any(|rfd| scan(&rfd.params, &rfd.return_type))
771 })
772}
773
774fn type_contains_named(ty: &Type, wanted: &str) -> bool {
782 match ty {
783 Type::Named { name, .. } => name == wanted,
784 Type::Result(ok, err) => {
785 type_contains_named(ok, wanted) || type_contains_named(err, wanted)
786 }
787 Type::Option(inner) | Type::List(inner) | Type::Vector(inner) => {
788 type_contains_named(inner, wanted)
789 }
790 Type::Tuple(items) => items.iter().any(|t| type_contains_named(t, wanted)),
791 Type::Map(k, v) => type_contains_named(k, wanted) || type_contains_named(v, wanted),
792 Type::Fn(params, ret, _effects) => {
793 params.iter().any(|t| type_contains_named(t, wanted))
794 || type_contains_named(ret, wanted)
795 }
796 Type::Int
797 | Type::Float
798 | Type::Str
799 | Type::Bool
800 | Type::Unit
801 | Type::Invalid
802 | Type::Var(_) => false,
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 use super::{render_generated_module, synthesize_rust_module_cascade, transpile};
809 use crate::codegen::build_context;
810 use crate::source::parse_source;
811
812 fn ctx_from_source(source: &str, project_name: &str) -> crate::codegen::CodegenContext {
813 let mut items = parse_source(source).expect("source should parse");
814 let pipeline_result = crate::ir::pipeline::run(
825 &mut items,
826 crate::ir::PipelineConfig {
827 typecheck: Some(crate::ir::TypecheckMode::Full { base_dir: None }),
828 run_build_symbols: true,
829 ..Default::default()
830 },
831 );
832 let tc = pipeline_result.typecheck.expect("typecheck was requested");
833 assert!(
834 tc.errors.is_empty(),
835 "source should typecheck without errors: {:?}",
836 tc.errors
837 );
838 build_context(
839 items,
840 &tc,
841 pipeline_result.analysis.as_ref(),
842 project_name.to_string(),
843 vec![],
844 pipeline_result.symbol_table,
845 pipeline_result.resolved_items,
846 )
847 }
848
849 fn ctx_from_multi(
854 entry_src: &str,
855 dep_sources: &[(&str, &str)],
856 project_name: &str,
857 ) -> crate::codegen::CodegenContext {
858 let mut entry_items = parse_source(entry_src).expect("entry source should parse");
859 crate::ir::pipeline::tco(&mut entry_items);
860
861 let loaded: Vec<crate::source::LoadedModule> = dep_sources
866 .iter()
867 .map(|(prefix, src)| {
868 let items = parse_source(src).expect("dep source should parse");
869 crate::source::LoadedModule {
870 dep_name: prefix.to_string(),
871 items,
872 path: std::path::PathBuf::from(format!("{}.av", prefix)),
873 }
874 })
875 .collect();
876
877 let modules: Vec<crate::codegen::ModuleInfo> = loaded
878 .iter()
879 .map(|lm| {
880 let depends = lm
881 .items
882 .iter()
883 .find_map(|i| match i {
884 crate::ast::TopLevel::Module(m) => Some(m.depends.clone()),
885 _ => None,
886 })
887 .unwrap_or_default();
888 let type_defs = lm
889 .items
890 .iter()
891 .filter_map(|i| match i {
892 crate::ast::TopLevel::TypeDef(td) => Some(td.clone()),
893 _ => None,
894 })
895 .collect();
896 let fn_defs = lm
897 .items
898 .iter()
899 .filter_map(|i| match i {
900 crate::ast::TopLevel::FnDef(fd) if fd.name != "main" => Some(fd.clone()),
901 _ => None,
902 })
903 .collect();
904 crate::codegen::ModuleInfo {
905 prefix: lm.dep_name.clone(),
906 depends,
907 type_defs,
908 fn_defs,
909 verify_laws: crate::codegen::collect_verify_laws(&lm.items),
910 analysis: None,
911 }
912 })
913 .collect();
914
915 let tc = crate::ir::pipeline::typecheck(
916 &entry_items,
917 &crate::ir::TypecheckMode::WithLoaded(&loaded),
918 );
919 assert!(
920 tc.errors.is_empty(),
921 "entry+dep source should typecheck without errors: {:?}",
922 tc.errors
923 );
924 let symbol_table = crate::ir::SymbolTable::build(&entry_items, &modules);
925 let resolved_items = crate::ir::hir::resolve_program(&symbol_table, &entry_items);
926 build_context(
927 entry_items,
928 &tc,
929 None,
930 project_name.to_string(),
931 modules,
932 symbol_table,
933 resolved_items,
934 )
935 }
936
937 fn generated_rust_entry_file(out: &crate::codegen::ProjectOutput) -> &str {
938 out.files
939 .iter()
940 .find_map(|(name, content)| {
941 (name == "src/aver_generated/entry/mod.rs").then_some(content.as_str())
942 })
943 .expect("expected generated Rust entry module")
944 }
945
946 fn generated_file<'a>(out: &'a crate::codegen::ProjectOutput, path: &str) -> &'a str {
947 out.files
948 .iter()
949 .find_map(|(name, content)| (name == path).then_some(content.as_str()))
950 .unwrap_or_else(|| panic!("expected generated file '{}'", path))
951 }
952
953 #[test]
954 fn cross_module_same_bare_name_fns_resolve_via_qualified_path() {
955 let mut ctx = ctx_from_multi(
969 r#"
970module Entry
971 depends [Worker]
972 intent = "Entry with own same-bare 'helper'."
973 effects []
974
975fn helper(n: Int) -> Int
976 n + 1
977
978fn main() -> Int
979 helper(10) + Worker.walk(20)
980"#,
981 &[(
982 "Worker",
983 r#"
984module Worker
985 exposes [walk]
986 intent = "Worker module with same-bare 'helper'."
987 effects []
988
989fn helper(n: Int) -> Int
990 n + 100
991
992fn walk(n: Int) -> Int
993 helper(n)
994"#,
995 )],
996 "cross_module_helper",
997 );
998
999 let out = transpile(&mut ctx);
1000
1001 let worker = generated_file(&out, "src/aver_generated/worker/mod.rs");
1002 assert!(
1003 worker.contains("pub fn helper"),
1004 "Worker module must emit its OWN helper:\n{worker}"
1005 );
1006 assert!(
1007 worker.contains("n.add(&aver_rt::AverInt::from_i64(100))"),
1008 "Worker.helper body must keep its OWN literal (100):\n{worker}"
1009 );
1010 assert!(
1015 worker.contains("crate::aver_generated::worker::helper"),
1016 "Worker.walk must call its own helper via the canonical \
1017 crate path (not bare-name `helper(n)`):\n{worker}"
1018 );
1019
1020 let entry = generated_rust_entry_file(&out);
1021 assert!(
1022 entry.contains("pub fn helper"),
1023 "Entry module must emit its OWN helper:\n{entry}"
1024 );
1025 assert!(
1026 entry.contains("n.add(&aver_rt::AverInt::from_i64(1))"),
1027 "Entry.helper body must keep its OWN literal (1):\n{entry}"
1028 );
1029 assert!(
1033 entry.contains("crate::aver_generated::worker::walk"),
1034 "main()'s Worker.walk(20) must qualify through the \
1035 canonical crate path:\n{entry}"
1036 );
1037 }
1038
1039 #[test]
1040 fn emission_banner_appears_in_root_main() {
1041 let mut ctx = ctx_from_source(
1042 r#"
1043module Demo
1044
1045fn main() -> Int
1046 1
1047"#,
1048 "demo",
1049 );
1050
1051 let out = transpile(&mut ctx);
1052 let root_main = generated_file(&out, "src/main.rs");
1053
1054 assert!(root_main.contains("// Aver Rust emission"));
1055 }
1056
1057 #[test]
1058 fn generated_module_imports_direct_depends() {
1059 let rendered = render_generated_module(
1060 vec!["Domain.Types".to_string(), "App.Commands".to_string()],
1061 vec!["pub fn demo() {}".to_string()],
1062 );
1063
1064 assert!(rendered.contains("use crate::aver_generated::domain::types::*;"));
1065 assert!(rendered.contains("use crate::aver_generated::app::commands::*;"));
1066 assert!(rendered.contains("pub fn demo() {}"));
1067 }
1068
1069 #[test]
1070 fn module_tree_files_do_not_reexport_children() {
1071 let modules = vec![(
1072 vec!["app".to_string(), "cli".to_string()],
1073 "pub fn run() {}".to_string(),
1074 )];
1075 let files = synthesize_rust_module_cascade("src/aver_generated", &modules);
1076
1077 let root_mod = files
1078 .iter()
1079 .find(|(path, _)| path == "src/aver_generated/mod.rs")
1080 .map(|(_, content)| content)
1081 .expect("root mod.rs should exist");
1082
1083 assert!(root_mod.contains("pub mod app;"));
1084 assert!(!root_mod.contains("pub use app::*;"));
1085 }
1086
1087 #[test]
1088 fn list_cons_match_uses_cloned_uncons_fast_path_when_optimized() {
1089 let mut ctx = ctx_from_source(
1090 r#"
1091module Demo
1092
1093fn headPlusTailLen(xs: List<Int>) -> Int
1094 match xs
1095 [] -> 0
1096 [h, ..t] -> h + List.len(t)
1097"#,
1098 "demo",
1099 );
1100
1101 let out = transpile(&mut ctx);
1102 let entry = generated_rust_entry_file(&out);
1103
1104 assert!(entry.contains("aver_list_match!"));
1106 }
1107
1108 #[test]
1109 fn list_cons_match_stays_structured_in_semantic_mode() {
1110 let mut ctx = ctx_from_source(
1111 r#"
1112module Demo
1113
1114fn headPlusTailLen(xs: List<Int>) -> Int
1115 match xs
1116 [] -> 0
1117 [h, ..t] -> h + List.len(t)
1118"#,
1119 "demo",
1120 );
1121
1122 let out = transpile(&mut ctx);
1123 let entry = generated_rust_entry_file(&out);
1124
1125 assert!(entry.contains("aver_list_match!"));
1127 }
1128
1129 #[test]
1130 fn list_literal_clones_ident_when_used_afterward() {
1131 let mut ctx = ctx_from_source(
1132 r#"
1133module Demo
1134
1135record Audit
1136 message: String
1137
1138fn useTwice(audit: Audit) -> List<Audit>
1139 first = [audit]
1140 List.concat(first, [audit])
1141"#,
1142 "demo",
1143 );
1144
1145 let out = transpile(&mut ctx);
1146 let entry = generated_rust_entry_file(&out);
1147
1148 assert!(entry.contains("let first = aver_rt::AverList::from_vec(vec![audit.clone()]);"));
1152 assert!(entry.contains("aver_rt::AverList::from_vec(vec![audit.clone()])"));
1154 }
1155
1156 #[test]
1157 fn record_update_clones_base_when_value_is_used_afterward() {
1158 let mut ctx = ctx_from_source(
1159 r#"
1160module Demo
1161
1162record PaymentState
1163 paymentId: String
1164 currency: String
1165
1166fn touch(state: PaymentState) -> String
1167 updated = PaymentState.update(state, currency = "EUR")
1168 "{updated.currency}-{state.paymentId}"
1169"#,
1170 "demo",
1171 );
1172
1173 let out = transpile(&mut ctx);
1174 let entry = generated_rust_entry_file(&out);
1175
1176 assert!(entry.contains("..state.clone()"));
1180 }
1181
1182 #[test]
1183 fn mutual_tco_generates_trampoline_instead_of_regular_calls() {
1184 let mut ctx = ctx_from_source(
1185 r#"
1186module Demo
1187
1188fn isEven(n: Int) -> Bool
1189 match n == 0
1190 true -> true
1191 false -> isOdd(n - 1)
1192
1193fn isOdd(n: Int) -> Bool
1194 match n == 0
1195 true -> false
1196 false -> isEven(n - 1)
1197"#,
1198 "demo",
1199 );
1200
1201 let out = transpile(&mut ctx);
1202 let entry = generated_rust_entry_file(&out);
1203
1204 assert!(entry.contains("enum __MutualTco1"));
1206 assert!(entry.contains("fn __mutual_tco_trampoline_1"));
1207 assert!(entry.contains("loop {"));
1208
1209 assert!(entry.contains("pub fn isEven"));
1211 assert!(entry.contains("pub fn isOdd"));
1212 assert!(entry.contains("__mutual_tco_trampoline_1("));
1213
1214 assert!(!entry.contains("isOdd((n - 1i64))"));
1216 }
1217
1218 #[test]
1219 fn field_access_does_not_double_clone() {
1220 let mut ctx = ctx_from_source(
1221 r#"
1222module Demo
1223
1224record User
1225 name: String
1226 age: Int
1227
1228fn greet(u: User) -> String
1229 u.name
1230"#,
1231 "demo",
1232 );
1233
1234 let out = transpile(&mut ctx);
1235 let entry = generated_rust_entry_file(&out);
1236
1237 assert!(
1239 !entry.contains(".clone().clone()"),
1240 "double clone detected in generated code:\n{}",
1241 entry
1242 );
1243 }
1244
1245 #[test]
1246 fn borrowed_record_field_return_clones_for_owned_result() {
1247 let mut ctx = ctx_from_source(
1248 r#"
1249module Demo
1250
1251record User
1252 name: String
1253
1254fn getName(user: User) -> String
1255 user.name
1256"#,
1257 "demo",
1258 );
1259
1260 let out = transpile(&mut ctx);
1261 let entry = generated_rust_entry_file(&out);
1262
1263 assert!(entry.contains("pub fn getName(user: &User) -> AverStr"));
1264 assert!(
1265 entry.contains("user.name.clone()"),
1266 "missing owned clone:\n{}",
1267 entry
1268 );
1269 }
1270
1271 #[test]
1272 fn vector_get_with_literal_default_lowers_to_direct_unwrap_or_code() {
1273 let mut ctx = ctx_from_source(
1274 r#"
1275module Demo
1276
1277fn cellAt(grid: Vector<Int>, idx: Int) -> Int
1278 Option.withDefault(Vector.get(grid, idx), 0)
1279"#,
1280 "demo",
1281 );
1282
1283 let out = transpile(&mut ctx);
1284 let entry = generated_rust_entry_file(&out);
1285
1286 assert!(entry.contains(
1287 "(idx).to_usize().and_then(|__i| grid.get(__i).cloned()).unwrap_or(aver_rt::AverInt::from_i64(0))"
1288 ));
1289 }
1290
1291 #[test]
1292 fn vector_set_default_stays_structured_in_semantic_mode() {
1293 let mut ctx = ctx_from_source(
1294 r#"
1295module Demo
1296
1297fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
1298 Option.withDefault(Vector.set(vec, idx, value), vec)
1299"#,
1300 "demo",
1301 );
1302
1303 let out = transpile(&mut ctx);
1304 let entry = generated_rust_entry_file(&out);
1305
1306 assert!(entry.contains("set_unchecked"));
1308 assert!(!entry.contains(".unwrap_or("));
1309 }
1310
1311 #[test]
1312 fn vector_set_default_uses_ir_leaf_fast_path_when_optimized() {
1313 let mut ctx = ctx_from_source(
1314 r#"
1315module Demo
1316
1317fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
1318 Option.withDefault(Vector.set(vec, idx, value), vec)
1319"#,
1320 "demo",
1321 );
1322
1323 let out = transpile(&mut ctx);
1324 let entry = generated_rust_entry_file(&out);
1325
1326 assert!(entry.contains("set_unchecked"));
1327 assert!(!entry.contains(".unwrap_or("));
1328 }
1329
1330 #[test]
1331 fn vector_set_uses_owned_update_lowering() {
1332 let mut ctx = ctx_from_source(
1333 r#"
1334module Demo
1335
1336fn update(vec: Vector<Int>, idx: Int, value: Int) -> Option<Vector<Int>>
1337 Vector.set(vec, idx, value)
1338"#,
1339 "demo",
1340 );
1341
1342 let out = transpile(&mut ctx);
1343 let entry = generated_rust_entry_file(&out);
1344
1345 assert!(entry.contains(".set_owned("));
1346 assert!(!entry.contains(".set(idx as usize,"));
1347 }
1348
1349 #[test]
1350 fn map_remove_uses_owned_update_lowering() {
1351 let mut ctx = ctx_from_source(
1352 r#"
1353module Demo
1354
1355fn dropKey(m: Map<String, Int>, key: String) -> Map<String, Int>
1356 Map.remove(m, key)
1357"#,
1358 "demo",
1359 );
1360
1361 let out = transpile(&mut ctx);
1362 let entry = generated_rust_entry_file(&out);
1363
1364 assert!(entry.contains(".remove_owned(&"));
1365 }
1366
1367 #[test]
1368 fn semantic_keeps_known_leaf_wrapper_call_structured() {
1369 let mut ctx = ctx_from_source(
1370 r#"
1371module Demo
1372
1373fn cellAt(grid: Vector<Int>, idx: Int) -> Int
1374 Option.withDefault(Vector.get(grid, idx), 0)
1375
1376fn read(grid: Vector<Int>, idx: Int) -> Int
1377 cellAt(grid, idx)
1378"#,
1379 "demo",
1380 );
1381
1382 let out = transpile(&mut ctx);
1383 let entry = generated_rust_entry_file(&out);
1384
1385 assert!(entry.contains("cellAt(grid, idx)"));
1386 assert!(!entry.contains("__aver_thin_arg0"));
1387 }
1388
1389 #[test]
1390 fn optimized_keeps_known_leaf_wrapper_callsite_and_leaves_absorption_to_rust() {
1391 let mut ctx = ctx_from_source(
1392 r#"
1393module Demo
1394
1395fn cellAt(grid: Vector<Int>, idx: Int) -> Int
1396 Option.withDefault(Vector.get(grid, idx), 0)
1397
1398fn read(grid: Vector<Int>, idx: Int) -> Int
1399 cellAt(grid, idx)
1400"#,
1401 "demo",
1402 );
1403
1404 let out = transpile(&mut ctx);
1405 let entry = generated_rust_entry_file(&out);
1406
1407 assert!(entry.contains("cellAt(grid, idx)"));
1408 assert!(!entry.contains("__aver_thin_arg0"));
1409 }
1410
1411 #[test]
1412 fn optimized_keeps_known_dispatch_wrapper_callsite_and_leaves_absorption_to_rust() {
1413 let mut ctx = ctx_from_source(
1414 r#"
1415module Demo
1416
1417fn bucket(n: Int) -> Int
1418 match n == 0
1419 true -> 0
1420 false -> 1
1421
1422fn readBucket(n: Int) -> Int
1423 bucket(n)
1424"#,
1425 "demo",
1426 );
1427
1428 let out = transpile(&mut ctx);
1429 let entry = generated_rust_entry_file(&out);
1430
1431 assert!(entry.contains("bucket(n)"));
1432 assert!(!entry.contains("__aver_thin_arg0"));
1433 }
1434
1435 #[test]
1436 fn bool_match_on_gte_normalizes_to_base_comparison_when_optimized() {
1437 let mut ctx = ctx_from_source(
1438 r#"
1439module Demo
1440
1441fn bucket(n: Int) -> Int
1442 match n >= 10
1443 true -> 7
1444 false -> 3
1445"#,
1446 "demo",
1447 );
1448
1449 let out = transpile(&mut ctx);
1450 let entry = generated_rust_entry_file(&out);
1451
1452 assert!(entry.contains("if (n < aver_rt::AverInt::from_i64(10)) { aver_rt::AverInt::from_i64(3) } else { aver_rt::AverInt::from_i64(7) }"));
1453 }
1454
1455 #[test]
1456 fn bool_match_stays_as_match_in_semantic_mode() {
1457 let mut ctx = ctx_from_source(
1458 r#"
1459module Demo
1460
1461fn bucket(n: Int) -> Int
1462 match n >= 10
1463 true -> 7
1464 false -> 3
1465"#,
1466 "demo",
1467 );
1468
1469 let out = transpile(&mut ctx);
1470 let entry = generated_rust_entry_file(&out);
1471
1472 assert!(entry.contains("if (n < aver_rt::AverInt::from_i64(10)) { aver_rt::AverInt::from_i64(3) } else { aver_rt::AverInt::from_i64(7) }"));
1474 }
1475
1476 #[test]
1477 fn wave2_generic_user_sum_match_emits_native_rust_match() {
1478 let mut ctx = ctx_from_source(
1486 r#"
1487module Demo
1488
1489type Shape
1490 Circle(Float)
1491 Square(Float)
1492
1493fn area(sh: Shape) -> Float
1494 match sh
1495 Shape.Circle(r) -> r * r * 3.14
1496 Shape.Square(s) -> s * s
1497"#,
1498 "demo",
1499 );
1500
1501 let out = transpile(&mut ctx);
1502 let entry = generated_rust_entry_file(&out);
1503
1504 assert!(
1505 entry.contains("match sh.clone() {"),
1506 "generic user-sum match should emit a native Rust match on the cloned subject: {entry}"
1507 );
1508 assert!(entry.contains("Shape::Circle(r) =>"));
1509 assert!(entry.contains("Shape::Square(s) =>"));
1510 }
1511
1512 #[test]
1513 fn optimized_self_tco_uses_dispatch_table_for_wrapper_match() {
1514 let mut ctx = ctx_from_source(
1515 r#"
1516module Demo
1517
1518fn loop(r: Result<Int, String>) -> Int
1519 match r
1520 Result.Ok(n) -> n
1521 Result.Err(_) -> loop(Result.Ok(1))
1522"#,
1523 "demo",
1524 );
1525
1526 let out = transpile(&mut ctx);
1527 let entry = generated_rust_entry_file(&out);
1528
1529 assert!(entry.contains("match r {"));
1534 assert!(entry.contains("Ok(n)"));
1535 assert!(!entry.contains("__dispatch_subject"));
1536 }
1537
1538 #[test]
1539 fn optimized_mutual_tco_uses_dispatch_table_for_wrapper_match() {
1540 let mut ctx = ctx_from_source(
1541 r#"
1542module Demo
1543
1544fn left(r: Result<Int, String>) -> Int
1545 match r
1546 Result.Ok(n) -> n
1547 Result.Err(_) -> right(Result.Ok(1))
1548
1549fn right(r: Result<Int, String>) -> Int
1550 match r
1551 Result.Ok(n) -> n
1552 Result.Err(_) -> left(Result.Ok(1))
1553"#,
1554 "demo",
1555 );
1556
1557 let out = transpile(&mut ctx);
1558 let entry = generated_rust_entry_file(&out);
1559
1560 assert!(entry.contains("match r {"));
1565 assert!(entry.contains("Ok(n)"));
1566 assert!(!entry.contains("__dispatch_subject"));
1567 }
1568
1569 #[test]
1570 fn single_field_variant_display_avoids_vec_join() {
1571 let mut ctx = ctx_from_source(
1572 r#"
1573module Demo
1574
1575type Wrapper
1576 Wrap(Int)
1577 Pair(Int, Int)
1578 Empty
1579"#,
1580 "demo",
1581 );
1582
1583 let out = transpile(&mut ctx);
1584 let entry = generated_rust_entry_file(&out);
1585
1586 assert!(
1588 !entry.contains("vec![f0.aver_display_inner()].join"),
1589 "single-field variant should use direct format, not vec join:\n{}",
1590 entry
1591 );
1592 assert!(
1594 entry.contains("vec![f0.aver_display_inner(), f1.aver_display_inner()].join(\", \")"),
1595 "multi-field variant should use vec join:\n{}",
1596 entry
1597 );
1598 }
1599
1600 #[test]
1601 fn replay_codegen_wraps_guest_entry_in_scoped_runtime() {
1602 let mut ctx = ctx_from_source(
1603 r#"
1604module Demo
1605
1606fn runGuestProgram(path: String) -> Result<String, String>
1607 ! [Disk.readText]
1608 Disk.readText(path)
1609"#,
1610 "demo",
1611 );
1612 ctx.emit_replay_runtime = true;
1613 ctx.guest_entry = Some("runGuestProgram".to_string());
1614
1615 let out = transpile(&mut ctx);
1616 let entry = generated_rust_entry_file(&out);
1617 let replay_support = generated_file(&out, "src/replay_support.rs");
1618 let cargo_toml = generated_file(&out, "Cargo.toml");
1619
1620 assert!(entry.contains("aver_replay::with_guest_scope_result(\"runGuestProgram\""));
1621 assert!(replay_support.contains("pub mod aver_replay"));
1622 assert!(cargo_toml.contains("serde_json = \"1\""));
1623 }
1624
1625 #[test]
1626 fn replay_codegen_uses_guest_args_param_override_when_present() {
1627 let mut ctx = ctx_from_source(
1628 r#"
1629module Demo
1630
1631fn runGuestProgram(path: String, guestArgs: List<String>) -> Result<String, String>
1632 ! [Args.get]
1633 Result.Ok(String.join(Args.get(), ","))
1634"#,
1635 "demo",
1636 );
1637 ctx.emit_replay_runtime = true;
1638 ctx.guest_entry = Some("runGuestProgram".to_string());
1639
1640 let out = transpile(&mut ctx);
1641 let entry = generated_rust_entry_file(&out);
1642 let cargo_toml = generated_file(&out, "Cargo.toml");
1643
1644 assert!(entry.contains("aver_replay::with_guest_scope_args_result(\"runGuestProgram\""));
1645 assert!(entry.contains("guestArgs.clone()"));
1646 assert!(cargo_toml.contains("edition = \"2024\""));
1647 }
1648
1649 #[test]
1650 fn replay_codegen_wraps_root_main_when_no_guest_entry_is_set() {
1651 let mut ctx = ctx_from_source(
1652 r#"
1653module Demo
1654
1655fn main() -> Result<String, String>
1656 ! [Disk.readText]
1657 Disk.readText("demo.av")
1658"#,
1659 "demo",
1660 );
1661 ctx.emit_replay_runtime = true;
1662
1663 let out = transpile(&mut ctx);
1664 let root_main = generated_file(&out, "src/main.rs");
1665
1666 assert!(
1667 root_main.contains("aver_replay::with_guest_scope(\"main\", serde_json::Value::Null")
1668 );
1669 }
1670
1671 #[test]
1672 fn runtime_policy_codegen_uses_runtime_loader() {
1673 let mut ctx = ctx_from_source(
1674 r#"
1675module Demo
1676
1677fn main() -> Result<String, String>
1678 ! [Disk.readText]
1679 Disk.readText("demo.av")
1680"#,
1681 "demo",
1682 );
1683 ctx.emit_replay_runtime = true;
1684 ctx.runtime_policy_from_env = true;
1685
1686 let out = transpile(&mut ctx);
1687 let root_main = generated_file(&out, "src/main.rs");
1688 let replay_support = generated_file(&out, "src/replay_support.rs");
1689 let cargo_toml = generated_file(&out, "Cargo.toml");
1690
1691 assert!(!root_main.contains("mod policy_support;"));
1692 assert!(replay_support.contains("load_runtime_policy_from_env"));
1693 assert!(cargo_toml.contains("url = \"2\""));
1694 assert!(cargo_toml.contains("toml = \"0.8\""));
1695 }
1696
1697 #[test]
1698 fn replay_codegen_can_keep_embedded_policy_when_requested() {
1699 let mut ctx = ctx_from_source(
1700 r#"
1701module Demo
1702
1703fn main() -> Result<String, String>
1704 ! [Disk.readText]
1705 Disk.readText("demo.av")
1706"#,
1707 "demo",
1708 );
1709 ctx.emit_replay_runtime = true;
1710 ctx.policy = Some(crate::config::ProjectConfig {
1711 effect_policies: std::collections::HashMap::new(),
1712 check_suppressions: Vec::new(),
1713 independence_mode: crate::config::IndependenceMode::default(),
1714 shape_layers: Vec::new(),
1715 shape_expected: Vec::new(),
1716 });
1717
1718 let out = transpile(&mut ctx);
1719 let root_main = generated_file(&out, "src/main.rs");
1720 let replay_support = generated_file(&out, "src/replay_support.rs");
1721
1722 assert!(root_main.contains("mod policy_support;"));
1723 assert!(replay_support.contains("aver_policy::check_disk"));
1724 assert!(!replay_support.contains("RuntimeEffectPolicy"));
1725 }
1726
1727 #[test]
1728 fn self_host_support_is_emitted_as_separate_module() {
1729 let mut ctx = ctx_from_source(
1730 r#"
1731module Demo
1732
1733fn runGuestProgram(prog: Int, moduleFns: Int) -> Result<String, String>
1734 Result.Ok("ok")
1735"#,
1736 "demo",
1737 );
1738 ctx.emit_self_host_support = true;
1739 ctx.guest_entry = Some("runGuestProgram".to_string());
1740
1741 let out = transpile(&mut ctx);
1742 let root_main = generated_file(&out, "src/main.rs");
1743 let runtime_support = generated_file(&out, "src/runtime_support.rs");
1744 let self_host_support = generated_file(&out, "src/self_host_support.rs");
1745 let entry = generated_rust_entry_file(&out);
1746
1747 assert!(root_main.contains("mod self_host_support;"));
1748 assert!(!runtime_support.contains("with_fn_store"));
1749 assert!(self_host_support.contains("pub fn with_program_fn_store"));
1750 assert!(entry.contains("crate::self_host_support::with_program_fn_store("));
1751 }
1752
1753 #[test]
1754 fn independent_product_codegen_avoids_string_specific_error_type() {
1755 let mut ctx = ctx_from_source(
1756 r#"
1757module Demo
1758
1759fn left() -> Result<Int, Int>
1760 Result.Ok(1)
1761
1762fn right() -> Result<Int, Int>
1763 Result.Ok(2)
1764
1765fn main() -> Result<Tuple<Int, Int>, Int>
1766 data = (left(), right())?!
1767 Result.Ok(data)
1768"#,
1769 "demo",
1770 );
1771 ctx.emit_replay_runtime = true;
1772
1773 let out = transpile(&mut ctx);
1774 let entry = generated_rust_entry_file(&out);
1775
1776 assert!(!entry.contains("Ok::<_, aver_rt::AverStr>"));
1777 assert!(entry.contains("crate::aver_replay::exit_effect_group();"));
1778 assert!(entry.contains("match (_r0, _r1)"));
1779 assert!(!entry.contains("let _r0 = left()?;"));
1780 }
1781
1782 #[test]
1783 fn independent_product_codegen_emits_cancel_runtime_and_scope_propagation() {
1784 let mut ctx = ctx_from_source(
1785 r#"
1786module Demo
1787
1788fn left() -> Result<Int, String>
1789 Result.Ok(1)
1790
1791fn right() -> Result<Int, String>
1792 Result.Ok(2)
1793
1794fn main() -> Result<Tuple<Int, Int>, String>
1795 data = (left(), right())?!
1796 Result.Ok(data)
1797"#,
1798 "demo",
1799 );
1800 ctx.emit_replay_runtime = true;
1801 ctx.policy = Some(crate::config::ProjectConfig {
1802 effect_policies: std::collections::HashMap::new(),
1803 check_suppressions: Vec::new(),
1804 independence_mode: crate::config::IndependenceMode::Cancel,
1805 shape_layers: Vec::new(),
1806 shape_expected: Vec::new(),
1807 });
1808
1809 let out = transpile(&mut ctx);
1810 let entry = generated_rust_entry_file(&out);
1811 let runtime_support = generated_file(&out, "src/runtime_support.rs");
1812 let replay_support = generated_file(&out, "src/replay_support.rs");
1813
1814 assert!(entry.contains("crate::run_cancelable_branch"));
1815 assert!(entry.contains("capture_parallel_scope_context"));
1816 assert!(entry.contains("_s.spawn(move ||"));
1817 assert!(runtime_support.contains("pub fn run_cancelable_branch"));
1818 assert!(runtime_support.contains("panic_any(AverCancelled)"));
1819 assert!(replay_support.contains("pub fn capture_parallel_scope_context"));
1820 assert!(replay_support.contains("pub fn independence_mode_is_cancel()"));
1821 }
1822
1823 #[test]
1824 fn runtime_policy_codegen_parses_independence_mode() {
1825 let mut ctx = ctx_from_source(
1826 r#"
1827module Demo
1828
1829fn main() -> Result<String, String>
1830 ! [Disk.readText]
1831 Disk.readText("demo.av")
1832"#,
1833 "demo",
1834 );
1835 ctx.emit_replay_runtime = true;
1836 ctx.runtime_policy_from_env = true;
1837
1838 let out = transpile(&mut ctx);
1839 let replay_support = generated_file(&out, "src/replay_support.rs");
1840
1841 assert!(replay_support.contains("independence_mode_cancel"));
1842 assert!(replay_support.contains("[independence].mode must be 'complete' or 'cancel'"));
1843 }
1844
1845 #[test]
1846 fn effectful_codegen_inserts_cancel_checkpoint_before_builtin_calls() {
1847 let mut ctx = ctx_from_source(
1848 r#"
1849module Demo
1850
1851fn main() -> Result<String, String>
1852 ! [Disk.readText]
1853 Disk.readText("demo.av")
1854"#,
1855 "demo",
1856 );
1857
1858 let out = transpile(&mut ctx);
1859 let entry = generated_rust_entry_file(&out);
1860
1861 assert!(entry.contains("crate::cancel_checkpoint(); (aver_rt::read_text"));
1862 }
1863
1864 #[test]
1865 fn replay_support_matches_group_effects_by_occurrence_and_args() {
1866 let mut ctx = ctx_from_source(
1867 r#"
1868module Demo
1869
1870fn left() -> Result<Int, String>
1871 Result.Ok(1)
1872
1873fn right() -> Result<Int, String>
1874 Result.Ok(2)
1875
1876fn main() -> Result<Tuple<Int, Int>, String>
1877 data = (left(), right())?!
1878 Result.Ok(data)
1879"#,
1880 "demo",
1881 );
1882 ctx.emit_replay_runtime = true;
1883
1884 let out = transpile(&mut ctx);
1885 let replay_support = generated_file(&out, "src/replay_support.rs");
1886
1887 assert!(replay_support.contains("candidate.effect_occurrence"));
1888 assert!(replay_support.contains("candidate.args != args"));
1889 }
1890}