1mod bindings;
2pub(crate) mod calls;
3mod collectors;
4pub(crate) mod control_flow;
5pub(crate) mod definitions;
6pub(crate) mod expressions;
7mod imports;
8pub(crate) mod names;
9mod output;
10pub(crate) mod patterns;
11pub(crate) mod queries;
12pub(crate) mod statements;
13pub(crate) mod types;
14mod utils;
15
16pub(crate) use bindings::Bindings;
17pub(crate) use calls::go_interop::GoCallStrategy;
18pub(crate) use definitions::enum_layout::EnumLayout;
19pub(crate) use names::go_name;
20pub(crate) use names::go_name::escape_reserved;
21pub(crate) use output::OutputCollector;
22pub(crate) use types::emitter::{ArmPosition, EmitFlags, LineIndex, LoopContext, Position};
23pub(crate) use types::prelude::PreludeType;
24pub(crate) use utils::is_order_sensitive;
25pub(crate) use utils::write_line;
26
27pub use names::go_name::PRELUDE_IMPORT_PATH;
28pub use output::OutputFile;
29
30use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
31use std::sync::Arc;
32
33use ecow::EcoString;
34use imports::ImportBuilder;
35use syntax::ast::{Generic, Span};
36use syntax::program::{Definition, EmitInput, File, ModuleId, MutationInfo, UnusedInfo};
37use syntax::types::{Symbol, Type};
38
39#[derive(Clone, Debug, Default)]
40pub struct EmitOptions {
41 pub debug: bool,
42}
43
44pub struct TestEmitConfig<'a> {
45 pub definitions: &'a HashMap<Symbol, Definition>,
46 pub module_id: &'a str,
47 pub go_module: &'a str,
48 pub unused: &'a UnusedInfo,
49 pub mutations: &'a MutationInfo,
50 pub ufcs_methods: &'a HashSet<(String, String)>,
51 pub go_package_names: &'a HashMap<String, String>,
52}
53
54struct EmitContext<'a> {
55 definitions: &'a HashMap<Symbol, Definition>,
56 unused: &'a UnusedInfo,
57 mutations: &'a MutationInfo,
58 ufcs_methods: &'a HashSet<(String, String)>,
59 go_package_names: &'a HashMap<String, String>,
60 entry_module: ModuleId,
61 go_module: String,
62 options: EmitOptions,
63 line_indexes: Arc<HashMap<u32, LineIndex>>,
65}
66
67struct ModuleData {
68 make_functions: HashMap<String, String>,
69 enum_layouts: HashMap<String, EnumLayout>,
70 tag_exported_fields: HashSet<String>,
74 exported_method_names: HashSet<String>,
78 impl_bounds: HashMap<String, Vec<Generic>>,
82 unconstrained_impl_receivers: HashSet<String>,
85 module_aliases: HashMap<String, String>,
88 reverse_module_aliases: HashMap<String, String>,
91 absorbed_ref_generics: HashSet<String>,
96 go_call_strategies: HashMap<String, GoCallStrategy>,
98}
99
100struct ScopeState {
101 next_var: usize,
102 bindings: Bindings,
103 declared: Vec<HashSet<String>>,
105 scope_depth: usize,
107 loop_stack: Vec<LoopContext>,
109 assign_targets: HashSet<String>,
111 go_const_bindings: HashSet<String>,
113}
114
115impl ScopeState {
116 fn reset_for_top_level(&mut self) {
117 self.next_var = 0;
118 self.bindings.reset();
119 self.declared.clear();
120 self.declared.push(HashSet::default());
121 }
122}
123
124pub struct Emitter<'a> {
125 ctx: EmitContext<'a>,
126 module: ModuleData,
127 scope: ScopeState,
128
129 current_module: ModuleId,
130
131 synthesized_adapter_types: HashMap<(EcoString, EcoString), String>,
132 pending_adapter_types: Vec<String>,
133
134 flags: EmitFlags,
136 ensure_imported: HashSet<ModuleId>,
137
138 position: Position,
142 current_return_context: Option<ReturnContext>,
143 assign_target_ty: Option<Type>,
145 emitting_call_callee: bool,
148 in_condition: bool,
152 skip_array_return_wrap: bool,
156 current_slot_expected_ty: Option<Type>,
159 suppress_go_fn_short_circuit: bool,
162}
163
164#[derive(Clone)]
168pub(crate) struct ReturnContext {
169 pub(crate) ty: Type,
170 pub(crate) force_tagged: bool,
171}
172
173impl ReturnContext {
174 pub(crate) fn new(ty: Type) -> Self {
175 Self {
176 ty,
177 force_tagged: false,
178 }
179 }
180
181 pub(crate) fn tagged(ty: Type) -> Self {
182 Self {
183 ty,
184 force_tagged: true,
185 }
186 }
187}
188
189impl<'a> Emitter<'a> {
190 pub fn emit(analysis: &'a EmitInput, go_module: &str, options: EmitOptions) -> Vec<OutputFile> {
191 let mut output = vec![];
192
193 let line_indexes: Arc<HashMap<u32, LineIndex>> = Arc::new(if options.debug {
194 analysis
195 .files
196 .iter()
197 .map(|(file_id, file)| {
198 let path = if file.module_id == analysis.entry_module_id {
199 format!("src/{}", file.name)
200 } else {
201 format!("{}/{}", file.module_id, file.name)
202 };
203 (*file_id, LineIndex::from_source(path, &file.source))
204 })
205 .collect()
206 } else {
207 HashMap::default()
208 });
209
210 for (module_id, module_info) in &analysis.modules {
211 if analysis.cached_modules.contains(module_id) {
212 continue;
213 }
214
215 let ctx = EmitContext {
216 definitions: &analysis.definitions,
217 unused: &analysis.unused,
218 mutations: &analysis.mutations,
219 ufcs_methods: &analysis.ufcs_methods,
220 go_package_names: &analysis.go_package_names,
221 entry_module: analysis.entry_module_id.to_string(),
222 go_module: go_module.to_string(),
223 options: options.clone(),
224 line_indexes: line_indexes.clone(),
225 };
226 let mut emitter = Self::new(ctx, module_id);
227
228 let files: Vec<_> = module_info
229 .file_ids
230 .iter()
231 .filter_map(|fid| analysis.files.get(fid))
232 .collect();
233
234 let mut module_output = emitter.emit_files(&files, module_id);
235
236 if module_id != &analysis.entry_module_id {
237 for file in &mut module_output {
238 file.name = format!("{}/{}", module_info.path, file.name);
239 }
240 }
241
242 output.extend(module_output);
243 }
244
245 output
246 }
247
248 pub fn new_for_tests(config: &TestEmitConfig<'a>, source: Option<&str>) -> Self {
249 let (debug, line_indexes) = match source {
250 Some(src) => (
251 true,
252 Arc::new(HashMap::from_iter([(
253 0u32,
254 LineIndex::from_source("src/test.lis".to_string(), src),
255 )])),
256 ),
257 None => (false, Arc::new(HashMap::default())),
258 };
259 let ctx = EmitContext {
260 definitions: config.definitions,
261 unused: config.unused,
262 mutations: config.mutations,
263 ufcs_methods: config.ufcs_methods,
264 go_package_names: config.go_package_names,
265 entry_module: config.module_id.to_string(),
266 go_module: config.go_module.to_string(),
267 options: EmitOptions { debug },
268 line_indexes,
269 };
270 Self::new(ctx, config.module_id)
271 }
272
273 fn new(ctx: EmitContext<'a>, current_module: &str) -> Self {
274 Self {
275 ctx,
276 module: ModuleData {
277 make_functions: HashMap::default(),
278 enum_layouts: HashMap::default(),
279 tag_exported_fields: HashSet::default(),
280 exported_method_names: HashSet::default(),
281 impl_bounds: HashMap::default(),
282 unconstrained_impl_receivers: HashSet::default(),
283 module_aliases: HashMap::default(),
284 reverse_module_aliases: HashMap::default(),
285 absorbed_ref_generics: HashSet::default(),
286 go_call_strategies: HashMap::default(),
287 },
288 scope: ScopeState {
289 next_var: 0,
290 bindings: Bindings::new(),
291 declared: vec![HashSet::default()],
292 scope_depth: 0,
293 loop_stack: Vec::new(),
294 assign_targets: HashSet::default(),
295 go_const_bindings: HashSet::default(),
296 },
297 current_module: current_module.to_string(),
298 synthesized_adapter_types: HashMap::default(),
299 pending_adapter_types: Vec::new(),
300 flags: EmitFlags::default(),
301 ensure_imported: HashSet::default(),
302 position: Position::Expression,
303 current_return_context: None,
304 assign_target_ty: None,
305 emitting_call_callee: false,
306 in_condition: false,
307 skip_array_return_wrap: false,
308 current_slot_expected_ty: None,
309 suppress_go_fn_short_circuit: false,
310 }
311 }
312
313 pub(crate) fn emit_condition_operand(
314 &mut self,
315 output: &mut String,
316 expression: &syntax::ast::Expression,
317 ) -> String {
318 let prev = self.in_condition;
319 self.in_condition = true;
320 let result = self.emit_operand(output, expression);
321 self.in_condition = prev;
322 result
323 }
324
325 pub(crate) fn push_loop(&mut self, result_var: impl Into<String>) {
326 self.scope.loop_stack.push(LoopContext {
327 result_var: result_var.into(),
328 label: None,
329 });
330 }
331
332 pub(crate) fn pop_loop(&mut self) {
333 self.scope.loop_stack.pop();
334 }
335
336 pub(crate) fn current_loop_result_var(&self) -> Option<&str> {
337 self.scope
338 .loop_stack
339 .last()
340 .map(|ctx| ctx.result_var.as_str())
341 }
342
343 pub(crate) fn current_loop_label(&self) -> Option<&str> {
344 self.scope
345 .loop_stack
346 .last()
347 .and_then(|ctx| ctx.label.as_deref())
348 }
349
350 pub(crate) fn with_position<F, R>(&mut self, position: Position, f: F) -> R
351 where
352 F: FnOnce(&mut Self) -> R,
353 {
354 let saved = std::mem::replace(&mut self.position, position);
355 let result = f(self);
356 self.position = saved;
357 result
358 }
359
360 pub(crate) fn wrap_value(&self, value: &str) -> String {
361 if value.is_empty() {
362 return String::new();
363 }
364 match &self.position {
365 Position::Tail => format!("return {}\n", value),
366 Position::Statement => format!("{}\n", value),
367 Position::Expression => value.to_string(),
368 Position::Assign(var) => format!("{} = {}\n", var, value),
369 }
370 }
371
372 pub(crate) fn emit_unreachable_if_needed(&self, output: &mut String, has_catchall: bool) {
373 if self.position.is_tail() && !has_catchall {
374 output.push_str("panic(\"unreachable\")\n");
375 }
376 }
377
378 pub(crate) fn compute_arm_position(
386 &mut self,
387 output: Option<&mut String>,
388 ty: &Type,
389 ) -> ArmPosition {
390 if self.position.is_tail() {
391 return ArmPosition::from_position(Position::Tail);
392 }
393
394 if let Some(var) = self.position.assign_target() {
395 return ArmPosition::from_position(Position::Assign(var.to_string()));
396 }
397
398 if self.position.is_expression() && !ty.is_unit() {
399 let var = self.fresh_var(Some("result"));
400 if let Some(out) = output {
401 let go_ty = self.go_type_as_string(ty);
402 write_line!(out, "var {} {}", var, go_ty);
403 }
404 return ArmPosition::with_result_var(var);
405 }
406
407 ArmPosition::from_position(Position::Statement)
408 }
409
410 pub(crate) fn try_declare(&mut self, go_name: &str) -> bool {
414 if let Some(current_scope) = self.scope.declared.last_mut() {
415 if current_scope.contains(go_name) {
416 false
417 } else {
418 current_scope.insert(go_name.to_string());
419 true
420 }
421 } else {
422 true
423 }
424 }
425
426 pub(crate) fn is_declared(&self, go_name: &str) -> bool {
427 self.scope
428 .declared
429 .iter()
430 .any(|scope| scope.contains(go_name))
431 }
432
433 pub(crate) fn declare(&mut self, go_name: &str) {
436 if let Some(current_scope) = self.scope.declared.last_mut() {
437 current_scope.insert(go_name.to_string());
438 }
439 }
440
441 pub(crate) fn enter_scope(&mut self) {
442 self.scope.scope_depth += 1;
443 self.scope.bindings.save();
444 self.scope.declared.push(HashSet::default());
445 }
446
447 pub(crate) fn exit_scope(&mut self) {
448 self.scope.scope_depth = self.scope.scope_depth.saturating_sub(1);
449 self.scope.bindings.restore();
450 if self.scope.declared.len() > 1 {
451 self.scope.declared.pop();
452 }
453 }
454
455 pub(crate) fn current_module(&self) -> &str {
456 &self.current_module
457 }
458
459 pub(crate) fn module_alias_for_type(&self, ty: &Type) -> Option<String> {
460 if let Type::Nominal { id, .. } = ty {
461 let module = names::go_name::module_of_type_id(id);
462 self.module.module_aliases.get(module).cloned()
463 } else {
464 None
465 }
466 }
467
468 pub(crate) fn maybe_line_directive(&self, span: &Span) -> String {
469 if !self.ctx.options.debug || span.is_dummy() {
470 return String::new();
471 }
472
473 let Some(source) = self.ctx.line_indexes.get(&span.file_id) else {
474 return String::new();
475 };
476
477 let line = source.line_for_offset(span.byte_offset);
478 let col = source.col_for_offset(span.byte_offset);
479
480 format!("//line {}:{}:{}\n", source.path, line, col)
481 }
482
483 fn unused_imports_for_current_module<'u>(
484 unused: &'u UnusedInfo,
485 current_module: &str,
486 ) -> &'u HashSet<EcoString> {
487 static EMPTY: std::sync::LazyLock<HashSet<EcoString>> =
488 std::sync::LazyLock::new(HashSet::default);
489 unused
490 .imports_by_module
491 .get(current_module)
492 .unwrap_or(&EMPTY)
493 }
494
495 pub fn emit_files(&mut self, files: &[&File], module_id: &str) -> Vec<OutputFile> {
496 self.current_module = module_id.to_string();
497 self.collect_module_aliases(files);
498 self.collect_go_call_strategies();
499 self.collect_exported_method_names(files);
500 self.collect_impl_bounds(files);
501 self.collect_enum_layouts();
502 let mut make_functions_by_file = self.collect_make_functions();
503
504 let mut output_files = Vec::new();
505
506 let package_name = if module_id == self.ctx.entry_module {
507 "main".to_string()
508 } else {
509 let raw = module_id.rsplit('/').next().unwrap_or(module_id);
510 go_name::sanitize_package_name(raw).into_owned()
511 };
512
513 for file in files {
514 let mut source = OutputCollector::new();
515
516 if let Some(functions) = make_functions_by_file.remove(&file.id) {
517 for function in functions {
518 source.collect_with_blank(function);
519 }
520 }
521
522 self.pending_adapter_types.clear();
523
524 for expression in &file.items {
525 self.scope.reset_for_top_level();
526 let code = self.emit_top_item(expression);
527 if !code.is_empty() {
528 source.collect_with_blank(code);
529 }
530 }
531
532 for adapter_decl in std::mem::take(&mut self.pending_adapter_types) {
533 source.collect_with_blank(adapter_decl);
534 }
535
536 let unused_imports =
537 Self::unused_imports_for_current_module(self.ctx.unused, &self.current_module);
538 let mut import_builder = ImportBuilder::new(
539 &self.ctx.go_module,
540 unused_imports,
541 self.ctx.go_package_names,
542 );
543 import_builder.collect_from_file(file);
544
545 let ensure_imported = std::mem::take(&mut self.ensure_imported);
546 import_builder.extend_with_modules(&ensure_imported);
547
548 let flags = std::mem::take(&mut self.flags);
549 if flags.needs_fmt {
550 import_builder.require_fmt();
551 }
552 if flags.needs_stdlib {
553 import_builder.require_stdlib();
554 }
555 if flags.needs_errors {
556 import_builder.require_errors();
557 }
558 if flags.needs_slices {
559 import_builder.require_slices();
560 }
561 if flags.needs_strings {
562 import_builder.require_strings();
563 }
564 if flags.needs_maps {
565 import_builder.require_maps();
566 }
567
568 let rendered_source = source.render();
569 import_builder.filter_unreferenced(&rendered_source);
570
571 output_files.push(OutputFile {
572 name: file.go_filename(),
573 imports: import_builder.build(),
574 source: rendered_source,
575 package_name: package_name.clone(),
576 });
577 }
578
579 output_files
580 }
581}