Fork of daniellemaywood.uk/gleam — Wasm codegen work
100 kB
3172 lines
1// TODO: Refactor this module to be methods on structs rather than free
2// functions with a load of arguments. See the JavaScript code generator and the
3// formatter for examples.
4
5mod pattern;
6#[cfg(test)]
7mod tests;
8
9use crate::build::{Target, module_erlang_name};
10use crate::strings::{convert_string_escape_chars, to_snake_case};
11use crate::type_::is_prelude_module;
12use crate::{
13 Result,
14 ast::{CustomType, Function, Import, ModuleConstant, TypeAlias, *},
15 docvec,
16 line_numbers::LineNumbers,
17 pretty::*,
18 type_::{
19 ModuleValueConstructor, PatternConstructor, Type, TypeVar, TypedCallArg, ValueConstructor,
20 ValueConstructorVariant,
21 },
22};
23use camino::Utf8Path;
24use ecow::{EcoString, eco_format};
25use itertools::Itertools;
26use pattern::pattern;
27use regex::{Captures, Regex};
28use std::collections::HashSet;
29use std::sync::OnceLock;
30use std::{collections::HashMap, ops::Deref, str::FromStr, sync::Arc};
31use vec1::Vec1;
32
33const INDENT: isize = 4;
34const MAX_COLUMNS: isize = 80;
35
36fn module_name_atom(module: &str) -> Document<'static> {
37 atom_string(module.replace('/', "@").into())
38}
39
40#[derive(Debug, Clone)]
41struct Env<'a> {
42 module: &'a str,
43 function: &'a str,
44 line_numbers: &'a LineNumbers,
45 needs_function_docs: bool,
46 echo_used: bool,
47 current_scope_vars: im::HashMap<String, usize>,
48 erl_function_scope_vars: im::HashMap<String, usize>,
49}
50
51impl<'env> Env<'env> {
52 pub fn new(module: &'env str, function: &'env str, line_numbers: &'env LineNumbers) -> Self {
53 let vars: im::HashMap<_, _> = std::iter::once(("_".into(), 0)).collect();
54 Self {
55 current_scope_vars: vars.clone(),
56 erl_function_scope_vars: vars,
57 needs_function_docs: false,
58 echo_used: false,
59 line_numbers,
60 function,
61 module,
62 }
63 }
64
65 pub fn local_var_name<'a>(&mut self, name: &str) -> Document<'a> {
66 match self.current_scope_vars.get(name) {
67 None => {
68 let _ = self.current_scope_vars.insert(name.to_string(), 0);
69 let _ = self.erl_function_scope_vars.insert(name.to_string(), 0);
70 variable_name(name).to_doc()
71 }
72 Some(0) => variable_name(name).to_doc(),
73 Some(n) => {
74 use std::fmt::Write;
75 let mut name = variable_name(name);
76 write!(name, "@{n}").expect("pushing number suffix to name");
77 name.to_doc()
78 }
79 }
80 }
81
82 pub fn next_local_var_name<'a>(&mut self, name: &str) -> Document<'a> {
83 let next = self.erl_function_scope_vars.get(name).map_or(0, |i| i + 1);
84 let _ = self.erl_function_scope_vars.insert(name.to_string(), next);
85 let _ = self.current_scope_vars.insert(name.to_string(), next);
86 self.local_var_name(name)
87 }
88}
89
90pub fn records(module: &TypedModule) -> Vec<(&str, String)> {
91 module
92 .definitions
93 .iter()
94 .filter_map(|definition| match definition {
95 Definition::CustomType(CustomType {
96 publicity: Publicity::Public,
97 constructors,
98 location,
99 ..
100 }) if !module.unused_definition_positions.contains(&location.start) => {
101 Some(constructors)
102 }
103 _ => None,
104 })
105 .flatten()
106 .filter(|constructor| !constructor.arguments.is_empty())
107 .filter_map(|constructor| {
108 constructor
109 .arguments
110 .iter()
111 .map(
112 |RecordConstructorArg {
113 label,
114 ast: _,
115 location: _,
116 type_,
117 ..
118 }| {
119 label
120 .as_ref()
121 .map(|(_, label)| (label.as_str(), type_.clone()))
122 },
123 )
124 .collect::<Option<Vec<_>>>()
125 .map(|fields| (constructor.name.as_str(), fields))
126 })
127 .map(|(name, fields)| (name, record_definition(name, &fields)))
128 .collect()
129}
130
131pub fn record_definition(name: &str, fields: &[(&str, Arc<Type>)]) -> String {
132 let name = to_snake_case(name);
133 let type_printer = TypePrinter::new("").var_as_any();
134 let fields = fields.iter().map(move |(name, type_)| {
135 let type_ = type_printer.print(type_);
136 docvec![atom_string((*name).into()), " :: ", type_.group()]
137 });
138 let fields = break_("", "")
139 .append(join(fields, break_(",", ", ")))
140 .nest(INDENT)
141 .append(break_("", ""))
142 .group();
143 docvec!["-record(", atom_string(name), ", {", fields, "}).", line()]
144 .to_pretty_string(MAX_COLUMNS)
145}
146
147pub fn module<'a>(
148 module: &'a TypedModule,
149 line_numbers: &'a LineNumbers,
150 root: &'a Utf8Path,
151) -> Result<String> {
152 Ok(module_document(module, line_numbers, root)?.to_pretty_string(MAX_COLUMNS))
153}
154
155fn module_document<'a>(
156 module: &'a TypedModule,
157 line_numbers: &'a LineNumbers,
158 root: &'a Utf8Path,
159) -> Result<Document<'a>> {
160 let mut exports = vec![];
161 let mut type_defs = vec![];
162 let mut type_exports = vec![];
163
164 let header = "-module("
165 .to_doc()
166 .append(module.erlang_name())
167 .append(").")
168 .append(line());
169
170 // We need to know which private functions are referenced in importable
171 // constants so that we can export them anyway in the generated Erlang.
172 // This is because otherwise when the constant is used in another module it
173 // would result in an error as it tries to reference this private function.
174 let overridden_publicity = find_private_functions_referenced_in_importable_constants(module);
175
176 for definition in &module.definitions {
177 register_imports_and_exports(
178 definition,
179 &mut exports,
180 &mut type_exports,
181 &mut type_defs,
182 &module.name,
183 &overridden_publicity,
184 &module.unused_definition_positions,
185 );
186 }
187
188 let exports = match (!exports.is_empty(), !type_exports.is_empty()) {
189 (false, false) => return Ok(header),
190 (true, false) => "-export(["
191 .to_doc()
192 .append(join(exports, ", ".to_doc()))
193 .append("]).")
194 .append(lines(2)),
195
196 (true, true) => "-export(["
197 .to_doc()
198 .append(join(exports, ", ".to_doc()))
199 .append("]).")
200 .append(line())
201 .append("-export_type([")
202 .to_doc()
203 .append(join(type_exports, ", ".to_doc()))
204 .append("]).")
205 .append(lines(2)),
206
207 (false, true) => "-export_type(["
208 .to_doc()
209 .append(join(type_exports, ", ".to_doc()))
210 .append("]).")
211 .append(lines(2)),
212 };
213
214 let type_defs = if type_defs.is_empty() {
215 nil()
216 } else {
217 join(type_defs, lines(2)).append(lines(2))
218 };
219
220 let src_path_full = &module.type_info.src_path;
221 let src_path_relative = EcoString::from(
222 src_path_full
223 .strip_prefix(root)
224 .unwrap_or(src_path_full)
225 .as_str(),
226 )
227 .replace("\\", "\\\\");
228
229 let mut needs_function_docs = false;
230 let mut echo_used = false;
231 let mut statements = Vec::with_capacity(module.definitions.len());
232 for definition in module.definitions.iter() {
233 if let Some((statement_document, env)) = module_statement(
234 definition,
235 &module.name,
236 module.type_info.is_internal,
237 line_numbers,
238 src_path_relative.clone(),
239 &module.unused_definition_positions,
240 ) {
241 needs_function_docs = needs_function_docs || env.needs_function_docs;
242 echo_used = echo_used || env.echo_used;
243 statements.push(statement_document);
244 }
245 }
246
247 let module_doc = if module.type_info.is_internal {
248 Some(hidden_module_doc().append(lines(2)))
249 } else if module.documentation.is_empty() {
250 None
251 } else {
252 Some(module_doc(&module.documentation).append(lines(2)))
253 };
254
255 // We're going to need the documentation directives if any of the module's
256 // functions need it, or if the module has a module comment that we want to
257 // include in the generated Erlang source, or if the module is internal.
258 let needs_doc_directive = needs_function_docs || module_doc.is_some();
259 let documentation_directive = if needs_doc_directive {
260 "-if(?OTP_RELEASE >= 27).
261-define(MODULEDOC(Str), -moduledoc(Str)).
262-define(DOC(Str), -doc(Str)).
263-else.
264-define(MODULEDOC(Str), -compile([])).
265-define(DOC(Str), -compile([])).
266-endif."
267 .to_doc()
268 .append(lines(2))
269 } else {
270 nil()
271 };
272
273 let module = docvec![
274 header,
275 "-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).",
276 line(),
277 "-define(FILEPATH, \"",
278 src_path_relative,
279 "\").",
280 line(),
281 exports,
282 documentation_directive,
283 module_doc,
284 type_defs,
285 join(statements, lines(2)),
286 ];
287
288 let module = if echo_used {
289 module
290 .append(lines(2))
291 .append(std::include_str!("../templates/echo.erl").to_doc())
292 } else {
293 module
294 };
295
296 Ok(module.append(line()))
297}
298
299fn register_imports_and_exports(
300 definition: &TypedDefinition,
301 exports: &mut Vec<Document<'_>>,
302 type_exports: &mut Vec<Document<'_>>,
303 type_defs: &mut Vec<Document<'_>>,
304 module_name: &str,
305 overridden_publicity: &im::HashSet<EcoString>,
306 unused_definition_positions: &HashSet<u32>,
307) {
308 // Do not generate any code for unused items
309 if unused_definition_positions.contains(&definition.location().start) {
310 return;
311 }
312
313 match definition {
314 Definition::Function(Function {
315 publicity,
316 name: Some((_, name)),
317 arguments: args,
318 implementations,
319 ..
320 }) if publicity.is_importable() || overridden_publicity.contains(name) => {
321 // If the function isn't for this target then don't attempt to export it
322 if implementations.supports(Target::Erlang) {
323 let function_name = escape_erlang_existing_name(name);
324 exports.push(
325 atom_string(function_name.into())
326 .append("/")
327 .append(args.len()),
328 )
329 }
330 }
331
332 Definition::CustomType(CustomType {
333 name,
334 constructors,
335 typed_parameters,
336 opaque,
337 ..
338 }) => {
339 // Erlang doesn't allow phantom type variables in type definitions but gleam does
340 // so we check the type declaratinon against its constroctors and generate a phantom
341 // value that uses the unused type variables.
342 let type_var_usages = collect_type_var_usages(HashMap::new(), typed_parameters);
343 let mut constructor_var_usages = HashMap::new();
344 for c in constructors {
345 constructor_var_usages = collect_type_var_usages(
346 constructor_var_usages,
347 c.arguments.iter().map(|a| &a.type_),
348 );
349 }
350 let phantom_vars: Vec<_> = type_var_usages
351 .keys()
352 .filter(|&id| !constructor_var_usages.contains_key(id))
353 .sorted()
354 .map(|&id| Type::Var {
355 type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id })),
356 })
357 .collect();
358 let phantom_vars_constructor = if !phantom_vars.is_empty() {
359 let type_printer = TypePrinter::new(module_name);
360 Some(tuple(
361 std::iter::once("gleam_phantom".to_doc())
362 .chain(phantom_vars.iter().map(|pv| type_printer.print(pv))),
363 ))
364 } else {
365 None
366 };
367 // Type Exports
368 type_exports.push(
369 erl_safe_type_name(to_snake_case(name))
370 .to_doc()
371 .append("/")
372 .append(typed_parameters.len()),
373 );
374 // Type definitions
375 let definition = if constructors.is_empty() {
376 let constructors =
377 std::iter::once("any()".to_doc()).chain(phantom_vars_constructor);
378 join(constructors, break_(" |", " | "))
379 } else {
380 let constructors = constructors
381 .iter()
382 .map(|constructor| {
383 let name = atom_string(to_snake_case(&constructor.name));
384 if constructor.arguments.is_empty() {
385 name
386 } else {
387 let type_printer = TypePrinter::new(module_name);
388 let args = constructor
389 .arguments
390 .iter()
391 .map(|argument| type_printer.print(&argument.type_));
392 tuple(std::iter::once(name).chain(args))
393 }
394 })
395 .chain(phantom_vars_constructor);
396 join(constructors, break_(" |", " | "))
397 }
398 .nest(INDENT);
399 let type_printer = TypePrinter::new(module_name);
400 let params = join(
401 typed_parameters
402 .iter()
403 .map(|type_| type_printer.print(type_)),
404 ", ".to_doc(),
405 );
406 let doc = if *opaque { "-opaque " } else { "-type " }
407 .to_doc()
408 .append(erl_safe_type_name(to_snake_case(name)))
409 .append("(")
410 .append(params)
411 .append(") :: ")
412 .append(definition)
413 .group()
414 .append(".");
415 type_defs.push(doc);
416 }
417
418 Definition::Function(Function { .. })
419 | Definition::Import(Import { .. })
420 | Definition::TypeAlias(TypeAlias { .. })
421 | Definition::ModuleConstant(ModuleConstant { .. }) => (),
422 }
423}
424
425fn module_statement<'a>(
426 statement: &'a TypedDefinition,
427 module: &'a str,
428 is_internal_module: bool,
429 line_numbers: &'a LineNumbers,
430 src_path: EcoString,
431 unused_definition_positions: &HashSet<u32>,
432) -> Option<(Document<'a>, Env<'a>)> {
433 match statement {
434 // Do not generate any code for unused items
435 Definition::Function(function)
436 if unused_definition_positions.contains(&function.location.start) =>
437 {
438 None
439 }
440
441 Definition::Function(function) => {
442 module_function(function, module, is_internal_module, line_numbers, src_path)
443 }
444
445 Definition::TypeAlias(TypeAlias { .. })
446 | Definition::CustomType(CustomType { .. })
447 | Definition::Import(Import { .. })
448 | Definition::ModuleConstant(ModuleConstant { .. }) => None,
449 }
450}
451
452fn module_function<'a>(
453 function: &'a TypedFunction,
454 module: &'a str,
455 is_internal_module: bool,
456 line_numbers: &'a LineNumbers,
457 src_path: EcoString,
458) -> Option<(Document<'a>, Env<'a>)> {
459 // Private external functions don't need to render anything, the underlying
460 // Erlang implementation is used directly at the call site.
461 if function.external_erlang.is_some() && function.publicity.is_private() {
462 return None;
463 }
464
465 // If the function has no suitable Erlang implementation then there is nothing
466 // to generate for it.
467 if !function.implementations.supports(Target::Erlang) {
468 return None;
469 }
470
471 let (_, function_name) = function
472 .name
473 .as_ref()
474 .expect("A module's function must be named");
475 let function_name = escape_erlang_existing_name(function_name);
476 let file_attribute = file_attribute(src_path, function, line_numbers);
477
478 let mut env = Env::new(module, function_name, line_numbers);
479 let var_usages = collect_type_var_usages(
480 HashMap::new(),
481 std::iter::once(&function.return_type).chain(function.arguments.iter().map(|a| &a.type_)),
482 );
483 let type_printer = TypePrinter::new(module).with_var_usages(&var_usages);
484 let args_spec = function
485 .arguments
486 .iter()
487 .map(|a| type_printer.print(&a.type_));
488 let return_spec = type_printer.print(&function.return_type);
489
490 let spec = fun_spec(function_name, args_spec, return_spec);
491 let arguments = if function.external_erlang.is_some() {
492 external_fun_args(&function.arguments, &mut env)
493 } else {
494 fun_args(&function.arguments, &mut env)
495 };
496
497 let body = function
498 .external_erlang
499 .as_ref()
500 .map(|(module, function, _location)| {
501 docvec![
502 atom(module),
503 ":",
504 atom(escape_erlang_existing_name(function)),
505 arguments.clone()
506 ]
507 })
508 .unwrap_or_else(|| statement_sequence(&function.body, &mut env));
509
510 let attributes = file_attribute;
511 let attributes = if is_internal_module || function.publicity.is_internal() {
512 // If a function is marked as internal or comes from an internal module
513 // we want to hide its documentation in the Erlang shell!
514 // So the doc directive will look like this: `-doc(false).`
515 env.needs_function_docs = true;
516 docvec![attributes, line(), hidden_function_doc()]
517 } else {
518 match &function.documentation {
519 Some((_, documentation)) => {
520 env.needs_function_docs = true;
521 let doc_lines = documentation
522 .trim_end()
523 .split('\n')
524 .map(EcoString::from)
525 .collect_vec();
526 docvec![attributes, line(), function_doc(&doc_lines)]
527 }
528 _ => attributes,
529 }
530 };
531
532 Some((
533 docvec![
534 attributes,
535 line(),
536 spec,
537 atom_string(escape_erlang_existing_name(function_name).into()),
538 arguments,
539 " ->",
540 line().append(body).nest(INDENT).group(),
541 ".",
542 ],
543 env,
544 ))
545}
546
547fn file_attribute<'a>(
548 path: EcoString,
549 function: &'a TypedFunction,
550 line_numbers: &'a LineNumbers,
551) -> Document<'a> {
552 let line = line_numbers.line_number(function.location.start);
553 docvec!["-file(\"", path, "\", ", line, ")."]
554}
555
556enum DocCommentKind {
557 Module,
558 Function,
559}
560
561enum DocCommentContent<'a> {
562 String(&'a Vec<EcoString>),
563 False,
564}
565
566fn hidden_module_doc<'a>() -> Document<'a> {
567 doc_attribute(DocCommentKind::Module, DocCommentContent::False)
568}
569
570fn module_doc<'a>(content: &Vec<EcoString>) -> Document<'a> {
571 doc_attribute(DocCommentKind::Module, DocCommentContent::String(content))
572}
573
574fn hidden_function_doc<'a>() -> Document<'a> {
575 doc_attribute(DocCommentKind::Function, DocCommentContent::False)
576}
577
578fn function_doc<'a>(content: &Vec<EcoString>) -> Document<'a> {
579 doc_attribute(DocCommentKind::Function, DocCommentContent::String(content))
580}
581
582fn doc_attribute<'a>(kind: DocCommentKind, content: DocCommentContent<'_>) -> Document<'a> {
583 let prefix = match kind {
584 DocCommentKind::Module => "?MODULEDOC",
585 DocCommentKind::Function => "?DOC",
586 };
587
588 match content {
589 DocCommentContent::False => prefix.to_doc().append("(false)."),
590 DocCommentContent::String(doc_lines) => {
591 let is_multiline_doc_comment = doc_lines.len() > 1;
592 let doc_lines = join(
593 doc_lines.iter().map(|line| {
594 let line = line.replace("\\", "\\\\").replace("\"", "\\\"");
595 docvec!["\"", line, "\\n\""]
596 }),
597 line(),
598 );
599 if is_multiline_doc_comment {
600 let nested_documentation = docvec![line(), doc_lines].nest(INDENT);
601 docvec![prefix, "(", nested_documentation, line(), ")."]
602 } else {
603 docvec![prefix, "(", doc_lines, ")."]
604 }
605 }
606 }
607}
608
609fn external_fun_args<'a>(args: &'a [TypedArg], env: &mut Env<'a>) -> Document<'a> {
610 wrap_args(args.iter().map(|a| {
611 let name = match &a.names {
612 ArgNames::Discard { name, .. }
613 | ArgNames::LabelledDiscard { name, .. }
614 | ArgNames::Named { name, .. }
615 | ArgNames::NamedLabelled { name, .. } => name,
616 };
617 if name.chars().all(|c| c == '_') {
618 env.next_local_var_name("argument")
619 } else {
620 env.next_local_var_name(name)
621 }
622 }))
623}
624
625fn fun_args<'a>(args: &'a [TypedArg], env: &mut Env<'a>) -> Document<'a> {
626 wrap_args(args.iter().map(|a| match &a.names {
627 ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => "_".to_doc(),
628 ArgNames::Named { name, .. } | ArgNames::NamedLabelled { name, .. } => {
629 env.next_local_var_name(name)
630 }
631 }))
632}
633
634fn wrap_args<'a, I>(args: I) -> Document<'a>
635where
636 I: IntoIterator<Item = Document<'a>>,
637{
638 break_("", "")
639 .append(join(args, break_(",", ", ")))
640 .nest(INDENT)
641 .append(break_("", ""))
642 .surround("(", ")")
643 .group()
644}
645
646fn fun_spec<'a>(
647 name: &'a str,
648 args: impl IntoIterator<Item = Document<'a>>,
649 return_: Document<'a>,
650) -> Document<'a> {
651 "-spec "
652 .to_doc()
653 .append(atom(name))
654 .append(wrap_args(args))
655 .append(" -> ")
656 .append(return_)
657 .append(".")
658 .append(line())
659 .group()
660}
661
662fn atom_string(value: EcoString) -> Document<'static> {
663 escape_atom_string(value).to_doc()
664}
665
666fn atom_pattern() -> &'static Regex {
667 static ATOM_PATTERN: OnceLock<Regex> = OnceLock::new();
668 ATOM_PATTERN.get_or_init(|| Regex::new(r"^[a-z][a-z0-9_@]*$").expect("atom RE regex"))
669}
670
671fn atom(value: &str) -> Document<'_> {
672 if is_erlang_reserved_word(value) {
673 // Escape because of keyword collision
674 eco_format!("'{value}'").to_doc()
675 } else if atom_pattern().is_match(value) {
676 // No need to escape
677 EcoString::from(value).to_doc()
678 } else {
679 // Escape because of characters contained
680 eco_format!("'{value}'").to_doc()
681 }
682}
683
684pub fn escape_atom_string(value: EcoString) -> EcoString {
685 if is_erlang_reserved_word(&value) {
686 // Escape because of keyword collision
687 eco_format!("'{value}'")
688 } else if atom_pattern().is_match(&value) {
689 value
690 } else {
691 // Escape because of characters contained
692 eco_format!("'{value}'")
693 }
694}
695
696fn unicode_escape_sequence_pattern() -> &'static Regex {
697 static PATTERN: OnceLock<Regex> = OnceLock::new();
698 PATTERN.get_or_init(|| {
699 Regex::new(r#"(\\+)(u)"#).expect("Unicode escape sequence regex cannot be constructed")
700 })
701}
702
703fn string_inner(value: &str) -> Document<'_> {
704 let content = unicode_escape_sequence_pattern()
705 // `\\u`-s should not be affected, so that "\\u..." is not converted to
706 // "\\x...". That's why capturing groups is used to exclude cases that
707 // shouldn't be replaced.
708 .replace_all(value, |caps: &Captures<'_>| {
709 let slashes = caps.get(1).map_or("", |m| m.as_str());
710
711 if slashes.len() % 2 == 0 {
712 format!("{slashes}u")
713 } else {
714 format!("{slashes}x")
715 }
716 });
717 EcoString::from(content).to_doc()
718}
719
720fn string(value: &str) -> Document<'_> {
721 string_inner(value).surround("<<\"", "\"/utf8>>")
722}
723
724fn string_length_utf8_bytes(str: &EcoString) -> usize {
725 convert_string_escape_chars(str).len()
726}
727
728fn tuple<'a>(elements: impl IntoIterator<Item = Document<'a>>) -> Document<'a> {
729 join(elements, break_(",", ", "))
730 .nest(INDENT)
731 .surround("{", "}")
732 .group()
733}
734
735fn const_string_concatenate_bit_array<'a>(
736 elements: impl IntoIterator<Item = Document<'a>>,
737) -> Document<'a> {
738 join(elements, break_(",", ", "))
739 .nest(INDENT)
740 .surround("<<", ">>")
741 .group()
742}
743
744fn const_string_concatenate<'a>(
745 left: &'a TypedConstant,
746 right: &'a TypedConstant,
747 env: &mut Env<'a>,
748) -> Document<'a> {
749 let left = const_string_concatenate_argument(left, env);
750 let right = const_string_concatenate_argument(right, env);
751 const_string_concatenate_bit_array([left, right])
752}
753
754fn const_string_concatenate_inner<'a>(
755 left: &'a TypedConstant,
756 right: &'a TypedConstant,
757 env: &mut Env<'a>,
758) -> Document<'a> {
759 let left = const_string_concatenate_argument(left, env);
760 let right = const_string_concatenate_argument(right, env);
761 join([left, right], break_(",", ", "))
762}
763
764fn const_string_concatenate_argument<'a>(
765 value: &'a TypedConstant,
766 env: &mut Env<'a>,
767) -> Document<'a> {
768 match value {
769 Constant::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"],
770
771 Constant::Var {
772 constructor: Some(constructor),
773 ..
774 } => match &constructor.variant {
775 ValueConstructorVariant::ModuleConstant {
776 literal: Constant::String { value, .. },
777 ..
778 } => docvec!['"', string_inner(value), "\"/utf8"],
779 ValueConstructorVariant::ModuleConstant {
780 literal: Constant::StringConcatenation { left, right, .. },
781 ..
782 } => const_string_concatenate_inner(left, right, env),
783 _ => const_inline(value, env),
784 },
785
786 Constant::StringConcatenation { left, right, .. } => {
787 const_string_concatenate_inner(left, right, env)
788 }
789
790 _ => const_inline(value, env),
791 }
792}
793
794fn string_concatenate<'a>(
795 left: &'a TypedExpr,
796 right: &'a TypedExpr,
797 env: &mut Env<'a>,
798) -> Document<'a> {
799 let left = string_concatenate_argument(left, env);
800 let right = string_concatenate_argument(right, env);
801 bit_array([left, right])
802}
803
804fn string_concatenate_argument<'a>(value: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
805 match value {
806 TypedExpr::Var {
807 constructor:
808 ValueConstructor {
809 variant:
810 ValueConstructorVariant::ModuleConstant {
811 literal: Constant::String { value, .. },
812 ..
813 },
814 ..
815 },
816 ..
817 }
818 | TypedExpr::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"],
819
820 TypedExpr::Var {
821 name,
822 constructor:
823 ValueConstructor {
824 variant: ValueConstructorVariant::LocalVariable { .. },
825 ..
826 },
827 ..
828 } => docvec![env.local_var_name(name), "/binary"],
829
830 TypedExpr::BinOp {
831 name: BinOp::Concatenate,
832 ..
833 } => docvec![expr(value, env), "/binary"],
834
835 _ => docvec!["(", maybe_block_expr(value, env), ")/binary"],
836 }
837}
838
839fn bit_array<'a>(elements: impl IntoIterator<Item = Document<'a>>) -> Document<'a> {
840 join(elements, break_(",", ", "))
841 .nest(INDENT)
842 .surround("<<", ">>")
843 .group()
844}
845
846fn const_segment<'a>(
847 value: &'a TypedConstant,
848 options: &'a [TypedConstantBitArraySegmentOption],
849 env: &mut Env<'a>,
850) -> Document<'a> {
851 let value_is_a_string_literal = matches!(value, Constant::String { .. });
852
853 let create_document = |env: &mut Env<'a>| {
854 match value {
855 // Skip the normal <<value/utf8>> surrounds
856 Constant::String { value, .. } => value.to_doc().surround("\"", "\""),
857
858 // As normal
859 Constant::Int { .. } | Constant::Float { .. } | Constant::BitArray { .. } => {
860 const_inline(value, env)
861 }
862
863 // Wrap anything else in parentheses
864 value => const_inline(value, env).surround("(", ")"),
865 }
866 };
867
868 let size = |value: &'a TypedConstant, env: &mut Env<'a>| match value {
869 Constant::Int { .. } => Some(":".to_doc().append(const_inline(value, env))),
870 _ => Some(
871 ":".to_doc()
872 .append(const_inline(value, env).surround("(", ")")),
873 ),
874 };
875
876 let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc());
877
878 bit_array_segment(
879 create_document,
880 options,
881 size,
882 unit,
883 value_is_a_string_literal,
884 false,
885 env,
886 )
887}
888
889enum Position {
890 Tail,
891 NotTail,
892}
893
894fn statement<'a>(
895 statement: &'a TypedStatement,
896 env: &mut Env<'a>,
897 position: Position,
898) -> Document<'a> {
899 match statement {
900 Statement::Expression(e) => expr(e, env),
901 Statement::Assignment(a) => assignment(a, env, position),
902 Statement::Use(use_) => expr(&use_.call, env),
903 Statement::Assert(a) => assert(a, env),
904 }
905}
906
907fn expr_segment<'a>(
908 value: &'a TypedExpr,
909 options: &'a [BitArrayOption<TypedExpr>],
910 env: &mut Env<'a>,
911) -> Document<'a> {
912 let value_is_a_string_literal = matches!(value, TypedExpr::String { .. });
913
914 let create_document = |env: &mut Env<'a>| {
915 match value {
916 // Skip the normal <<value/utf8>> surrounds and set the string literal flag
917 TypedExpr::String { value, .. } => string_inner(value).surround("\"", "\""),
918
919 // As normal
920 TypedExpr::Int { .. }
921 | TypedExpr::Float { .. }
922 | TypedExpr::Var { .. }
923 | TypedExpr::BitArray { .. } => expr(value, env),
924
925 // Wrap anything else in parentheses
926 value => expr(value, env).surround("(", ")"),
927 }
928 };
929
930 let size = |expression: &'a TypedExpr, env: &mut Env<'a>| match expression {
931 TypedExpr::Int { value, .. } => {
932 let v = value.replace("_", "");
933 let v = u64::from_str(&v).unwrap_or(0);
934 Some(eco_format!(":{v}").to_doc())
935 }
936
937 _ => {
938 let inner_expr = maybe_block_expr(expression, env).surround("(", ")");
939 // The value of size must be a non-negative integer, we use lists:max here to ensure
940 // it is at least 0;
941 let value_guard = ":(lists:max(["
942 .to_doc()
943 .append(inner_expr)
944 .append(", 0]))")
945 .group();
946 Some(value_guard)
947 }
948 };
949
950 let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc());
951
952 bit_array_segment(
953 create_document,
954 options,
955 size,
956 unit,
957 value_is_a_string_literal,
958 false,
959 env,
960 )
961}
962
963fn bit_array_segment<'a, Value: 'a, CreateDoc, SizeToDoc, UnitToDoc>(
964 mut create_document: CreateDoc,
965 options: &'a [BitArrayOption<Value>],
966 mut size_to_doc: SizeToDoc,
967 mut unit_to_doc: UnitToDoc,
968 value_is_a_string_literal: bool,
969 value_is_a_discard: bool,
970 env: &mut Env<'a>,
971) -> Document<'a>
972where
973 CreateDoc: FnMut(&mut Env<'a>) -> Document<'a>,
974 SizeToDoc: FnMut(&'a Value, &mut Env<'a>) -> Option<Document<'a>>,
975 UnitToDoc: FnMut(&'a u8) -> Option<Document<'a>>,
976{
977 let mut size: Option<Document<'a>> = None;
978 let mut unit: Option<Document<'a>> = None;
979 let mut others = Vec::new();
980
981 // Erlang only allows valid codepoint integers to be used as values for utf segments
982 // We want to support <<string_var:utf8>> for all string variables, but <<StringVar/utf8>> is invalid
983 // To work around this we use the binary type specifier for these segments instead
984 let override_type = if !value_is_a_string_literal && !value_is_a_discard {
985 Some("binary")
986 } else {
987 None
988 };
989
990 for option in options {
991 use BitArrayOption as Opt;
992 if !others.is_empty() && !matches!(option, Opt::Size { .. } | Opt::Unit { .. }) {
993 others.push("-".to_doc());
994 }
995 match option {
996 Opt::Utf8 { .. } => others.push(override_type.unwrap_or("utf8").to_doc()),
997 Opt::Utf16 { .. } => others.push(override_type.unwrap_or("utf16").to_doc()),
998 Opt::Utf32 { .. } => others.push(override_type.unwrap_or("utf32").to_doc()),
999 Opt::Int { .. } => others.push("integer".to_doc()),
1000 Opt::Float { .. } => others.push("float".to_doc()),
1001 Opt::Bytes { .. } => others.push("binary".to_doc()),
1002 Opt::Bits { .. } => others.push("bitstring".to_doc()),
1003 Opt::Utf8Codepoint { .. } => others.push("utf8".to_doc()),
1004 Opt::Utf16Codepoint { .. } => others.push("utf16".to_doc()),
1005 Opt::Utf32Codepoint { .. } => others.push("utf32".to_doc()),
1006 Opt::Signed { .. } => others.push("signed".to_doc()),
1007 Opt::Unsigned { .. } => others.push("unsigned".to_doc()),
1008 Opt::Big { .. } => others.push("big".to_doc()),
1009 Opt::Little { .. } => others.push("little".to_doc()),
1010 Opt::Native { .. } => others.push("native".to_doc()),
1011 Opt::Size { value, .. } => size = size_to_doc(value, env),
1012 Opt::Unit { value, .. } => unit = unit_to_doc(value),
1013 }
1014 }
1015
1016 let mut document = create_document(env);
1017
1018 document = document.append(size);
1019 let others_is_empty = others.is_empty();
1020
1021 if !others_is_empty {
1022 document = document.append("/").append(others);
1023 }
1024
1025 if unit.is_some() {
1026 if !others_is_empty {
1027 document = document.append("-").append(unit)
1028 } else {
1029 document = document.append("/").append(unit)
1030 }
1031 }
1032
1033 document
1034}
1035
1036fn block<'a>(statements: &'a Vec1<TypedStatement>, env: &mut Env<'a>) -> Document<'a> {
1037 if statements.len() == 1 {
1038 if let Statement::Expression(expression) = statements.first() {
1039 if !needs_begin_end_wrapping(expression) {
1040 return docvec!['(', expr(expression, env), ')'];
1041 }
1042 }
1043 }
1044
1045 let vars = env.current_scope_vars.clone();
1046 let document = statement_sequence(statements, env);
1047 env.current_scope_vars = vars;
1048
1049 begin_end(document)
1050}
1051
1052fn statement_sequence<'a>(statements: &'a [TypedStatement], env: &mut Env<'a>) -> Document<'a> {
1053 let count = statements.len();
1054 let mut documents = Vec::with_capacity(count * 3);
1055 for (i, expression) in statements.iter().enumerate() {
1056 let position = if i + 1 == count {
1057 Position::Tail
1058 } else {
1059 Position::NotTail
1060 };
1061 documents.push(statement(expression, env, position).group());
1062
1063 if i + 1 < count {
1064 // This isn't the final expression so add the delimeters
1065 documents.push(",".to_doc());
1066 documents.push(line());
1067 }
1068 }
1069 if count == 1 {
1070 documents.to_doc()
1071 } else {
1072 documents.to_doc().force_break()
1073 }
1074}
1075
1076fn float_div<'a>(left: &'a TypedExpr, right: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
1077 if right.non_zero_compile_time_number() {
1078 return binop_exprs(left, "/", right, env);
1079 }
1080
1081 let left = expr(left, env);
1082 let right = expr(right, env);
1083 let denominator = env.next_local_var_name("gleam@denominator");
1084 let clauses = docvec![
1085 line(),
1086 "+0.0 -> +0.0;",
1087 line(),
1088 "-0.0 -> -0.0;",
1089 line(),
1090 denominator.clone(),
1091 " -> ",
1092 binop_documents(left, "/", denominator)
1093 ];
1094 docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"]
1095}
1096
1097fn int_div<'a>(
1098 left: &'a TypedExpr,
1099 right: &'a TypedExpr,
1100 op: &'static str,
1101 env: &mut Env<'a>,
1102) -> Document<'a> {
1103 if right.non_zero_compile_time_number() {
1104 return binop_exprs(left, op, right, env);
1105 }
1106
1107 let left = expr(left, env);
1108 let right = expr(right, env);
1109 let denominator = env.next_local_var_name("gleam@denominator");
1110 let clauses = docvec![
1111 line(),
1112 "0 -> 0;",
1113 line(),
1114 denominator.clone(),
1115 " -> ",
1116 binop_documents(left, op, denominator)
1117 ];
1118 docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"]
1119}
1120
1121fn bin_op<'a>(
1122 name: &'a BinOp,
1123 left: &'a TypedExpr,
1124 right: &'a TypedExpr,
1125 env: &mut Env<'a>,
1126) -> Document<'a> {
1127 let op = match name {
1128 BinOp::And => "andalso",
1129 BinOp::Or => "orelse",
1130 BinOp::LtInt | BinOp::LtFloat => "<",
1131 BinOp::LtEqInt | BinOp::LtEqFloat => "=<",
1132 BinOp::Eq => "=:=",
1133 BinOp::NotEq => "/=",
1134 BinOp::GtInt | BinOp::GtFloat => ">",
1135 BinOp::GtEqInt | BinOp::GtEqFloat => ">=",
1136 BinOp::AddInt => "+",
1137 BinOp::AddFloat => "+",
1138 BinOp::SubInt => "-",
1139 BinOp::SubFloat => "-",
1140 BinOp::MultInt => "*",
1141 BinOp::MultFloat => "*",
1142 BinOp::DivFloat => return float_div(left, right, env),
1143 BinOp::DivInt => return int_div(left, right, "div", env),
1144 BinOp::RemainderInt => return int_div(left, right, "rem", env),
1145 BinOp::Concatenate => return string_concatenate(left, right, env),
1146 };
1147
1148 binop_exprs(left, op, right, env)
1149}
1150
1151fn binop_exprs<'a>(
1152 left: &'a TypedExpr,
1153 op: &'static str,
1154 right: &'a TypedExpr,
1155 env: &mut Env<'a>,
1156) -> Document<'a> {
1157 let left = match left {
1158 TypedExpr::BinOp { .. } => expr(left, env).surround("(", ")"),
1159 _ => maybe_block_expr(left, env),
1160 };
1161 let right = match right {
1162 TypedExpr::BinOp { .. } => expr(right, env).surround("(", ")"),
1163 _ => maybe_block_expr(right, env),
1164 };
1165 binop_documents(left, op, right)
1166}
1167
1168fn binop_documents<'a>(left: Document<'a>, op: &'static str, right: Document<'a>) -> Document<'a> {
1169 left.append(break_("", " "))
1170 .append(op)
1171 .group()
1172 .append(" ")
1173 .append(right)
1174}
1175
1176fn let_assert<'a>(
1177 value: &'a TypedExpr,
1178 pattern: &'a TypedPattern,
1179 env: &mut Env<'a>,
1180 message: Option<&'a TypedExpr>,
1181 position: Position,
1182 location: SrcSpan,
1183) -> Document<'a> {
1184 // If the pattern will never fail, like a tuple or a simple variable, we
1185 // simply treat it as if it were a `let` assignment.
1186 if pattern.always_matches() {
1187 return let_(value, pattern, env);
1188 }
1189
1190 let mut vars: Vec<&str> = vec![];
1191 let subject = maybe_block_expr(value, env);
1192
1193 // The code we generated for a `let assert` assignment looks something like
1194 // this. For this Gleam code:
1195 //
1196 // ```gleam
1197 // let assert [a, b, c] = [1, 2, 3]
1198 // ```
1199 //
1200 // We generate (roughly) the following Erlang:
1201 //
1202 // ```erlang
1203 // {A, B, C} = case [1, 2, 3] of
1204 // [A, B, C] -> {A, B, C};
1205 // _ -> erlang:error(...)
1206 // end.
1207 // ```
1208 // This is the most efficient way to properly extract all the required
1209 // variables from the pattern. However, if the `let assert` assignment is
1210 // the last in a block, like this:
1211 //
1212 // ```gleam
1213 // let x = {
1214 // let assert [a, b, c] = [1, 2, 3]
1215 // }
1216 // ```
1217 //
1218 // The generated Erlang code will end up assigning the value `#(1, 2, 3)`
1219 // to the variable `x`, instead of `[1, 2, 3]`. In this case, we must
1220 // generate slightly different code. Since we know we won't be using the
1221 // bound variables anywhere (there is nothing else in this scope to
1222 // reference them), we can safely remove the assignment from the generated
1223 // code, and generate the following:
1224 //
1225 // ```erlang
1226 // X = begin
1227 // _assert_subject = [1, 2, 3]
1228 // case _assert_subject of
1229 // [A, B, C] -> _assert_subject;
1230 // _ -> erlang:error(...)
1231 // end
1232 // end.
1233 // ```
1234 //
1235 // That correctly assigns `[1, 2, 3]` to the `x` variable.
1236 //
1237 let is_tail = match position {
1238 Position::Tail => true,
1239 Position::NotTail => false,
1240 };
1241
1242 let (subject_assignment, subject) = if is_tail && !value.is_var() {
1243 let variable = env.next_local_var_name(ASSERT_SUBJECT_VARIABLE);
1244 let assignment = docvec![variable.clone(), " = ", subject, ",", line()];
1245 (assignment, variable)
1246 } else {
1247 (nil(), subject)
1248 };
1249
1250 let mut guards = vec![];
1251 let pattern_document = pattern::to_doc(pattern, &mut vars, env, &mut guards);
1252 let clause_guard = optional_clause_guard(None, guards, env);
1253
1254 let message = match message {
1255 Some(message) => expr(message, env),
1256 None => string("Pattern match failed, no pattern matched the value."),
1257 };
1258
1259 let value_document = match vars.as_slice() {
1260 _ if is_tail => subject.clone(),
1261 [] => "nil".to_doc(),
1262 [variable] => env.local_var_name(variable),
1263 variables => {
1264 let variables = variables
1265 .iter()
1266 .map(|variable| env.local_var_name(variable));
1267 docvec![
1268 break_("{", "{"),
1269 join(variables, break_(",", ", ")).nest(INDENT),
1270 "}"
1271 ]
1272 .group()
1273 }
1274 };
1275
1276 let assignment = match vars.as_slice() {
1277 _ if is_tail => nil(),
1278 [] => nil(),
1279 [variable] => env.next_local_var_name(variable).append(" = "),
1280 variables => {
1281 let variables = variables
1282 .iter()
1283 .map(|variable| env.next_local_var_name(variable));
1284 docvec![
1285 break_("{", "{"),
1286 join(variables, break_(",", ", ")).nest(INDENT),
1287 "} = "
1288 ]
1289 .group()
1290 }
1291 };
1292
1293 let clauses = docvec![
1294 pattern_document,
1295 clause_guard,
1296 " -> ",
1297 value_document,
1298 ";",
1299 line(),
1300 env.next_local_var_name(ASSERT_FAIL_VARIABLE),
1301 " ->",
1302 docvec![
1303 line(),
1304 erlang_error(
1305 "let_assert",
1306 &message,
1307 location,
1308 vec![
1309 ("value", env.local_var_name(ASSERT_FAIL_VARIABLE)),
1310 ("start", location.start.to_doc()),
1311 ("'end'", value.location().end.to_doc()),
1312 ("pattern_start", pattern.location().start.to_doc()),
1313 ("pattern_end", pattern.location().end.to_doc()),
1314 ],
1315 env,
1316 )
1317 .nest(INDENT)
1318 ]
1319 .nest(INDENT)
1320 ];
1321 docvec![
1322 subject_assignment,
1323 assignment,
1324 "case ",
1325 subject,
1326 " of",
1327 docvec![line(), clauses].nest(INDENT),
1328 line(),
1329 "end",
1330 ]
1331}
1332
1333fn let_<'a>(value: &'a TypedExpr, pat: &'a TypedPattern, env: &mut Env<'a>) -> Document<'a> {
1334 let body = maybe_block_expr(value, env).group();
1335 let mut guards = vec![];
1336 pattern(pat, env, &mut guards).append(" = ").append(body)
1337}
1338
1339fn float<'a>(value: &str) -> Document<'a> {
1340 let mut value = value.replace('_', "");
1341 if value.ends_with('.') {
1342 value.push('0')
1343 }
1344
1345 match value.split('.').collect_vec().as_slice() {
1346 ["0", "0"] => "+0.0".to_doc(),
1347 [before_dot, after_dot] if after_dot.starts_with('e') => {
1348 eco_format!("{before_dot}.0{after_dot}").to_doc()
1349 }
1350 _ => EcoString::from(value).to_doc(),
1351 }
1352}
1353
1354fn expr_list<'a>(
1355 elements: &'a [TypedExpr],
1356 tail: &'a Option<Box<TypedExpr>>,
1357 env: &mut Env<'a>,
1358) -> Document<'a> {
1359 let elements = join(
1360 elements
1361 .iter()
1362 .map(|element| maybe_block_expr(element, env)),
1363 break_(",", ", "),
1364 );
1365 list(
1366 elements,
1367 tail.as_ref().map(|element| maybe_block_expr(element, env)),
1368 )
1369}
1370
1371fn list<'a>(elements: Document<'a>, tail: Option<Document<'a>>) -> Document<'a> {
1372 let elements = match tail {
1373 Some(tail) if elements.is_empty() => return tail.to_doc(),
1374
1375 Some(tail) => elements.append(break_(" |", " | ")).append(tail),
1376
1377 None => elements,
1378 };
1379
1380 elements.to_doc().nest(INDENT).surround("[", "]").group()
1381}
1382
1383fn var<'a>(name: &'a str, constructor: &'a ValueConstructor, env: &mut Env<'a>) -> Document<'a> {
1384 match &constructor.variant {
1385 ValueConstructorVariant::Record {
1386 name: record_name, ..
1387 } => match constructor.type_.deref() {
1388 Type::Fn { args, .. } => {
1389 let chars = incrementing_args_list(args.len());
1390 "fun("
1391 .to_doc()
1392 .append(chars.clone())
1393 .append(") -> {")
1394 .append(atom_string(to_snake_case(record_name)))
1395 .append(", ")
1396 .append(chars)
1397 .append("} end")
1398 }
1399 _ => atom_string(to_snake_case(record_name)),
1400 },
1401
1402 ValueConstructorVariant::LocalVariable { .. } => env.local_var_name(name),
1403
1404 ValueConstructorVariant::ModuleConstant { literal, .. }
1405 | ValueConstructorVariant::LocalConstant { literal } => const_inline(literal, env),
1406
1407 ValueConstructorVariant::ModuleFn {
1408 arity,
1409 external_erlang: Some((module, name)),
1410 ..
1411 } if module == env.module => function_reference(None, name, *arity),
1412
1413 ValueConstructorVariant::ModuleFn {
1414 arity,
1415 external_erlang: Some((module, name)),
1416 ..
1417 } => function_reference(Some(module), name, *arity),
1418
1419 ValueConstructorVariant::ModuleFn { arity, module, .. } if module == env.module => {
1420 function_reference(None, name, *arity)
1421 }
1422
1423 ValueConstructorVariant::ModuleFn {
1424 arity,
1425 module,
1426 name,
1427 ..
1428 } => function_reference(Some(module), name, *arity),
1429 }
1430}
1431
1432fn function_reference<'a>(module: Option<&'a str>, name: &'a str, arity: usize) -> Document<'a> {
1433 match module {
1434 None => "fun ".to_doc(),
1435 Some(module) => "fun ".to_doc().append(module_name_atom(module)).append(":"),
1436 }
1437 .append(atom(escape_erlang_existing_name(name)))
1438 .append("/")
1439 .append(arity)
1440}
1441
1442fn int<'a>(value: &str) -> Document<'a> {
1443 let mut value = value.replace('_', "");
1444 if value.starts_with("0x") {
1445 value.replace_range(..2, "16#");
1446 } else if value.starts_with("0o") {
1447 value.replace_range(..2, "8#");
1448 } else if value.starts_with("0b") {
1449 value.replace_range(..2, "2#");
1450 }
1451
1452 EcoString::from(value).to_doc()
1453}
1454
1455fn const_inline<'a>(literal: &'a TypedConstant, env: &mut Env<'a>) -> Document<'a> {
1456 match literal {
1457 Constant::Int { value, .. } => int(value),
1458 Constant::Float { value, .. } => float(value),
1459 Constant::String { value, .. } => string(value),
1460 Constant::Tuple { elements, .. } => {
1461 tuple(elements.iter().map(|element| const_inline(element, env)))
1462 }
1463
1464 Constant::List { elements, .. } => join(
1465 elements.iter().map(|element| const_inline(element, env)),
1466 break_(",", ", "),
1467 )
1468 .nest(INDENT)
1469 .surround("[", "]")
1470 .group(),
1471
1472 Constant::BitArray { segments, .. } => bit_array(
1473 segments
1474 .iter()
1475 .map(|s| const_segment(&s.value, &s.options, env)),
1476 ),
1477
1478 Constant::Record {
1479 tag, type_, args, ..
1480 } if args.is_empty() => match type_.deref() {
1481 Type::Fn { args, .. } => record_constructor_function(tag, args.len()),
1482 _ => atom_string(to_snake_case(tag)),
1483 },
1484
1485 Constant::Record { tag, args, .. } => {
1486 let args = args.iter().map(|a| const_inline(&a.value, env));
1487 let tag = atom_string(to_snake_case(tag));
1488 tuple(std::iter::once(tag).chain(args))
1489 }
1490
1491 Constant::Var {
1492 name, constructor, ..
1493 } => var(
1494 name,
1495 constructor
1496 .as_ref()
1497 .expect("This is guaranteed to hold a value."),
1498 env,
1499 ),
1500
1501 Constant::StringConcatenation { left, right, .. } => {
1502 const_string_concatenate(left, right, env)
1503 }
1504
1505 Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"),
1506 }
1507}
1508
1509fn record_constructor_function(tag: &EcoString, arity: usize) -> Document<'_> {
1510 let chars = incrementing_args_list(arity);
1511 "fun("
1512 .to_doc()
1513 .append(chars.clone())
1514 .append(") -> {")
1515 .append(atom_string(to_snake_case(tag)))
1516 .append(", ")
1517 .append(chars)
1518 .append("} end")
1519}
1520
1521fn clause<'a>(clause: &'a TypedClause, env: &mut Env<'a>) -> Document<'a> {
1522 let Clause {
1523 guard,
1524 pattern: pat,
1525 alternative_patterns,
1526 then,
1527 ..
1528 } = clause;
1529
1530 // These are required to get the alternative patterns working properly.
1531 // Simply rendering the duplicate erlang clauses breaks the variable
1532 // rewriting because each pattern would define different (rewritten)
1533 // variables names.
1534 let mut then_doc = None;
1535 let initial_erlang_vars = env.erl_function_scope_vars.clone();
1536 let mut end_erlang_vars = im::HashMap::new();
1537
1538 let doc = join(
1539 std::iter::once(pat)
1540 .chain(alternative_patterns)
1541 .map(|patterns| {
1542 let mut additional_guards = vec![];
1543 env.erl_function_scope_vars = initial_erlang_vars.clone();
1544
1545 let patterns_doc = if patterns.len() == 1 {
1546 let p = patterns.first().expect("Single pattern clause printing");
1547 pattern(p, env, &mut additional_guards)
1548 } else {
1549 tuple(
1550 patterns
1551 .iter()
1552 .map(|p| pattern(p, env, &mut additional_guards)),
1553 )
1554 };
1555
1556 let guard = optional_clause_guard(guard.as_ref(), additional_guards, env);
1557 if then_doc.is_none() {
1558 then_doc = Some(clause_consequence(then, env));
1559 end_erlang_vars = env.erl_function_scope_vars.clone();
1560 }
1561
1562 patterns_doc.append(
1563 guard
1564 .append(" ->")
1565 .append(line().append(then_doc.clone()).nest(INDENT).group()),
1566 )
1567 }),
1568 ";".to_doc().append(lines(2)),
1569 );
1570
1571 env.erl_function_scope_vars = end_erlang_vars;
1572 doc
1573}
1574
1575fn clause_consequence<'a>(consequence: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
1576 match consequence {
1577 TypedExpr::Block { statements, .. } => statement_sequence(statements, env),
1578 _ => expr(consequence, env),
1579 }
1580}
1581
1582fn optional_clause_guard<'a>(
1583 guard: Option<&'a TypedClauseGuard>,
1584 additional_guards: Vec<Document<'a>>,
1585 env: &mut Env<'a>,
1586) -> Document<'a> {
1587 let guard_doc = guard.map(|guard| bare_clause_guard(guard, env));
1588
1589 let guards_count = guard_doc.iter().len() + additional_guards.len();
1590 let guards_docs = additional_guards.into_iter().chain(guard_doc).map(|guard| {
1591 if guards_count > 1 {
1592 guard.surround("(", ")")
1593 } else {
1594 guard
1595 }
1596 });
1597 let doc = join(guards_docs, " andalso ".to_doc());
1598 if doc.is_empty() {
1599 doc
1600 } else {
1601 " when ".to_doc().append(doc)
1602 }
1603}
1604
1605fn bare_clause_guard<'a>(guard: &'a TypedClauseGuard, env: &mut Env<'a>) -> Document<'a> {
1606 match guard {
1607 ClauseGuard::Not { expression, .. } => docvec!["not ", bare_clause_guard(expression, env)],
1608
1609 ClauseGuard::Or { left, right, .. } => clause_guard(left, env)
1610 .append(" orelse ")
1611 .append(clause_guard(right, env)),
1612
1613 ClauseGuard::And { left, right, .. } => clause_guard(left, env)
1614 .append(" andalso ")
1615 .append(clause_guard(right, env)),
1616
1617 ClauseGuard::Equals { left, right, .. } => clause_guard(left, env)
1618 .append(" =:= ")
1619 .append(clause_guard(right, env)),
1620
1621 ClauseGuard::NotEquals { left, right, .. } => clause_guard(left, env)
1622 .append(" =/= ")
1623 .append(clause_guard(right, env)),
1624
1625 ClauseGuard::GtInt { left, right, .. } => clause_guard(left, env)
1626 .append(" > ")
1627 .append(clause_guard(right, env)),
1628
1629 ClauseGuard::GtEqInt { left, right, .. } => clause_guard(left, env)
1630 .append(" >= ")
1631 .append(clause_guard(right, env)),
1632
1633 ClauseGuard::LtInt { left, right, .. } => clause_guard(left, env)
1634 .append(" < ")
1635 .append(clause_guard(right, env)),
1636
1637 ClauseGuard::LtEqInt { left, right, .. } => clause_guard(left, env)
1638 .append(" =< ")
1639 .append(clause_guard(right, env)),
1640
1641 ClauseGuard::GtFloat { left, right, .. } => clause_guard(left, env)
1642 .append(" > ")
1643 .append(clause_guard(right, env)),
1644
1645 ClauseGuard::GtEqFloat { left, right, .. } => clause_guard(left, env)
1646 .append(" >= ")
1647 .append(clause_guard(right, env)),
1648
1649 ClauseGuard::LtFloat { left, right, .. } => clause_guard(left, env)
1650 .append(" < ")
1651 .append(clause_guard(right, env)),
1652
1653 ClauseGuard::LtEqFloat { left, right, .. } => clause_guard(left, env)
1654 .append(" =< ")
1655 .append(clause_guard(right, env)),
1656
1657 ClauseGuard::AddInt { left, right, .. } => clause_guard(left, env)
1658 .append(" + ")
1659 .append(clause_guard(right, env)),
1660
1661 ClauseGuard::AddFloat { left, right, .. } => clause_guard(left, env)
1662 .append(" + ")
1663 .append(clause_guard(right, env)),
1664
1665 ClauseGuard::SubInt { left, right, .. } => clause_guard(left, env)
1666 .append(" - ")
1667 .append(clause_guard(right, env)),
1668
1669 ClauseGuard::SubFloat { left, right, .. } => clause_guard(left, env)
1670 .append(" - ")
1671 .append(clause_guard(right, env)),
1672
1673 ClauseGuard::MultInt { left, right, .. } => clause_guard(left, env)
1674 .append(" * ")
1675 .append(clause_guard(right, env)),
1676
1677 ClauseGuard::MultFloat { left, right, .. } => clause_guard(left, env)
1678 .append(" * ")
1679 .append(clause_guard(right, env)),
1680
1681 ClauseGuard::DivInt { left, right, .. } => clause_guard(left, env)
1682 .append(" div ")
1683 .append(clause_guard(right, env)),
1684
1685 ClauseGuard::DivFloat { left, right, .. } => clause_guard(left, env)
1686 .append(" / ")
1687 .append(clause_guard(right, env)),
1688
1689 ClauseGuard::RemainderInt { left, right, .. } => clause_guard(left, env)
1690 .append(" rem ")
1691 .append(clause_guard(right, env)),
1692
1693 // Only local variables are supported and the typer ensures that all
1694 // ClauseGuard::Vars are local variables
1695 ClauseGuard::Var { name, .. } => env.local_var_name(name),
1696
1697 ClauseGuard::TupleIndex { tuple, index, .. } => tuple_index_inline(tuple, *index, env),
1698
1699 ClauseGuard::FieldAccess {
1700 container, index, ..
1701 } => tuple_index_inline(container, index.expect("Unable to find index") + 1, env),
1702
1703 ClauseGuard::ModuleSelect { literal, .. } => const_inline(literal, env),
1704
1705 ClauseGuard::Constant(constant) => const_inline(constant, env),
1706 }
1707}
1708
1709fn tuple_index_inline<'a>(
1710 tuple: &'a TypedClauseGuard,
1711 index: u64,
1712 env: &mut Env<'a>,
1713) -> Document<'a> {
1714 let index_doc = eco_format!("{}", (index + 1)).to_doc();
1715 let tuple_doc = bare_clause_guard(tuple, env);
1716 "erlang:element"
1717 .to_doc()
1718 .append(wrap_args([index_doc, tuple_doc]))
1719}
1720
1721fn clause_guard<'a>(guard: &'a TypedClauseGuard, env: &mut Env<'a>) -> Document<'a> {
1722 match guard {
1723 // Binary operators are wrapped in parens
1724 ClauseGuard::Or { .. }
1725 | ClauseGuard::And { .. }
1726 | ClauseGuard::Equals { .. }
1727 | ClauseGuard::NotEquals { .. }
1728 | ClauseGuard::GtInt { .. }
1729 | ClauseGuard::GtEqInt { .. }
1730 | ClauseGuard::LtInt { .. }
1731 | ClauseGuard::LtEqInt { .. }
1732 | ClauseGuard::GtFloat { .. }
1733 | ClauseGuard::GtEqFloat { .. }
1734 | ClauseGuard::LtFloat { .. }
1735 | ClauseGuard::LtEqFloat { .. }
1736 | ClauseGuard::AddInt { .. }
1737 | ClauseGuard::AddFloat { .. }
1738 | ClauseGuard::SubInt { .. }
1739 | ClauseGuard::SubFloat { .. }
1740 | ClauseGuard::MultInt { .. }
1741 | ClauseGuard::MultFloat { .. }
1742 | ClauseGuard::DivInt { .. }
1743 | ClauseGuard::DivFloat { .. }
1744 | ClauseGuard::RemainderInt { .. } => "("
1745 .to_doc()
1746 .append(bare_clause_guard(guard, env))
1747 .append(")"),
1748
1749 // Other expressions are not
1750 ClauseGuard::Constant(_)
1751 | ClauseGuard::Not { .. }
1752 | ClauseGuard::Var { .. }
1753 | ClauseGuard::TupleIndex { .. }
1754 | ClauseGuard::FieldAccess { .. }
1755 | ClauseGuard::ModuleSelect { .. } => bare_clause_guard(guard, env),
1756 }
1757}
1758
1759fn clauses<'a>(cs: &'a [TypedClause], env: &mut Env<'a>) -> Document<'a> {
1760 join(
1761 cs.iter().map(|c| {
1762 let vars = env.current_scope_vars.clone();
1763 let erl = clause(c, env);
1764 env.current_scope_vars = vars; // Reset the known variables now the clauses' scope has ended
1765 erl
1766 }),
1767 ";".to_doc().append(lines(2)),
1768 )
1769}
1770
1771fn case<'a>(subjects: &'a [TypedExpr], cs: &'a [TypedClause], env: &mut Env<'a>) -> Document<'a> {
1772 let subjects_doc = if subjects.len() == 1 {
1773 let subject = subjects
1774 .first()
1775 .expect("erl case printing of single subject");
1776 maybe_block_expr(subject, env).group()
1777 } else {
1778 tuple(
1779 subjects
1780 .iter()
1781 .map(|element| maybe_block_expr(element, env)),
1782 )
1783 };
1784 "case "
1785 .to_doc()
1786 .append(subjects_doc)
1787 .append(" of")
1788 .append(line().append(clauses(cs, env)).nest(INDENT))
1789 .append(line())
1790 .append("end")
1791 .group()
1792}
1793
1794fn call<'a>(fun: &'a TypedExpr, args: &'a [TypedCallArg], env: &mut Env<'a>) -> Document<'a> {
1795 docs_args_call(
1796 fun,
1797 args.iter()
1798 .map(|arg| maybe_block_expr(&arg.value, env))
1799 .collect(),
1800 env,
1801 )
1802}
1803
1804fn module_fn_with_args<'a>(
1805 module: &'a str,
1806 name: &'a str,
1807 args: Vec<Document<'a>>,
1808 env: &Env<'a>,
1809) -> Document<'a> {
1810 let name = escape_erlang_existing_name(name);
1811 let args = wrap_args(args);
1812 if module == env.module {
1813 atom(name).append(args)
1814 } else {
1815 atom_string(module.replace('/', "@").into())
1816 .append(":")
1817 .append(atom(name))
1818 .append(args)
1819 }
1820}
1821
1822fn docs_args_call<'a>(
1823 fun: &'a TypedExpr,
1824 mut args: Vec<Document<'a>>,
1825 env: &mut Env<'a>,
1826) -> Document<'a> {
1827 match fun {
1828 TypedExpr::ModuleSelect {
1829 constructor: ModuleValueConstructor::Record { name, .. },
1830 ..
1831 }
1832 | TypedExpr::Var {
1833 constructor:
1834 ValueConstructor {
1835 variant: ValueConstructorVariant::Record { name, .. },
1836 ..
1837 },
1838 ..
1839 } => tuple(std::iter::once(atom_string(to_snake_case(name))).chain(args)),
1840
1841 TypedExpr::Var {
1842 constructor:
1843 ValueConstructor {
1844 variant:
1845 ValueConstructorVariant::ModuleFn {
1846 external_erlang: Some((module, name)),
1847 ..
1848 }
1849 | ValueConstructorVariant::ModuleFn { module, name, .. },
1850 ..
1851 },
1852 ..
1853 } => module_fn_with_args(module, name, args, env),
1854
1855 // Match against a Constant::Var that contains a function.
1856 // We want this to be emitted like a normal function call, not a function variable
1857 // substitution.
1858 TypedExpr::Var {
1859 constructor:
1860 ValueConstructor {
1861 variant:
1862 ValueConstructorVariant::ModuleConstant {
1863 literal:
1864 Constant::Var {
1865 constructor: Some(constructor),
1866 ..
1867 },
1868 ..
1869 },
1870 ..
1871 },
1872 ..
1873 } if constructor.variant.is_module_fn() => match &constructor.variant {
1874 ValueConstructorVariant::ModuleFn {
1875 external_erlang: Some((module, name)),
1876 ..
1877 }
1878 | ValueConstructorVariant::ModuleFn { module, name, .. } => {
1879 module_fn_with_args(module, name, args, env)
1880 }
1881 _ => {
1882 unreachable!("The above clause guard ensures that this is a module fn")
1883 }
1884 },
1885
1886 TypedExpr::ModuleSelect {
1887 constructor:
1888 ModuleValueConstructor::Fn {
1889 external_erlang: Some((module, name)),
1890 ..
1891 }
1892 | ModuleValueConstructor::Fn { module, name, .. },
1893 ..
1894 } => {
1895 let args = wrap_args(args);
1896 let name = escape_erlang_existing_name(name);
1897 // We use the constructor Fn variant's `module` and function `name`.
1898 // It would also be valid to use the module and label as in the
1899 // Gleam code, but using the variant can result in an optimisation
1900 // in which the target function is used for `external fn`s, removing
1901 // one layer of wrapping.
1902 // This also enables an optimisation in the Erlang compiler in which
1903 // some Erlang BIFs can be replaced with literals if their arguments
1904 // are literals, such as `binary_to_atom`.
1905 atom_string(module_erlang_name(module))
1906 .append(":")
1907 .append(atom_string(name.into()))
1908 .append(args)
1909 }
1910
1911 TypedExpr::Fn { kind, body, .. } if kind.is_capture() => {
1912 if let Statement::Expression(TypedExpr::Call {
1913 fun,
1914 args: inner_args,
1915 ..
1916 }) = body.first()
1917 {
1918 let mut merged_args = Vec::with_capacity(inner_args.len());
1919 for arg in inner_args {
1920 match &arg.value {
1921 TypedExpr::Var { name, .. } if name == CAPTURE_VARIABLE => {
1922 merged_args.push(args.swap_remove(0))
1923 }
1924 e => merged_args.push(maybe_block_expr(e, env)),
1925 }
1926 }
1927 docs_args_call(fun, merged_args, env)
1928 } else {
1929 panic!("Erl printing: Capture was not a call")
1930 }
1931 }
1932
1933 TypedExpr::Fn { .. }
1934 | TypedExpr::Call { .. }
1935 | TypedExpr::Todo { .. }
1936 | TypedExpr::Panic { .. }
1937 | TypedExpr::RecordAccess { .. }
1938 | TypedExpr::TupleIndex { .. } => {
1939 let args = wrap_args(args);
1940 expr(fun, env).surround("(", ")").append(args)
1941 }
1942
1943 other => {
1944 let args = wrap_args(args);
1945 maybe_block_expr(other, env).append(args)
1946 }
1947 }
1948}
1949
1950fn record_update<'a>(
1951 record: &'a Option<Box<TypedAssignment>>,
1952 constructor: &'a TypedExpr,
1953 args: &'a [TypedCallArg],
1954 env: &mut Env<'a>,
1955) -> Document<'a> {
1956 let vars = env.current_scope_vars.clone();
1957
1958 let document = match record.as_ref() {
1959 Some(record) => docvec![
1960 assignment(record, env, Position::NotTail),
1961 ",",
1962 line(),
1963 call(constructor, args, env)
1964 ],
1965 None => call(constructor, args, env),
1966 };
1967
1968 env.current_scope_vars = vars;
1969
1970 document
1971}
1972
1973/// Wrap a document in begin end
1974///
1975fn begin_end(document: Document<'_>) -> Document<'_> {
1976 docvec!["begin", line().append(document).nest(INDENT), line(), "end"].force_break()
1977}
1978
1979fn maybe_block_expr<'a>(expression: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
1980 if needs_begin_end_wrapping(expression) {
1981 begin_end(expr(expression, env))
1982 } else {
1983 expr(expression, env)
1984 }
1985}
1986
1987fn needs_begin_end_wrapping(expression: &TypedExpr) -> bool {
1988 match expression {
1989 // Record updates are 1 expression if there's no assignment, multiple otherwise.
1990 TypedExpr::RecordUpdate {
1991 record_assignment, ..
1992 } => record_assignment.is_some(),
1993
1994 TypedExpr::Pipeline { .. } => true,
1995
1996 TypedExpr::Int { .. }
1997 | TypedExpr::Float { .. }
1998 | TypedExpr::String { .. }
1999 | TypedExpr::Var { .. }
2000 | TypedExpr::Fn { .. }
2001 | TypedExpr::List { .. }
2002 | TypedExpr::Call { .. }
2003 | TypedExpr::BinOp { .. }
2004 | TypedExpr::Case { .. }
2005 | TypedExpr::RecordAccess { .. }
2006 | TypedExpr::Block { .. }
2007 | TypedExpr::ModuleSelect { .. }
2008 | TypedExpr::Tuple { .. }
2009 | TypedExpr::TupleIndex { .. }
2010 | TypedExpr::Todo { .. }
2011 | TypedExpr::Echo { .. }
2012 | TypedExpr::Panic { .. }
2013 | TypedExpr::BitArray { .. }
2014 | TypedExpr::NegateBool { .. }
2015 | TypedExpr::NegateInt { .. }
2016 | TypedExpr::Invalid { .. } => false,
2017 }
2018}
2019
2020fn todo<'a>(message: Option<&'a TypedExpr>, location: SrcSpan, env: &mut Env<'a>) -> Document<'a> {
2021 let message = match message {
2022 Some(m) => expr(m, env),
2023 None => string("`todo` expression evaluated. This code has not yet been implemented."),
2024 };
2025 erlang_error("todo", &message, location, vec![], env)
2026}
2027
2028fn panic<'a>(location: SrcSpan, message: Option<&'a TypedExpr>, env: &mut Env<'a>) -> Document<'a> {
2029 let message = match message {
2030 Some(m) => expr(m, env),
2031 None => string("`panic` expression evaluated."),
2032 };
2033 erlang_error("panic", &message, location, vec![], env)
2034}
2035
2036fn echo<'a>(
2037 body: Document<'a>,
2038 message: Option<&'a TypedExpr>,
2039 location: &SrcSpan,
2040 env: &mut Env<'a>,
2041) -> Document<'a> {
2042 env.echo_used = true;
2043
2044 let message = message
2045 .as_ref()
2046 .map(|message| maybe_block_expr(message, env))
2047 .unwrap_or("nil".to_doc());
2048
2049 "echo".to_doc().append(wrap_args(vec![
2050 body,
2051 message,
2052 env.line_numbers.line_number(location.start).to_doc(),
2053 ]))
2054}
2055
2056fn erlang_error<'a>(
2057 name: &'a str,
2058 message: &Document<'a>,
2059 location: SrcSpan,
2060 fields: Vec<(&'a str, Document<'a>)>,
2061 env: &Env<'a>,
2062) -> Document<'a> {
2063 let mut fields_doc = docvec![
2064 "gleam_error => ",
2065 name,
2066 ",",
2067 line(),
2068 "message => ",
2069 message.clone(),
2070 ",",
2071 line(),
2072 "file => <<?FILEPATH/utf8>>,",
2073 line(),
2074 "module => ",
2075 env.module.to_doc().surround("<<\"", "\"/utf8>>"),
2076 ",",
2077 line(),
2078 "function => ",
2079 string(env.function),
2080 ",",
2081 line(),
2082 "line => ",
2083 env.line_numbers.line_number(location.start),
2084 ];
2085 for (key, value) in fields {
2086 fields_doc = fields_doc
2087 .append(",")
2088 .append(line())
2089 .append(key)
2090 .append(" => ")
2091 .append(value);
2092 }
2093 let error = docvec!["#{", fields_doc.group().nest(INDENT), "}"];
2094 docvec!["erlang:error", wrap_args([error.group()])]
2095}
2096
2097fn expr<'a>(expression: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
2098 match expression {
2099 TypedExpr::Todo {
2100 message: label,
2101 location,
2102 ..
2103 } => todo(label.as_deref(), *location, env),
2104
2105 TypedExpr::Panic {
2106 location, message, ..
2107 } => panic(*location, message.as_deref(), env),
2108
2109 TypedExpr::Echo {
2110 expression,
2111 location,
2112 message,
2113 ..
2114 } => {
2115 let expression = expression
2116 .as_ref()
2117 .expect("echo with no expression outside of pipe");
2118 let expression = maybe_block_expr(expression, env);
2119 echo(expression, message.as_deref(), location, env)
2120 }
2121
2122 TypedExpr::Int { value, .. } => int(value),
2123 TypedExpr::Float { value, .. } => float(value),
2124 TypedExpr::String { value, .. } => string(value),
2125
2126 TypedExpr::Pipeline {
2127 first_value,
2128 assignments,
2129 finally,
2130 ..
2131 } => pipeline(first_value, assignments, finally, env),
2132
2133 TypedExpr::Block { statements, .. } => block(statements, env),
2134
2135 TypedExpr::TupleIndex { tuple, index, .. } => tuple_index(tuple, *index, env),
2136
2137 TypedExpr::Var {
2138 name, constructor, ..
2139 } => var(name, constructor, env),
2140
2141 TypedExpr::Fn { args, body, .. } => fun(args, body, env),
2142
2143 TypedExpr::NegateBool { value, .. } => negate_with("not ", value, env),
2144
2145 TypedExpr::NegateInt { value, .. } => negate_with("- ", value, env),
2146
2147 TypedExpr::List { elements, tail, .. } => expr_list(elements, tail, env),
2148
2149 TypedExpr::Call { fun, args, .. } => call(fun, args, env),
2150
2151 TypedExpr::ModuleSelect {
2152 constructor: ModuleValueConstructor::Record { name, arity: 0, .. },
2153 ..
2154 } => atom_string(to_snake_case(name)),
2155
2156 TypedExpr::ModuleSelect {
2157 constructor: ModuleValueConstructor::Constant { literal, .. },
2158 ..
2159 } => const_inline(literal, env),
2160
2161 TypedExpr::ModuleSelect {
2162 constructor: ModuleValueConstructor::Record { name, arity, .. },
2163 ..
2164 } => record_constructor_function(name, *arity as usize),
2165
2166 TypedExpr::ModuleSelect {
2167 type_,
2168 constructor:
2169 ModuleValueConstructor::Fn {
2170 external_erlang: Some((module, name)),
2171 ..
2172 }
2173 | ModuleValueConstructor::Fn { module, name, .. },
2174 ..
2175 } => module_select_fn(type_.clone(), module, name),
2176
2177 TypedExpr::RecordAccess { record, index, .. } => tuple_index(record, index + 1, env),
2178
2179 TypedExpr::RecordUpdate {
2180 record_assignment,
2181 constructor,
2182 args,
2183 ..
2184 } => record_update(record_assignment, constructor, args, env),
2185
2186 TypedExpr::Case {
2187 subjects, clauses, ..
2188 } => case(subjects, clauses, env),
2189
2190 TypedExpr::BinOp {
2191 name, left, right, ..
2192 } => bin_op(name, left, right, env),
2193
2194 TypedExpr::Tuple { elements, .. } => tuple(
2195 elements
2196 .iter()
2197 .map(|element| maybe_block_expr(element, env)),
2198 ),
2199
2200 TypedExpr::BitArray { segments, .. } => bit_array(
2201 segments
2202 .iter()
2203 .map(|s| expr_segment(&s.value, &s.options, env)),
2204 ),
2205
2206 TypedExpr::Invalid { .. } => panic!("invalid expressions should not reach code generation"),
2207 }
2208}
2209
2210fn pipeline<'a>(
2211 first_value: &'a TypedPipelineAssignment,
2212 assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)],
2213 finally: &'a TypedExpr,
2214 env: &mut Env<'a>,
2215) -> Document<'a> {
2216 let mut documents = Vec::with_capacity((assignments.len() + 1) * 3);
2217
2218 let all_assignments = std::iter::once(first_value)
2219 .chain(assignments.iter().map(|(assignment, _kind)| assignment));
2220
2221 let echo_doc = |var_name: &Option<Document<'a>>,
2222 message: Option<&'a TypedExpr>,
2223 location: &SrcSpan,
2224 env: &mut Env<'a>| {
2225 let name = var_name
2226 .to_owned()
2227 .expect("echo with no previous step in a pipe");
2228 echo(name, message, location, env)
2229 };
2230
2231 let mut prev_local_var_name = None;
2232 for a in all_assignments {
2233 match a.value.as_ref() {
2234 // An echo in a pipeline won't result in an assignment, instead it
2235 // just prints the previous variable assigned in the pipeline.
2236 TypedExpr::Echo {
2237 expression: None,
2238 message,
2239 location,
2240 ..
2241 } => documents.push(echo_doc(
2242 &prev_local_var_name,
2243 message.as_deref(),
2244 location,
2245 env,
2246 )),
2247
2248 // Otherwise we assign the intermediate pipe value to a variable.
2249 _ => {
2250 let body = maybe_block_expr(&a.value, env).group();
2251 let name = env.next_local_var_name(&a.name);
2252 prev_local_var_name = Some(name.clone());
2253 documents.push(docvec![name, " = ", body]);
2254 }
2255 };
2256 documents.push(",".to_doc());
2257 documents.push(line());
2258 }
2259
2260 match finally {
2261 TypedExpr::Echo {
2262 expression: None,
2263 message,
2264 location,
2265 ..
2266 } => documents.push(echo_doc(
2267 &prev_local_var_name,
2268 message.as_deref(),
2269 location,
2270 env,
2271 )),
2272 _ => documents.push(expr(finally, env)),
2273 }
2274
2275 documents.to_doc()
2276}
2277
2278fn assignment<'a>(
2279 assignment: &'a TypedAssignment,
2280 env: &mut Env<'a>,
2281 position: Position,
2282) -> Document<'a> {
2283 match &assignment.kind {
2284 AssignmentKind::Let | AssignmentKind::Generated => {
2285 let_(&assignment.value, &assignment.pattern, env)
2286 }
2287 AssignmentKind::Assert {
2288 message, location, ..
2289 } => let_assert(
2290 &assignment.value,
2291 &assignment.pattern,
2292 env,
2293 message.as_ref(),
2294 position,
2295 *location,
2296 ),
2297 }
2298}
2299
2300fn assert<'a>(assert: &'a TypedAssert, env: &mut Env<'a>) -> Document<'a> {
2301 let Assert {
2302 value,
2303 location,
2304 message,
2305 } = assert;
2306
2307 let message = match message {
2308 Some(message) => expr(message, env),
2309 None => string("Assertion failed."),
2310 };
2311
2312 let mut assignments = Vec::new();
2313
2314 let (subject, mut fields) = match value {
2315 TypedExpr::Call { fun, args, .. } => assert_call(fun, args, &mut assignments, env),
2316 TypedExpr::BinOp {
2317 name, left, right, ..
2318 } => {
2319 let operator = match name {
2320 BinOp::And => {
2321 return assert_and(left, right, message, *location, env);
2322 }
2323 BinOp::Or => {
2324 return assert_or(left, right, message, *location, env);
2325 }
2326 BinOp::Eq => "=:=",
2327 BinOp::NotEq => "/=",
2328 BinOp::LtInt | BinOp::LtFloat => "<",
2329 BinOp::LtEqInt | BinOp::LtEqFloat => "=<",
2330 BinOp::GtInt | BinOp::GtFloat => ">",
2331 BinOp::GtEqInt | BinOp::GtEqFloat => ">=",
2332 BinOp::AddInt
2333 | BinOp::AddFloat
2334 | BinOp::SubInt
2335 | BinOp::SubFloat
2336 | BinOp::MultInt
2337 | BinOp::MultFloat
2338 | BinOp::DivInt
2339 | BinOp::DivFloat
2340 | BinOp::RemainderInt
2341 | BinOp::Concatenate => {
2342 panic!("Non-boolean operators cannot appear here in well-typed code")
2343 }
2344 };
2345
2346 let left_document = assign_to_variable(left, &mut assignments, env);
2347 let right_document = assign_to_variable(right, &mut assignments, env);
2348 (
2349 binop_documents(left_document.clone(), operator, right_document.clone()),
2350 vec![
2351 ("kind", atom("binary_operator")),
2352 ("operator", atom(name.name())),
2353 (
2354 "left",
2355 asserted_expression(
2356 AssertExpression::from_expression(left),
2357 Some(left_document),
2358 left.location(),
2359 ),
2360 ),
2361 (
2362 "right",
2363 asserted_expression(
2364 AssertExpression::from_expression(right),
2365 Some(right_document),
2366 right.location(),
2367 ),
2368 ),
2369 ],
2370 )
2371 }
2372
2373 _ => (
2374 maybe_block_expr(value, env),
2375 vec![
2376 ("kind", atom("expression")),
2377 (
2378 "expression",
2379 asserted_expression(
2380 AssertExpression::from_expression(value),
2381 Some("false".to_doc()),
2382 value.location(),
2383 ),
2384 ),
2385 ],
2386 ),
2387 };
2388
2389 fields.push(("start", location.start.to_doc()));
2390 fields.push(("'end'", value.location().end.to_doc()));
2391 fields.push(("expression_start", value.location().start.to_doc()));
2392
2393 let clauses = docvec![
2394 line(),
2395 "true -> nil;",
2396 line(),
2397 "false -> ",
2398 erlang_error("assert", &message, *location, fields, env),
2399 ];
2400
2401 docvec![
2402 assignments,
2403 "case ",
2404 subject,
2405 " of",
2406 clauses.nest(INDENT),
2407 line(),
2408 "end"
2409 ]
2410}
2411
2412fn assert_call<'a>(
2413 function: &'a TypedExpr,
2414 arguments: &'a Vec<CallArg<TypedExpr>>,
2415 assignments: &mut Vec<Document<'a>>,
2416 env: &mut Env<'a>,
2417) -> (Document<'a>, Vec<(&'static str, Document<'a>)>) {
2418 let argument_variables = arguments
2419 .iter()
2420 .map(|argument| assign_to_variable(&argument.value, assignments, env))
2421 .collect_vec();
2422
2423 let arguments = join(
2424 argument_variables
2425 .iter()
2426 .zip(arguments)
2427 .map(|(variable, argument)| {
2428 asserted_expression(
2429 AssertExpression::from_expression(&argument.value),
2430 Some(variable.clone()),
2431 argument.location(),
2432 )
2433 }),
2434 break_(",", ", "),
2435 )
2436 .nest(INDENT)
2437 .surround("[", "]");
2438
2439 (
2440 docs_args_call(function, argument_variables, env),
2441 vec![("kind", atom("function_call")), ("arguments", arguments)],
2442 )
2443}
2444
2445/// In Gleam, the `&&` operator is short-circuiting, meaning that we can't
2446/// pre-evaluate both sides of it, and use them in the exception that is
2447/// thrown.
2448/// Instead, we need to implement this short-circuiting logic ourself.
2449///
2450/// If we short-circuit, we must leave the second expression unevaluated,
2451/// and signal that using the `unevaluated` variant, as detailed in the
2452/// exception format. For the first expression, we know it must be `false`,
2453/// otherwise we would have continued by evaluating the second expression.
2454///
2455/// Similarly, if we do evaluate the second expression and fail, we know
2456/// that the first expression must have evaluated to `true`, and the second
2457/// to `false`. This way, we avoid needing to evaluate either expression
2458/// twice.
2459///
2460/// The generated code then looks something like this:
2461/// ```erlang
2462/// case expr1 of
2463/// true -> case expr2 of
2464/// true -> true;
2465/// false -> <throw exception>
2466/// end;
2467/// false -> <throw exception>
2468/// end
2469/// ```
2470///
2471fn assert_and<'a>(
2472 left: &'a TypedExpr,
2473 right: &'a TypedExpr,
2474 message: Document<'a>,
2475 location: SrcSpan,
2476 env: &mut Env<'a>,
2477) -> Document<'a> {
2478 let left_kind = AssertExpression::from_expression(left);
2479 let right_kind = AssertExpression::from_expression(right);
2480
2481 let fields_if_short_circuiting = vec![
2482 ("kind", atom("binary_operator")),
2483 ("operator", atom("&&")),
2484 (
2485 "left",
2486 asserted_expression(left_kind, Some("false".to_doc()), left.location()),
2487 ),
2488 (
2489 "right",
2490 asserted_expression(AssertExpression::Unevaluated, None, right.location()),
2491 ),
2492 ("start", location.start.to_doc()),
2493 ("'end'", right.location().end.to_doc()),
2494 ("expression_start", left.location().start.to_doc()),
2495 ];
2496
2497 let fields = vec![
2498 ("kind", atom("binary_operator")),
2499 ("operator", atom("&&")),
2500 (
2501 "left",
2502 asserted_expression(left_kind, Some("true".to_doc()), left.location()),
2503 ),
2504 (
2505 "right",
2506 asserted_expression(right_kind, Some("false".to_doc()), right.location()),
2507 ),
2508 ("start", location.start.to_doc()),
2509 ("'end'", right.location().end.to_doc()),
2510 ("expression_start", left.location().start.to_doc()),
2511 ];
2512
2513 let right_clauses = docvec![
2514 line(),
2515 "true -> nil;",
2516 line(),
2517 "false -> ",
2518 erlang_error("assert", &message, location, fields, env),
2519 ];
2520
2521 let left_clauses = docvec![
2522 line(),
2523 "true -> ",
2524 docvec![
2525 "case ",
2526 maybe_block_expr(right, env),
2527 " of",
2528 right_clauses.nest(INDENT),
2529 line(),
2530 "end"
2531 ]
2532 .nest(INDENT),
2533 ";",
2534 line(),
2535 "false -> ",
2536 erlang_error(
2537 "assert",
2538 &message,
2539 location,
2540 fields_if_short_circuiting,
2541 env
2542 ),
2543 ];
2544
2545 docvec![
2546 "case ",
2547 maybe_block_expr(left, env),
2548 " of",
2549 left_clauses.nest(INDENT),
2550 line(),
2551 "end"
2552 ]
2553}
2554
2555/// Similar to `&&`, `||` is also short-circuiting in Gleam. However, if `||`
2556/// short-circuits, that's because the first expression evaluated to `true`,
2557/// meaning the whole assertion succeeds. This allows us to directly use Erlang's
2558/// `orelse` operator as the subject of the `case` expression.
2559///
2560/// The only difference is that due to the nature of `||`, if the assertion fails,
2561/// we know that both sides must have evaluated to `false`, so we don't
2562/// need to store the values of them in variables beforehand.
2563fn assert_or<'a>(
2564 left: &'a TypedExpr,
2565 right: &'a TypedExpr,
2566 message: Document<'a>,
2567 location: SrcSpan,
2568 env: &mut Env<'a>,
2569) -> Document<'a> {
2570 let fields = vec![
2571 ("kind", atom("binary_operator")),
2572 ("operator", atom("||")),
2573 (
2574 "left",
2575 asserted_expression(
2576 AssertExpression::from_expression(left),
2577 Some("false".to_doc()),
2578 left.location(),
2579 ),
2580 ),
2581 (
2582 "right",
2583 asserted_expression(
2584 AssertExpression::from_expression(right),
2585 Some("false".to_doc()),
2586 right.location(),
2587 ),
2588 ),
2589 ("start", location.start.to_doc()),
2590 ("'end'", right.location().end.to_doc()),
2591 ("expression_start", left.location().start.to_doc()),
2592 ];
2593
2594 let clauses = docvec![
2595 line(),
2596 "true -> nil;",
2597 line(),
2598 "false -> ",
2599 erlang_error("assert", &message, location, fields, env),
2600 ];
2601
2602 docvec![
2603 "case ",
2604 docvec![
2605 maybe_block_expr(left, env),
2606 " orelse ",
2607 maybe_block_expr(right, env)
2608 ]
2609 .nest(INDENT),
2610 " of",
2611 clauses.nest(INDENT),
2612 line(),
2613 "end"
2614 ]
2615}
2616
2617fn assign_to_variable<'a>(
2618 value: &'a TypedExpr,
2619 assignments: &mut Vec<Document<'a>>,
2620 env: &mut Env<'a>,
2621) -> Document<'a> {
2622 if value.is_var() {
2623 expr(value, env)
2624 } else {
2625 let value = maybe_block_expr(value, env);
2626 let variable = env.next_local_var_name(ASSERT_SUBJECT_VARIABLE);
2627 let definition = docvec![variable.clone(), " = ", value, ",", line()];
2628 assignments.push(definition);
2629 variable
2630 }
2631}
2632
2633#[derive(Debug, Clone, Copy)]
2634enum AssertExpression {
2635 Literal,
2636 Expression,
2637 Unevaluated,
2638}
2639
2640impl AssertExpression {
2641 fn from_expression(expression: &TypedExpr) -> Self {
2642 if expression.is_literal() {
2643 Self::Literal
2644 } else {
2645 Self::Expression
2646 }
2647 }
2648}
2649
2650fn asserted_expression(
2651 kind: AssertExpression,
2652 value: Option<Document<'_>>,
2653 location: SrcSpan,
2654) -> Document<'_> {
2655 let kind = match kind {
2656 AssertExpression::Literal => atom("literal"),
2657 AssertExpression::Expression => atom("expression"),
2658 AssertExpression::Unevaluated => atom("unevaluated"),
2659 };
2660
2661 let start = location.start.to_doc();
2662 let end = location.end.to_doc();
2663
2664 let value_field = if let Some(value) = value {
2665 docvec!["value => ", value, ",", line()]
2666 } else {
2667 nil()
2668 };
2669
2670 let fields_doc = docvec![
2671 "kind => ",
2672 kind,
2673 ",",
2674 line(),
2675 value_field,
2676 "start => ",
2677 start,
2678 ",",
2679 line(),
2680 // `end` is a keyword in Erlang, so we have to quote it
2681 "'end' => ",
2682 end,
2683 line(),
2684 ];
2685
2686 "#{".to_doc()
2687 .append(fields_doc.group().nest(INDENT))
2688 .append("}")
2689}
2690
2691fn negate_with<'a>(op: &'static str, value: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> {
2692 docvec![op, maybe_block_expr(value, env)]
2693}
2694
2695fn tuple_index<'a>(tuple: &'a TypedExpr, index: u64, env: &mut Env<'a>) -> Document<'a> {
2696 let index_doc = eco_format!("{}", (index + 1)).to_doc();
2697 let tuple_doc = maybe_block_expr(tuple, env);
2698 "erlang:element"
2699 .to_doc()
2700 .append(wrap_args([index_doc, tuple_doc]))
2701}
2702
2703fn module_select_fn<'a>(type_: Arc<Type>, module_name: &'a str, label: &'a str) -> Document<'a> {
2704 match crate::type_::collapse_links(type_).as_ref() {
2705 Type::Fn { args, .. } => function_reference(Some(module_name), label, args.len()),
2706
2707 _ => module_name_atom(module_name)
2708 .append(":")
2709 .append(atom(label))
2710 .append("()"),
2711 }
2712}
2713
2714fn fun<'a>(args: &'a [TypedArg], body: &'a [TypedStatement], env: &mut Env<'a>) -> Document<'a> {
2715 let current_scope_vars = env.current_scope_vars.clone();
2716 let doc = "fun"
2717 .to_doc()
2718 .append(fun_args(args, env).append(" ->"))
2719 .append(
2720 break_("", " ")
2721 .append(statement_sequence(body, env))
2722 .nest(INDENT),
2723 )
2724 .append(break_("", " "))
2725 .append("end")
2726 .group();
2727 env.current_scope_vars = current_scope_vars;
2728 doc
2729}
2730
2731fn incrementing_args_list(arity: usize) -> EcoString {
2732 let arguments = (0..arity).map(|c| format!("Field@{c}"));
2733 Itertools::intersperse(arguments, ", ".into())
2734 .collect::<String>()
2735 .into()
2736}
2737
2738fn variable_name(name: &str) -> EcoString {
2739 let mut chars = name.chars();
2740 let first_char = chars.next();
2741 let first_uppercased = first_char.into_iter().flat_map(char::to_uppercase);
2742
2743 first_uppercased.chain(chars).collect::<EcoString>()
2744}
2745
2746/// When rendering a type variable to an erlang type spec we need all type variables with the
2747/// same id to end up with the same name in the generated erlang.
2748/// This function converts a usize into base 26 A-Z for this purpose.
2749fn id_to_type_var(id: u64) -> Document<'static> {
2750 if id < 26 {
2751 let mut name = EcoString::from("");
2752 name.push(char::from_u32((id % 26 + 65) as u32).expect("id_to_type_var 0"));
2753 return name.to_doc();
2754 }
2755 let mut name = vec![];
2756 let mut last_char = id;
2757 while last_char >= 26 {
2758 name.push(char::from_u32((last_char % 26 + 65) as u32).expect("id_to_type_var 1"));
2759 last_char /= 26;
2760 }
2761 name.push(char::from_u32((last_char % 26 + 64) as u32).expect("id_to_type_var 2"));
2762 name.reverse();
2763 name.into_iter().collect::<EcoString>().to_doc()
2764}
2765
2766pub fn is_erlang_reserved_word(name: &str) -> bool {
2767 matches!(
2768 name,
2769 "!" | "receive"
2770 | "bnot"
2771 | "div"
2772 | "rem"
2773 | "band"
2774 | "bor"
2775 | "bxor"
2776 | "bsl"
2777 | "bsr"
2778 | "not"
2779 | "and"
2780 | "or"
2781 | "xor"
2782 | "orelse"
2783 | "andalso"
2784 | "when"
2785 | "end"
2786 | "fun"
2787 | "try"
2788 | "catch"
2789 | "after"
2790 | "begin"
2791 | "let"
2792 | "query"
2793 | "cond"
2794 | "if"
2795 | "of"
2796 | "case"
2797 | "maybe"
2798 | "else"
2799 )
2800}
2801
2802// Includes shell_default & user_default which are looked for by the erlang shell
2803pub fn is_erlang_standard_library_module(name: &str) -> bool {
2804 matches!(
2805 name,
2806 "array"
2807 | "base64"
2808 | "beam_lib"
2809 | "binary"
2810 | "c"
2811 | "calendar"
2812 | "dets"
2813 | "dict"
2814 | "digraph"
2815 | "digraph_utils"
2816 | "epp"
2817 | "erl_anno"
2818 | "erl_eval"
2819 | "erl_expand_records"
2820 | "erl_id_trans"
2821 | "erl_internal"
2822 | "erl_lint"
2823 | "erl_parse"
2824 | "erl_pp"
2825 | "erl_scan"
2826 | "erl_tar"
2827 | "ets"
2828 | "file_sorter"
2829 | "filelib"
2830 | "filename"
2831 | "gb_sets"
2832 | "gb_trees"
2833 | "gen_event"
2834 | "gen_fsm"
2835 | "gen_server"
2836 | "gen_statem"
2837 | "io"
2838 | "io_lib"
2839 | "lists"
2840 | "log_mf_h"
2841 | "maps"
2842 | "math"
2843 | "ms_transform"
2844 | "orddict"
2845 | "ordsets"
2846 | "pool"
2847 | "proc_lib"
2848 | "proplists"
2849 | "qlc"
2850 | "queue"
2851 | "rand"
2852 | "random"
2853 | "re"
2854 | "sets"
2855 | "shell"
2856 | "shell_default"
2857 | "shell_docs"
2858 | "slave"
2859 | "sofs"
2860 | "string"
2861 | "supervisor"
2862 | "supervisor_bridge"
2863 | "sys"
2864 | "timer"
2865 | "unicode"
2866 | "uri_string"
2867 | "user_default"
2868 | "win32reg"
2869 | "zip"
2870 )
2871}
2872
2873// Includes the functions that are autogenerated by erlang itself
2874pub fn escape_erlang_existing_name(name: &str) -> &str {
2875 match name {
2876 "module_info" => "moduleInfo",
2877 _ => name,
2878 }
2879}
2880
2881// A TypeVar can either be rendered as an actual type variable such as `A` or `B`,
2882// or it can be rendered as `any()` depending on how many usages it has. If it
2883// has only 1 usage it is an `any()` type. If it has more than 1 usage it is a
2884// type variable. This function gathers usages for this determination.
2885//
2886// Examples:
2887// fn(a) -> String // `a` is `any()`
2888// fn() -> Result(a, b) // `a` and `b` are `any()`
2889// fn(a) -> a // `a` is a type var
2890fn collect_type_var_usages<'a>(
2891 mut ids: HashMap<u64, u64>,
2892 types: impl IntoIterator<Item = &'a Arc<Type>>,
2893) -> HashMap<u64, u64> {
2894 for type_ in types {
2895 type_var_ids(type_, &mut ids);
2896 }
2897 ids
2898}
2899
2900fn result_type_var_ids(ids: &mut HashMap<u64, u64>, arg_ok: &Type, arg_err: &Type) {
2901 let mut ok_ids = HashMap::new();
2902 type_var_ids(arg_ok, &mut ok_ids);
2903
2904 let mut err_ids = HashMap::new();
2905 type_var_ids(arg_err, &mut err_ids);
2906
2907 let mut result_counts = ok_ids;
2908 for (id, count) in err_ids {
2909 let _ = result_counts
2910 .entry(id)
2911 .and_modify(|current_count| {
2912 if *current_count < count {
2913 *current_count = count;
2914 }
2915 })
2916 .or_insert(count);
2917 }
2918 for (id, count) in result_counts {
2919 let _ = ids
2920 .entry(id)
2921 .and_modify(|current_count| {
2922 *current_count += count;
2923 })
2924 .or_insert(count);
2925 }
2926}
2927
2928fn type_var_ids(type_: &Type, ids: &mut HashMap<u64, u64>) {
2929 match type_ {
2930 Type::Var { type_ } => match type_.borrow().deref() {
2931 TypeVar::Generic { id, .. } | TypeVar::Unbound { id, .. } => {
2932 let count = ids.entry(*id).or_insert(0);
2933 *count += 1;
2934 }
2935 TypeVar::Link { type_ } => type_var_ids(type_, ids),
2936 },
2937 Type::Named {
2938 args, module, name, ..
2939 } => match args[..] {
2940 [ref arg_ok, ref arg_err] if is_prelude_module(module) && name == "Result" => {
2941 result_type_var_ids(ids, arg_ok, arg_err)
2942 }
2943 _ => {
2944 for arg in args {
2945 type_var_ids(arg, ids)
2946 }
2947 }
2948 },
2949 Type::Fn { args, return_ } => {
2950 for arg in args {
2951 type_var_ids(arg, ids)
2952 }
2953 type_var_ids(return_, ids);
2954 }
2955 Type::Tuple { elements } => {
2956 for element in elements {
2957 type_var_ids(element, ids)
2958 }
2959 }
2960 }
2961}
2962
2963fn erl_safe_type_name(mut name: EcoString) -> EcoString {
2964 if matches!(
2965 name.as_str(),
2966 "any"
2967 | "arity"
2968 | "atom"
2969 | "binary"
2970 | "bitstring"
2971 | "boolean"
2972 | "byte"
2973 | "char"
2974 | "dynamic"
2975 | "float"
2976 | "function"
2977 | "identifier"
2978 | "integer"
2979 | "iodata"
2980 | "iolist"
2981 | "list"
2982 | "map"
2983 | "maybe_improper_list"
2984 | "mfa"
2985 | "module"
2986 | "neg_integer"
2987 | "nil"
2988 | "no_return"
2989 | "node"
2990 | "non_neg_integer"
2991 | "none"
2992 | "nonempty_improper_list"
2993 | "nonempty_list"
2994 | "nonempty_string"
2995 | "number"
2996 | "pid"
2997 | "port"
2998 | "pos_integer"
2999 | "reference"
3000 | "string"
3001 | "term"
3002 | "timeout"
3003 | "tuple"
3004 ) {
3005 name.push('_');
3006 name
3007 } else {
3008 escape_atom_string(name)
3009 }
3010}
3011
3012#[derive(Debug)]
3013struct TypePrinter<'a> {
3014 var_as_any: bool,
3015 current_module: &'a str,
3016 var_usages: Option<&'a HashMap<u64, u64>>,
3017}
3018
3019impl<'a> TypePrinter<'a> {
3020 fn new(current_module: &'a str) -> Self {
3021 Self {
3022 current_module,
3023 var_usages: None,
3024 var_as_any: false,
3025 }
3026 }
3027
3028 pub fn with_var_usages(mut self, var_usages: &'a HashMap<u64, u64>) -> Self {
3029 self.var_usages = Some(var_usages);
3030 self
3031 }
3032
3033 pub fn print(&self, type_: &Type) -> Document<'static> {
3034 match type_ {
3035 Type::Var { type_ } => self.print_var(&type_.borrow()),
3036
3037 Type::Named {
3038 name, module, args, ..
3039 } if is_prelude_module(module) => self.print_prelude_type(name, args),
3040
3041 Type::Named {
3042 name, module, args, ..
3043 } => self.print_type_app(module, name, args),
3044
3045 Type::Fn { args, return_ } => self.print_fn(args, return_),
3046
3047 Type::Tuple { elements } => tuple(elements.iter().map(|element| self.print(element))),
3048 }
3049 }
3050
3051 fn print_var(&self, type_: &TypeVar) -> Document<'static> {
3052 match type_ {
3053 TypeVar::Generic { .. } | TypeVar::Unbound { .. } if self.var_as_any => {
3054 "any()".to_doc()
3055 }
3056 TypeVar::Generic { id, .. } | TypeVar::Unbound { id, .. } => match &self.var_usages {
3057 Some(usages) => match usages.get(id) {
3058 Some(&0) => nil(),
3059 Some(&1) => "any()".to_doc(),
3060 _ => id_to_type_var(*id),
3061 },
3062 None => id_to_type_var(*id),
3063 },
3064 TypeVar::Link { type_ } => self.print(type_),
3065 }
3066 }
3067
3068 fn print_prelude_type(&self, name: &str, args: &[Arc<Type>]) -> Document<'static> {
3069 match name {
3070 "Nil" => "nil".to_doc(),
3071 "Int" | "UtfCodepoint" => "integer()".to_doc(),
3072 "String" => "binary()".to_doc(),
3073 "Bool" => "boolean()".to_doc(),
3074 "Float" => "float()".to_doc(),
3075 "BitArray" => "bitstring()".to_doc(),
3076 "List" => {
3077 let arg0 = self.print(args.first().expect("print_prelude_type list"));
3078 "list(".to_doc().append(arg0).append(")")
3079 }
3080 "Result" => match args {
3081 [arg_ok, arg_err] => {
3082 let ok = tuple(["ok".to_doc(), self.print(arg_ok)]);
3083 let error = tuple(["error".to_doc(), self.print(arg_err)]);
3084 docvec![ok, break_(" |", " | "), error].nest(INDENT).group()
3085 }
3086 _ => panic!("print_prelude_type result expects ok and err"),
3087 },
3088 // Getting here should mean we either forgot a built-in type or there is a
3089 // compiler error
3090 name => panic!("{name} is not a built-in type."),
3091 }
3092 }
3093
3094 fn print_type_app(&self, module: &str, name: &str, args: &[Arc<Type>]) -> Document<'static> {
3095 let args = join(args.iter().map(|a| self.print(a)), ", ".to_doc());
3096 let name = erl_safe_type_name(to_snake_case(name)).to_doc();
3097 if self.current_module == module {
3098 docvec![name, "(", args, ")"]
3099 } else {
3100 docvec![module_name_atom(module), ":", name, "(", args, ")"]
3101 }
3102 }
3103
3104 fn print_fn(&self, args: &[Arc<Type>], return_: &Type) -> Document<'static> {
3105 let args = join(args.iter().map(|a| self.print(a)), ", ".to_doc());
3106 let return_ = self.print(return_);
3107 "fun(("
3108 .to_doc()
3109 .append(args)
3110 .append(") -> ")
3111 .append(return_)
3112 .append(")")
3113 }
3114
3115 /// Print type vars as `any()`.
3116 fn var_as_any(mut self) -> Self {
3117 self.var_as_any = true;
3118 self
3119 }
3120}
3121
3122fn find_private_functions_referenced_in_importable_constants(
3123 module: &TypedModule,
3124) -> im::HashSet<EcoString> {
3125 let mut overridden_publicity = im::HashSet::new();
3126
3127 for definition in module.definitions.iter() {
3128 if let Definition::ModuleConstant(constant) = definition {
3129 if constant.publicity.is_importable() {
3130 find_referenced_private_functions(&constant.value, &mut overridden_publicity)
3131 }
3132 }
3133 }
3134 overridden_publicity
3135}
3136
3137fn find_referenced_private_functions(
3138 constant: &TypedConstant,
3139 already_found: &mut im::HashSet<EcoString>,
3140) {
3141 match constant {
3142 Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"),
3143
3144 Constant::Int { .. }
3145 | Constant::Float { .. }
3146 | Constant::String { .. }
3147 | Constant::BitArray { .. } => (),
3148
3149 TypedConstant::Var {
3150 name, constructor, ..
3151 } => {
3152 if let Some(ValueConstructor { type_, .. }) = constructor.as_deref() {
3153 if let Type::Fn { .. } = **type_ {
3154 let _ = already_found.insert(name.clone());
3155 }
3156 }
3157 }
3158
3159 TypedConstant::Record { args, .. } => args
3160 .iter()
3161 .for_each(|arg| find_referenced_private_functions(&arg.value, already_found)),
3162
3163 TypedConstant::StringConcatenation { left, right, .. } => {
3164 find_referenced_private_functions(left, already_found);
3165 find_referenced_private_functions(right, already_found);
3166 }
3167
3168 Constant::Tuple { elements, .. } | Constant::List { elements, .. } => elements
3169 .iter()
3170 .for_each(|element| find_referenced_private_functions(element, already_found)),
3171 }
3172}