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