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