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