1mod builtins;
5mod expr;
6mod liveness;
7mod pattern;
8mod policy;
9mod project;
10mod replay;
11mod runtime;
12mod self_host;
13mod syntax;
14mod toplevel;
15mod types;
16
17use std::collections::{BTreeMap, HashSet};
18
19use crate::ast::{FnDef, TopLevel, TypeDef};
20use crate::codegen::common::module_prefix_to_rust_segments;
21use crate::codegen::{CodegenContext, ProjectOutput};
22use crate::types::Type;
23
24#[derive(Default)]
25struct ModuleTreeNode {
26 content: Option<String>,
27 children: BTreeMap<String, ModuleTreeNode>,
28}
29
30pub fn transpile(ctx: &CodegenContext) -> ProjectOutput {
32 let has_embedded_policy = ctx.policy.is_some();
33 let has_runtime_policy = ctx.runtime_policy_from_env;
34 let embedded_independence_cancel = ctx
35 .policy
36 .as_ref()
37 .is_some_and(|config| config.independence_mode == crate::config::IndependenceMode::Cancel);
38 let used_services = detect_used_services(ctx);
39 let needs_http_types = needs_named_type(ctx, "Header")
40 || needs_named_type(ctx, "HttpResponse")
41 || needs_named_type(ctx, "HttpRequest");
42 let needs_tcp_types = needs_named_type(ctx, "Tcp.Connection");
43 let needs_terminal_types = needs_named_type(ctx, "Terminal.Size");
44
45 let has_tcp_runtime = used_services.contains("Tcp");
46 let has_http_runtime = used_services.contains("Http");
47 let has_http_server_runtime = used_services.contains("HttpServer");
48 let has_terminal_runtime = used_services.contains("Terminal");
49
50 let has_tcp_types = has_tcp_runtime || needs_tcp_types;
51 let has_http_types = has_http_runtime || has_http_server_runtime || needs_http_types;
52 let has_http_server_types = has_http_server_runtime || needs_named_type(ctx, "HttpRequest");
53 let has_terminal_types = has_terminal_runtime || needs_terminal_types;
54
55 let main_fn = ctx.fn_defs.iter().find(|fd| fd.name == "main");
56 let top_level_stmts: Vec<_> = ctx
57 .items
58 .iter()
59 .filter_map(|item| {
60 if let TopLevel::Stmt(stmt) = item {
61 Some(stmt)
62 } else {
63 None
64 }
65 })
66 .collect();
67 let verify_blocks: Vec<_> = ctx
68 .items
69 .iter()
70 .filter_map(|item| {
71 if let TopLevel::Verify(vb) = item {
72 Some(vb)
73 } else {
74 None
75 }
76 })
77 .collect();
78
79 let mut files = vec![
80 (
81 "Cargo.toml".to_string(),
82 project::generate_cargo_toml(
83 &ctx.project_name,
84 &used_services,
85 has_embedded_policy,
86 has_runtime_policy,
87 ctx.emit_replay_runtime,
88 &std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("aver-rt"),
89 ),
90 ),
91 (
92 "src/main.rs".to_string(),
93 render_root_main(
94 main_fn,
95 has_embedded_policy,
96 ctx.emit_replay_runtime,
97 ctx.guest_entry.as_deref(),
98 !verify_blocks.is_empty(),
99 ctx.emit_self_host_support,
100 ),
101 ),
102 (
103 "src/runtime_support.rs".to_string(),
104 render_runtime_support(
105 has_tcp_types,
106 has_http_types,
107 has_http_server_types,
108 ctx.emit_replay_runtime,
109 embedded_independence_cancel,
110 ),
111 ),
112 ];
113
114 if ctx.emit_self_host_support {
115 files.push((
116 "src/self_host_support.rs".to_string(),
117 self_host::generate_self_host_support(),
118 ));
119 }
120
121 if has_embedded_policy && let Some(config) = &ctx.policy {
122 files.push((
123 "src/policy_support.rs".to_string(),
124 format!("{}\n", policy::generate_policy_runtime(config)),
125 ));
126 }
127
128 if ctx.emit_replay_runtime {
129 files.push((
130 "src/replay_support.rs".to_string(),
131 replay::generate_replay_runtime(
132 has_embedded_policy,
133 has_runtime_policy,
134 has_terminal_types,
135 has_tcp_types,
136 has_http_types,
137 has_http_server_types,
138 embedded_independence_cancel,
139 ),
140 ));
141 }
142
143 if !verify_blocks.is_empty() {
144 files.push((
145 "src/verify.rs".to_string(),
146 render_verify_module(&verify_blocks, ctx),
147 ));
148 }
149
150 let mut module_tree = ModuleTreeNode::default();
151 insert_module_content(
152 &mut module_tree,
153 &[String::from("entry")],
154 render_generated_module(
155 root_module_depends(&ctx.items),
156 entry_module_sections(ctx, main_fn, &top_level_stmts),
157 ),
158 );
159
160 for module in &ctx.modules {
161 let path = module_prefix_to_rust_segments(&module.prefix);
162 insert_module_content(
163 &mut module_tree,
164 &path,
165 render_generated_module(module.depends.clone(), module_sections(module, ctx)),
166 );
167 }
168
169 emit_module_tree_files(&module_tree, "src/aver_generated", &mut files);
170 files.sort_by(|left, right| left.0.cmp(&right.0));
171
172 ProjectOutput { files }
173}
174
175fn render_root_main(
176 main_fn: Option<&FnDef>,
177 has_policy: bool,
178 has_replay: bool,
179 guest_entry: Option<&str>,
180 has_verify: bool,
181 has_self_host_support: bool,
182) -> String {
183 let mut sections = vec![
184 "#![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(),
185 "// Aver Rust emission".to_string(),
186 "#[macro_use] extern crate aver_rt;".to_string(),
187 "pub use ::aver_rt::AverMap as HashMap;".to_string(),
188 "pub use ::aver_rt::AverStr;".to_string(),
189 String::new(),
190 "mod runtime_support;".to_string(),
191 "pub use runtime_support::*;".to_string(),
192 ];
193
194 if has_policy {
195 sections.push(String::new());
196 sections.push("mod policy_support;".to_string());
197 sections.push("pub use policy_support::*;".to_string());
198 }
199
200 if has_replay {
201 sections.push(String::new());
202 sections.push("mod replay_support;".to_string());
203 sections.push("pub use replay_support::*;".to_string());
204 }
205
206 if has_self_host_support {
207 sections.push(String::new());
208 sections.push("mod self_host_support;".to_string());
209 }
210
211 sections.push(String::new());
212 sections.push("pub mod aver_generated;".to_string());
213
214 if has_verify {
215 sections.push(String::new());
216 sections.push("#[cfg(test)]".to_string());
217 sections.push("mod verify;".to_string());
218 }
219
220 sections.push(String::new());
222 let returns_result = main_fn.is_some_and(|fd| fd.return_type.starts_with("Result<"));
223 let result_unit_string =
224 main_fn.is_some_and(|fd| fd.return_type.replace(' ', "") == "Result<Unit,String>");
225 if returns_result {
226 if result_unit_string {
227 sections.push("fn main() {".to_string());
228 sections.push(" let child = std::thread::Builder::new()".to_string());
229 sections.push(" .stack_size(256 * 1024 * 1024)".to_string());
230 if has_replay && guest_entry.is_none() {
231 sections.push(" .spawn(|| {".to_string());
232 sections.push(" let __result = aver_replay::with_guest_scope(\"main\", serde_json::Value::Null, aver_generated::entry::main);".to_string());
233 sections.push(" __result.map_err(|e| e.to_string())".to_string());
234 sections.push(" })".to_string());
235 } else {
236 sections.push(" .spawn(|| {".to_string());
237 sections
238 .push(" let __result = aver_generated::entry::main();".to_string());
239 sections.push(" __result.map_err(|e| e.to_string())".to_string());
240 sections.push(" })".to_string());
241 }
242 sections.push(" .expect(\"thread spawn\");".to_string());
243 sections.push(" match child.join().expect(\"thread join\") {".to_string());
244 sections.push(" Ok(()) => {}".to_string());
245 sections.push(" Err(e) => {".to_string());
246 sections.push(" eprintln!(\"{}\", e);".to_string());
247 sections.push(" std::process::exit(1);".to_string());
248 sections.push(" }".to_string());
249 sections.push(" }".to_string());
250 } else {
251 let ret_type = types::type_annotation_to_rust(&main_fn.unwrap().return_type);
252 sections.push(format!("fn main() -> {} {{", ret_type));
253 if has_replay && guest_entry.is_none() {
254 sections.push(
255 " aver_replay::with_guest_scope(\"main\", serde_json::Value::Null, aver_generated::entry::main)"
256 .to_string(),
257 );
258 } else {
259 sections.push(" aver_generated::entry::main()".to_string());
260 }
261 }
262 } else {
263 sections.push("fn main() {".to_string());
264 if main_fn.is_some() {
265 sections.push(" let child = std::thread::Builder::new()".to_string());
266 sections.push(" .stack_size(256 * 1024 * 1024)".to_string());
267 if has_replay && guest_entry.is_none() {
268 sections.push(" .spawn(|| aver_replay::with_guest_scope(\"main\", serde_json::Value::Null, || aver_generated::entry::main()))".to_string());
269 } else {
270 sections.push(" .spawn(|| aver_generated::entry::main())".to_string());
271 }
272 sections.push(" .expect(\"thread spawn\");".to_string());
273 sections.push(" child.join().expect(\"thread join\");".to_string());
274 }
275 }
276 sections.push("}".to_string());
277 sections.push(String::new());
278
279 sections.join("\n")
280}
281
282fn render_runtime_support(
283 has_tcp_types: bool,
284 has_http_types: bool,
285 has_http_server_types: bool,
286 has_replay: bool,
287 embedded_independence_cancel: bool,
288) -> String {
289 let mut sections = vec![runtime::generate_runtime(
290 has_replay,
291 has_http_server_types,
292 embedded_independence_cancel,
293 )];
294 if has_tcp_types {
295 sections.push(runtime::generate_tcp_types());
296 }
297 if has_http_types {
298 sections.push(runtime::generate_http_types());
299 }
300 if has_http_server_types {
301 sections.push(runtime::generate_http_server_types());
302 }
303 format!("{}\n", sections.join("\n\n"))
304}
305
306fn render_verify_module(
307 verify_blocks: &[&crate::ast::VerifyBlock],
308 ctx: &CodegenContext,
309) -> String {
310 [
311 "#[allow(unused_imports)]".to_string(),
312 "use crate::*;".to_string(),
313 "#[allow(unused_imports)]".to_string(),
314 "use crate::aver_generated::entry::*;".to_string(),
315 String::new(),
316 toplevel::emit_verify_blocks(verify_blocks, ctx),
317 String::new(),
318 ]
319 .join("\n")
320}
321
322fn render_generated_module(depends: Vec<String>, sections: Vec<String>) -> String {
323 if sections.is_empty() {
324 String::new()
325 } else {
326 let mut lines = vec![
327 "#[allow(unused_imports)]".to_string(),
328 "use crate::*;".to_string(),
329 ];
330 for dep in depends {
331 let path = module_prefix_to_rust_segments(&dep).join("::");
332 lines.push("#[allow(unused_imports)]".to_string());
333 lines.push(format!("use crate::aver_generated::{}::*;", path));
334 }
335 lines.push(String::new());
336 lines.push(sections.join("\n\n"));
337 lines.push(String::new());
338 lines.join("\n")
339 }
340}
341
342fn entry_module_sections(
343 ctx: &CodegenContext,
344 main_fn: Option<&FnDef>,
345 top_level_stmts: &[&crate::ast::Stmt],
346) -> Vec<String> {
347 let mut sections = Vec::new();
348
349 for td in &ctx.type_defs {
350 if is_shared_runtime_type(td) {
351 continue;
352 }
353 sections.push(toplevel::emit_public_type_def(td, ctx));
354 if ctx.emit_replay_runtime {
355 sections.push(replay::emit_replay_value_impl(td));
356 }
357 }
358
359 let non_main_fns: Vec<&FnDef> = ctx.fn_defs.iter().filter(|fd| fd.name != "main").collect();
361 let mutual_groups = toplevel::find_mutual_tco_groups(&non_main_fns);
362 let mut mutual_tco_members: HashSet<String> = HashSet::new();
363
364 for (group_id, group_indices) in mutual_groups.iter().enumerate() {
365 let group_fns: Vec<&FnDef> = group_indices.iter().map(|&idx| non_main_fns[idx]).collect();
366 for fd in &group_fns {
367 mutual_tco_members.insert(fd.name.clone());
368 }
369 sections.push(toplevel::emit_mutual_tco_block(
370 group_id + 1,
371 &group_fns,
372 ctx,
373 "pub ",
374 ));
375 }
376
377 for fd in &ctx.fn_defs {
378 if fd.name == "main" || mutual_tco_members.contains(&fd.name) {
379 continue;
380 }
381 let is_memo = ctx.memo_fns.contains(&fd.name);
382 sections.push(toplevel::emit_public_fn_def(fd, is_memo, ctx));
383 }
384
385 if main_fn.is_some() || !top_level_stmts.is_empty() {
386 sections.push(toplevel::emit_public_main(main_fn, top_level_stmts, ctx));
387 }
388
389 sections
390}
391
392fn module_sections(module: &crate::codegen::ModuleInfo, ctx: &CodegenContext) -> Vec<String> {
393 let mut sections = Vec::new();
394
395 for td in &module.type_defs {
396 if is_shared_runtime_type(td) {
397 continue;
398 }
399 sections.push(toplevel::emit_public_type_def(td, ctx));
400 if ctx.emit_replay_runtime {
401 sections.push(replay::emit_replay_value_impl(td));
402 }
403 }
404
405 let fn_refs: Vec<&FnDef> = module.fn_defs.iter().collect();
407 let mutual_groups = toplevel::find_mutual_tco_groups(&fn_refs);
408 let mut mutual_tco_members: HashSet<String> = HashSet::new();
409
410 for (group_id, group_indices) in mutual_groups.iter().enumerate() {
411 let group_fns: Vec<&FnDef> = group_indices.iter().map(|&idx| fn_refs[idx]).collect();
412 for fd in &group_fns {
413 mutual_tco_members.insert(fd.name.clone());
414 }
415 sections.push(toplevel::emit_mutual_tco_block(
416 group_id + 1,
417 &group_fns,
418 ctx,
419 "pub ",
420 ));
421 }
422
423 for fd in &module.fn_defs {
424 if mutual_tco_members.contains(&fd.name) {
425 continue;
426 }
427 let is_memo = ctx.memo_fns.contains(&fd.name);
428 sections.push(toplevel::emit_public_fn_def(fd, is_memo, ctx));
429 }
430
431 sections
432}
433
434fn insert_module_content(node: &mut ModuleTreeNode, segments: &[String], content: String) {
435 let child = node.children.entry(segments[0].clone()).or_default();
436 if segments.len() == 1 {
437 child.content = Some(content);
438 } else {
439 insert_module_content(child, &segments[1..], content);
440 }
441}
442
443fn emit_module_tree_files(node: &ModuleTreeNode, rel_dir: &str, files: &mut Vec<(String, String)>) {
444 let mut parts = Vec::new();
445
446 if let Some(content) = &node.content
447 && !content.trim().is_empty()
448 {
449 parts.push(content.trim_end().to_string());
450 }
451
452 for child_name in node.children.keys() {
453 parts.push(format!("pub mod {};", child_name));
454 }
455
456 let mut mod_rs = parts.join("\n\n");
457 if !mod_rs.is_empty() {
458 mod_rs.push('\n');
459 }
460 files.push((format!("{}/mod.rs", rel_dir), mod_rs));
461
462 for (child_name, child) in &node.children {
463 emit_module_tree_files(child, &format!("{}/{}", rel_dir, child_name), files);
464 }
465}
466
467fn root_module_depends(items: &[TopLevel]) -> Vec<String> {
468 items
469 .iter()
470 .find_map(|item| {
471 if let TopLevel::Module(module) = item {
472 Some(module.depends.clone())
473 } else {
474 None
475 }
476 })
477 .unwrap_or_default()
478}
479
480fn detect_used_services(ctx: &CodegenContext) -> HashSet<String> {
482 let mut services = HashSet::new();
483 for item in &ctx.items {
484 if let TopLevel::FnDef(fd) = item {
485 for eff in &fd.effects {
486 services.insert(eff.node.clone());
487 if let Some((service, _)) = eff.node.split_once('.') {
488 services.insert(service.to_string());
489 }
490 }
491 }
492 }
493 for module in &ctx.modules {
494 for fd in &module.fn_defs {
495 for eff in &fd.effects {
496 services.insert(eff.node.clone());
497 if let Some((service, _)) = eff.node.split_once('.') {
498 services.insert(service.to_string());
499 }
500 }
501 }
502 }
503 services
504}
505
506fn is_shared_runtime_type(td: &TypeDef) -> bool {
507 matches!(
508 td,
509 TypeDef::Product { name, .. }
510 if matches!(name.as_str(), "Header" | "HttpResponse" | "HttpRequest")
511 )
512}
513
514fn needs_named_type(ctx: &CodegenContext, wanted: &str) -> bool {
515 ctx.fn_sigs.values().any(|(params, ret, _effects)| {
516 params.iter().any(|p| type_contains_named(p, wanted)) || type_contains_named(ret, wanted)
517 })
518}
519
520fn type_contains_named(ty: &Type, wanted: &str) -> bool {
521 match ty {
522 Type::Named(name) => name == wanted,
523 Type::Result(ok, err) => {
524 type_contains_named(ok, wanted) || type_contains_named(err, wanted)
525 }
526 Type::Option(inner) | Type::List(inner) | Type::Vector(inner) => {
527 type_contains_named(inner, wanted)
528 }
529 Type::Tuple(items) => items.iter().any(|t| type_contains_named(t, wanted)),
530 Type::Map(k, v) => type_contains_named(k, wanted) || type_contains_named(v, wanted),
531 Type::Fn(params, ret, _effects) => {
532 params.iter().any(|t| type_contains_named(t, wanted))
533 || type_contains_named(ret, wanted)
534 }
535 Type::Int | Type::Float | Type::Str | Type::Bool | Type::Unit | Type::Unknown => false,
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::{
542 ModuleTreeNode, emit_module_tree_files, insert_module_content, render_generated_module,
543 transpile,
544 };
545 use crate::codegen::build_context;
546 use crate::source::parse_source;
547 use crate::tco;
548 use crate::types::checker::run_type_check_full;
549 use std::collections::HashSet;
550
551 fn ctx_from_source(source: &str, project_name: &str) -> crate::codegen::CodegenContext {
552 let mut items = parse_source(source).expect("source should parse");
553 tco::transform_program(&mut items);
554 let tc = run_type_check_full(&items, None);
555 assert!(
556 tc.errors.is_empty(),
557 "source should typecheck without errors: {:?}",
558 tc.errors
559 );
560 build_context(items, &tc, HashSet::new(), project_name.to_string(), vec![])
561 }
562
563 fn generated_rust_entry_file(out: &crate::codegen::ProjectOutput) -> &str {
564 out.files
565 .iter()
566 .find_map(|(name, content)| {
567 (name == "src/aver_generated/entry/mod.rs").then_some(content.as_str())
568 })
569 .expect("expected generated Rust entry module")
570 }
571
572 fn generated_file<'a>(out: &'a crate::codegen::ProjectOutput, path: &str) -> &'a str {
573 out.files
574 .iter()
575 .find_map(|(name, content)| (name == path).then_some(content.as_str()))
576 .unwrap_or_else(|| panic!("expected generated file '{}'", path))
577 }
578
579 #[test]
580 fn emission_banner_appears_in_root_main() {
581 let ctx = ctx_from_source(
582 r#"
583module Demo
584
585fn main() -> Int
586 1
587"#,
588 "demo",
589 );
590
591 let out = transpile(&ctx);
592 let root_main = generated_file(&out, "src/main.rs");
593
594 assert!(root_main.contains("// Aver Rust emission"));
595 }
596
597 #[test]
598 fn generated_module_imports_direct_depends() {
599 let rendered = render_generated_module(
600 vec!["Domain.Types".to_string(), "App.Commands".to_string()],
601 vec!["pub fn demo() {}".to_string()],
602 );
603
604 assert!(rendered.contains("use crate::aver_generated::domain::types::*;"));
605 assert!(rendered.contains("use crate::aver_generated::app::commands::*;"));
606 assert!(rendered.contains("pub fn demo() {}"));
607 }
608
609 #[test]
610 fn module_tree_files_do_not_reexport_children() {
611 let mut tree = ModuleTreeNode::default();
612 insert_module_content(
613 &mut tree,
614 &["app".to_string(), "cli".to_string()],
615 "pub fn run() {}".to_string(),
616 );
617
618 let mut files = Vec::new();
619 emit_module_tree_files(&tree, "src/aver_generated", &mut files);
620
621 let root_mod = files
622 .iter()
623 .find(|(path, _)| path == "src/aver_generated/mod.rs")
624 .map(|(_, content)| content)
625 .expect("root mod.rs should exist");
626
627 assert!(root_mod.contains("pub mod app;"));
628 assert!(!root_mod.contains("pub use app::*;"));
629 }
630
631 #[test]
632 fn list_cons_match_uses_cloned_uncons_fast_path_when_optimized() {
633 let ctx = ctx_from_source(
634 r#"
635module Demo
636
637fn headPlusTailLen(xs: List<Int>) -> Int
638 match xs
639 [] -> 0
640 [h, ..t] -> h + List.len(t)
641"#,
642 "demo",
643 );
644
645 let out = transpile(&ctx);
646 let entry = generated_rust_entry_file(&out);
647
648 assert!(entry.contains("aver_list_match!"));
650 }
651
652 #[test]
653 fn list_cons_match_stays_structured_in_semantic_mode() {
654 let ctx = ctx_from_source(
655 r#"
656module Demo
657
658fn headPlusTailLen(xs: List<Int>) -> Int
659 match xs
660 [] -> 0
661 [h, ..t] -> h + List.len(t)
662"#,
663 "demo",
664 );
665
666 let out = transpile(&ctx);
667 let entry = generated_rust_entry_file(&out);
668
669 assert!(entry.contains("aver_list_match!"));
671 }
672
673 #[test]
674 fn list_literal_clones_ident_when_used_afterward() {
675 let ctx = ctx_from_source(
676 r#"
677module Demo
678
679record Audit
680 message: String
681
682fn useTwice(audit: Audit) -> List<Audit>
683 first = [audit]
684 [audit]
685"#,
686 "demo",
687 );
688
689 let out = transpile(&ctx);
690 let entry = generated_rust_entry_file(&out);
691
692 assert!(entry.contains("let first = aver_rt::AverList::from_vec(vec![audit.clone()]);"));
693 assert!(entry.contains("aver_rt::AverList::from_vec(vec![audit.clone()])"));
695 }
696
697 #[test]
698 fn record_update_clones_base_when_value_is_used_afterward() {
699 let ctx = ctx_from_source(
700 r#"
701module Demo
702
703record PaymentState
704 paymentId: String
705 currency: String
706
707fn touch(state: PaymentState) -> String
708 updated = PaymentState.update(state, currency = "EUR")
709 state.paymentId
710"#,
711 "demo",
712 );
713
714 let out = transpile(&ctx);
715 let entry = generated_rust_entry_file(&out);
716
717 assert!(entry.contains("..state.clone()"));
718 }
719
720 #[test]
721 fn mutual_tco_generates_trampoline_instead_of_regular_calls() {
722 let ctx = ctx_from_source(
723 r#"
724module Demo
725
726fn isEven(n: Int) -> Bool
727 match n == 0
728 true -> true
729 false -> isOdd(n - 1)
730
731fn isOdd(n: Int) -> Bool
732 match n == 0
733 true -> false
734 false -> isEven(n - 1)
735"#,
736 "demo",
737 );
738
739 let out = transpile(&ctx);
740 let entry = generated_rust_entry_file(&out);
741
742 assert!(entry.contains("enum __MutualTco1"));
744 assert!(entry.contains("fn __mutual_tco_trampoline_1"));
745 assert!(entry.contains("loop {"));
746
747 assert!(entry.contains("pub fn isEven"));
749 assert!(entry.contains("pub fn isOdd"));
750 assert!(entry.contains("__mutual_tco_trampoline_1("));
751
752 assert!(!entry.contains("isOdd((n - 1i64))"));
754 }
755
756 #[test]
757 fn field_access_does_not_double_clone() {
758 let ctx = ctx_from_source(
759 r#"
760module Demo
761
762record User
763 name: String
764 age: Int
765
766fn greet(u: User) -> String
767 u.name
768"#,
769 "demo",
770 );
771
772 let out = transpile(&ctx);
773 let entry = generated_rust_entry_file(&out);
774
775 assert!(
777 !entry.contains(".clone().clone()"),
778 "double clone detected in generated code:\n{}",
779 entry
780 );
781 }
782
783 #[test]
784 fn vector_get_with_literal_default_lowers_to_direct_unwrap_or_code() {
785 let ctx = ctx_from_source(
786 r#"
787module Demo
788
789fn cellAt(grid: Vector<Int>, idx: Int) -> Int
790 Option.withDefault(Vector.get(grid, idx), 0)
791"#,
792 "demo",
793 );
794
795 let out = transpile(&ctx);
796 let entry = generated_rust_entry_file(&out);
797
798 assert!(entry.contains("grid.get(idx as usize).cloned().unwrap_or(0i64)"));
799 }
800
801 #[test]
802 fn vector_set_default_stays_structured_in_semantic_mode() {
803 let ctx = ctx_from_source(
804 r#"
805module Demo
806
807fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
808 Option.withDefault(Vector.set(vec, idx, value), vec)
809"#,
810 "demo",
811 );
812
813 let out = transpile(&ctx);
814 let entry = generated_rust_entry_file(&out);
815
816 assert!(entry.contains("set_unchecked"));
818 assert!(!entry.contains(".unwrap_or("));
819 }
820
821 #[test]
822 fn vector_set_default_uses_ir_leaf_fast_path_when_optimized() {
823 let ctx = ctx_from_source(
824 r#"
825module Demo
826
827fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
828 Option.withDefault(Vector.set(vec, idx, value), vec)
829"#,
830 "demo",
831 );
832
833 let out = transpile(&ctx);
834 let entry = generated_rust_entry_file(&out);
835
836 assert!(entry.contains("set_unchecked"));
837 assert!(!entry.contains(".unwrap_or("));
838 }
839
840 #[test]
841 fn vector_set_uses_owned_update_lowering() {
842 let ctx = ctx_from_source(
843 r#"
844module Demo
845
846fn update(vec: Vector<Int>, idx: Int, value: Int) -> Option<Vector<Int>>
847 Vector.set(vec, idx, value)
848"#,
849 "demo",
850 );
851
852 let out = transpile(&ctx);
853 let entry = generated_rust_entry_file(&out);
854
855 assert!(entry.contains(".set_owned("));
856 assert!(!entry.contains(".set(idx as usize,"));
857 }
858
859 #[test]
860 fn map_remove_uses_owned_update_lowering() {
861 let ctx = ctx_from_source(
862 r#"
863module Demo
864
865fn dropKey(m: Map<String, Int>, key: String) -> Map<String, Int>
866 Map.remove(m, key)
867"#,
868 "demo",
869 );
870
871 let out = transpile(&ctx);
872 let entry = generated_rust_entry_file(&out);
873
874 assert!(entry.contains(".remove_owned(&"));
875 }
876
877 #[test]
878 fn semantic_keeps_known_leaf_wrapper_call_structured() {
879 let ctx = ctx_from_source(
880 r#"
881module Demo
882
883fn cellAt(grid: Vector<Int>, idx: Int) -> Int
884 Option.withDefault(Vector.get(grid, idx), 0)
885
886fn read(grid: Vector<Int>, idx: Int) -> Int
887 cellAt(grid, idx)
888"#,
889 "demo",
890 );
891
892 let out = transpile(&ctx);
893 let entry = generated_rust_entry_file(&out);
894
895 assert!(entry.contains("cellAt(grid, idx)"));
896 assert!(!entry.contains("__aver_thin_arg0"));
897 }
898
899 #[test]
900 fn optimized_keeps_known_leaf_wrapper_callsite_and_leaves_absorption_to_rust() {
901 let ctx = ctx_from_source(
902 r#"
903module Demo
904
905fn cellAt(grid: Vector<Int>, idx: Int) -> Int
906 Option.withDefault(Vector.get(grid, idx), 0)
907
908fn read(grid: Vector<Int>, idx: Int) -> Int
909 cellAt(grid, idx)
910"#,
911 "demo",
912 );
913
914 let out = transpile(&ctx);
915 let entry = generated_rust_entry_file(&out);
916
917 assert!(entry.contains("cellAt(grid, idx)"));
918 assert!(!entry.contains("__aver_thin_arg0"));
919 }
920
921 #[test]
922 fn optimized_keeps_known_dispatch_wrapper_callsite_and_leaves_absorption_to_rust() {
923 let ctx = ctx_from_source(
924 r#"
925module Demo
926
927fn bucket(n: Int) -> Int
928 match n == 0
929 true -> 0
930 false -> 1
931
932fn readBucket(n: Int) -> Int
933 bucket(n)
934"#,
935 "demo",
936 );
937
938 let out = transpile(&ctx);
939 let entry = generated_rust_entry_file(&out);
940
941 assert!(entry.contains("bucket(n)"));
942 assert!(!entry.contains("__aver_thin_arg0"));
943 }
944
945 #[test]
946 fn bool_match_on_gte_normalizes_to_base_comparison_when_optimized() {
947 let ctx = ctx_from_source(
948 r#"
949module Demo
950
951fn bucket(n: Int) -> Int
952 match n >= 10
953 true -> 7
954 false -> 3
955"#,
956 "demo",
957 );
958
959 let out = transpile(&ctx);
960 let entry = generated_rust_entry_file(&out);
961
962 assert!(entry.contains("if (n < 10i64) { 3i64 } else { 7i64 }"));
963 }
964
965 #[test]
966 fn bool_match_stays_as_match_in_semantic_mode() {
967 let ctx = ctx_from_source(
968 r#"
969module Demo
970
971fn bucket(n: Int) -> Int
972 match n >= 10
973 true -> 7
974 false -> 3
975"#,
976 "demo",
977 );
978
979 let out = transpile(&ctx);
980 let entry = generated_rust_entry_file(&out);
981
982 assert!(entry.contains("if (n < 10i64) { 3i64 } else { 7i64 }"));
984 }
985
986 #[test]
987 fn optimized_self_tco_uses_dispatch_table_for_wrapper_match() {
988 let ctx = ctx_from_source(
989 r#"
990module Demo
991
992fn loop(r: Result<Int, String>) -> Int
993 match r
994 Result.Ok(n) -> n
995 Result.Err(_) -> loop(Result.Ok(1))
996"#,
997 "demo",
998 );
999
1000 let out = transpile(&ctx);
1001 let entry = generated_rust_entry_file(&out);
1002
1003 assert!(entry.contains("match r {"));
1005 assert!(entry.contains("Ok(n)"));
1006 assert!(!entry.contains("__dispatch_subject"));
1007 }
1008
1009 #[test]
1010 fn optimized_mutual_tco_uses_dispatch_table_for_wrapper_match() {
1011 let ctx = ctx_from_source(
1012 r#"
1013module Demo
1014
1015fn left(r: Result<Int, String>) -> Int
1016 match r
1017 Result.Ok(n) -> n
1018 Result.Err(_) -> right(Result.Ok(1))
1019
1020fn right(r: Result<Int, String>) -> Int
1021 match r
1022 Result.Ok(n) -> n
1023 Result.Err(_) -> left(Result.Ok(1))
1024"#,
1025 "demo",
1026 );
1027
1028 let out = transpile(&ctx);
1029 let entry = generated_rust_entry_file(&out);
1030
1031 assert!(entry.contains("match r {"));
1033 assert!(entry.contains("Ok(n)"));
1034 assert!(!entry.contains("__dispatch_subject"));
1035 }
1036
1037 #[test]
1038 fn single_field_variant_display_avoids_vec_join() {
1039 let ctx = ctx_from_source(
1040 r#"
1041module Demo
1042
1043type Wrapper
1044 Wrap(Int)
1045 Pair(Int, Int)
1046 Empty
1047"#,
1048 "demo",
1049 );
1050
1051 let out = transpile(&ctx);
1052 let entry = generated_rust_entry_file(&out);
1053
1054 assert!(
1056 !entry.contains("vec![f0.aver_display_inner()].join"),
1057 "single-field variant should use direct format, not vec join:\n{}",
1058 entry
1059 );
1060 assert!(
1062 entry.contains("vec![f0.aver_display_inner(), f1.aver_display_inner()].join(\", \")"),
1063 "multi-field variant should use vec join:\n{}",
1064 entry
1065 );
1066 }
1067
1068 #[test]
1069 fn replay_codegen_wraps_guest_entry_in_scoped_runtime() {
1070 let mut ctx = ctx_from_source(
1071 r#"
1072module Demo
1073
1074fn runGuestProgram(path: String) -> Result<String, String>
1075 ! [Disk.readText]
1076 Disk.readText(path)
1077"#,
1078 "demo",
1079 );
1080 ctx.emit_replay_runtime = true;
1081 ctx.guest_entry = Some("runGuestProgram".to_string());
1082
1083 let out = transpile(&ctx);
1084 let entry = generated_rust_entry_file(&out);
1085 let replay_support = generated_file(&out, "src/replay_support.rs");
1086 let cargo_toml = generated_file(&out, "Cargo.toml");
1087
1088 assert!(entry.contains("aver_replay::with_guest_scope_result(\"runGuestProgram\""));
1089 assert!(replay_support.contains("pub mod aver_replay"));
1090 assert!(cargo_toml.contains("serde_json = \"1\""));
1091 }
1092
1093 #[test]
1094 fn replay_codegen_uses_guest_args_param_override_when_present() {
1095 let mut ctx = ctx_from_source(
1096 r#"
1097module Demo
1098
1099fn runGuestProgram(path: String, guestArgs: List<String>) -> Result<String, String>
1100 ! [Args.get]
1101 Result.Ok(String.join(Args.get(), ","))
1102"#,
1103 "demo",
1104 );
1105 ctx.emit_replay_runtime = true;
1106 ctx.guest_entry = Some("runGuestProgram".to_string());
1107
1108 let out = transpile(&ctx);
1109 let entry = generated_rust_entry_file(&out);
1110 let cargo_toml = generated_file(&out, "Cargo.toml");
1111
1112 assert!(entry.contains("aver_replay::with_guest_scope_args_result(\"runGuestProgram\""));
1113 assert!(entry.contains("guestArgs.clone()"));
1114 assert!(cargo_toml.contains("edition = \"2024\""));
1115 }
1116
1117 #[test]
1118 fn replay_codegen_wraps_root_main_when_no_guest_entry_is_set() {
1119 let mut ctx = ctx_from_source(
1120 r#"
1121module Demo
1122
1123fn main() -> Result<String, String>
1124 ! [Disk.readText]
1125 Disk.readText("demo.av")
1126"#,
1127 "demo",
1128 );
1129 ctx.emit_replay_runtime = true;
1130
1131 let out = transpile(&ctx);
1132 let root_main = generated_file(&out, "src/main.rs");
1133
1134 assert!(
1135 root_main.contains("aver_replay::with_guest_scope(\"main\", serde_json::Value::Null")
1136 );
1137 }
1138
1139 #[test]
1140 fn runtime_policy_codegen_uses_runtime_loader() {
1141 let mut ctx = ctx_from_source(
1142 r#"
1143module Demo
1144
1145fn main() -> Result<String, String>
1146 ! [Disk.readText]
1147 Disk.readText("demo.av")
1148"#,
1149 "demo",
1150 );
1151 ctx.emit_replay_runtime = true;
1152 ctx.runtime_policy_from_env = true;
1153
1154 let out = transpile(&ctx);
1155 let root_main = generated_file(&out, "src/main.rs");
1156 let replay_support = generated_file(&out, "src/replay_support.rs");
1157 let cargo_toml = generated_file(&out, "Cargo.toml");
1158
1159 assert!(!root_main.contains("mod policy_support;"));
1160 assert!(replay_support.contains("load_runtime_policy_from_env"));
1161 assert!(cargo_toml.contains("url = \"2\""));
1162 assert!(cargo_toml.contains("toml = \"0.8\""));
1163 }
1164
1165 #[test]
1166 fn replay_codegen_can_keep_embedded_policy_when_requested() {
1167 let mut ctx = ctx_from_source(
1168 r#"
1169module Demo
1170
1171fn main() -> Result<String, String>
1172 ! [Disk.readText]
1173 Disk.readText("demo.av")
1174"#,
1175 "demo",
1176 );
1177 ctx.emit_replay_runtime = true;
1178 ctx.policy = Some(crate::config::ProjectConfig {
1179 effect_policies: std::collections::HashMap::new(),
1180 check_suppressions: Vec::new(),
1181 independence_mode: crate::config::IndependenceMode::default(),
1182 });
1183
1184 let out = transpile(&ctx);
1185 let root_main = generated_file(&out, "src/main.rs");
1186 let replay_support = generated_file(&out, "src/replay_support.rs");
1187
1188 assert!(root_main.contains("mod policy_support;"));
1189 assert!(replay_support.contains("aver_policy::check_disk"));
1190 assert!(!replay_support.contains("RuntimeEffectPolicy"));
1191 }
1192
1193 #[test]
1194 fn self_host_support_is_emitted_as_separate_module() {
1195 let mut ctx = ctx_from_source(
1196 r#"
1197module Demo
1198
1199fn runGuestProgram(prog: Int, moduleFns: Int) -> Result<String, String>
1200 Result.Ok("ok")
1201"#,
1202 "demo",
1203 );
1204 ctx.emit_self_host_support = true;
1205 ctx.guest_entry = Some("runGuestProgram".to_string());
1206
1207 let out = transpile(&ctx);
1208 let root_main = generated_file(&out, "src/main.rs");
1209 let runtime_support = generated_file(&out, "src/runtime_support.rs");
1210 let self_host_support = generated_file(&out, "src/self_host_support.rs");
1211 let entry = generated_rust_entry_file(&out);
1212
1213 assert!(root_main.contains("mod self_host_support;"));
1214 assert!(!runtime_support.contains("with_fn_store"));
1215 assert!(self_host_support.contains("pub fn with_program_fn_store"));
1216 assert!(entry.contains("crate::self_host_support::with_program_fn_store("));
1217 }
1218
1219 #[test]
1220 fn independent_product_codegen_avoids_string_specific_error_type() {
1221 let mut ctx = ctx_from_source(
1222 r#"
1223module Demo
1224
1225fn left() -> Result<Int, Int>
1226 Result.Ok(1)
1227
1228fn right() -> Result<Int, Int>
1229 Result.Ok(2)
1230
1231fn main() -> Result<(Int, Int), Int>
1232 data = (left(), right())?!
1233 Result.Ok(data)
1234"#,
1235 "demo",
1236 );
1237 ctx.emit_replay_runtime = true;
1238
1239 let out = transpile(&ctx);
1240 let entry = generated_rust_entry_file(&out);
1241
1242 assert!(!entry.contains("Ok::<_, aver_rt::AverStr>"));
1243 assert!(entry.contains("crate::aver_replay::exit_effect_group();"));
1244 assert!(entry.contains("match (_r0, _r1)"));
1245 assert!(!entry.contains("let _r0 = left()?;"));
1246 }
1247
1248 #[test]
1249 fn independent_product_codegen_emits_cancel_runtime_and_scope_propagation() {
1250 let mut ctx = ctx_from_source(
1251 r#"
1252module Demo
1253
1254fn left() -> Result<Int, String>
1255 Result.Ok(1)
1256
1257fn right() -> Result<Int, String>
1258 Result.Ok(2)
1259
1260fn main() -> Result<(Int, Int), String>
1261 data = (left(), right())?!
1262 Result.Ok(data)
1263"#,
1264 "demo",
1265 );
1266 ctx.emit_replay_runtime = true;
1267 ctx.policy = Some(crate::config::ProjectConfig {
1268 effect_policies: std::collections::HashMap::new(),
1269 check_suppressions: Vec::new(),
1270 independence_mode: crate::config::IndependenceMode::Cancel,
1271 });
1272
1273 let out = transpile(&ctx);
1274 let entry = generated_rust_entry_file(&out);
1275 let runtime_support = generated_file(&out, "src/runtime_support.rs");
1276 let replay_support = generated_file(&out, "src/replay_support.rs");
1277
1278 assert!(entry.contains("crate::run_cancelable_branch"));
1279 assert!(entry.contains("capture_parallel_scope_context"));
1280 assert!(entry.contains("_s.spawn(move ||"));
1281 assert!(runtime_support.contains("pub fn run_cancelable_branch"));
1282 assert!(runtime_support.contains("panic_any(AverCancelled)"));
1283 assert!(replay_support.contains("pub fn capture_parallel_scope_context"));
1284 assert!(replay_support.contains("pub fn independence_mode_is_cancel()"));
1285 }
1286
1287 #[test]
1288 fn runtime_policy_codegen_parses_independence_mode() {
1289 let mut ctx = ctx_from_source(
1290 r#"
1291module Demo
1292
1293fn main() -> Result<String, String>
1294 ! [Disk.readText]
1295 Disk.readText("demo.av")
1296"#,
1297 "demo",
1298 );
1299 ctx.emit_replay_runtime = true;
1300 ctx.runtime_policy_from_env = true;
1301
1302 let out = transpile(&ctx);
1303 let replay_support = generated_file(&out, "src/replay_support.rs");
1304
1305 assert!(replay_support.contains("independence_mode_cancel"));
1306 assert!(replay_support.contains("[independence].mode must be 'complete' or 'cancel'"));
1307 }
1308
1309 #[test]
1310 fn effectful_codegen_inserts_cancel_checkpoint_before_builtin_calls() {
1311 let ctx = ctx_from_source(
1312 r#"
1313module Demo
1314
1315fn main() -> Result<String, String>
1316 ! [Disk.readText]
1317 Disk.readText("demo.av")
1318"#,
1319 "demo",
1320 );
1321
1322 let out = transpile(&ctx);
1323 let entry = generated_rust_entry_file(&out);
1324
1325 assert!(entry.contains("crate::cancel_checkpoint(); (aver_rt::read_text"));
1326 }
1327
1328 #[test]
1329 fn replay_support_matches_group_effects_by_occurrence_and_args() {
1330 let mut ctx = ctx_from_source(
1331 r#"
1332module Demo
1333
1334fn left() -> Result<Int, String>
1335 Result.Ok(1)
1336
1337fn right() -> Result<Int, String>
1338 Result.Ok(2)
1339
1340fn main() -> Result<(Int, Int), String>
1341 data = (left(), right())?!
1342 Result.Ok(data)
1343"#,
1344 "demo",
1345 );
1346 ctx.emit_replay_runtime = true;
1347
1348 let out = transpile(&ctx);
1349 let replay_support = generated_file(&out, "src/replay_support.rs");
1350
1351 assert!(replay_support.contains("candidate.effect_occurrence"));
1352 assert!(replay_support.contains("candidate.args != args"));
1353 }
1354}