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