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