1use std::collections::HashMap;
20use std::fmt::Write;
21use std::path::PathBuf;
22
23use bock_air::{AIRNode, AirInterpolationPart, EnumVariantPayload, NodeKind, ResultVariant};
24use bock_ast::{AssignOp, BinOp, ImportItems, Literal, TypeExpr, UnaryOp, Visibility};
25use bock_types::AIRModule;
26
27use crate::error::CodegenError;
28use crate::generator::{CodeGenerator, GeneratedCode, OutputFile, SourceMap};
29use crate::profile::TargetProfile;
30
31fn rs_module_uses_concurrency(items: &[AIRNode]) -> bool {
33 items.iter().any(|n| {
34 let s = format!("{n:?}");
35 s.contains("\"Channel\"") || s.contains("\"spawn\"")
36 })
37}
38
39const CONCURRENCY_RUNTIME_RS: &str = "\
42// ── Bock concurrency runtime ──
43use std::sync::Arc;
44pub struct __BockChannel<T> {
45 tx: tokio::sync::mpsc::UnboundedSender<T>,
46 rx: tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<T>>,
47}
48pub fn __bock_channel_new<T: Send + 'static>() -> (Arc<__BockChannel<T>>, Arc<__BockChannel<T>>) {
49 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
50 let ch = Arc::new(__BockChannel { tx, rx: tokio::sync::Mutex::new(rx) });
51 (ch.clone(), ch)
52}
53impl<T> __BockChannel<T> {
54 pub fn send(&self, v: T) { let _ = self.tx.send(v); }
55 pub async fn recv(&self) -> T {
56 let mut guard = self.rx.lock().await;
57 guard.recv().await.expect(\"channel closed\")
58 }
59 pub fn close(&self) {}
60}
61pub fn __bock_spawn<T: Send + 'static>(f: impl std::future::Future<Output = T> + Send + 'static) -> tokio::task::JoinHandle<T> {
62 tokio::spawn(f)
63}
64";
65
66#[derive(Debug)]
68pub struct RsGenerator {
69 profile: TargetProfile,
70}
71
72impl RsGenerator {
73 #[must_use]
75 pub fn new() -> Self {
76 Self {
77 profile: TargetProfile::rust(),
78 }
79 }
80}
81
82impl Default for RsGenerator {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88impl CodeGenerator for RsGenerator {
89 fn target(&self) -> &TargetProfile {
90 &self.profile
91 }
92
93 fn generate_module(&self, module: &AIRModule) -> Result<GeneratedCode, CodegenError> {
94 let mut ctx = RsEmitCtx::new();
95 ctx.emit_node(module)?;
96 let content = ctx.finish();
97 let source_map = SourceMap {
98 generated_file: "output.rs".to_string(),
99 ..Default::default()
100 };
101 Ok(GeneratedCode {
102 files: vec![OutputFile {
103 path: PathBuf::from("output.rs"),
104 content,
105 }],
106 source_map: Some(source_map),
107 })
108 }
109}
110
111struct RsEmitCtx {
115 buf: String,
116 indent: usize,
117 needs_rc_import: bool,
119 needs_arc_import: bool,
121 task_bound_names: std::collections::HashSet<String>,
127 effect_ops: HashMap<String, String>,
129 current_handler_vars: HashMap<String, String>,
131 fn_effects: HashMap<String, Vec<String>>,
133 composite_effects: HashMap<String, Vec<String>>,
135}
136
137impl RsEmitCtx {
138 fn new() -> Self {
139 Self {
140 buf: String::with_capacity(4096),
141 indent: 0,
142 needs_rc_import: false,
143 needs_arc_import: false,
144 task_bound_names: std::collections::HashSet::new(),
145 effect_ops: HashMap::new(),
146 current_handler_vars: HashMap::new(),
147 fn_effects: HashMap::new(),
148 composite_effects: HashMap::new(),
149 }
150 }
151
152 fn finish(mut self) -> String {
153 if self.buf.is_empty() {
154 return self.buf;
155 }
156 let mut prefix = String::from(
157 "#![allow(unused_variables, unused_imports, unused_parens, dead_code, non_upper_case_globals)]\n\n",
158 );
159 if self.needs_rc_import {
160 prefix.push_str("use std::rc::Rc;\n");
161 }
162 if self.needs_arc_import {
163 prefix.push_str("use std::sync::Arc;\n");
164 }
165 if !prefix.ends_with("\n\n") {
166 prefix.push('\n');
167 }
168 self.buf.insert_str(0, &prefix);
169 self.buf
170 }
171
172 fn indent_str(&self) -> String {
173 " ".repeat(self.indent)
174 }
175
176 fn write_indent(&mut self) {
177 let indent = self.indent_str();
178 self.buf.push_str(&indent);
179 }
180
181 fn writeln(&mut self, s: &str) {
182 self.write_indent();
183 self.buf.push_str(s);
184 self.buf.push('\n');
185 }
186
187 fn expr_to_string(&mut self, node: &AIRNode) -> Result<String, CodegenError> {
191 let start = self.buf.len();
192 self.emit_expr(node)?;
193 let s = self.buf[start..].to_string();
194 self.buf.truncate(start);
195 Ok(s)
196 }
197
198 fn map_prelude_call(
200 &mut self,
201 callee: &AIRNode,
202 args: &[bock_air::AirArg],
203 ) -> Result<Option<String>, CodegenError> {
204 let name = match &callee.kind {
205 NodeKind::Identifier { name } => name.name.as_str(),
206 _ => return Ok(None),
207 };
208 let arg_strs: Vec<String> = args
209 .iter()
210 .map(|a| self.expr_to_string(&a.value))
211 .collect::<Result<_, _>>()?;
212 let code = match name {
213 "println" => {
214 let a = arg_strs.first().map_or(String::new(), |s| s.clone());
215 format!("println!(\"{{}}\", {a})")
216 }
217 "print" => {
218 let a = arg_strs.first().map_or(String::new(), |s| s.clone());
219 format!("print!(\"{{}}\", {a})")
220 }
221 "debug" => {
222 let a = arg_strs.first().map_or(String::new(), |s| s.clone());
223 format!("dbg!(&{a})")
224 }
225 "assert" => {
226 let a = arg_strs.first().map_or(String::new(), |s| s.clone());
227 format!("assert!({a})")
228 }
229 "todo" => "todo!()".to_string(),
230 "unreachable" => "unreachable!()".to_string(),
231 "sleep" => {
232 let a = arg_strs.first().map_or(String::new(), |s| s.clone());
233 format!(
234 "tokio::time::sleep(std::time::Duration::from_nanos(({a}) as u64))"
235 )
236 }
237 _ => return Ok(None),
238 };
239 Ok(Some(code))
240 }
241
242 fn try_emit_time_assoc_call(
246 &mut self,
247 callee: &AIRNode,
248 args: &[bock_air::AirArg],
249 ) -> Result<bool, CodegenError> {
250 let NodeKind::FieldAccess { object, field } = &callee.kind else {
251 return Ok(false);
252 };
253 let NodeKind::Identifier { name: type_name } = &object.kind else {
254 return Ok(false);
255 };
256 let arg_strs: Vec<String> = args
257 .iter()
258 .map(|a| self.expr_to_string(&a.value))
259 .collect::<Result<_, _>>()?;
260 let arg0 = || arg_strs.first().cloned().unwrap_or_default();
261 let code = match (type_name.name.as_str(), field.name.as_str()) {
262 ("Duration", "zero") => "0i64".to_string(),
263 ("Duration", "nanos") => format!("(({}) as i64)", arg0()),
264 ("Duration", "micros") => format!("((({}) as i64) * 1_000)", arg0()),
265 ("Duration", "millis") => format!("((({}) as i64) * 1_000_000)", arg0()),
266 ("Duration", "seconds") => format!("((({}) as i64) * 1_000_000_000)", arg0()),
267 ("Duration", "minutes") => format!("((({}) as i64) * 60_000_000_000)", arg0()),
268 ("Duration", "hours") => format!("((({}) as i64) * 3_600_000_000_000)", arg0()),
269 ("Instant", "now") => "std::time::Instant::now()".to_string(),
270 _ => return Ok(false),
271 };
272 self.buf.push_str(&code);
273 Ok(true)
274 }
275
276 fn try_emit_time_desugared_method(
278 &mut self,
279 callee: &AIRNode,
280 args: &[bock_air::AirArg],
281 ) -> Result<bool, CodegenError> {
282 let NodeKind::FieldAccess { object, field } = &callee.kind else {
283 return Ok(false);
284 };
285 if let NodeKind::Identifier { name } = &object.kind {
286 if matches!(name.name.as_str(), "Duration" | "Instant") {
287 return Ok(false);
288 }
289 }
290 if !is_time_method_name(&field.name) {
291 return Ok(false);
292 }
293 let remaining: Vec<bock_air::AirArg> = args.iter().skip(1).cloned().collect();
294 self.try_emit_time_method(object, &field.name, &remaining)
295 }
296
297 fn try_emit_concurrency_call(
301 &mut self,
302 callee: &AIRNode,
303 args: &[bock_air::AirArg],
304 ) -> Result<bool, CodegenError> {
305 if let NodeKind::Identifier { name } = &callee.kind {
306 if name.name == "spawn" {
307 self.buf.push_str("__bock_spawn(async move { ");
311 for (i, arg) in args.iter().enumerate() {
312 if i > 0 {
313 self.buf.push_str(", ");
314 }
315 self.emit_expr(&arg.value)?;
316 self.buf.push_str(".await");
317 }
318 self.buf.push_str(" })");
319 return Ok(true);
320 }
321 }
322 let NodeKind::FieldAccess { object, field } = &callee.kind else {
323 return Ok(false);
324 };
325 if let NodeKind::Identifier { name: type_name } = &object.kind {
326 if type_name.name == "Channel" && field.name == "new" {
327 self.buf.push_str("__bock_channel_new()");
328 return Ok(true);
329 }
330 }
331 if matches!(field.name.as_str(), "send" | "recv" | "close") {
332 self.emit_expr(object)?;
333 let _ = write!(self.buf, ".{}", field.name);
334 self.buf.push('(');
335 for (i, arg) in args.iter().skip(1).enumerate() {
336 if i > 0 {
337 self.buf.push_str(", ");
338 }
339 self.emit_expr(&arg.value)?;
340 }
341 self.buf.push(')');
342 return Ok(true);
343 }
344 Ok(false)
345 }
346
347 fn try_emit_time_method(
349 &mut self,
350 receiver: &AIRNode,
351 method: &str,
352 args: &[bock_air::AirArg],
353 ) -> Result<bool, CodegenError> {
354 let recv_str = self.expr_to_string(receiver)?;
355 let arg_strs: Vec<String> = args
356 .iter()
357 .map(|a| self.expr_to_string(&a.value))
358 .collect::<Result<_, _>>()?;
359 let code = match method {
360 "as_nanos" => format!("({recv_str})"),
361 "as_millis" => format!("(({recv_str}) / 1_000_000)"),
362 "as_seconds" => format!("(({recv_str}) / 1_000_000_000)"),
363 "is_zero" => format!("(({recv_str}) == 0)"),
364 "is_negative" => format!("(({recv_str}) < 0)"),
365 "abs" => format!("(({recv_str}) as i64).abs()"),
366 "elapsed" => {
367 format!("(({recv_str}).elapsed().as_nanos() as i64)")
368 }
369 "duration_since" => {
370 let other = arg_strs.first().cloned().unwrap_or_default();
371 format!(
372 "((({recv_str}).saturating_duration_since({other})).as_nanos() as i64)"
373 )
374 }
375 _ => return Ok(false),
376 };
377 self.buf.push_str(&code);
378 Ok(true)
379 }
380
381 fn type_to_rs(&mut self, node: &AIRNode) -> String {
385 match &node.kind {
386 NodeKind::TypeNamed { path, args } => {
387 let name = path
388 .segments
389 .iter()
390 .map(|s| s.name.as_str())
391 .collect::<Vec<_>>()
392 .join("::");
393 let rs_name = self.map_type_name(&name);
394 if args.is_empty() {
395 rs_name
396 } else {
397 let arg_strs: Vec<String> = args.iter().map(|a| self.type_to_rs(a)).collect();
398 format!("{rs_name}<{}>", arg_strs.join(", "))
399 }
400 }
401 NodeKind::TypeTuple { elems } => {
402 let elem_strs: Vec<String> = elems.iter().map(|e| self.type_to_rs(e)).collect();
403 format!("({})", elem_strs.join(", "))
404 }
405 NodeKind::TypeFunction { params, ret, .. } => {
406 let param_strs: Vec<String> = params.iter().map(|p| self.type_to_rs(p)).collect();
407 format!("fn({}) -> {}", param_strs.join(", "), self.type_to_rs(ret))
408 }
409 NodeKind::TypeOptional { inner } => {
410 format!("Option<{}>", self.type_to_rs(inner))
411 }
412 NodeKind::TypeSelf => "Self".into(),
413 _ => "_".into(),
414 }
415 }
416
417 fn map_type_name(&mut self, name: &str) -> String {
419 match name {
420 "Int" => "i64".into(),
421 "Float" => "f64".into(),
422 "Bool" => "bool".into(),
423 "String" => "String".into(),
424 "Void" | "Unit" => "()".into(),
425 "List" => "Vec".into(),
426 "Map" => "std::collections::HashMap".into(),
427 "Set" => "std::collections::HashSet".into(),
428 "Any" => "Box<dyn std::any::Any>".into(),
429 "Never" => "!".into(),
430 "Optional" => "Option".into(),
431 "Rc" => {
432 self.needs_rc_import = true;
433 "Rc".into()
434 }
435 "Arc" => {
436 self.needs_arc_import = true;
437 "Arc".into()
438 }
439 other => other.into(),
440 }
441 }
442
443 fn ast_type_to_rs(&mut self, ty: &TypeExpr) -> String {
445 match ty {
446 TypeExpr::Named { path, args, .. } => {
447 let name = path
448 .segments
449 .iter()
450 .map(|s| s.name.as_str())
451 .collect::<Vec<_>>()
452 .join("::");
453 let rs_name = self.map_type_name(&name);
454 if args.is_empty() {
455 rs_name
456 } else {
457 let arg_strs: Vec<String> =
458 args.iter().map(|a| self.ast_type_to_rs(a)).collect();
459 format!("{rs_name}<{}>", arg_strs.join(", "))
460 }
461 }
462 TypeExpr::Tuple { elems, .. } => {
463 let elem_strs: Vec<String> = elems.iter().map(|e| self.ast_type_to_rs(e)).collect();
464 format!("({})", elem_strs.join(", "))
465 }
466 TypeExpr::Function { params, ret, .. } => {
467 let param_strs: Vec<String> =
468 params.iter().map(|p| self.ast_type_to_rs(p)).collect();
469 format!(
470 "fn({}) -> {}",
471 param_strs.join(", "),
472 self.ast_type_to_rs(ret)
473 )
474 }
475 TypeExpr::Optional { inner, .. } => {
476 format!("Option<{}>", self.ast_type_to_rs(inner))
477 }
478 TypeExpr::SelfType { .. } => "Self".into(),
479 }
480 }
481
482 fn generic_params_to_rs(&self, params: &[bock_ast::GenericParam]) -> String {
484 if params.is_empty() {
485 return String::new();
486 }
487 let items: Vec<String> = params
488 .iter()
489 .map(|p| {
490 if p.bounds.is_empty() {
491 p.name.name.clone()
492 } else {
493 let bounds: Vec<String> = p
494 .bounds
495 .iter()
496 .map(|b| {
497 b.segments
498 .iter()
499 .map(|s| s.name.as_str())
500 .collect::<Vec<_>>()
501 .join("::")
502 })
503 .collect();
504 format!("{}: {}", p.name.name, bounds.join(" + "))
505 }
506 })
507 .collect();
508 format!("<{}>", items.join(", "))
509 }
510
511 fn where_clause_to_rs(&self, clauses: &[bock_ast::TypeConstraint]) -> String {
513 if clauses.is_empty() {
514 return String::new();
515 }
516 let items: Vec<String> = clauses
517 .iter()
518 .map(|c| {
519 let bounds: Vec<String> = c
520 .bounds
521 .iter()
522 .map(|b| {
523 b.segments
524 .iter()
525 .map(|s| s.name.as_str())
526 .collect::<Vec<_>>()
527 .join("::")
528 })
529 .collect();
530 format!("{}: {}", c.param.name, bounds.join(" + "))
531 })
532 .collect();
533 format!("\nwhere\n {}", items.join(",\n "))
534 }
535
536 fn emit_node(&mut self, node: &AIRNode) -> Result<(), CodegenError> {
539 match &node.kind {
540 NodeKind::Module { imports, items, .. } => {
541 if rs_module_uses_concurrency(items) {
542 self.buf.push_str(CONCURRENCY_RUNTIME_RS);
543 self.buf.push('\n');
544 }
545 for imp in imports {
546 self.emit_node(imp)?;
547 }
548 if !imports.is_empty() && !items.is_empty() {
549 self.buf.push('\n');
550 }
551 for (i, item) in items.iter().enumerate() {
552 if i > 0 {
553 self.buf.push('\n');
554 }
555 self.emit_node(item)?;
556 }
557 Ok(())
558 }
559 NodeKind::ImportDecl { path, items } => {
560 let path_str = path
561 .segments
562 .iter()
563 .map(|s| s.name.as_str())
564 .collect::<Vec<_>>()
565 .join("::");
566 match items {
567 ImportItems::Module => {
568 self.writeln(&format!("use {path_str};"));
569 }
570 ImportItems::Named(names) => {
571 let names_str = names
572 .iter()
573 .map(|n| n.name.name.as_str())
574 .collect::<Vec<_>>()
575 .join(", ");
576 self.writeln(&format!("use {path_str}::{{{names_str}}};"));
577 }
578 ImportItems::Glob => {
579 self.writeln(&format!("use {path_str}::*;"));
580 }
581 }
582 Ok(())
583 }
584 NodeKind::FnDecl {
585 visibility,
586 is_async,
587 name,
588 generic_params,
589 params,
590 return_type,
591 effect_clause,
592 where_clause,
593 body,
594 ..
595 } => self.emit_fn_decl(
596 *visibility,
597 *is_async,
598 &name.name,
599 generic_params,
600 params,
601 return_type.as_deref(),
602 effect_clause,
603 where_clause,
604 body,
605 ),
606 NodeKind::RecordDecl {
607 visibility,
608 name,
609 generic_params,
610 fields,
611 ..
612 } => {
613 let vis = vis_str(*visibility);
614 let generics = self.generic_params_to_rs(generic_params);
615 self.writeln(&format!("{vis}struct {}{generics} {{", name.name));
616 self.indent += 1;
617 for f in fields {
618 let ty = self.ast_type_to_rs(&f.ty);
619 self.writeln(&format!("pub {}: {ty},", to_snake_case(&f.name.name)));
620 }
621 self.indent -= 1;
622 self.writeln("}");
623 Ok(())
624 }
625 NodeKind::EnumDecl {
626 visibility,
627 name,
628 generic_params,
629 variants,
630 ..
631 } => {
632 let vis = vis_str(*visibility);
633 let generics = self.generic_params_to_rs(generic_params);
634 self.writeln(&format!("{vis}enum {}{generics} {{", name.name));
635 self.indent += 1;
636 for variant in variants {
637 self.emit_enum_variant(variant)?;
638 }
639 self.indent -= 1;
640 self.writeln("}");
641 Ok(())
642 }
643 NodeKind::ClassDecl {
644 visibility,
645 name,
646 generic_params,
647 fields,
648 methods,
649 ..
650 } => {
651 let vis = vis_str(*visibility);
653 let generics = self.generic_params_to_rs(generic_params);
654 self.writeln(&format!("{vis}struct {}{generics} {{", name.name));
655 self.indent += 1;
656 for f in fields {
657 let ty = self.ast_type_to_rs(&f.ty);
658 self.writeln(&format!("pub {}: {ty},", to_snake_case(&f.name.name)));
659 }
660 self.indent -= 1;
661 self.writeln("}");
662 self.buf.push('\n');
663 if !methods.is_empty() {
665 self.writeln(&format!("impl{generics} {}{generics} {{", name.name));
666 self.indent += 1;
667 for (i, method) in methods.iter().enumerate() {
668 if i > 0 {
669 self.buf.push('\n');
670 }
671 self.emit_method(method)?;
672 }
673 self.indent -= 1;
674 self.writeln("}");
675 }
676 Ok(())
677 }
678 NodeKind::TraitDecl {
679 visibility,
680 name,
681 generic_params,
682 methods,
683 ..
684 } => {
685 let vis = vis_str(*visibility);
686 let generics = self.generic_params_to_rs(generic_params);
687 self.writeln(&format!("{vis}trait {}{generics} {{", name.name));
688 self.indent += 1;
689 for (i, method) in methods.iter().enumerate() {
690 if i > 0 {
691 self.buf.push('\n');
692 }
693 self.emit_trait_method(method)?;
694 }
695 self.indent -= 1;
696 self.writeln("}");
697 Ok(())
698 }
699 NodeKind::ImplBlock {
700 generic_params,
701 trait_path,
702 target,
703 where_clause,
704 methods,
705 ..
706 } => {
707 let generics = self.generic_params_to_rs(generic_params);
708 let target_name = self.type_expr_to_string(target);
709 let where_cl = self.where_clause_to_rs(where_clause);
710 if let Some(tp) = trait_path {
711 let trait_name = tp
712 .segments
713 .iter()
714 .map(|s| s.name.as_str())
715 .collect::<Vec<_>>()
716 .join("::");
717 self.writeln(&format!(
718 "impl{generics} {trait_name} for {target_name}{where_cl} {{"
719 ));
720 } else {
721 self.writeln(&format!("impl{generics} {target_name}{where_cl} {{"));
722 }
723 let suppress_vis = trait_path.is_some();
724 self.indent += 1;
725 for (i, method) in methods.iter().enumerate() {
726 if i > 0 {
727 self.buf.push('\n');
728 }
729 self.emit_method_inner(method, suppress_vis)?;
730 }
731 self.indent -= 1;
732 self.writeln("}");
733 Ok(())
734 }
735 NodeKind::EffectDecl {
736 visibility,
737 name,
738 components,
739 generic_params,
740 operations,
741 ..
742 } => {
743 if !components.is_empty() {
744 let comp_names: Vec<String> = components
745 .iter()
746 .map(|tp| {
747 tp.segments
748 .last()
749 .map_or("effect".to_string(), |s| s.name.clone())
750 })
751 .collect();
752 self.writeln(&format!(
753 "// composite effect {} = {}",
754 name.name,
755 comp_names.join(" + ")
756 ));
757 self.composite_effects
758 .insert(name.name.clone(), comp_names);
759 return Ok(());
760 }
761 for op in operations {
763 if let NodeKind::FnDecl { name: op_name, .. } = &op.kind {
764 self.effect_ops
765 .insert(op_name.name.clone(), name.name.clone());
766 }
767 }
768 let vis = vis_str(*visibility);
770 let generics = self.generic_params_to_rs(generic_params);
771 self.writeln(&format!("{vis}trait {}{generics} {{", name.name));
772 self.indent += 1;
773 for (i, op) in operations.iter().enumerate() {
774 if i > 0 {
775 self.buf.push('\n');
776 }
777 self.emit_trait_method(op)?;
778 }
779 self.indent -= 1;
780 self.writeln("}");
781 Ok(())
782 }
783 NodeKind::TypeAlias {
784 visibility,
785 name,
786 generic_params,
787 ty,
788 where_clause,
789 ..
790 } => {
791 let vis = vis_str(*visibility);
792 let generics = self.generic_params_to_rs(generic_params);
793 let ty_str = self.type_to_rs(ty);
794 let where_cl = self.where_clause_to_rs(where_clause);
795 self.writeln(&format!(
796 "{vis}type {}{generics}{where_cl} = {ty_str};",
797 name.name
798 ));
799 Ok(())
800 }
801 NodeKind::ConstDecl {
802 visibility,
803 name,
804 value,
805 ty,
806 ..
807 } => {
808 let vis = vis_str(*visibility);
809 let ty_str = self.type_to_rs(ty);
810 let ind = self.indent_str();
811 let _ = write!(
812 self.buf,
813 "{ind}{vis}const {}: {ty_str} = ",
814 to_upper_snake_case(&name.name)
815 );
816 self.emit_expr(value)?;
817 self.buf.push_str(";\n");
818 Ok(())
819 }
820 NodeKind::ModuleHandle { effect, handler } => {
821 let effect_name =
829 effect.segments.last().map_or("effect", |s| s.name.as_str());
830 let const_name =
831 format!("__{}_HANDLER", to_snake_case(effect_name).to_uppercase());
832 let handler_type = record_construct_type(handler);
833 let ind = self.indent_str();
834 if let Some(type_name) = handler_type {
835 let _ = write!(self.buf, "{ind}const {const_name}: {type_name} = ");
836 self.emit_expr(handler)?;
837 self.buf.push_str(";\n");
838 self.current_handler_vars
839 .insert(effect_name.to_string(), const_name);
840 } else {
841 let _ = write!(self.buf, "{ind}// module handle: {effect_name} with ");
845 self.emit_expr(handler)?;
846 self.buf.push('\n');
847 }
848 Ok(())
849 }
850 NodeKind::PropertyTest { name, .. } => {
851 self.writeln(&format!("// property test: {name}"));
852 Ok(())
853 }
854 NodeKind::LetBinding { .. }
856 | NodeKind::If { .. }
857 | NodeKind::For { .. }
858 | NodeKind::While { .. }
859 | NodeKind::Loop { .. }
860 | NodeKind::Return { .. }
861 | NodeKind::Break { .. }
862 | NodeKind::Continue
863 | NodeKind::Guard { .. }
864 | NodeKind::Match { .. }
865 | NodeKind::Block { .. }
866 | NodeKind::HandlingBlock { .. }
867 | NodeKind::Assign { .. } => self.emit_stmt(node),
868 _ => {
870 self.write_indent();
871 self.emit_expr(node)?;
872 self.buf.push_str(";\n");
873 Ok(())
874 }
875 }
876 }
877
878 #[allow(clippy::too_many_arguments)]
881 fn emit_fn_decl(
882 &mut self,
883 visibility: Visibility,
884 is_async: bool,
885 name: &str,
886 generic_params: &[bock_ast::GenericParam],
887 params: &[AIRNode],
888 return_type: Option<&AIRNode>,
889 effect_clause: &[bock_ast::TypePath],
890 where_clause: &[bock_ast::TypeConstraint],
891 body: &AIRNode,
892 ) -> Result<(), CodegenError> {
893 let vis = vis_str(visibility);
894 let async_kw = if is_async { "async " } else { "" };
895 let generics = self.generic_params_to_rs(generic_params);
896 let param_strs = self.collect_param_strs(params);
897 let effects = self.effects_params(effect_clause);
898 let mut all_params = param_strs;
899 all_params.extend(effects);
900 let ret = return_type
901 .map(|t| format!(" -> {}", self.type_to_rs(t)))
902 .unwrap_or_default();
903 let where_cl = self.where_clause_to_rs(where_clause);
904 if !effect_clause.is_empty() {
905 let effect_names = self.expand_effect_names(effect_clause);
906 self.fn_effects.insert(name.to_string(), effect_names);
907 }
908 let fn_name = to_snake_case(name);
909 if is_async && fn_name == "main" {
913 self.writeln("#[tokio::main]");
914 }
915 self.writeln(&format!(
916 "{vis}{async_kw}fn {fn_name}{generics}({}){ret}{where_cl} {{",
917 all_params.join(", "),
918 ));
919 self.indent += 1;
920 let old_handler_vars = self.current_handler_vars.clone();
921 let expanded = self.expand_effect_names(effect_clause);
922 for ename in &expanded {
923 self.current_handler_vars
924 .insert(ename.clone(), to_snake_case(ename));
925 }
926 self.emit_block_body(body)?;
927 self.current_handler_vars = old_handler_vars;
928 self.indent -= 1;
929 self.writeln("}");
930 Ok(())
931 }
932
933 fn emit_method_inner(
936 &mut self,
937 method: &AIRNode,
938 suppress_vis: bool,
939 ) -> Result<(), CodegenError> {
940 if let NodeKind::FnDecl {
941 visibility,
942 is_async,
943 name,
944 generic_params,
945 params,
946 return_type,
947 effect_clause,
948 where_clause,
949 body,
950 ..
951 } = &method.kind
952 {
953 let vis = if suppress_vis {
954 ""
955 } else {
956 vis_str(*visibility)
957 };
958 let async_kw = if *is_async { "async " } else { "" };
959 let generics = self.generic_params_to_rs(generic_params);
960 let param_strs = self.collect_param_strs(params);
961 let effects = self.effects_params(effect_clause);
962 let mut all_params = vec!["&self".to_string()];
963 all_params.extend(param_strs);
964 all_params.extend(effects);
965 let ret = return_type
966 .as_deref()
967 .map(|t| format!(" -> {}", self.type_to_rs(t)))
968 .unwrap_or_default();
969 let where_cl = self.where_clause_to_rs(where_clause);
970 let fn_name = to_snake_case(&name.name);
971 self.writeln(&format!(
972 "{vis}{async_kw}fn {fn_name}{generics}({}){ret}{where_cl} {{",
973 all_params.join(", "),
974 ));
975 self.indent += 1;
976 let old_handler_vars = self.current_handler_vars.clone();
977 let expanded = self.expand_effect_names(effect_clause);
978 for ename in &expanded {
979 self.current_handler_vars
980 .insert(ename.clone(), to_snake_case(ename));
981 }
982 self.emit_block_body(body)?;
983 self.current_handler_vars = old_handler_vars;
984 self.indent -= 1;
985 self.writeln("}");
986 }
987 Ok(())
988 }
989
990 fn emit_method(&mut self, method: &AIRNode) -> Result<(), CodegenError> {
992 self.emit_method_inner(method, false)
993 }
994
995 fn emit_trait_method(&mut self, method: &AIRNode) -> Result<(), CodegenError> {
997 if let NodeKind::FnDecl {
998 is_async,
999 name,
1000 generic_params,
1001 params,
1002 return_type,
1003 effect_clause,
1004 where_clause,
1005 body,
1006 ..
1007 } = &method.kind
1008 {
1009 let async_kw = if *is_async { "async " } else { "" };
1010 let generics = self.generic_params_to_rs(generic_params);
1011 let param_strs = self.collect_param_strs(params);
1012 let effects = self.effects_params(effect_clause);
1013 let mut all_params = vec!["&self".to_string()];
1014 all_params.extend(param_strs);
1015 all_params.extend(effects);
1016 let ret = return_type
1017 .as_deref()
1018 .map(|t| format!(" -> {}", self.type_to_rs(t)))
1019 .unwrap_or_default();
1020 let where_cl = self.where_clause_to_rs(where_clause);
1021 let fn_name = to_snake_case(&name.name);
1022
1023 let has_body = if let NodeKind::Block { stmts, tail } = &body.kind {
1025 !stmts.is_empty() || tail.is_some()
1026 } else {
1027 true
1028 };
1029
1030 if has_body {
1031 self.writeln(&format!(
1032 "{async_kw}fn {fn_name}{generics}({}){ret}{where_cl} {{",
1033 all_params.join(", "),
1034 ));
1035 self.indent += 1;
1036 self.emit_block_body(body)?;
1037 self.indent -= 1;
1038 self.writeln("}");
1039 } else {
1040 self.writeln(&format!(
1041 "{async_kw}fn {fn_name}{generics}({}){ret}{where_cl};",
1042 all_params.join(", "),
1043 ));
1044 }
1045 }
1046 Ok(())
1047 }
1048
1049 fn collect_param_strs(&mut self, params: &[AIRNode]) -> Vec<String> {
1050 let mut result = Vec::new();
1051 for p in params {
1052 if let NodeKind::Param {
1053 pattern,
1054 ty,
1055 default,
1056 } = &p.kind
1057 {
1058 let name = to_snake_case(&self.pattern_to_binding_name(pattern));
1059 let type_ann = ty
1060 .as_ref()
1061 .map(|t| format!(": {}", self.type_to_rs(t)))
1062 .unwrap_or_else(|| ": _".into());
1063 if let Some(def) = default {
1064 let mut ctx = RsEmitCtx::new();
1066 ctx.indent = self.indent;
1067 if ctx.emit_expr(def).is_ok() {
1068 let def_str = ctx.buf;
1069 result.push(format!("{name}{type_ann} /* = {def_str} */"));
1070 continue;
1071 }
1072 }
1073 result.push(format!("{name}{type_ann}"));
1074 }
1075 }
1076 result
1077 }
1078
1079 fn expand_effect_names(&self, effects: &[bock_ast::TypePath]) -> Vec<String> {
1081 let mut result = Vec::new();
1082 for tp in effects {
1083 let name = tp
1084 .segments
1085 .last()
1086 .map_or("effect".to_string(), |s| s.name.clone());
1087 if let Some(components) = self.composite_effects.get(&name) {
1088 result.extend(components.iter().cloned());
1089 } else {
1090 result.push(name);
1091 }
1092 }
1093 result
1094 }
1095
1096 fn effects_params(&self, effects: &[bock_ast::TypePath]) -> Vec<String> {
1102 let expanded = self.expand_effect_names(effects);
1103 expanded
1104 .iter()
1105 .map(|name| {
1106 let param_name = to_snake_case(name);
1107 format!("{param_name}: &impl {name}")
1108 })
1109 .collect()
1110 }
1111
1112 fn build_effects_call_args_rs(&self, fn_name: &str) -> Option<String> {
1114 let effects = self.fn_effects.get(fn_name)?;
1115 let entries: Vec<String> = effects
1116 .iter()
1117 .filter_map(|e| {
1118 let handler_var = self.current_handler_vars.get(e)?;
1119 Some(format!("&{handler_var}"))
1120 })
1121 .collect();
1122 if entries.is_empty() {
1123 return None;
1124 }
1125 Some(entries.join(", "))
1126 }
1127
1128 fn emit_enum_variant(&mut self, variant: &AIRNode) -> Result<(), CodegenError> {
1131 if let NodeKind::EnumVariant { name, payload } = &variant.kind {
1132 let vname = &name.name;
1133 match payload {
1134 EnumVariantPayload::Unit => {
1135 self.writeln(&format!("{vname},"));
1136 }
1137 EnumVariantPayload::Struct(fields) => {
1138 self.writeln(&format!("{vname} {{"));
1139 self.indent += 1;
1140 for f in fields {
1141 let ty = self.ast_type_to_rs(&f.ty);
1142 self.writeln(&format!("{}: {ty},", to_snake_case(&f.name.name)));
1143 }
1144 self.indent -= 1;
1145 self.writeln("},");
1146 }
1147 EnumVariantPayload::Tuple(elems) => {
1148 let types: Vec<String> = elems.iter().map(|e| self.type_to_rs(e)).collect();
1149 self.writeln(&format!("{vname}({}),", types.join(", ")));
1150 }
1151 }
1152 }
1153 Ok(())
1154 }
1155
1156 fn emit_stmt(&mut self, node: &AIRNode) -> Result<(), CodegenError> {
1159 match &node.kind {
1160 NodeKind::LetBinding {
1161 pattern,
1162 value,
1163 ty,
1164 is_mut,
1165 } => {
1166 let binding = self.pattern_to_rs_binding(pattern);
1167 let type_ann = ty
1168 .as_ref()
1169 .map(|t| format!(": {}", self.type_to_rs(t)))
1170 .unwrap_or_default();
1171 let mut_kw = if *is_mut { "mut " } else { "" };
1172 let ind = self.indent_str();
1173 let _ = write!(self.buf, "{ind}let {mut_kw}{binding}{type_ann} = ");
1174 let wrap_task = matches!(&value.kind, NodeKind::Call { .. })
1175 && self.task_bound_names.contains(&binding);
1176 if wrap_task {
1177 self.buf.push_str("tokio::spawn(");
1178 self.emit_expr(value)?;
1179 self.buf.push(')');
1180 } else {
1181 self.emit_expr(value)?;
1182 }
1183 self.buf.push_str(";\n");
1184 Ok(())
1185 }
1186 NodeKind::If {
1187 let_pattern,
1188 condition,
1189 then_block,
1190 else_block,
1191 } => {
1192 let ind = self.indent_str();
1193 if let Some(pat) = let_pattern {
1194 let binding = self.pattern_to_rs_binding(pat);
1195 let _ = write!(self.buf, "{ind}if let Some({binding}) = ");
1196 self.emit_expr(condition)?;
1197 self.buf.push_str(" {\n");
1198 } else {
1199 let _ = write!(self.buf, "{ind}if ");
1200 self.emit_expr(condition)?;
1201 self.buf.push_str(" {\n");
1202 }
1203 self.indent += 1;
1204 self.emit_block_body(then_block)?;
1205 self.indent -= 1;
1206 if let Some(else_b) = else_block {
1207 if matches!(else_b.kind, NodeKind::If { .. }) {
1208 let ind = self.indent_str();
1209 let _ = write!(self.buf, "{ind}}} else ");
1210 self.emit_if_continuing(else_b)?;
1212 return Ok(());
1213 }
1214 self.writeln("} else {");
1215 self.indent += 1;
1216 self.emit_block_body(else_b)?;
1217 self.indent -= 1;
1218 }
1219 self.writeln("}");
1220 Ok(())
1221 }
1222 NodeKind::For {
1223 pattern,
1224 iterable,
1225 body,
1226 } => {
1227 let binding = self.pattern_to_rs_binding(pattern);
1228 let ind = self.indent_str();
1229 let _ = write!(self.buf, "{ind}for {binding} in ");
1230 self.emit_expr(iterable)?;
1231 self.buf.push_str(" {\n");
1232 self.indent += 1;
1233 self.emit_block_body(body)?;
1234 self.indent -= 1;
1235 self.writeln("}");
1236 Ok(())
1237 }
1238 NodeKind::While { condition, body } => {
1239 let ind = self.indent_str();
1240 let _ = write!(self.buf, "{ind}while ");
1241 self.emit_expr(condition)?;
1242 self.buf.push_str(" {\n");
1243 self.indent += 1;
1244 self.emit_block_body(body)?;
1245 self.indent -= 1;
1246 self.writeln("}");
1247 Ok(())
1248 }
1249 NodeKind::Loop { body } => {
1250 self.writeln("loop {");
1251 self.indent += 1;
1252 self.emit_block_body(body)?;
1253 self.indent -= 1;
1254 self.writeln("}");
1255 Ok(())
1256 }
1257 NodeKind::Return { value } => {
1258 if let Some(val) = value {
1259 let ind = self.indent_str();
1260 let _ = write!(self.buf, "{ind}return ");
1261 self.emit_expr(val)?;
1262 self.buf.push_str(";\n");
1263 } else {
1264 self.writeln("return;");
1265 }
1266 Ok(())
1267 }
1268 NodeKind::Break { value } => {
1269 if let Some(val) = value {
1270 let ind = self.indent_str();
1271 let _ = write!(self.buf, "{ind}break ");
1272 self.emit_expr(val)?;
1273 self.buf.push_str(";\n");
1274 } else {
1275 self.writeln("break;");
1276 }
1277 Ok(())
1278 }
1279 NodeKind::Continue => {
1280 self.writeln("continue;");
1281 Ok(())
1282 }
1283 NodeKind::Guard {
1284 condition,
1285 else_block,
1286 ..
1287 } => {
1288 let ind = self.indent_str();
1289 let _ = write!(self.buf, "{ind}if !(");
1290 self.emit_expr(condition)?;
1291 self.buf.push_str(") {\n");
1292 self.indent += 1;
1293 self.emit_block_body(else_block)?;
1294 self.indent -= 1;
1295 self.writeln("}");
1296 Ok(())
1297 }
1298 NodeKind::Match { scrutinee, arms } => self.emit_match(scrutinee, arms),
1299 NodeKind::Block { stmts, tail } => {
1300 for s in stmts {
1301 self.emit_node(s)?;
1302 }
1303 if let Some(t) = tail {
1304 self.write_indent();
1305 self.emit_expr(t)?;
1306 self.buf.push('\n');
1307 }
1308 Ok(())
1309 }
1310 NodeKind::HandlingBlock { handlers, body } => {
1311 self.writeln("{");
1313 self.indent += 1;
1314 let old_handler_vars = self.current_handler_vars.clone();
1315 for h in handlers {
1316 let effect_name =
1317 h.effect.segments.last().map_or("effect", |s| s.name.as_str());
1318 let var_name = format!("__{}", to_snake_case(effect_name));
1319 let ind = self.indent_str();
1320 let _ = write!(self.buf, "{ind}let {var_name} = ");
1321 self.emit_expr(&h.handler)?;
1322 self.buf.push_str(";\n");
1323 self.current_handler_vars
1324 .insert(effect_name.to_string(), var_name);
1325 }
1326 if let NodeKind::Block { stmts, tail } = &body.kind {
1327 for s in stmts {
1328 self.emit_node(s)?;
1329 }
1330 if let Some(t) = tail {
1331 self.write_indent();
1332 self.emit_expr(t)?;
1333 self.buf.push('\n');
1334 }
1335 } else {
1336 self.emit_stmt(body)?;
1337 }
1338 self.current_handler_vars = old_handler_vars;
1339 self.indent -= 1;
1340 self.writeln("}");
1341 Ok(())
1342 }
1343 NodeKind::Assign { op, target, value } => {
1344 let ind = self.indent_str();
1345 let _ = write!(self.buf, "{ind}");
1346 self.emit_expr(target)?;
1347 let op_str = match op {
1348 AssignOp::Assign => " = ",
1349 AssignOp::AddAssign => " += ",
1350 AssignOp::SubAssign => " -= ",
1351 AssignOp::MulAssign => " *= ",
1352 AssignOp::DivAssign => " /= ",
1353 AssignOp::RemAssign => " %= ",
1354 };
1355 self.buf.push_str(op_str);
1356 self.emit_expr(value)?;
1357 self.buf.push_str(";\n");
1358 Ok(())
1359 }
1360 _ => {
1361 self.write_indent();
1362 self.emit_expr(node)?;
1363 self.buf.push_str(";\n");
1364 Ok(())
1365 }
1366 }
1367 }
1368
1369 fn emit_if_continuing(&mut self, node: &AIRNode) -> Result<(), CodegenError> {
1371 if let NodeKind::If {
1372 let_pattern,
1373 condition,
1374 then_block,
1375 else_block,
1376 } = &node.kind
1377 {
1378 if let Some(pat) = let_pattern {
1379 let binding = self.pattern_to_rs_binding(pat);
1380 let _ = write!(self.buf, "if let Some({binding}) = ");
1381 self.emit_expr(condition)?;
1382 self.buf.push_str(" {\n");
1383 } else {
1384 let _ = write!(self.buf, "if ");
1385 self.emit_expr(condition)?;
1386 self.buf.push_str(" {\n");
1387 }
1388 self.indent += 1;
1389 self.emit_block_body(then_block)?;
1390 self.indent -= 1;
1391 if let Some(else_b) = else_block {
1392 if matches!(else_b.kind, NodeKind::If { .. }) {
1393 let ind = self.indent_str();
1394 let _ = write!(self.buf, "{ind}}} else ");
1395 self.emit_if_continuing(else_b)?;
1396 return Ok(());
1397 }
1398 self.writeln("} else {");
1399 self.indent += 1;
1400 self.emit_block_body(else_b)?;
1401 self.indent -= 1;
1402 }
1403 self.writeln("}");
1404 }
1405 Ok(())
1406 }
1407
1408 fn emit_expr(&mut self, node: &AIRNode) -> Result<(), CodegenError> {
1411 match &node.kind {
1412 NodeKind::Literal { lit } => {
1413 match lit {
1414 Literal::Int(s) => {
1415 self.buf.push_str(s);
1416 self.buf.push_str("_i64");
1417 }
1418 Literal::Float(s) => {
1419 self.buf.push_str(s);
1420 self.buf.push_str("_f64");
1421 }
1422 Literal::Bool(b) => {
1423 self.buf.push_str(if *b { "true" } else { "false" });
1424 }
1425 Literal::Char(s) => {
1426 self.buf.push('\'');
1427 self.buf.push_str(s);
1428 self.buf.push('\'');
1429 }
1430 Literal::String(s) => {
1431 self.buf.push('"');
1432 self.buf.push_str(&escape_rs_string(s));
1433 self.buf.push('"');
1434 self.buf.push_str(".to_string()");
1435 }
1436 Literal::Unit => self.buf.push_str("()"),
1437 }
1438 Ok(())
1439 }
1440 NodeKind::Identifier { name } => {
1441 self.buf.push_str(&identifier_to_rs(&name.name));
1442 Ok(())
1443 }
1444 NodeKind::BinaryOp { op, left, right } => {
1445 self.buf.push('(');
1446 self.emit_expr(left)?;
1447 let op_str = match op {
1448 BinOp::Add => " + ",
1449 BinOp::Sub => " - ",
1450 BinOp::Mul => " * ",
1451 BinOp::Div => " / ",
1452 BinOp::Rem => " % ",
1453 BinOp::Pow => ".pow(",
1454 BinOp::Eq => " == ",
1455 BinOp::Ne => " != ",
1456 BinOp::Lt => " < ",
1457 BinOp::Le => " <= ",
1458 BinOp::Gt => " > ",
1459 BinOp::Ge => " >= ",
1460 BinOp::And => " && ",
1461 BinOp::Or => " || ",
1462 BinOp::BitAnd => " & ",
1463 BinOp::BitOr => " | ",
1464 BinOp::BitXor => " ^ ",
1465 BinOp::Compose => " /* compose */ ",
1466 BinOp::Is => " /* is */ ",
1467 };
1468 self.buf.push_str(op_str);
1469 if *op == BinOp::Pow {
1470 self.emit_expr(right)?;
1471 self.buf.push(')');
1472 } else {
1473 self.emit_expr(right)?;
1474 }
1475 self.buf.push(')');
1476 Ok(())
1477 }
1478 NodeKind::UnaryOp { op, operand } => {
1479 let op_str = match op {
1480 UnaryOp::Neg => "-",
1481 UnaryOp::Not => "!",
1482 UnaryOp::BitNot => "!",
1483 };
1484 self.buf.push_str(op_str);
1485 self.emit_expr(operand)?;
1486 Ok(())
1487 }
1488 NodeKind::Call { callee, args, .. } => {
1489 if let NodeKind::Identifier { name } = &callee.kind {
1491 if let Some(effect_name) = self.effect_ops.get(&name.name).cloned() {
1492 if let Some(handler_var) =
1493 self.current_handler_vars.get(&effect_name).cloned()
1494 {
1495 let _ = write!(
1496 self.buf,
1497 "{}.{}",
1498 handler_var,
1499 to_snake_case(&name.name)
1500 );
1501 self.buf.push('(');
1502 for (i, arg) in args.iter().enumerate() {
1503 if i > 0 {
1504 self.buf.push_str(", ");
1505 }
1506 self.emit_expr(&arg.value)?;
1507 }
1508 self.buf.push(')');
1509 return Ok(());
1510 }
1511 }
1512 }
1513 if let Some(code) = self.map_prelude_call(callee, args)? {
1514 self.buf.push_str(&code);
1515 return Ok(());
1516 }
1517 if self.try_emit_time_assoc_call(callee, args)? {
1518 return Ok(());
1519 }
1520 if self.try_emit_time_desugared_method(callee, args)? {
1521 return Ok(());
1522 }
1523 if self.try_emit_concurrency_call(callee, args)? {
1524 return Ok(());
1525 }
1526 let effects_args = if let NodeKind::Identifier { name } = &callee.kind {
1528 self.build_effects_call_args_rs(&name.name)
1529 } else {
1530 None
1531 };
1532 self.emit_expr(callee)?;
1533 self.buf.push('(');
1534 for (i, arg) in args.iter().enumerate() {
1535 if i > 0 {
1536 self.buf.push_str(", ");
1537 }
1538 self.emit_expr(&arg.value)?;
1539 }
1540 if let Some(ea) = effects_args {
1541 if !args.is_empty() {
1542 self.buf.push_str(", ");
1543 }
1544 self.buf.push_str(&ea);
1545 }
1546 self.buf.push(')');
1547 Ok(())
1548 }
1549 NodeKind::MethodCall {
1550 receiver,
1551 method,
1552 args,
1553 ..
1554 } => {
1555 if self.try_emit_time_method(receiver, &method.name, args)? {
1556 return Ok(());
1557 }
1558 self.emit_expr(receiver)?;
1559 let _ = write!(self.buf, ".{}", to_snake_case(&method.name));
1560 self.buf.push('(');
1561 for (i, arg) in args.iter().enumerate() {
1562 if i > 0 {
1563 self.buf.push_str(", ");
1564 }
1565 self.emit_expr(&arg.value)?;
1566 }
1567 self.buf.push(')');
1568 Ok(())
1569 }
1570 NodeKind::FieldAccess { object, field } => {
1571 self.emit_expr(object)?;
1572 let _ = write!(self.buf, ".{}", to_snake_case(&field.name));
1573 Ok(())
1574 }
1575 NodeKind::Index { object, index } => {
1576 self.emit_expr(object)?;
1577 self.buf.push('[');
1578 self.emit_expr(index)?;
1579 self.buf.push(']');
1580 Ok(())
1581 }
1582 NodeKind::Lambda { params, body } => {
1583 let param_strs = self.collect_param_strs(params);
1584 let _ = write!(self.buf, "|{}| ", param_strs.join(", "));
1585 self.emit_expr(body)?;
1586 Ok(())
1587 }
1588 NodeKind::Pipe { left, right } => self.emit_pipe(left, right),
1589 NodeKind::Compose { left, right } => {
1590 let _ = write!(self.buf, "|x| ");
1592 self.emit_expr(right)?;
1593 self.buf.push('(');
1594 self.emit_expr(left)?;
1595 self.buf.push_str("(x))");
1596 Ok(())
1597 }
1598 NodeKind::Await { expr } => {
1599 let is_spawned_handle = if let NodeKind::Identifier { name } = &expr.kind {
1604 self.task_bound_names.contains(&to_snake_case(&name.name))
1605 } else {
1606 false
1607 };
1608 self.emit_expr(expr)?;
1609 if is_spawned_handle {
1610 self.buf.push_str(".await.unwrap()");
1611 } else {
1612 self.buf.push_str(".await");
1613 }
1614 Ok(())
1615 }
1616 NodeKind::Propagate { expr } => {
1617 self.emit_expr(expr)?;
1618 self.buf.push('?');
1619 Ok(())
1620 }
1621 NodeKind::Range { lo, hi, inclusive } => {
1622 self.emit_expr(lo)?;
1623 if *inclusive {
1624 self.buf.push_str("..=");
1625 } else {
1626 self.buf.push_str("..");
1627 }
1628 self.emit_expr(hi)?;
1629 Ok(())
1630 }
1631 NodeKind::RecordConstruct {
1632 path,
1633 fields,
1634 spread,
1635 } => {
1636 let type_name = path
1637 .segments
1638 .iter()
1639 .map(|s| s.name.as_str())
1640 .collect::<Vec<_>>()
1641 .join("::");
1642 self.buf.push_str(&type_name);
1643 self.buf.push_str(" { ");
1644 for (i, f) in fields.iter().enumerate() {
1645 if i > 0 {
1646 self.buf.push_str(", ");
1647 }
1648 let fname = to_snake_case(&f.name.name);
1649 if let Some(val) = &f.value {
1650 let _ = write!(self.buf, "{fname}: ");
1651 self.emit_expr(val)?;
1652 } else {
1653 self.buf.push_str(&fname);
1654 }
1655 }
1656 if let Some(sp) = spread {
1657 if !fields.is_empty() {
1658 self.buf.push_str(", ");
1659 }
1660 self.buf.push_str("..");
1661 self.emit_expr(sp)?;
1662 }
1663 self.buf.push_str(" }");
1664 Ok(())
1665 }
1666 NodeKind::ListLiteral { elems } => {
1667 self.buf.push_str("vec![");
1668 for (i, e) in elems.iter().enumerate() {
1669 if i > 0 {
1670 self.buf.push_str(", ");
1671 }
1672 self.emit_expr(e)?;
1673 }
1674 self.buf.push(']');
1675 Ok(())
1676 }
1677 NodeKind::MapLiteral { entries } => {
1678 if entries.is_empty() {
1679 self.buf.push_str("std::collections::HashMap::new()");
1680 } else {
1681 self.buf.push_str("std::collections::HashMap::from([");
1682 for (i, entry) in entries.iter().enumerate() {
1683 if i > 0 {
1684 self.buf.push_str(", ");
1685 }
1686 self.buf.push('(');
1687 self.emit_expr(&entry.key)?;
1688 self.buf.push_str(", ");
1689 self.emit_expr(&entry.value)?;
1690 self.buf.push(')');
1691 }
1692 self.buf.push_str("])");
1693 }
1694 Ok(())
1695 }
1696 NodeKind::SetLiteral { elems } => {
1697 if elems.is_empty() {
1698 self.buf.push_str("std::collections::HashSet::new()");
1699 } else {
1700 self.buf.push_str("std::collections::HashSet::from([");
1701 for (i, e) in elems.iter().enumerate() {
1702 if i > 0 {
1703 self.buf.push_str(", ");
1704 }
1705 self.emit_expr(e)?;
1706 }
1707 self.buf.push_str("])");
1708 }
1709 Ok(())
1710 }
1711 NodeKind::TupleLiteral { elems } => {
1712 self.buf.push('(');
1713 for (i, e) in elems.iter().enumerate() {
1714 if i > 0 {
1715 self.buf.push_str(", ");
1716 }
1717 self.emit_expr(e)?;
1718 }
1719 if elems.len() == 1 {
1720 self.buf.push(',');
1721 }
1722 self.buf.push(')');
1723 Ok(())
1724 }
1725 NodeKind::Interpolation { parts } => {
1726 self.buf.push_str("format!(\"");
1728 let mut format_args: Vec<String> = Vec::new();
1729 for part in parts {
1730 match part {
1731 AirInterpolationPart::Literal(s) => {
1732 self.buf.push_str(&escape_format_string(s));
1733 }
1734 AirInterpolationPart::Expr(expr) => {
1735 self.buf.push_str("{}");
1736 let mut sub = RsEmitCtx::new();
1737 sub.indent = self.indent;
1738 sub.emit_expr(expr)?;
1739 format_args.push(sub.buf);
1740 }
1741 }
1742 }
1743 self.buf.push('"');
1744 for arg in format_args {
1745 self.buf.push_str(", ");
1746 self.buf.push_str(&arg);
1747 }
1748 self.buf.push(')');
1749 Ok(())
1750 }
1751 NodeKind::Placeholder => {
1752 self.buf.push('_');
1753 Ok(())
1754 }
1755 NodeKind::Unreachable => {
1756 self.buf.push_str("unreachable!()");
1757 Ok(())
1758 }
1759 NodeKind::ResultConstruct { variant, value } => {
1760 match variant {
1761 ResultVariant::Ok => {
1762 self.buf.push_str("Ok(");
1763 if let Some(v) = value {
1764 self.emit_expr(v)?;
1765 } else {
1766 self.buf.push_str("()");
1767 }
1768 self.buf.push(')');
1769 }
1770 ResultVariant::Err => {
1771 self.buf.push_str("Err(");
1772 if let Some(v) = value {
1773 self.emit_expr(v)?;
1774 } else {
1775 self.buf.push_str("()");
1776 }
1777 self.buf.push(')');
1778 }
1779 }
1780 Ok(())
1781 }
1782 NodeKind::Assign { op, target, value } => {
1783 self.emit_expr(target)?;
1784 let op_str = match op {
1785 AssignOp::Assign => " = ",
1786 AssignOp::AddAssign => " += ",
1787 AssignOp::SubAssign => " -= ",
1788 AssignOp::MulAssign => " *= ",
1789 AssignOp::DivAssign => " /= ",
1790 AssignOp::RemAssign => " %= ",
1791 };
1792 self.buf.push_str(op_str);
1793 self.emit_expr(value)?;
1794 Ok(())
1795 }
1796 NodeKind::If {
1797 condition,
1798 then_block,
1799 else_block,
1800 ..
1801 } => {
1802 self.buf.push_str("if ");
1804 self.emit_expr(condition)?;
1805 self.buf.push_str(" { ");
1806 self.emit_block_as_expr(then_block)?;
1807 self.buf.push_str(" } else { ");
1808 if let Some(eb) = else_block {
1809 self.emit_block_as_expr(eb)?;
1810 } else {
1811 self.buf.push_str("()");
1812 }
1813 self.buf.push_str(" }");
1814 Ok(())
1815 }
1816 NodeKind::Block { stmts, tail } => {
1817 if stmts.is_empty() {
1818 if let Some(t) = tail {
1819 return self.emit_expr(t);
1820 }
1821 }
1822 self.buf.push_str("{\n");
1824 self.indent += 1;
1825 for s in stmts {
1826 self.emit_node(s)?;
1827 }
1828 if let Some(t) = tail {
1829 self.write_indent();
1830 self.emit_expr(t)?;
1831 self.buf.push('\n');
1832 }
1833 self.indent -= 1;
1834 self.write_indent();
1835 self.buf.push('}');
1836 Ok(())
1837 }
1838 NodeKind::Match { scrutinee, arms } => {
1839 self.buf.push_str("match ");
1841 self.emit_expr(scrutinee)?;
1842 self.buf.push_str(" {\n");
1843 self.indent += 1;
1844 for arm in arms {
1845 self.emit_match_arm(arm)?;
1846 }
1847 self.indent -= 1;
1848 self.write_indent();
1849 self.buf.push('}');
1850 Ok(())
1851 }
1852 NodeKind::Move { expr } => {
1854 self.emit_expr(expr)
1856 }
1857 NodeKind::Borrow { expr } => {
1858 self.buf.push('&');
1859 self.emit_expr(expr)?;
1860 Ok(())
1861 }
1862 NodeKind::MutableBorrow { expr } => {
1863 self.buf.push_str("&mut ");
1864 self.emit_expr(expr)?;
1865 Ok(())
1866 }
1867 NodeKind::EffectOp {
1869 effect,
1870 operation,
1871 args,
1872 } => {
1873 let effect_name = effect.segments.last().map_or("effect", |s| s.name.as_str());
1874 let _ = write!(
1875 self.buf,
1876 "{}.{}",
1877 to_snake_case(effect_name),
1878 to_snake_case(&operation.name)
1879 );
1880 self.buf.push('(');
1881 for (i, arg) in args.iter().enumerate() {
1882 if i > 0 {
1883 self.buf.push_str(", ");
1884 }
1885 self.emit_expr(&arg.value)?;
1886 }
1887 self.buf.push(')');
1888 Ok(())
1889 }
1890 NodeKind::TypeNamed { .. }
1892 | NodeKind::TypeTuple { .. }
1893 | NodeKind::TypeFunction { .. }
1894 | NodeKind::TypeOptional { .. }
1895 | NodeKind::TypeSelf => {
1896 self.buf.push_str("/* type */");
1897 Ok(())
1898 }
1899 NodeKind::EffectRef { path } => {
1900 let name = path
1901 .segments
1902 .iter()
1903 .map(|s| s.name.as_str())
1904 .collect::<Vec<_>>()
1905 .join("::");
1906 self.buf.push_str(&name);
1907 Ok(())
1908 }
1909 NodeKind::Error => {
1910 self.buf.push_str("/* error */");
1911 Ok(())
1912 }
1913 _ => {
1914 self.buf.push_str("/* unsupported */");
1915 Ok(())
1916 }
1917 }
1918 }
1919
1920 fn emit_match(&mut self, scrutinee: &AIRNode, arms: &[AIRNode]) -> Result<(), CodegenError> {
1923 let ind = self.indent_str();
1924 let _ = write!(self.buf, "{ind}match ");
1925 self.emit_expr(scrutinee)?;
1926 self.buf.push_str(" {\n");
1927 self.indent += 1;
1928 for arm in arms {
1929 self.emit_match_arm(arm)?;
1930 }
1931 self.indent -= 1;
1932 self.writeln("}");
1933 Ok(())
1934 }
1935
1936 fn emit_match_arm(&mut self, arm: &AIRNode) -> Result<(), CodegenError> {
1937 if let NodeKind::MatchArm {
1938 pattern,
1939 guard,
1940 body,
1941 } = &arm.kind
1942 {
1943 let ind = self.indent_str();
1944 let _ = write!(self.buf, "{ind}");
1945 self.emit_pattern(pattern)?;
1946 if let Some(g) = guard {
1947 self.buf.push_str(" if ");
1948 self.emit_expr(g)?;
1949 }
1950 self.buf.push_str(" => ");
1951 if let NodeKind::Block { stmts, tail } = &body.kind {
1953 if stmts.is_empty() {
1954 if let Some(t) = tail {
1955 self.emit_expr(t)?;
1956 self.buf.push_str(",\n");
1957 return Ok(());
1958 }
1959 }
1960 self.buf.push_str("{\n");
1961 self.indent += 1;
1962 self.emit_block_body(body)?;
1963 self.indent -= 1;
1964 self.writeln("}");
1965 } else {
1966 self.emit_expr(body)?;
1967 self.buf.push_str(",\n");
1968 }
1969 }
1970 Ok(())
1971 }
1972
1973 fn emit_pattern(&mut self, pat: &AIRNode) -> Result<(), CodegenError> {
1974 match &pat.kind {
1975 NodeKind::WildcardPat => {
1976 self.buf.push('_');
1977 }
1978 NodeKind::BindPat { name, is_mut } => {
1979 if *is_mut {
1980 self.buf.push_str("mut ");
1981 }
1982 self.buf.push_str(&to_snake_case(&name.name));
1983 }
1984 NodeKind::LiteralPat { lit } => match lit {
1985 Literal::Int(s) => {
1986 self.buf.push_str(s);
1987 self.buf.push_str("_i64");
1988 }
1989 Literal::Float(s) => self.buf.push_str(s),
1990 Literal::Bool(b) => self.buf.push_str(if *b { "true" } else { "false" }),
1991 Literal::Char(s) => {
1992 self.buf.push('\'');
1993 self.buf.push_str(s);
1994 self.buf.push('\'');
1995 }
1996 Literal::String(s) => {
1997 self.buf.push('"');
1998 self.buf.push_str(&escape_rs_string(s));
1999 self.buf.push('"');
2000 }
2001 Literal::Unit => self.buf.push_str("()"),
2002 },
2003 NodeKind::ConstructorPat { path, fields } => {
2004 let variant_name = path
2005 .segments
2006 .iter()
2007 .map(|s| s.name.as_str())
2008 .collect::<Vec<_>>()
2009 .join("::");
2010 if fields.is_empty() {
2011 self.buf.push_str(&variant_name);
2012 } else {
2013 let _ = write!(self.buf, "{variant_name}(");
2014 for (i, f) in fields.iter().enumerate() {
2015 if i > 0 {
2016 self.buf.push_str(", ");
2017 }
2018 self.emit_pattern(f)?;
2019 }
2020 self.buf.push(')');
2021 }
2022 }
2023 NodeKind::RecordPat { path, fields, rest } => {
2024 let type_name = path
2025 .segments
2026 .iter()
2027 .map(|s| s.name.as_str())
2028 .collect::<Vec<_>>()
2029 .join("::");
2030 let _ = write!(self.buf, "{type_name} {{ ");
2031 for (i, f) in fields.iter().enumerate() {
2032 if i > 0 {
2033 self.buf.push_str(", ");
2034 }
2035 let field_name = to_snake_case(&f.name.name);
2036 if let Some(pat) = &f.pattern {
2037 let _ = write!(self.buf, "{field_name}: ");
2038 self.emit_pattern(pat)?;
2039 } else {
2040 self.buf.push_str(&field_name);
2041 }
2042 }
2043 if *rest {
2044 if !fields.is_empty() {
2045 self.buf.push_str(", ");
2046 }
2047 self.buf.push_str("..");
2048 }
2049 self.buf.push_str(" }");
2050 }
2051 NodeKind::TuplePat { elems } => {
2052 self.buf.push('(');
2053 for (i, e) in elems.iter().enumerate() {
2054 if i > 0 {
2055 self.buf.push_str(", ");
2056 }
2057 self.emit_pattern(e)?;
2058 }
2059 self.buf.push(')');
2060 }
2061 NodeKind::ListPat { elems, rest } => {
2062 self.buf.push('[');
2063 for (i, e) in elems.iter().enumerate() {
2064 if i > 0 {
2065 self.buf.push_str(", ");
2066 }
2067 self.emit_pattern(e)?;
2068 }
2069 if let Some(r) = rest {
2070 if !elems.is_empty() {
2071 self.buf.push_str(", ");
2072 }
2073 self.emit_pattern(r)?;
2074 self.buf.push_str(" @ ..");
2075 }
2076 self.buf.push(']');
2077 }
2078 NodeKind::OrPat { alternatives } => {
2079 for (i, p) in alternatives.iter().enumerate() {
2080 if i > 0 {
2081 self.buf.push_str(" | ");
2082 }
2083 self.emit_pattern(p)?;
2084 }
2085 }
2086 NodeKind::GuardPat { pattern, guard } => {
2087 self.emit_pattern(pattern)?;
2088 self.buf.push_str(" if ");
2089 self.emit_expr(guard)?;
2090 }
2091 NodeKind::RangePat { lo, hi, inclusive } => {
2092 self.emit_pattern(lo)?;
2093 if *inclusive {
2094 self.buf.push_str("..=");
2095 } else {
2096 self.buf.push_str("..");
2097 }
2098 self.emit_pattern(hi)?;
2099 }
2100 NodeKind::RestPat => {
2101 self.buf.push_str("..");
2102 }
2103 _ => {
2104 self.buf.push('_');
2105 }
2106 }
2107 Ok(())
2108 }
2109
2110 fn emit_pipe(&mut self, left: &AIRNode, right: &AIRNode) -> Result<(), CodegenError> {
2113 if let NodeKind::Call { callee, args, .. } = &right.kind {
2114 let has_placeholder = args
2115 .iter()
2116 .any(|a| matches!(a.value.kind, NodeKind::Placeholder));
2117 if has_placeholder {
2118 self.emit_expr(callee)?;
2119 self.buf.push('(');
2120 for (i, arg) in args.iter().enumerate() {
2121 if i > 0 {
2122 self.buf.push_str(", ");
2123 }
2124 if matches!(arg.value.kind, NodeKind::Placeholder) {
2125 self.emit_expr(left)?;
2126 } else {
2127 self.emit_expr(&arg.value)?;
2128 }
2129 }
2130 self.buf.push(')');
2131 return Ok(());
2132 }
2133 }
2134 self.emit_expr(right)?;
2135 self.buf.push('(');
2136 self.emit_expr(left)?;
2137 self.buf.push(')');
2138 Ok(())
2139 }
2140
2141 fn emit_block_body(&mut self, node: &AIRNode) -> Result<(), CodegenError> {
2144 if let NodeKind::Block { stmts, tail } = &node.kind {
2145 if stmts.is_empty() && tail.is_none() {
2146 return Ok(());
2148 }
2149 let task_bindings = Self::collect_task_bindings(stmts);
2155 let prev = std::mem::replace(&mut self.task_bound_names, task_bindings);
2156 for s in stmts {
2157 self.emit_node(s)?;
2158 }
2159 self.task_bound_names = prev;
2160 if let Some(t) = tail {
2161 self.write_indent();
2163 self.emit_expr(t)?;
2164 self.buf.push('\n');
2165 }
2166 } else {
2167 self.write_indent();
2169 self.emit_expr(node)?;
2170 self.buf.push('\n');
2171 }
2172 Ok(())
2173 }
2174
2175 fn collect_task_bindings(
2184 stmts: &[AIRNode],
2185 ) -> std::collections::HashSet<String> {
2186 let mut awaited: std::collections::HashSet<String> =
2187 std::collections::HashSet::new();
2188 for s in stmts {
2189 Self::collect_awaited_identifiers(s, &mut awaited);
2190 }
2191 let mut out = std::collections::HashSet::new();
2192 for s in stmts {
2193 if let NodeKind::LetBinding { pattern, value, .. } = &s.kind {
2194 if let NodeKind::BindPat { name, .. } = &pattern.kind {
2195 let rs_name = to_snake_case(&name.name);
2196 if matches!(&value.kind, NodeKind::Call { .. })
2197 && awaited.contains(&rs_name)
2198 {
2199 out.insert(rs_name);
2200 }
2201 }
2202 }
2203 }
2204 out
2205 }
2206
2207 fn collect_awaited_identifiers(
2212 node: &AIRNode,
2213 out: &mut std::collections::HashSet<String>,
2214 ) {
2215 match &node.kind {
2216 NodeKind::Await { expr } => {
2217 if let NodeKind::Identifier { name } = &expr.kind {
2218 out.insert(to_snake_case(&name.name));
2219 }
2220 Self::collect_awaited_identifiers(expr, out);
2221 }
2222 NodeKind::Lambda { .. } | NodeKind::FnDecl { .. } => {
2223 }
2225 NodeKind::Block { stmts, tail } => {
2226 for s in stmts {
2227 Self::collect_awaited_identifiers(s, out);
2228 }
2229 if let Some(t) = tail {
2230 Self::collect_awaited_identifiers(t, out);
2231 }
2232 }
2233 NodeKind::LetBinding { value, .. } => {
2234 Self::collect_awaited_identifiers(value, out);
2235 }
2236 NodeKind::Call { callee, args, .. } => {
2237 Self::collect_awaited_identifiers(callee, out);
2238 for a in args {
2239 Self::collect_awaited_identifiers(&a.value, out);
2240 }
2241 }
2242 NodeKind::MethodCall { receiver, args, .. } => {
2243 Self::collect_awaited_identifiers(receiver, out);
2244 for a in args {
2245 Self::collect_awaited_identifiers(&a.value, out);
2246 }
2247 }
2248 NodeKind::BinaryOp { left, right, .. } => {
2249 Self::collect_awaited_identifiers(left, out);
2250 Self::collect_awaited_identifiers(right, out);
2251 }
2252 NodeKind::UnaryOp { operand, .. } => {
2253 Self::collect_awaited_identifiers(operand, out);
2254 }
2255 NodeKind::If {
2256 condition,
2257 then_block,
2258 else_block,
2259 ..
2260 } => {
2261 Self::collect_awaited_identifiers(condition, out);
2262 Self::collect_awaited_identifiers(then_block, out);
2263 if let Some(e) = else_block {
2264 Self::collect_awaited_identifiers(e, out);
2265 }
2266 }
2267 NodeKind::While { condition, body } => {
2268 Self::collect_awaited_identifiers(condition, out);
2269 Self::collect_awaited_identifiers(body, out);
2270 }
2271 NodeKind::For { iterable, body, .. } => {
2272 Self::collect_awaited_identifiers(iterable, out);
2273 Self::collect_awaited_identifiers(body, out);
2274 }
2275 NodeKind::Return { value: Some(v) } | NodeKind::Break { value: Some(v) } => {
2276 Self::collect_awaited_identifiers(v, out);
2277 }
2278 NodeKind::Assign { value, .. } => {
2279 Self::collect_awaited_identifiers(value, out);
2280 }
2281 NodeKind::TupleLiteral { elems }
2282 | NodeKind::ListLiteral { elems } => {
2283 for e in elems {
2284 Self::collect_awaited_identifiers(e, out);
2285 }
2286 }
2287 _ => {}
2288 }
2289 }
2290
2291 fn emit_block_as_expr(&mut self, node: &AIRNode) -> Result<(), CodegenError> {
2292 if let NodeKind::Block { stmts, tail } = &node.kind {
2293 if stmts.is_empty() {
2294 if let Some(t) = tail {
2295 return self.emit_expr(t);
2296 }
2297 }
2298 }
2299 self.emit_expr(node)
2300 }
2301
2302 fn pattern_to_binding_name(&self, pat: &AIRNode) -> String {
2303 match &pat.kind {
2304 NodeKind::BindPat { name, .. } => to_snake_case(&name.name),
2305 NodeKind::WildcardPat => "_".into(),
2306 NodeKind::TuplePat { elems } => {
2307 format!(
2308 "({})",
2309 elems
2310 .iter()
2311 .map(|e| self.pattern_to_binding_name(e))
2312 .collect::<Vec<_>>()
2313 .join(", ")
2314 )
2315 }
2316 _ => "_".into(),
2317 }
2318 }
2319
2320 fn pattern_to_rs_binding(&self, pat: &AIRNode) -> String {
2321 self.pattern_to_binding_name(pat)
2322 }
2323
2324 fn type_expr_to_string(&mut self, node: &AIRNode) -> String {
2325 match &node.kind {
2326 NodeKind::TypeNamed { path, args } => {
2327 let name = path
2328 .segments
2329 .iter()
2330 .map(|s| s.name.as_str())
2331 .collect::<Vec<_>>()
2332 .join("::");
2333 if args.is_empty() {
2334 name
2335 } else {
2336 let arg_strs: Vec<String> = args.iter().map(|a| self.type_to_rs(a)).collect();
2337 format!("{name}<{}>", arg_strs.join(", "))
2338 }
2339 }
2340 NodeKind::Identifier { name } => name.name.clone(),
2341 _ => "Unknown".into(),
2342 }
2343 }
2344}
2345
2346fn vis_str(v: Visibility) -> &'static str {
2350 match v {
2351 Visibility::Public => "pub ",
2352 Visibility::Private => "",
2353 Visibility::Internal => "pub(crate) ",
2354 }
2355}
2356
2357fn record_construct_type(node: &AIRNode) -> Option<String> {
2361 if let NodeKind::RecordConstruct { path, .. } = &node.kind {
2362 let joined = path
2363 .segments
2364 .iter()
2365 .map(|s| s.name.as_str())
2366 .collect::<Vec<_>>()
2367 .join("::");
2368 Some(joined)
2369 } else {
2370 None
2371 }
2372}
2373
2374fn identifier_to_rs(s: &str) -> String {
2378 if s.chars().next().is_some_and(char::is_uppercase) {
2379 s.to_string()
2380 } else {
2381 to_snake_case(s)
2382 }
2383}
2384
2385fn is_time_method_name(name: &str) -> bool {
2388 matches!(
2389 name,
2390 "as_nanos"
2391 | "as_millis"
2392 | "as_seconds"
2393 | "is_zero"
2394 | "is_negative"
2395 | "abs"
2396 | "elapsed"
2397 | "duration_since"
2398 )
2399}
2400
2401fn to_snake_case(s: &str) -> String {
2403 if s.is_empty() || s == "_" {
2404 return s.to_string();
2405 }
2406 if s.contains('_') && !s.chars().any(|c| c.is_uppercase()) {
2407 return s.to_string();
2408 }
2409 if !s.chars().any(|c| c.is_uppercase()) {
2410 return s.to_string();
2411 }
2412 if s.len() == 1 {
2413 return s.to_lowercase();
2414 }
2415
2416 let mut result = String::with_capacity(s.len() + 4);
2417 let chars: Vec<char> = s.chars().collect();
2418
2419 for (i, &ch) in chars.iter().enumerate() {
2420 if ch.is_uppercase() {
2421 let prev_is_upper = i > 0 && chars[i - 1].is_uppercase();
2422 let prev_is_underscore = i > 0 && chars[i - 1] == '_';
2423 let next_is_lower = i + 1 < chars.len() && chars[i + 1].is_lowercase();
2424 if i > 0 && !prev_is_underscore && (!prev_is_upper || next_is_lower) {
2425 result.push('_');
2426 }
2427 result.push(
2428 ch.to_lowercase()
2429 .next()
2430 .expect("lowercase yields at least one char"),
2431 );
2432 } else {
2433 result.push(ch);
2434 }
2435 }
2436 result
2437}
2438
2439fn to_upper_snake_case(s: &str) -> String {
2441 to_snake_case(s).to_uppercase()
2442}
2443
2444fn escape_rs_string(s: &str) -> String {
2446 let mut out = String::with_capacity(s.len());
2447 for ch in s.chars() {
2448 match ch {
2449 '"' => out.push_str("\\\""),
2450 '\\' => out.push_str("\\\\"),
2451 '\n' => out.push_str("\\n"),
2452 '\r' => out.push_str("\\r"),
2453 '\t' => out.push_str("\\t"),
2454 _ => out.push(ch),
2455 }
2456 }
2457 out
2458}
2459
2460fn escape_format_string(s: &str) -> String {
2462 let mut out = String::with_capacity(s.len());
2463 for ch in s.chars() {
2464 match ch {
2465 '"' => out.push_str("\\\""),
2466 '\\' => out.push_str("\\\\"),
2467 '{' => out.push_str("{{"),
2468 '}' => out.push_str("}}"),
2469 _ => out.push(ch),
2470 }
2471 }
2472 out
2473}
2474
2475#[cfg(test)]
2478mod tests {
2479 use super::*;
2480 use bock_air::{AirArg, AirMapEntry, AirRecordField};
2481 use bock_ast::{GenericParam, Ident, ImportedName, ModulePath, RecordDeclField, TypePath};
2482 use bock_errors::{FileId, Span};
2483
2484 fn span() -> Span {
2485 Span {
2486 file: FileId(0),
2487 start: 0,
2488 end: 0,
2489 }
2490 }
2491
2492 fn ident(name: &str) -> Ident {
2493 Ident {
2494 name: name.to_string(),
2495 span: span(),
2496 }
2497 }
2498
2499 fn type_path(segments: &[&str]) -> TypePath {
2500 TypePath {
2501 segments: segments.iter().map(|s| ident(s)).collect(),
2502 span: span(),
2503 }
2504 }
2505
2506 fn mod_path(segments: &[&str]) -> ModulePath {
2507 ModulePath {
2508 segments: segments.iter().map(|s| ident(s)).collect(),
2509 span: span(),
2510 }
2511 }
2512
2513 fn imported_name(name: &str) -> ImportedName {
2514 ImportedName {
2515 span: span(),
2516 name: ident(name),
2517 alias: None,
2518 }
2519 }
2520
2521 fn record_field(name: &str, ty_name: &str) -> RecordDeclField {
2522 RecordDeclField {
2523 id: 0,
2524 span: span(),
2525 name: ident(name),
2526 ty: TypeExpr::Named {
2527 id: 0,
2528 path: type_path(&[ty_name]),
2529 args: vec![],
2530 span: span(),
2531 },
2532 default: None,
2533 }
2534 }
2535
2536 fn node(id: u32, kind: NodeKind) -> AIRNode {
2537 AIRNode::new(id, span(), kind)
2538 }
2539
2540 fn int_lit(id: u32, val: &str) -> AIRNode {
2541 node(
2542 id,
2543 NodeKind::Literal {
2544 lit: Literal::Int(val.into()),
2545 },
2546 )
2547 }
2548
2549 fn str_lit(id: u32, val: &str) -> AIRNode {
2550 node(
2551 id,
2552 NodeKind::Literal {
2553 lit: Literal::String(val.into()),
2554 },
2555 )
2556 }
2557
2558 fn bool_lit(id: u32, val: bool) -> AIRNode {
2559 node(
2560 id,
2561 NodeKind::Literal {
2562 lit: Literal::Bool(val),
2563 },
2564 )
2565 }
2566
2567 fn id_node(id: u32, name: &str) -> AIRNode {
2568 node(id, NodeKind::Identifier { name: ident(name) })
2569 }
2570
2571 fn bind_pat(id: u32, name: &str) -> AIRNode {
2572 node(
2573 id,
2574 NodeKind::BindPat {
2575 name: ident(name),
2576 is_mut: false,
2577 },
2578 )
2579 }
2580
2581 fn typed_param_node(id: u32, name: &str, ty_name: &str) -> AIRNode {
2582 node(
2583 id,
2584 NodeKind::Param {
2585 pattern: Box::new(bind_pat(id + 100, name)),
2586 ty: Some(Box::new(node(
2587 id + 200,
2588 NodeKind::TypeNamed {
2589 path: type_path(&[ty_name]),
2590 args: vec![],
2591 },
2592 ))),
2593 default: None,
2594 },
2595 )
2596 }
2597
2598 fn block(id: u32, stmts: Vec<AIRNode>, tail: Option<AIRNode>) -> AIRNode {
2599 node(
2600 id,
2601 NodeKind::Block {
2602 stmts,
2603 tail: tail.map(Box::new),
2604 },
2605 )
2606 }
2607
2608 fn module(imports: Vec<AIRNode>, items: Vec<AIRNode>) -> AIRNode {
2609 node(
2610 0,
2611 NodeKind::Module {
2612 path: None,
2613 annotations: vec![],
2614 imports,
2615 items,
2616 },
2617 )
2618 }
2619
2620 fn gen(module: &AIRNode) -> String {
2621 let gen = RsGenerator::new();
2622 let result = gen.generate_module(module).unwrap();
2623 result.files[0].content.clone()
2624 }
2625
2626 fn check_rs_syntax(code: &str) -> bool {
2628 use std::io::Write;
2629 use std::process::Command;
2630 let id = std::thread::current().id();
2631 let dir = std::env::temp_dir().join(format!("bock_rs_test_{id:?}"));
2632 let _ = std::fs::create_dir_all(&dir);
2633 let path = dir.join("test_output.rs");
2634 {
2635 let mut f = std::fs::File::create(&path).unwrap();
2636 f.write_all(code.as_bytes()).unwrap();
2637 }
2638 let output = Command::new("rustc")
2639 .args([
2640 "--edition",
2641 "2021",
2642 "--crate-type",
2643 "lib",
2644 "-o",
2645 dir.join("test_output.rlib").to_str().unwrap(),
2646 ])
2647 .arg(&path)
2648 .stderr(std::process::Stdio::piped())
2649 .output();
2650 match output {
2651 Ok(o) => {
2652 if !o.status.success() {
2653 eprintln!("rustc stderr: {}", String::from_utf8_lossy(&o.stderr));
2654 }
2655 o.status.success()
2656 }
2657 Err(_) => false,
2658 }
2659 }
2660
2661 #[test]
2664 fn implements_code_generator_trait() {
2665 let gen = RsGenerator::new();
2666 assert_eq!(gen.target().id, "rust");
2667 }
2668
2669 #[test]
2670 fn empty_module() {
2671 let m = module(vec![], vec![]);
2672 let out = gen(&m);
2673 assert_eq!(out, "");
2674 }
2675
2676 #[test]
2677 fn simple_function() {
2678 let body = block(2, vec![], Some(int_lit(3, "42")));
2679 let f = node(
2680 1,
2681 NodeKind::FnDecl {
2682 annotations: vec![],
2683 visibility: Visibility::Private,
2684 is_async: false,
2685 name: ident("answer"),
2686 generic_params: vec![],
2687 params: vec![],
2688 return_type: None,
2689 effect_clause: vec![],
2690 where_clause: vec![],
2691 body: Box::new(body),
2692 },
2693 );
2694 let out = gen(&module(vec![], vec![f]));
2695 assert!(out.contains("fn answer()"), "got: {out}");
2696 assert!(out.contains("42"), "got: {out}");
2697 }
2698
2699 #[test]
2700 fn public_function_with_params() {
2701 let body = block(
2702 5,
2703 vec![],
2704 Some(node(
2705 6,
2706 NodeKind::BinaryOp {
2707 op: BinOp::Add,
2708 left: Box::new(id_node(7, "a")),
2709 right: Box::new(id_node(8, "b")),
2710 },
2711 )),
2712 );
2713 let f = node(
2714 1,
2715 NodeKind::FnDecl {
2716 annotations: vec![],
2717 visibility: Visibility::Public,
2718 is_async: false,
2719 name: ident("add"),
2720 generic_params: vec![],
2721 params: vec![
2722 typed_param_node(2, "a", "Int"),
2723 typed_param_node(3, "b", "Int"),
2724 ],
2725 return_type: Some(Box::new(node(
2726 4,
2727 NodeKind::TypeNamed {
2728 path: type_path(&["Int"]),
2729 args: vec![],
2730 },
2731 ))),
2732 effect_clause: vec![],
2733 where_clause: vec![],
2734 body: Box::new(body),
2735 },
2736 );
2737 let out = gen(&module(vec![], vec![f]));
2738 assert!(
2739 out.contains("pub fn add(a: i64, b: i64) -> i64"),
2740 "got: {out}"
2741 );
2742 assert!(out.contains("(a + b)"), "got: {out}");
2743 }
2744
2745 #[test]
2746 fn record_to_struct() {
2747 let record = node(
2748 1,
2749 NodeKind::RecordDecl {
2750 annotations: vec![],
2751 visibility: Visibility::Public,
2752 name: ident("Point"),
2753 generic_params: vec![],
2754 fields: vec![record_field("x", "Float"), record_field("y", "Float")],
2755 },
2756 );
2757 let out = gen(&module(vec![], vec![record]));
2758 assert!(out.contains("pub struct Point {"), "got: {out}");
2759 assert!(out.contains("pub x: f64,"), "got: {out}");
2760 assert!(out.contains("pub y: f64,"), "got: {out}");
2761 }
2762
2763 #[test]
2764 fn enum_with_variants() {
2765 let e = node(
2766 1,
2767 NodeKind::EnumDecl {
2768 annotations: vec![],
2769 visibility: Visibility::Public,
2770 name: ident("Color"),
2771 generic_params: vec![],
2772 variants: vec![
2773 node(
2774 2,
2775 NodeKind::EnumVariant {
2776 name: ident("Red"),
2777 payload: EnumVariantPayload::Unit,
2778 },
2779 ),
2780 node(
2781 3,
2782 NodeKind::EnumVariant {
2783 name: ident("Green"),
2784 payload: EnumVariantPayload::Unit,
2785 },
2786 ),
2787 node(
2788 4,
2789 NodeKind::EnumVariant {
2790 name: ident("Rgb"),
2791 payload: EnumVariantPayload::Struct(vec![
2792 record_field("r", "Int"),
2793 record_field("g", "Int"),
2794 ]),
2795 },
2796 ),
2797 node(
2798 7,
2799 NodeKind::EnumVariant {
2800 name: ident("Custom"),
2801 payload: EnumVariantPayload::Tuple(vec![node(
2802 8,
2803 NodeKind::TypeNamed {
2804 path: type_path(&["String"]),
2805 args: vec![],
2806 },
2807 )]),
2808 },
2809 ),
2810 ],
2811 },
2812 );
2813 let out = gen(&module(vec![], vec![e]));
2814 assert!(out.contains("pub enum Color {"), "got: {out}");
2815 assert!(out.contains("Red,"), "got: {out}");
2816 assert!(out.contains("Green,"), "got: {out}");
2817 assert!(out.contains("Rgb {"), "got: {out}");
2818 assert!(out.contains("r: i64,"), "got: {out}");
2819 assert!(out.contains("Custom(String),"), "got: {out}");
2820 }
2821
2822 #[test]
2823 fn trait_declaration() {
2824 let t = node(
2825 1,
2826 NodeKind::TraitDecl {
2827 annotations: vec![],
2828 visibility: Visibility::Public,
2829 is_platform: false,
2830 name: ident("Printable"),
2831 generic_params: vec![],
2832 associated_types: vec![],
2833 methods: vec![node(
2834 2,
2835 NodeKind::FnDecl {
2836 annotations: vec![],
2837 visibility: Visibility::Public,
2838 is_async: false,
2839 name: ident("print"),
2840 generic_params: vec![],
2841 params: vec![],
2842 return_type: None,
2843 effect_clause: vec![],
2844 where_clause: vec![],
2845 body: Box::new(block(3, vec![], None)),
2846 },
2847 )],
2848 },
2849 );
2850 let out = gen(&module(vec![], vec![t]));
2851 assert!(out.contains("pub trait Printable {"), "got: {out}");
2852 assert!(out.contains("fn print(&self);"), "got: {out}");
2853 }
2854
2855 #[test]
2856 fn impl_block() {
2857 let imp = node(
2858 1,
2859 NodeKind::ImplBlock {
2860 annotations: vec![],
2861 generic_params: vec![],
2862 trait_path: Some(type_path(&["Printable"])),
2863 target: Box::new(node(
2864 2,
2865 NodeKind::TypeNamed {
2866 path: type_path(&["Point"]),
2867 args: vec![],
2868 },
2869 )),
2870 where_clause: vec![],
2871 methods: vec![node(
2872 3,
2873 NodeKind::FnDecl {
2874 annotations: vec![],
2875 visibility: Visibility::Public,
2876 is_async: false,
2877 name: ident("print"),
2878 generic_params: vec![],
2879 params: vec![],
2880 return_type: None,
2881 effect_clause: vec![],
2882 where_clause: vec![],
2883 body: Box::new(block(4, vec![], Some(str_lit(5, "point")))),
2884 },
2885 )],
2886 },
2887 );
2888 let out = gen(&module(vec![], vec![imp]));
2889 assert!(out.contains("impl Printable for Point {"), "got: {out}");
2890 assert!(out.contains("fn print(&self)"), "got: {out}");
2891 }
2892
2893 #[test]
2894 fn effect_as_trait() {
2895 let eff = node(
2896 1,
2897 NodeKind::EffectDecl {
2898 annotations: vec![],
2899 visibility: Visibility::Public,
2900 name: ident("Log"),
2901 generic_params: vec![],
2902 components: vec![],
2903 operations: vec![node(
2904 2,
2905 NodeKind::FnDecl {
2906 annotations: vec![],
2907 visibility: Visibility::Public,
2908 is_async: false,
2909 name: ident("log"),
2910 generic_params: vec![],
2911 params: vec![typed_param_node(3, "msg", "String")],
2912 return_type: None,
2913 effect_clause: vec![],
2914 where_clause: vec![],
2915 body: Box::new(block(4, vec![], None)),
2916 },
2917 )],
2918 },
2919 );
2920 let out = gen(&module(vec![], vec![eff]));
2921 assert!(out.contains("pub trait Log {"), "got: {out}");
2922 assert!(out.contains("fn log(&self, msg: String)"), "got: {out}");
2923 }
2924
2925 #[test]
2926 fn function_with_effects() {
2927 let body = block(3, vec![], Some(int_lit(4, "0")));
2928 let f = node(
2929 1,
2930 NodeKind::FnDecl {
2931 annotations: vec![],
2932 visibility: Visibility::Public,
2933 is_async: false,
2934 name: ident("process"),
2935 generic_params: vec![],
2936 params: vec![typed_param_node(2, "data", "String")],
2937 return_type: Some(Box::new(node(
2938 5,
2939 NodeKind::TypeNamed {
2940 path: type_path(&["Int"]),
2941 args: vec![],
2942 },
2943 ))),
2944 effect_clause: vec![type_path(&["Log"]), type_path(&["Clock"])],
2945 where_clause: vec![],
2946 body: Box::new(body),
2947 },
2948 );
2949 let out = gen(&module(vec![], vec![f]));
2950 assert!(
2951 out.contains("pub fn process(data: String, log: &impl Log, clock: &impl Clock) -> i64"),
2952 "got: {out}"
2953 );
2954 }
2955
2956 #[test]
2957 fn ownership_borrow() {
2958 let borrow = node(
2959 1,
2960 NodeKind::Borrow {
2961 expr: Box::new(id_node(2, "x")),
2962 },
2963 );
2964 let m = module(
2965 vec![],
2966 vec![node(
2967 3,
2968 NodeKind::FnDecl {
2969 annotations: vec![],
2970 visibility: Visibility::Private,
2971 is_async: false,
2972 name: ident("test"),
2973 generic_params: vec![],
2974 params: vec![],
2975 return_type: None,
2976 effect_clause: vec![],
2977 where_clause: vec![],
2978 body: Box::new(block(4, vec![], Some(borrow))),
2979 },
2980 )],
2981 );
2982 let out = gen(&m);
2983 assert!(out.contains("&x"), "got: {out}");
2984 }
2985
2986 #[test]
2987 fn ownership_mutable_borrow() {
2988 let mborrow = node(
2989 1,
2990 NodeKind::MutableBorrow {
2991 expr: Box::new(id_node(2, "x")),
2992 },
2993 );
2994 let m = module(
2995 vec![],
2996 vec![node(
2997 3,
2998 NodeKind::FnDecl {
2999 annotations: vec![],
3000 visibility: Visibility::Private,
3001 is_async: false,
3002 name: ident("test"),
3003 generic_params: vec![],
3004 params: vec![],
3005 return_type: None,
3006 effect_clause: vec![],
3007 where_clause: vec![],
3008 body: Box::new(block(4, vec![], Some(mborrow))),
3009 },
3010 )],
3011 );
3012 let out = gen(&m);
3013 assert!(out.contains("&mut x"), "got: {out}");
3014 }
3015
3016 #[test]
3017 fn let_binding_with_mut() {
3018 let let_node = node(
3019 1,
3020 NodeKind::LetBinding {
3021 is_mut: true,
3022 pattern: Box::new(bind_pat(2, "x")),
3023 ty: Some(Box::new(node(
3024 3,
3025 NodeKind::TypeNamed {
3026 path: type_path(&["Int"]),
3027 args: vec![],
3028 },
3029 ))),
3030 value: Box::new(int_lit(4, "42")),
3031 },
3032 );
3033 let m = module(
3034 vec![],
3035 vec![node(
3036 5,
3037 NodeKind::FnDecl {
3038 annotations: vec![],
3039 visibility: Visibility::Private,
3040 is_async: false,
3041 name: ident("test"),
3042 generic_params: vec![],
3043 params: vec![],
3044 return_type: None,
3045 effect_clause: vec![],
3046 where_clause: vec![],
3047 body: Box::new(block(6, vec![let_node], None)),
3048 },
3049 )],
3050 );
3051 let out = gen(&m);
3052 assert!(out.contains("let mut x: i64 = 42_i64;"), "got: {out}");
3053 }
3054
3055 #[test]
3056 fn match_expression() {
3057 let m_node = node(
3058 1,
3059 NodeKind::Match {
3060 scrutinee: Box::new(id_node(2, "color")),
3061 arms: vec![
3062 node(
3063 3,
3064 NodeKind::MatchArm {
3065 pattern: Box::new(node(
3066 4,
3067 NodeKind::ConstructorPat {
3068 path: type_path(&["Color", "Red"]),
3069 fields: vec![],
3070 },
3071 )),
3072 guard: None,
3073 body: Box::new(block(5, vec![], Some(str_lit(6, "red")))),
3074 },
3075 ),
3076 node(
3077 7,
3078 NodeKind::MatchArm {
3079 pattern: Box::new(node(8, NodeKind::WildcardPat)),
3080 guard: None,
3081 body: Box::new(block(9, vec![], Some(str_lit(10, "other")))),
3082 },
3083 ),
3084 ],
3085 },
3086 );
3087 let f = node(
3088 11,
3089 NodeKind::FnDecl {
3090 annotations: vec![],
3091 visibility: Visibility::Private,
3092 is_async: false,
3093 name: ident("test"),
3094 generic_params: vec![],
3095 params: vec![],
3096 return_type: None,
3097 effect_clause: vec![],
3098 where_clause: vec![],
3099 body: Box::new(block(12, vec![m_node], None)),
3100 },
3101 );
3102 let out = gen(&module(vec![], vec![f]));
3103 assert!(out.contains("match color"), "got: {out}");
3104 assert!(out.contains("Color::Red =>"), "got: {out}");
3105 assert!(out.contains("_ =>"), "got: {out}");
3106 }
3107
3108 #[test]
3109 fn string_interpolation() {
3110 let interp = node(
3111 1,
3112 NodeKind::Interpolation {
3113 parts: vec![
3114 AirInterpolationPart::Literal("Hello, ".into()),
3115 AirInterpolationPart::Expr(Box::new(id_node(2, "name"))),
3116 AirInterpolationPart::Literal("!".into()),
3117 ],
3118 },
3119 );
3120 let f = node(
3121 3,
3122 NodeKind::FnDecl {
3123 annotations: vec![],
3124 visibility: Visibility::Private,
3125 is_async: false,
3126 name: ident("test"),
3127 generic_params: vec![],
3128 params: vec![],
3129 return_type: None,
3130 effect_clause: vec![],
3131 where_clause: vec![],
3132 body: Box::new(block(4, vec![], Some(interp))),
3133 },
3134 );
3135 let out = gen(&module(vec![], vec![f]));
3136 assert!(out.contains("format!(\"Hello, {}!\", name)"), "got: {out}");
3137 }
3138
3139 #[test]
3140 fn result_construct() {
3141 let ok = node(
3142 1,
3143 NodeKind::ResultConstruct {
3144 variant: ResultVariant::Ok,
3145 value: Some(Box::new(int_lit(2, "42"))),
3146 },
3147 );
3148 let err = node(
3149 3,
3150 NodeKind::ResultConstruct {
3151 variant: ResultVariant::Err,
3152 value: Some(Box::new(str_lit(4, "oops"))),
3153 },
3154 );
3155 let f = node(
3156 5,
3157 NodeKind::FnDecl {
3158 annotations: vec![],
3159 visibility: Visibility::Private,
3160 is_async: false,
3161 name: ident("test"),
3162 generic_params: vec![],
3163 params: vec![],
3164 return_type: None,
3165 effect_clause: vec![],
3166 where_clause: vec![],
3167 body: Box::new(block(6, vec![], Some(ok))),
3168 },
3169 );
3170 let out = gen(&module(vec![], vec![f]));
3171 assert!(out.contains("Ok(42_i64)"), "got: {out}");
3172
3173 let f2 = node(
3174 7,
3175 NodeKind::FnDecl {
3176 annotations: vec![],
3177 visibility: Visibility::Private,
3178 is_async: false,
3179 name: ident("test2"),
3180 generic_params: vec![],
3181 params: vec![],
3182 return_type: None,
3183 effect_clause: vec![],
3184 where_clause: vec![],
3185 body: Box::new(block(8, vec![], Some(err))),
3186 },
3187 );
3188 let out2 = gen(&module(vec![], vec![f2]));
3189 assert!(out2.contains("Err(\"oops\".to_string())"), "got: {out2}");
3190 }
3191
3192 #[test]
3193 fn vec_literal() {
3194 let list = node(
3195 1,
3196 NodeKind::ListLiteral {
3197 elems: vec![int_lit(2, "1"), int_lit(3, "2"), int_lit(4, "3")],
3198 },
3199 );
3200 let f = node(
3201 5,
3202 NodeKind::FnDecl {
3203 annotations: vec![],
3204 visibility: Visibility::Private,
3205 is_async: false,
3206 name: ident("test"),
3207 generic_params: vec![],
3208 params: vec![],
3209 return_type: None,
3210 effect_clause: vec![],
3211 where_clause: vec![],
3212 body: Box::new(block(6, vec![], Some(list))),
3213 },
3214 );
3215 let out = gen(&module(vec![], vec![f]));
3216 assert!(out.contains("vec![1_i64, 2_i64, 3_i64]"), "got: {out}");
3217 }
3218
3219 #[test]
3220 fn propagate_operator() {
3221 let prop = node(
3222 1,
3223 NodeKind::Propagate {
3224 expr: Box::new(node(
3225 2,
3226 NodeKind::Call {
3227 callee: Box::new(id_node(3, "parse")),
3228 args: vec![],
3229 type_args: vec![],
3230 },
3231 )),
3232 },
3233 );
3234 let f = node(
3235 4,
3236 NodeKind::FnDecl {
3237 annotations: vec![],
3238 visibility: Visibility::Private,
3239 is_async: false,
3240 name: ident("test"),
3241 generic_params: vec![],
3242 params: vec![],
3243 return_type: None,
3244 effect_clause: vec![],
3245 where_clause: vec![],
3246 body: Box::new(block(5, vec![], Some(prop))),
3247 },
3248 );
3249 let out = gen(&module(vec![], vec![f]));
3250 assert!(out.contains("parse()?"), "got: {out}");
3251 }
3252
3253 #[test]
3254 fn range_expression() {
3255 let range = node(
3256 1,
3257 NodeKind::Range {
3258 lo: Box::new(int_lit(2, "0")),
3259 hi: Box::new(int_lit(3, "10")),
3260 inclusive: false,
3261 },
3262 );
3263 let range_incl = node(
3264 4,
3265 NodeKind::Range {
3266 lo: Box::new(int_lit(5, "0")),
3267 hi: Box::new(int_lit(6, "10")),
3268 inclusive: true,
3269 },
3270 );
3271 let f = node(
3272 7,
3273 NodeKind::FnDecl {
3274 annotations: vec![],
3275 visibility: Visibility::Private,
3276 is_async: false,
3277 name: ident("test"),
3278 generic_params: vec![],
3279 params: vec![],
3280 return_type: None,
3281 effect_clause: vec![],
3282 where_clause: vec![],
3283 body: Box::new(block(8, vec![], Some(range))),
3284 },
3285 );
3286 let out = gen(&module(vec![], vec![f]));
3287 assert!(out.contains("0_i64..10_i64"), "got: {out}");
3288
3289 let f2 = node(
3290 9,
3291 NodeKind::FnDecl {
3292 annotations: vec![],
3293 visibility: Visibility::Private,
3294 is_async: false,
3295 name: ident("test2"),
3296 generic_params: vec![],
3297 params: vec![],
3298 return_type: None,
3299 effect_clause: vec![],
3300 where_clause: vec![],
3301 body: Box::new(block(10, vec![], Some(range_incl))),
3302 },
3303 );
3304 let out2 = gen(&module(vec![], vec![f2]));
3305 assert!(out2.contains("0_i64..=10_i64"), "got: {out2}");
3306 }
3307
3308 #[test]
3309 fn generics_with_bounds() {
3310 let f = node(
3311 1,
3312 NodeKind::FnDecl {
3313 annotations: vec![],
3314 visibility: Visibility::Public,
3315 is_async: false,
3316 name: ident("show"),
3317 generic_params: vec![GenericParam {
3318 id: 100,
3319 span: span(),
3320 name: ident("T"),
3321 bounds: vec![type_path(&["Display"])],
3322 }],
3323 params: vec![typed_param_node(2, "val", "T")],
3324 return_type: Some(Box::new(node(
3325 3,
3326 NodeKind::TypeNamed {
3327 path: type_path(&["String"]),
3328 args: vec![],
3329 },
3330 ))),
3331 effect_clause: vec![],
3332 where_clause: vec![],
3333 body: Box::new(block(4, vec![], Some(id_node(5, "val")))),
3334 },
3335 );
3336 let out = gen(&module(vec![], vec![f]));
3337 assert!(
3338 out.contains("pub fn show<T: Display>(val: T) -> String"),
3339 "got: {out}"
3340 );
3341 }
3342
3343 #[test]
3344 fn type_alias() {
3345 let alias = node(
3346 1,
3347 NodeKind::TypeAlias {
3348 annotations: vec![],
3349 visibility: Visibility::Public,
3350 name: ident("Coord"),
3351 generic_params: vec![],
3352 ty: Box::new(node(
3353 2,
3354 NodeKind::TypeTuple {
3355 elems: vec![
3356 node(
3357 3,
3358 NodeKind::TypeNamed {
3359 path: type_path(&["Float"]),
3360 args: vec![],
3361 },
3362 ),
3363 node(
3364 4,
3365 NodeKind::TypeNamed {
3366 path: type_path(&["Float"]),
3367 args: vec![],
3368 },
3369 ),
3370 ],
3371 },
3372 )),
3373 where_clause: vec![],
3374 },
3375 );
3376 let out = gen(&module(vec![], vec![alias]));
3377 assert!(out.contains("pub type Coord = (f64, f64);"), "got: {out}");
3378 }
3379
3380 #[test]
3381 fn const_declaration() {
3382 let c = node(
3383 1,
3384 NodeKind::ConstDecl {
3385 annotations: vec![],
3386 visibility: Visibility::Public,
3387 name: ident("MaxSize"),
3388 ty: Box::new(node(
3389 2,
3390 NodeKind::TypeNamed {
3391 path: type_path(&["Int"]),
3392 args: vec![],
3393 },
3394 )),
3395 value: Box::new(int_lit(3, "100")),
3396 },
3397 );
3398 let out = gen(&module(vec![], vec![c]));
3399 assert!(
3400 out.contains("pub const MAX_SIZE: i64 = 100_i64;"),
3401 "got: {out}"
3402 );
3403 }
3404
3405 #[test]
3406 fn import_declaration() {
3407 let imp = node(
3408 1,
3409 NodeKind::ImportDecl {
3410 path: mod_path(&["std", "io"]),
3411 items: ImportItems::Named(vec![imported_name("Read"), imported_name("Write")]),
3412 },
3413 );
3414 let out = gen(&module(vec![imp], vec![]));
3415 assert!(out.contains("use std::io::{Read, Write};"), "got: {out}");
3416 }
3417
3418 #[test]
3419 fn for_loop() {
3420 let body = block(
3421 3,
3422 vec![node(
3423 4,
3424 NodeKind::LetBinding {
3425 is_mut: false,
3426 pattern: Box::new(bind_pat(5, "y")),
3427 ty: None,
3428 value: Box::new(id_node(6, "x")),
3429 },
3430 )],
3431 None,
3432 );
3433 let for_node = node(
3434 1,
3435 NodeKind::For {
3436 pattern: Box::new(bind_pat(2, "x")),
3437 iterable: Box::new(id_node(7, "items")),
3438 body: Box::new(body),
3439 },
3440 );
3441 let f = node(
3442 8,
3443 NodeKind::FnDecl {
3444 annotations: vec![],
3445 visibility: Visibility::Private,
3446 is_async: false,
3447 name: ident("test"),
3448 generic_params: vec![],
3449 params: vec![],
3450 return_type: None,
3451 effect_clause: vec![],
3452 where_clause: vec![],
3453 body: Box::new(block(9, vec![for_node], None)),
3454 },
3455 );
3456 let out = gen(&module(vec![], vec![f]));
3457 assert!(out.contains("for x in items {"), "got: {out}");
3458 assert!(out.contains("let y = x;"), "got: {out}");
3459 }
3460
3461 #[test]
3462 fn await_expression() {
3463 let aw = node(
3464 1,
3465 NodeKind::Await {
3466 expr: Box::new(node(
3467 2,
3468 NodeKind::Call {
3469 callee: Box::new(id_node(3, "fetch")),
3470 args: vec![],
3471 type_args: vec![],
3472 },
3473 )),
3474 },
3475 );
3476 let f = node(
3477 4,
3478 NodeKind::FnDecl {
3479 annotations: vec![],
3480 visibility: Visibility::Private,
3481 is_async: true,
3482 name: ident("test"),
3483 generic_params: vec![],
3484 params: vec![],
3485 return_type: None,
3486 effect_clause: vec![],
3487 where_clause: vec![],
3488 body: Box::new(block(5, vec![], Some(aw))),
3489 },
3490 );
3491 let out = gen(&module(vec![], vec![f]));
3492 assert!(out.contains("async fn test()"), "got: {out}");
3493 assert!(out.contains("fetch().await"), "got: {out}");
3494 }
3495
3496 #[test]
3497 fn async_main_gets_tokio_main_attribute() {
3498 let body = block(2, vec![], None);
3499 let f = node(
3500 1,
3501 NodeKind::FnDecl {
3502 annotations: vec![],
3503 visibility: Visibility::Private,
3504 is_async: true,
3505 name: ident("main"),
3506 generic_params: vec![],
3507 params: vec![],
3508 return_type: None,
3509 effect_clause: vec![],
3510 where_clause: vec![],
3511 body: Box::new(body),
3512 },
3513 );
3514 let out = gen(&module(vec![], vec![f]));
3515 assert!(out.contains("#[tokio::main]"), "got: {out}");
3516 assert!(out.contains("async fn main()"), "got: {out}");
3517 }
3518
3519 #[test]
3520 fn sync_main_no_tokio_attribute() {
3521 let body = block(2, vec![], None);
3522 let f = node(
3523 1,
3524 NodeKind::FnDecl {
3525 annotations: vec![],
3526 visibility: Visibility::Private,
3527 is_async: false,
3528 name: ident("main"),
3529 generic_params: vec![],
3530 params: vec![],
3531 return_type: None,
3532 effect_clause: vec![],
3533 where_clause: vec![],
3534 body: Box::new(body),
3535 },
3536 );
3537 let out = gen(&module(vec![], vec![f]));
3538 assert!(!out.contains("#[tokio::main]"), "got: {out}");
3539 assert!(out.contains("fn main()"), "got: {out}");
3540 }
3541
3542 #[test]
3543 fn concurrent_pattern_spawns_tasks() {
3544 let call_fetch = |id: u32, name: &str| {
3547 node(
3548 id,
3549 NodeKind::Call {
3550 callee: Box::new(id_node(id + 1, name)),
3551 args: vec![],
3552 type_args: vec![],
3553 },
3554 )
3555 };
3556 let let_stmt = |id: u32, name: &str, val: AIRNode| {
3557 node(
3558 id,
3559 NodeKind::LetBinding {
3560 is_mut: false,
3561 pattern: Box::new(bind_pat(id + 1, name)),
3562 ty: None,
3563 value: Box::new(val),
3564 },
3565 )
3566 };
3567 let await_id = |id: u32, name: &str| {
3568 node(
3569 id,
3570 NodeKind::Await {
3571 expr: Box::new(id_node(id + 1, name)),
3572 },
3573 )
3574 };
3575 let body = block(
3576 10,
3577 vec![
3578 let_stmt(20, "a", call_fetch(21, "task1")),
3579 let_stmt(30, "b", call_fetch(31, "task2")),
3580 let_stmt(40, "ra", await_id(41, "a")),
3581 let_stmt(50, "rb", await_id(51, "b")),
3582 ],
3583 Some(id_node(60, "ra")),
3584 );
3585 let f = node(
3586 1,
3587 NodeKind::FnDecl {
3588 annotations: vec![],
3589 visibility: Visibility::Private,
3590 is_async: true,
3591 name: ident("run"),
3592 generic_params: vec![],
3593 params: vec![],
3594 return_type: None,
3595 effect_clause: vec![],
3596 where_clause: vec![],
3597 body: Box::new(body),
3598 },
3599 );
3600 let out = gen(&module(vec![], vec![f]));
3601 assert!(
3602 out.contains("let a = tokio::spawn(task1());"),
3603 "task1 should be spawned, got: {out}"
3604 );
3605 assert!(
3606 out.contains("let b = tokio::spawn(task2());"),
3607 "task2 should be spawned, got: {out}"
3608 );
3609 assert!(
3610 out.contains("let ra = a.await.unwrap();"),
3611 "join handle `a` should be unwrapped on await, got: {out}"
3612 );
3613 assert!(
3614 out.contains("let rb = b.await.unwrap();"),
3615 "join handle `b` should be unwrapped on await, got: {out}"
3616 );
3617 }
3618
3619 #[test]
3620 fn sequential_await_no_spawn() {
3621 let await_call = node(
3623 20,
3624 NodeKind::Await {
3625 expr: Box::new(node(
3626 21,
3627 NodeKind::Call {
3628 callee: Box::new(id_node(22, "task1")),
3629 args: vec![],
3630 type_args: vec![],
3631 },
3632 )),
3633 },
3634 );
3635 let let_stmt = node(
3636 10,
3637 NodeKind::LetBinding {
3638 is_mut: false,
3639 pattern: Box::new(bind_pat(11, "a")),
3640 ty: None,
3641 value: Box::new(await_call),
3642 },
3643 );
3644 let body = block(30, vec![let_stmt], Some(id_node(40, "a")));
3645 let f = node(
3646 1,
3647 NodeKind::FnDecl {
3648 annotations: vec![],
3649 visibility: Visibility::Private,
3650 is_async: true,
3651 name: ident("run"),
3652 generic_params: vec![],
3653 params: vec![],
3654 return_type: None,
3655 effect_clause: vec![],
3656 where_clause: vec![],
3657 body: Box::new(body),
3658 },
3659 );
3660 let out = gen(&module(vec![], vec![f]));
3661 assert!(
3662 !out.contains("tokio::spawn"),
3663 "sequential await should not spawn, got: {out}"
3664 );
3665 assert!(out.contains("let a = task1().await;"), "got: {out}");
3666 }
3667
3668 #[test]
3669 fn record_construct() {
3670 let rc = node(
3671 1,
3672 NodeKind::RecordConstruct {
3673 path: type_path(&["Point"]),
3674 fields: vec![
3675 AirRecordField {
3676 name: ident("x"),
3677 value: Some(Box::new(int_lit(2, "1"))),
3678 },
3679 AirRecordField {
3680 name: ident("y"),
3681 value: Some(Box::new(int_lit(3, "2"))),
3682 },
3683 ],
3684 spread: None,
3685 },
3686 );
3687 let f = node(
3688 4,
3689 NodeKind::FnDecl {
3690 annotations: vec![],
3691 visibility: Visibility::Private,
3692 is_async: false,
3693 name: ident("test"),
3694 generic_params: vec![],
3695 params: vec![],
3696 return_type: None,
3697 effect_clause: vec![],
3698 where_clause: vec![],
3699 body: Box::new(block(5, vec![], Some(rc))),
3700 },
3701 );
3702 let out = gen(&module(vec![], vec![f]));
3703 assert!(out.contains("Point { x: 1_i64, y: 2_i64 }"), "got: {out}");
3704 }
3705
3706 #[test]
3707 fn map_literal() {
3708 let map = node(
3709 1,
3710 NodeKind::MapLiteral {
3711 entries: vec![AirMapEntry {
3712 key: str_lit(2, "key"),
3713 value: int_lit(3, "42"),
3714 }],
3715 },
3716 );
3717 let f = node(
3718 4,
3719 NodeKind::FnDecl {
3720 annotations: vec![],
3721 visibility: Visibility::Private,
3722 is_async: false,
3723 name: ident("test"),
3724 generic_params: vec![],
3725 params: vec![],
3726 return_type: None,
3727 effect_clause: vec![],
3728 where_clause: vec![],
3729 body: Box::new(block(5, vec![], Some(map))),
3730 },
3731 );
3732 let out = gen(&module(vec![], vec![f]));
3733 assert!(
3734 out.contains("std::collections::HashMap::from([(\"key\".to_string(), 42_i64)])"),
3735 "got: {out}"
3736 );
3737 }
3738
3739 #[test]
3740 fn tuple_literal() {
3741 let tup = node(
3742 1,
3743 NodeKind::TupleLiteral {
3744 elems: vec![int_lit(2, "1"), str_lit(3, "hello"), bool_lit(4, true)],
3745 },
3746 );
3747 let f = node(
3748 5,
3749 NodeKind::FnDecl {
3750 annotations: vec![],
3751 visibility: Visibility::Private,
3752 is_async: false,
3753 name: ident("test"),
3754 generic_params: vec![],
3755 params: vec![],
3756 return_type: None,
3757 effect_clause: vec![],
3758 where_clause: vec![],
3759 body: Box::new(block(6, vec![], Some(tup))),
3760 },
3761 );
3762 let out = gen(&module(vec![], vec![f]));
3763 assert!(
3764 out.contains("(1_i64, \"hello\".to_string(), true)"),
3765 "got: {out}"
3766 );
3767 }
3768
3769 #[test]
3770 fn unreachable_expression() {
3771 let unr = node(1, NodeKind::Unreachable);
3772 let f = node(
3773 2,
3774 NodeKind::FnDecl {
3775 annotations: vec![],
3776 visibility: Visibility::Private,
3777 is_async: false,
3778 name: ident("test"),
3779 generic_params: vec![],
3780 params: vec![],
3781 return_type: None,
3782 effect_clause: vec![],
3783 where_clause: vec![],
3784 body: Box::new(block(3, vec![], Some(unr))),
3785 },
3786 );
3787 let out = gen(&module(vec![], vec![f]));
3788 assert!(out.contains("unreachable!()"), "got: {out}");
3789 }
3790
3791 #[test]
3792 fn escape_strings() {
3793 assert_eq!(escape_rs_string("hello"), "hello");
3794 assert_eq!(escape_rs_string("he\"llo"), "he\\\"llo");
3795 assert_eq!(escape_rs_string("new\nline"), "new\\nline");
3796 }
3797
3798 #[test]
3799 fn escape_format_strings() {
3800 assert_eq!(escape_format_string("hello"), "hello");
3801 assert_eq!(escape_format_string("{test}"), "{{test}}");
3802 }
3803
3804 #[test]
3805 fn to_snake_case_conversions() {
3806 assert_eq!(to_snake_case("hello"), "hello");
3807 assert_eq!(to_snake_case("HelloWorld"), "hello_world");
3808 assert_eq!(to_snake_case("camelCase"), "camel_case");
3809 assert_eq!(to_snake_case("HTTPClient"), "http_client");
3810 assert_eq!(to_snake_case("_"), "_");
3811 }
3812
3813 #[test]
3814 fn to_upper_snake_case_conversions() {
3815 assert_eq!(to_upper_snake_case("MaxSize"), "MAX_SIZE");
3816 assert_eq!(to_upper_snake_case("httpClient"), "HTTP_CLIENT");
3817 }
3818
3819 #[test]
3822 #[ignore]
3823 fn e2e_simple_function_compiles() {
3824 let body = block(2, vec![], Some(int_lit(3, "42")));
3825 let f = node(
3826 1,
3827 NodeKind::FnDecl {
3828 annotations: vec![],
3829 visibility: Visibility::Public,
3830 is_async: false,
3831 name: ident("answer"),
3832 generic_params: vec![],
3833 params: vec![],
3834 return_type: Some(Box::new(node(
3835 4,
3836 NodeKind::TypeNamed {
3837 path: type_path(&["Int"]),
3838 args: vec![],
3839 },
3840 ))),
3841 effect_clause: vec![],
3842 where_clause: vec![],
3843 body: Box::new(body),
3844 },
3845 );
3846 let out = gen(&module(vec![], vec![f]));
3847 assert!(
3848 check_rs_syntax(&out),
3849 "Generated Rust does not compile:\n{out}"
3850 );
3851 }
3852
3853 #[test]
3854 #[ignore]
3855 fn e2e_struct_compiles() {
3856 let record = node(
3857 1,
3858 NodeKind::RecordDecl {
3859 annotations: vec![],
3860 visibility: Visibility::Public,
3861 name: ident("Point"),
3862 generic_params: vec![],
3863 fields: vec![record_field("x", "Float"), record_field("y", "Float")],
3864 },
3865 );
3866 let out = gen(&module(vec![], vec![record]));
3867 assert!(
3868 check_rs_syntax(&out),
3869 "Generated Rust does not compile:\n{out}"
3870 );
3871 }
3872
3873 #[test]
3874 #[ignore]
3875 fn e2e_enum_compiles() {
3876 let e = node(
3877 1,
3878 NodeKind::EnumDecl {
3879 annotations: vec![],
3880 visibility: Visibility::Public,
3881 name: ident("Color"),
3882 generic_params: vec![],
3883 variants: vec![
3884 node(
3885 2,
3886 NodeKind::EnumVariant {
3887 name: ident("Red"),
3888 payload: EnumVariantPayload::Unit,
3889 },
3890 ),
3891 node(
3892 3,
3893 NodeKind::EnumVariant {
3894 name: ident("Rgb"),
3895 payload: EnumVariantPayload::Struct(vec![record_field("r", "Int")]),
3896 },
3897 ),
3898 node(
3899 5,
3900 NodeKind::EnumVariant {
3901 name: ident("Custom"),
3902 payload: EnumVariantPayload::Tuple(vec![node(
3903 6,
3904 NodeKind::TypeNamed {
3905 path: type_path(&["String"]),
3906 args: vec![],
3907 },
3908 )]),
3909 },
3910 ),
3911 ],
3912 },
3913 );
3914 let out = gen(&module(vec![], vec![e]));
3915 assert!(
3916 check_rs_syntax(&out),
3917 "Generated Rust does not compile:\n{out}"
3918 );
3919 }
3920
3921 #[test]
3922 #[ignore]
3923 fn e2e_trait_and_impl_compiles() {
3924 let trait_decl = node(
3925 1,
3926 NodeKind::TraitDecl {
3927 annotations: vec![],
3928 visibility: Visibility::Public,
3929 is_platform: false,
3930 name: ident("Greet"),
3931 generic_params: vec![],
3932 associated_types: vec![],
3933 methods: vec![node(
3934 2,
3935 NodeKind::FnDecl {
3936 annotations: vec![],
3937 visibility: Visibility::Public,
3938 is_async: false,
3939 name: ident("greet"),
3940 generic_params: vec![],
3941 params: vec![],
3942 return_type: Some(Box::new(node(
3943 3,
3944 NodeKind::TypeNamed {
3945 path: type_path(&["String"]),
3946 args: vec![],
3947 },
3948 ))),
3949 effect_clause: vec![],
3950 where_clause: vec![],
3951 body: Box::new(block(4, vec![], None)),
3952 },
3953 )],
3954 },
3955 );
3956 let struct_decl = node(
3957 10,
3958 NodeKind::RecordDecl {
3959 annotations: vec![],
3960 visibility: Visibility::Public,
3961 name: ident("Person"),
3962 generic_params: vec![],
3963 fields: vec![record_field("name", "String")],
3964 },
3965 );
3966 let impl_block = node(
3967 20,
3968 NodeKind::ImplBlock {
3969 annotations: vec![],
3970 generic_params: vec![],
3971 trait_path: Some(type_path(&["Greet"])),
3972 target: Box::new(node(
3973 21,
3974 NodeKind::TypeNamed {
3975 path: type_path(&["Person"]),
3976 args: vec![],
3977 },
3978 )),
3979 where_clause: vec![],
3980 methods: vec![node(
3981 22,
3982 NodeKind::FnDecl {
3983 annotations: vec![],
3984 visibility: Visibility::Public,
3985 is_async: false,
3986 name: ident("greet"),
3987 generic_params: vec![],
3988 params: vec![],
3989 return_type: Some(Box::new(node(
3990 23,
3991 NodeKind::TypeNamed {
3992 path: type_path(&["String"]),
3993 args: vec![],
3994 },
3995 ))),
3996 effect_clause: vec![],
3997 where_clause: vec![],
3998 body: Box::new(block(24, vec![], Some(str_lit(25, "hello")))),
3999 },
4000 )],
4001 },
4002 );
4003 let out = gen(&module(vec![], vec![trait_decl, struct_decl, impl_block]));
4004 assert!(
4005 check_rs_syntax(&out),
4006 "Generated Rust does not compile:\n{out}"
4007 );
4008 }
4009
4010 fn gen_prelude_call(func_name: &str, arg: AIRNode) -> String {
4014 let call = node(
4015 10,
4016 NodeKind::Call {
4017 callee: Box::new(id_node(11, func_name)),
4018 args: vec![AirArg {
4019 label: None,
4020 value: arg,
4021 }],
4022 type_args: vec![],
4023 },
4024 );
4025 let body = block(2, vec![call], None);
4026 let f = node(
4027 1,
4028 NodeKind::FnDecl {
4029 name: ident("main"),
4030 params: vec![],
4031 return_type: None,
4032 body: Box::new(body),
4033 generic_params: vec![],
4034 visibility: Visibility::Private,
4035 annotations: vec![],
4036 effect_clause: vec![],
4037 where_clause: vec![],
4038 is_async: false,
4039 },
4040 );
4041 gen(&module(vec![], vec![f]))
4042 }
4043
4044 fn gen_prelude_call_no_args(func_name: &str) -> String {
4046 let call = node(
4047 10,
4048 NodeKind::Call {
4049 callee: Box::new(id_node(11, func_name)),
4050 args: vec![],
4051 type_args: vec![],
4052 },
4053 );
4054 let body = block(2, vec![call], None);
4055 let f = node(
4056 1,
4057 NodeKind::FnDecl {
4058 name: ident("main"),
4059 params: vec![],
4060 return_type: None,
4061 body: Box::new(body),
4062 generic_params: vec![],
4063 visibility: Visibility::Private,
4064 annotations: vec![],
4065 effect_clause: vec![],
4066 where_clause: vec![],
4067 is_async: false,
4068 },
4069 );
4070 gen(&module(vec![], vec![f]))
4071 }
4072
4073 #[test]
4074 fn prelude_println_maps_to_println_macro() {
4075 let out = gen_prelude_call("println", str_lit(12, "hello"));
4076 assert!(
4077 out.contains("println!(\"{}\", "),
4078 "println should map to println! macro, got: {out}"
4079 );
4080 assert!(
4081 !out.contains("println("),
4082 "should not emit bare println(, got: {out}"
4083 );
4084 }
4085
4086 #[test]
4087 fn prelude_print_maps_to_print_macro() {
4088 let out = gen_prelude_call("print", str_lit(12, "hello"));
4089 assert!(
4090 out.contains("print!(\"{}\", "),
4091 "print should map to print! macro, got: {out}"
4092 );
4093 }
4094
4095 #[test]
4096 fn prelude_debug_maps_to_dbg_macro() {
4097 let out = gen_prelude_call("debug", str_lit(12, "val"));
4098 assert!(
4099 out.contains("dbg!(&"),
4100 "debug should map to dbg! macro, got: {out}"
4101 );
4102 }
4103
4104 #[test]
4105 fn prelude_assert_maps_to_assert_macro() {
4106 let out = gen_prelude_call("assert", bool_lit(12, true));
4107 assert!(
4108 out.contains("assert!("),
4109 "assert should map to assert! macro, got: {out}"
4110 );
4111 }
4112
4113 #[test]
4114 fn prelude_todo_maps_to_todo_macro() {
4115 let out = gen_prelude_call_no_args("todo");
4116 assert!(
4117 out.contains("todo!()"),
4118 "todo should map to todo! macro, got: {out}"
4119 );
4120 }
4121
4122 #[test]
4123 fn prelude_unreachable_maps_to_unreachable_macro() {
4124 let out = gen_prelude_call_no_args("unreachable");
4125 assert!(
4126 out.contains("unreachable!()"),
4127 "unreachable should map to unreachable! macro, got: {out}"
4128 );
4129 }
4130
4131 #[test]
4132 fn non_prelude_call_passes_through() {
4133 let out = gen_prelude_call("my_custom_func", str_lit(12, "arg"));
4134 assert!(
4135 out.contains("my_custom_func("),
4136 "non-prelude call should use snake_case, got: {out}"
4137 );
4138 }
4139
4140 #[test]
4141 fn handling_block_passes_handlers_to_effectful_call() {
4142 use bock_air::AirHandlerPair;
4143
4144 let effect_decl = node(
4146 1,
4147 NodeKind::EffectDecl {
4148 annotations: vec![],
4149 visibility: Visibility::Public,
4150 name: ident("Logger"),
4151 generic_params: vec![],
4152 components: vec![],
4153 operations: vec![node(
4154 2,
4155 NodeKind::FnDecl {
4156 annotations: vec![],
4157 visibility: Visibility::Public,
4158 is_async: false,
4159 name: ident("log"),
4160 generic_params: vec![],
4161 params: vec![typed_param_node(3, "msg", "String")],
4162 return_type: None,
4163 effect_clause: vec![],
4164 where_clause: vec![],
4165 body: Box::new(block(4, vec![], None)),
4166 },
4167 )],
4168 },
4169 );
4170
4171 let inner_fn = node(
4173 10,
4174 NodeKind::FnDecl {
4175 annotations: vec![],
4176 visibility: Visibility::Private,
4177 is_async: false,
4178 name: ident("inner"),
4179 generic_params: vec![],
4180 params: vec![],
4181 return_type: Some(Box::new(node(
4182 11,
4183 NodeKind::TypeNamed {
4184 path: type_path(&["String"]),
4185 args: vec![],
4186 },
4187 ))),
4188 effect_clause: vec![type_path(&["Logger"])],
4189 where_clause: vec![],
4190 body: Box::new(block(12, vec![], Some(str_lit(13, "hello")))),
4191 },
4192 );
4193
4194 let call_inner = node(
4196 20,
4197 NodeKind::Call {
4198 callee: Box::new(id_node(21, "inner")),
4199 args: vec![],
4200 type_args: vec![],
4201 },
4202 );
4203 let handling = node(
4204 30,
4205 NodeKind::HandlingBlock {
4206 handlers: vec![AirHandlerPair {
4207 effect: type_path(&["Logger"]),
4208 handler: Box::new(node(
4209 31,
4210 NodeKind::Call {
4211 callee: Box::new(id_node(32, "StdoutLogger")),
4212 args: vec![],
4213 type_args: vec![],
4214 },
4215 )),
4216 }],
4217 body: Box::new(block(33, vec![], Some(call_inner))),
4218 },
4219 );
4220 let main_fn = node(
4221 40,
4222 NodeKind::FnDecl {
4223 annotations: vec![],
4224 visibility: Visibility::Private,
4225 is_async: false,
4226 name: ident("main"),
4227 generic_params: vec![],
4228 params: vec![],
4229 return_type: None,
4230 effect_clause: vec![],
4231 where_clause: vec![],
4232 body: Box::new(block(41, vec![handling], None)),
4233 },
4234 );
4235
4236 let out = gen(&module(vec![], vec![effect_decl, inner_fn, main_fn]));
4237 assert!(
4239 out.contains("inner(&__logger)"),
4240 "handling block should pass handler to effectful call, got: {out}"
4241 );
4242 assert!(
4245 out.contains("let __logger = StdoutLogger()"),
4246 "handling block should instantiate handler, got: {out}"
4247 );
4248 }
4249
4250 #[test]
4251 fn nested_handling_blocks_shadow_handlers() {
4252 use bock_air::AirHandlerPair;
4253
4254 let effect_decl = node(
4256 1,
4257 NodeKind::EffectDecl {
4258 annotations: vec![],
4259 visibility: Visibility::Public,
4260 name: ident("Logger"),
4261 generic_params: vec![],
4262 components: vec![],
4263 operations: vec![node(
4264 2,
4265 NodeKind::FnDecl {
4266 annotations: vec![],
4267 visibility: Visibility::Public,
4268 is_async: false,
4269 name: ident("log"),
4270 generic_params: vec![],
4271 params: vec![typed_param_node(3, "msg", "String")],
4272 return_type: None,
4273 effect_clause: vec![],
4274 where_clause: vec![],
4275 body: Box::new(block(4, vec![], None)),
4276 },
4277 )],
4278 },
4279 );
4280
4281 let inner_fn = node(
4283 10,
4284 NodeKind::FnDecl {
4285 annotations: vec![],
4286 visibility: Visibility::Private,
4287 is_async: false,
4288 name: ident("inner"),
4289 generic_params: vec![],
4290 params: vec![],
4291 return_type: None,
4292 effect_clause: vec![type_path(&["Logger"])],
4293 where_clause: vec![],
4294 body: Box::new(block(12, vec![], Some(str_lit(13, "hello")))),
4295 },
4296 );
4297
4298 let inner_call = node(
4300 20,
4301 NodeKind::Call {
4302 callee: Box::new(id_node(21, "inner")),
4303 args: vec![],
4304 type_args: vec![],
4305 },
4306 );
4307 let inner_handling = node(
4308 30,
4309 NodeKind::HandlingBlock {
4310 handlers: vec![AirHandlerPair {
4311 effect: type_path(&["Logger"]),
4312 handler: Box::new(id_node(31, "inner_logger")),
4313 }],
4314 body: Box::new(block(32, vec![], Some(inner_call))),
4315 },
4316 );
4317 let outer_handling = node(
4318 40,
4319 NodeKind::HandlingBlock {
4320 handlers: vec![AirHandlerPair {
4321 effect: type_path(&["Logger"]),
4322 handler: Box::new(id_node(41, "outer_logger")),
4323 }],
4324 body: Box::new(block(42, vec![inner_handling], None)),
4325 },
4326 );
4327 let main_fn = node(
4328 50,
4329 NodeKind::FnDecl {
4330 annotations: vec![],
4331 visibility: Visibility::Private,
4332 is_async: false,
4333 name: ident("main"),
4334 generic_params: vec![],
4335 params: vec![],
4336 return_type: None,
4337 effect_clause: vec![],
4338 where_clause: vec![],
4339 body: Box::new(block(51, vec![outer_handling], None)),
4340 },
4341 );
4342
4343 let out = gen(&module(vec![], vec![effect_decl, inner_fn, main_fn]));
4344 assert!(
4346 out.contains("let __logger = inner_logger"),
4347 "inner handling should shadow outer handler, got: {out}"
4348 );
4349 assert!(
4350 out.contains("inner(&__logger)"),
4351 "call should use innermost handler, got: {out}"
4352 );
4353 }
4354}