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