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