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