Fork of daniellemaywood.uk/gleam — Wasm codegen work
108 kB
2926 lines
1use num_bigint::BigInt;
2use vec1::Vec1;
3
4use super::{decision::ASSIGNMENT_VAR, *};
5use crate::{
6 ast::*,
7 exhaustiveness::StringEncoding,
8 line_numbers::LineNumbers,
9 pretty::*,
10 type_::{
11 ModuleValueConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant,
12 },
13};
14use std::sync::Arc;
15
16#[derive(Debug, Clone)]
17pub enum Position {
18 /// We are compiling the last expression in a function, meaning that it should
19 /// use `return` to return the value it produces from the function.
20 Tail,
21 /// We are inside a function, but the value of this expression isn't being
22 /// used, so we don't need to do anything with the returned value.
23 Statement,
24 /// The value of this expression needs to be used inside another expression,
25 /// so we need to use the value that is returned by this expression.
26 Expression(Ordering),
27 /// We are compiling an expression inside a block, meaning we must assign
28 /// to the `_block` variable at the end of the scope, because blocks are not
29 /// expressions in JS.
30 /// Since JS doesn't have variable shadowing, we must store the name of the
31 /// variable being used, which will include the incrementing counter.
32 /// For example, `block$2`
33 Assign(EcoString),
34}
35
36impl Position {
37 /// Returns `true` if the position is [`Tail`].
38 ///
39 /// [`Tail`]: Position::Tail
40 #[must_use]
41 pub fn is_tail(&self) -> bool {
42 matches!(self, Self::Tail)
43 }
44
45 #[must_use]
46 pub fn ordering(&self) -> Ordering {
47 match self {
48 Self::Expression(ordering) => *ordering,
49 Self::Tail | Self::Assign(_) | Self::Statement => Ordering::Loose,
50 }
51 }
52}
53
54#[derive(Debug, Clone, Copy)]
55/// Determines whether we can lift blocks into statement level instead of using
56/// immediately invoked function expressions. Consider the following piece of code:
57///
58/// ```gleam
59/// some_function(function_with_side_effect(), {
60/// let a = 10
61/// other_function_with_side_effects(a)
62/// })
63/// ```
64/// Here, if we lift the block that is the second argument of the function, we
65/// would end up running `other_function_with_side_effects` before
66/// `function_with_side_effects`. This would be invalid, as code in Gleam should be
67/// evaluated left-to-right, top-to-bottom. In this case, the ordering would be
68/// `Strict`, indicating that we cannot lift the block.
69///
70/// However, in this example:
71///
72/// ```gleam
73/// let value = !{
74/// let value = False
75/// some_function_with_side_effect()
76/// value
77/// }
78/// ```
79/// The only expression is the block, meaning it can be safely lifted without
80/// changing the evaluation order of the program. So the ordering is `Loose`.
81///
82pub enum Ordering {
83 Strict,
84 Loose,
85}
86
87/// Tracking where the current function is a module function or an anonymous function.
88#[derive(Debug)]
89enum CurrentFunction {
90 /// The current function is a module function
91 ///
92 /// ```gleam
93 /// pub fn main() -> Nil {
94 /// // we are here
95 /// }
96 /// ```
97 Module,
98
99 /// The current function is a module function, but one of its arguments shadows
100 /// the reference to itself so it cannot recurse.
101 ///
102 /// ```gleam
103 /// pub fn main(main: fn() -> Nil) -> Nil {
104 /// // we are here
105 /// }
106 /// ```
107 ModuleWithShadowingArgument,
108
109 /// The current function is an anonymous function
110 ///
111 /// ```gleam
112 /// pub fn main() -> Nil {
113 /// fn() {
114 /// // we are here
115 /// }
116 /// }
117 /// ```
118 Anonymous,
119}
120
121impl CurrentFunction {
122 #[inline]
123 fn can_recurse(&self) -> bool {
124 match self {
125 CurrentFunction::Module => true,
126 CurrentFunction::ModuleWithShadowingArgument => false,
127 CurrentFunction::Anonymous => false,
128 }
129 }
130}
131
132#[derive(Debug)]
133pub(crate) struct Generator<'module, 'ast> {
134 module_name: EcoString,
135 src_path: EcoString,
136 line_numbers: &'module LineNumbers,
137 function_name: EcoString,
138 function_arguments: Vec<Option<&'module EcoString>>,
139 current_function: CurrentFunction,
140 pub current_scope_vars: im::HashMap<EcoString, usize>,
141 pub function_position: Position,
142 pub scope_position: Position,
143 // We register whether these features are used within an expression so that
144 // the module generator can output a suitable function if it is needed.
145 pub tracker: &'module mut UsageTracker,
146 // We track whether tail call recursion is used so that we can render a loop
147 // at the top level of the function to use in place of pushing new stack
148 // frames.
149 pub tail_recursion_used: bool,
150 /// Statements to be compiled when lifting blocks into statement scope.
151 /// For example, when compiling the following code:
152 /// ```gleam
153 /// let a = {
154 /// let b = 1
155 /// b + 1
156 /// }
157 /// ```
158 /// There will be 2 items in `statement_level`: The first will be `let _block;`
159 /// The second will be the generated code for the block being assigned to `a`.
160 /// This lets use return `_block` as the value that the block evaluated to,
161 /// while still including the necessary code in the output at the right place.
162 ///
163 /// Once the `let` statement has compiled its value, it will add anything accumulated
164 /// in `statement_level` to the generated code, so it will result in:
165 ///
166 /// ```javascript
167 /// let _block;
168 /// {...}
169 /// let a = _block;
170 /// ```
171 ///
172 statement_level: Vec<Document<'ast>>,
173
174 /// This will be true if we've generated a `let assert` statement that we know
175 /// is guaranteed to throw.
176 /// This means we can stop code generation for all the following statements
177 /// in the same block!
178 pub let_assert_always_panics: bool,
179
180 pub source_map_builder: Option<Rc<RefCell<DebugIgnore<sourcemap::SourceMapBuilder>>>>,
181}
182
183impl<'module, 'a> Generator<'module, 'a> {
184 #[allow(clippy::too_many_arguments)] // TODO: FIXME
185 pub fn new(
186 module_name: EcoString,
187 src_path: EcoString,
188 line_numbers: &'module LineNumbers,
189 function_name: EcoString,
190 function_arguments: Vec<Option<&'module EcoString>>,
191 tracker: &'module mut UsageTracker,
192 mut current_scope_vars: im::HashMap<EcoString, usize>,
193 source_map_builder: Option<Rc<RefCell<DebugIgnore<sourcemap::SourceMapBuilder>>>>,
194 ) -> Self {
195 let mut current_function = CurrentFunction::Module;
196 for &name in function_arguments.iter().flatten() {
197 // Initialise the function arguments
198 let _ = current_scope_vars.insert(name.clone(), 0);
199
200 // If any of the function arguments shadow the current function then
201 // recursion is no longer possible.
202 if function_name.as_ref() == name {
203 current_function = CurrentFunction::ModuleWithShadowingArgument;
204 }
205 }
206 Self {
207 tracker,
208 module_name,
209 src_path,
210 line_numbers,
211 function_name,
212 function_arguments,
213 tail_recursion_used: false,
214 current_scope_vars,
215 current_function,
216 function_position: Position::Tail,
217 scope_position: Position::Tail,
218 statement_level: Vec::new(),
219 let_assert_always_panics: false,
220 source_map_builder,
221 }
222 }
223
224 pub fn local_var(&mut self, name: &EcoString) -> EcoString {
225 match self.current_scope_vars.get(name) {
226 None => {
227 let _ = self.current_scope_vars.insert(name.clone(), 0);
228 maybe_escape_identifier(name)
229 }
230 Some(0) => maybe_escape_identifier(name),
231 Some(n) if name == "$" => eco_format!("${n}"),
232 Some(n) => eco_format!("{name}${n}"),
233 }
234 }
235
236 pub fn next_local_var(&mut self, name: &EcoString) -> EcoString {
237 let next = self.current_scope_vars.get(name).map_or(0, |i| i + 1);
238 let _ = self.current_scope_vars.insert(name.clone(), next);
239 self.local_var(name)
240 }
241
242 pub fn function_body(
243 &mut self,
244 body: &'a [TypedStatement],
245 arguments: &'a [TypedArg],
246 ) -> Document<'a> {
247 let body = self.statements(body);
248 if self.tail_recursion_used {
249 self.tail_call_loop(body, arguments)
250 } else {
251 body
252 }
253 }
254
255 fn tail_call_loop(&mut self, body: Document<'a>, arguments: &'a [TypedArg]) -> Document<'a> {
256 let loop_assignments = concat(arguments.iter().flat_map(|arg| {
257 arg.get_variable_name().map(|name| {
258 let var = maybe_escape_identifier(name);
259 docvec![
260 self.source_map_tracker(arg.location.start),
261 "let ",
262 var,
263 " = loop$",
264 name,
265 ";",
266 line()
267 ]
268 })
269 }));
270 docvec![
271 "while (true) {",
272 docvec![line(), loop_assignments, body].nest(INDENT),
273 line(),
274 "}"
275 ]
276 }
277
278 fn statement(&mut self, statement: &'a TypedStatement) -> Document<'a> {
279 let expression_doc = match statement {
280 Statement::Expression(expression) => self.expression(expression),
281 Statement::Assignment(assignment) => self.assignment(assignment),
282 Statement::Use(use_) => self.expression(&use_.call),
283 Statement::Assert(assert) => self.assert(assert),
284 };
285 self.add_statement_level(expression_doc)
286 }
287
288 fn add_statement_level(&mut self, expression: Document<'a>) -> Document<'a> {
289 if self.statement_level.is_empty() {
290 expression
291 } else {
292 let mut statements = std::mem::take(&mut self.statement_level);
293 statements.push(expression);
294 join(statements, line())
295 }
296 }
297
298 pub fn expression(&mut self, expression: &'a TypedExpr) -> Document<'a> {
299 let document = match expression {
300 TypedExpr::String { value, .. } => string(value),
301
302 TypedExpr::Int { value, .. } => int(value),
303 TypedExpr::Float { float_value, .. } => float_from_value(float_value.value()),
304
305 TypedExpr::List { elements, tail, .. } => {
306 self.not_in_tail_position(Some(Ordering::Strict), |this| match tail {
307 Some(tail) => {
308 this.tracker.prepend_used = true;
309 let tail = this.wrap_expression(tail);
310 prepend(
311 elements.iter().map(|element| this.wrap_expression(element)),
312 tail,
313 )
314 }
315 None => {
316 this.tracker.list_used = true;
317 list(elements.iter().map(|element| this.wrap_expression(element)))
318 }
319 })
320 }
321
322 TypedExpr::Tuple { elements, .. } => self.tuple(elements),
323 TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index),
324
325 TypedExpr::Case {
326 subjects,
327 clauses,
328 compiled_case,
329 ..
330 } => decision::case(compiled_case, clauses, subjects, self),
331
332 TypedExpr::Call { fun, arguments, .. } => self.call(fun, arguments),
333 TypedExpr::Fn {
334 arguments,
335 body,
336 kind,
337 ..
338 } => self.fn_(arguments, body, kind),
339
340 TypedExpr::RecordAccess { record, label, .. } => self.record_access(record, label),
341
342 TypedExpr::PositionalAccess { record, index, .. } => {
343 self.positional_access(record, *index)
344 }
345
346 TypedExpr::RecordUpdate {
347 updated_record_assigned_name,
348 updated_record,
349 constructor,
350 arguments,
351 ..
352 } => self.record_update(
353 updated_record_assigned_name,
354 updated_record,
355 constructor,
356 arguments,
357 ),
358
359 TypedExpr::Var {
360 name, constructor, ..
361 } => self.variable(name, constructor),
362
363 TypedExpr::Pipeline {
364 first_value,
365 assignments,
366 finally,
367 ..
368 } => self.pipeline(first_value, assignments.as_slice(), finally),
369
370 TypedExpr::Block { statements, .. } => self.block(statements),
371
372 TypedExpr::BinOp {
373 operator,
374 left,
375 right,
376 ..
377 } => self.bin_op(operator, left, right),
378
379 TypedExpr::Todo {
380 message, location, ..
381 } => self.todo(message.as_ref().map(|m| &**m), location),
382
383 TypedExpr::Panic {
384 location, message, ..
385 } => self.panic(location, message.as_ref().map(|m| &**m)),
386
387 TypedExpr::BitArray { segments, .. } => self.bit_array(segments),
388
389 TypedExpr::ModuleSelect {
390 module_alias,
391 label,
392 constructor,
393 ..
394 } => self.module_select(module_alias, label, constructor),
395
396 TypedExpr::NegateBool { value, .. } => self.negate_with("!", value),
397
398 TypedExpr::NegateInt { value, .. } => self.negate_with("- ", value),
399
400 TypedExpr::Echo {
401 expression,
402 message,
403 location,
404 ..
405 } => {
406 let expression = expression
407 .as_ref()
408 .expect("echo with no expression outside of pipe");
409 let expresion_doc =
410 self.not_in_tail_position(None, |this| this.wrap_expression(expression));
411 self.echo(expresion_doc, message.as_deref(), location)
412 }
413
414 TypedExpr::Invalid { .. } => {
415 panic!("invalid expressions should not reach code generation")
416 }
417 };
418 if expression.handles_own_return() {
419 docvec![
420 self.source_map_tracker(expression.location().start),
421 document
422 ]
423 } else {
424 docvec![
425 self.source_map_tracker(expression.location().start),
426 self.wrap_return(document)
427 ]
428 }
429 }
430
431 fn negate_with(&mut self, with: &'static str, value: &'a TypedExpr) -> Document<'a> {
432 self.not_in_tail_position(None, |this| docvec![with, this.wrap_expression(value)])
433 }
434
435 fn bit_array(&mut self, segments: &'a [TypedExprBitArraySegment]) -> Document<'a> {
436 self.tracker.bit_array_literal_used = true;
437
438 // Collect all the values used in segments.
439 let segments_array = array(segments.iter().map(|segment| {
440 let value = self.not_in_tail_position(Some(Ordering::Strict), |this| {
441 this.wrap_expression(&segment.value)
442 });
443
444 let details = self.bit_array_segment_details(segment);
445
446 match details.type_ {
447 BitArraySegmentType::BitArray => {
448 if segment.size().is_some() {
449 self.tracker.bit_array_slice_used = true;
450 docvec!["bitArraySlice(", value, ", 0, ", details.size, ")"]
451 } else {
452 value
453 }
454 }
455 BitArraySegmentType::Int => match (details.size_value, segment.value.as_ref()) {
456 (Some(size_value), TypedExpr::Int { int_value, .. })
457 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into()
458 && (&size_value % BigInt::from(8) == BigInt::ZERO) =>
459 {
460 let bytes = bit_array_segment_int_value_to_bytes(
461 int_value.clone(),
462 size_value,
463 segment.endianness(),
464 );
465
466 u8_slice(&bytes)
467 }
468
469 (Some(size_value), _) if size_value == 8.into() => value,
470
471 (Some(size_value), _) if size_value <= 0.into() => nil(),
472
473 _ => {
474 self.tracker.sized_integer_segment_used = true;
475 let size = details.size;
476 let is_big = bool(segment.endianness().is_big());
477 docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]
478 }
479 },
480 BitArraySegmentType::Float => {
481 self.tracker.float_bit_array_segment_used = true;
482 let size = details.size;
483 let is_big = bool(details.endianness.is_big());
484 docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]
485 }
486 BitArraySegmentType::String(StringEncoding::Utf8) => {
487 self.tracker.string_bit_array_segment_used = true;
488 docvec!["stringBits(", value, ")"]
489 }
490 BitArraySegmentType::String(StringEncoding::Utf16) => {
491 self.tracker.string_utf16_bit_array_segment_used = true;
492 let is_big = bool(details.endianness.is_big());
493 docvec!["stringToUtf16(", value, ", ", is_big, ")"]
494 }
495 BitArraySegmentType::String(StringEncoding::Utf32) => {
496 self.tracker.string_utf32_bit_array_segment_used = true;
497 let is_big = bool(details.endianness.is_big());
498 docvec!["stringToUtf32(", value, ", ", is_big, ")"]
499 }
500 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => {
501 self.tracker.codepoint_bit_array_segment_used = true;
502 docvec!["codepointBits(", value, ")"]
503 }
504 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => {
505 self.tracker.codepoint_utf16_bit_array_segment_used = true;
506 let is_big = bool(details.endianness.is_big());
507 docvec!["codepointToUtf16(", value, ", ", is_big, ")"]
508 }
509 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => {
510 self.tracker.codepoint_utf32_bit_array_segment_used = true;
511 let is_big = bool(details.endianness.is_big());
512 docvec!["codepointToUtf32(", value, ", ", is_big, ")"]
513 }
514 }
515 }));
516
517 docvec!["toBitArray(", segments_array, ")"]
518 }
519
520 fn bit_array_segment_details(
521 &mut self,
522 segment: &'a TypedExprBitArraySegment,
523 ) -> BitArraySegmentDetails<'a> {
524 let size = segment.size();
525 let unit = segment.unit();
526 let (size_value, size) = match size {
527 Some(TypedExpr::Int { int_value, .. }) => {
528 let size_value = int_value * unit;
529 let size = eco_format!("{}", size_value).to_doc();
530 (Some(size_value), size)
531 }
532 Some(size) => {
533 let mut size = self.not_in_tail_position(Some(Ordering::Strict), |this| {
534 this.wrap_expression(size)
535 });
536
537 if unit != 1 {
538 size = size.group().append(" * ".to_doc().append(unit.to_doc()));
539 }
540
541 (None, size)
542 }
543
544 None => {
545 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 };
546 (Some(BigInt::from(size_value)), docvec![size_value])
547 }
548 };
549
550 let type_ = BitArraySegmentType::from_segment(segment);
551
552 BitArraySegmentDetails {
553 type_,
554 size,
555 size_value,
556 endianness: segment.endianness(),
557 }
558 }
559
560 pub fn wrap_return(&mut self, document: Document<'a>) -> Document<'a> {
561 match &self.scope_position {
562 Position::Tail => docvec!["return ", document, ";"],
563 Position::Expression(_) | Position::Statement => document,
564 Position::Assign(name) => docvec![name.clone(), " = ", document, ";"],
565 }
566 }
567
568 pub fn not_in_tail_position<CompileFn, Output>(
569 &mut self,
570 // If ordering is None, it is inherited from the parent scope.
571 // It will be None in cases like `!x`, where `x` can be lifted
572 // only if the ordering is already loose.
573 ordering: Option<Ordering>,
574 compile: CompileFn,
575 ) -> Output
576 where
577 CompileFn: Fn(&mut Self) -> Output,
578 {
579 let new_ordering = ordering.unwrap_or(self.scope_position.ordering());
580
581 let function_position = std::mem::replace(
582 &mut self.function_position,
583 Position::Expression(new_ordering),
584 );
585 let scope_position =
586 std::mem::replace(&mut self.scope_position, Position::Expression(new_ordering));
587
588 let result = compile(self);
589
590 self.function_position = function_position;
591 self.scope_position = scope_position;
592 result
593 }
594
595 /// Use the `_block` variable if the expression is JS statement.
596 pub fn wrap_expression(&mut self, expression: &'a TypedExpr) -> Document<'a> {
597 match (expression, &self.scope_position) {
598 (_, Position::Tail | Position::Assign(_)) => self.expression(expression),
599 (
600 TypedExpr::Panic { .. }
601 | TypedExpr::Todo { .. }
602 | TypedExpr::Case { .. }
603 | TypedExpr::Pipeline { .. }
604 | TypedExpr::RecordUpdate {
605 // Record updates that assign a variable generate multiple statements
606 updated_record_assigned_name: Some(_),
607 ..
608 },
609 Position::Expression(Ordering::Loose),
610 ) => self.wrap_block(|this| this.expression(expression)),
611 (
612 TypedExpr::Panic { .. }
613 | TypedExpr::Todo { .. }
614 | TypedExpr::Case { .. }
615 | TypedExpr::Pipeline { .. }
616 | TypedExpr::RecordUpdate {
617 // Record updates that assign a variable generate multiple statements
618 updated_record_assigned_name: Some(_),
619 ..
620 },
621 Position::Expression(Ordering::Strict),
622 ) => self.immediately_invoked_function_expression(expression, |this, expr| {
623 this.expression(expr)
624 }),
625 _ => self.expression(expression),
626 }
627 }
628
629 /// Wrap an expression using the `_block` variable if required due to being
630 /// a JS statement, or in parens if required due to being an operator or
631 /// a function literal.
632 pub fn child_expression(&mut self, expression: &'a TypedExpr) -> Document<'a> {
633 match expression {
634 TypedExpr::BinOp { operator, .. } if operator.is_operator_to_wrap() => {}
635 TypedExpr::Fn { .. } => {}
636
637 TypedExpr::Int { .. }
638 | TypedExpr::Float { .. }
639 | TypedExpr::String { .. }
640 | TypedExpr::Block { .. }
641 | TypedExpr::Pipeline { .. }
642 | TypedExpr::Var { .. }
643 | TypedExpr::List { .. }
644 | TypedExpr::Call { .. }
645 | TypedExpr::BinOp { .. }
646 | TypedExpr::Case { .. }
647 | TypedExpr::RecordAccess { .. }
648 | TypedExpr::PositionalAccess { .. }
649 | TypedExpr::ModuleSelect { .. }
650 | TypedExpr::Tuple { .. }
651 | TypedExpr::TupleIndex { .. }
652 | TypedExpr::Todo { .. }
653 | TypedExpr::Panic { .. }
654 | TypedExpr::Echo { .. }
655 | TypedExpr::BitArray { .. }
656 | TypedExpr::RecordUpdate { .. }
657 | TypedExpr::NegateBool { .. }
658 | TypedExpr::NegateInt { .. }
659 | TypedExpr::Invalid { .. } => return self.wrap_expression(expression),
660 }
661
662 let document = self.expression(expression);
663 match &self.scope_position {
664 // Here the document is a return statement: `return <expr>;`
665 // or an assignment: `_block = <expr>;`
666 Position::Tail | Position::Assign(_) | Position::Statement => document,
667 Position::Expression(_) => docvec!["(", document, ")"],
668 }
669 }
670
671 /// Wrap an expression in an immediately invoked function expression
672 fn immediately_invoked_function_expression<T, ToDoc>(
673 &mut self,
674 statements: &'a T,
675 to_doc: ToDoc,
676 ) -> Document<'a>
677 where
678 ToDoc: FnOnce(&mut Self, &'a T) -> Document<'a>,
679 {
680 // Save initial state
681 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail);
682 let statement_level = std::mem::take(&mut self.statement_level);
683
684 // Set state for in this iife
685 let current_scope_vars = self.current_scope_vars.clone();
686
687 // Generate the expression
688 let result = to_doc(self, statements);
689 let doc = self.add_statement_level(result);
690 let doc = immediately_invoked_function_expression_document(doc);
691
692 // Reset
693 self.current_scope_vars = current_scope_vars;
694 self.scope_position = scope_position;
695 self.statement_level = statement_level;
696
697 self.wrap_return(doc)
698 }
699
700 fn wrap_block<CompileFn>(&mut self, compile: CompileFn) -> Document<'a>
701 where
702 CompileFn: Fn(&mut Self) -> Document<'a>,
703 {
704 let block_variable = self.next_local_var(&BLOCK_VARIABLE.into());
705
706 // Save initial state
707 let scope_position = std::mem::replace(
708 &mut self.scope_position,
709 Position::Assign(block_variable.clone()),
710 );
711 let function_position = std::mem::replace(
712 &mut self.function_position,
713 Position::Expression(Ordering::Strict),
714 );
715
716 // Generate the expression
717 let statement_doc = compile(self);
718
719 // Reset
720 self.scope_position = scope_position;
721 self.function_position = function_position;
722
723 self.statement_level
724 .push(docvec!["let ", block_variable.clone(), ";"]);
725 self.statement_level.push(statement_doc);
726
727 self.wrap_return(block_variable.to_doc())
728 }
729
730 fn variable(&mut self, name: &'a EcoString, constructor: &'a ValueConstructor) -> Document<'a> {
731 match &constructor.variant {
732 ValueConstructorVariant::Record { arity, .. } => {
733 let type_ = constructor.type_.clone();
734 let tracker = &mut self.tracker;
735 record_constructor(type_, None, name, *arity, tracker)
736 }
737 ValueConstructorVariant::ModuleFn { .. }
738 | ValueConstructorVariant::ModuleConstant { .. }
739 | ValueConstructorVariant::LocalVariable { .. } => self.local_var(name).to_doc(),
740 }
741 }
742
743 fn pipeline(
744 &mut self,
745 first_value: &'a TypedPipelineAssignment,
746 assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)],
747 finally: &'a TypedExpr,
748 ) -> Document<'a> {
749 let count = assignments.len();
750 let mut documents = Vec::with_capacity((count + 2) * 2);
751
752 let all_assignments = std::iter::once(first_value)
753 .chain(assignments.iter().map(|(assignment, _kind)| assignment));
754
755 let mut latest_local_var: Option<EcoString> = None;
756 for assignment in all_assignments {
757 // An echo in a pipeline won't result in an assignment, instead it
758 // just prints the previous variable assigned in the pipeline.
759 if let TypedExpr::Echo {
760 expression: None,
761 message,
762 location,
763 ..
764 } = assignment.value.as_ref()
765 {
766 documents.push(self.not_in_tail_position(Some(Ordering::Strict), |this| {
767 let var = latest_local_var
768 .as_ref()
769 .expect("echo with no previous step in a pipe");
770 this.echo(var.to_doc(), message.as_deref(), location)
771 }));
772 documents.push(";".to_doc());
773 } else {
774 // Otherwise we assign the intermediate pipe value to a variable.
775 let assignment_document =
776 self.not_in_tail_position(Some(Ordering::Strict), |this| {
777 this.simple_variable_assignment(
778 &assignment.name,
779 &assignment.value,
780 assignment.location,
781 )
782 });
783 documents.push(self.add_statement_level(assignment_document));
784 latest_local_var = Some(self.local_var(&assignment.name));
785 }
786
787 documents.push(line());
788 }
789
790 if let TypedExpr::Echo {
791 expression: None,
792 message,
793 location,
794 ..
795 } = finally
796 {
797 let var = latest_local_var.expect("echo with no previous step in a pipe");
798 documents.push(self.echo(var.to_doc(), message.as_deref(), location));
799 match &self.scope_position {
800 Position::Statement => documents.push(";".to_doc()),
801 Position::Expression(_) | Position::Tail | Position::Assign(_) => {}
802 }
803 } else {
804 let finally_doc = self.expression(finally);
805 documents.push(self.add_statement_level(finally_doc));
806
807 // Add a semicolon if needed, to ensure the pipeline is properly
808 // delimited
809 match &self.scope_position {
810 Position::Statement if expression_requires_semicolon(finally) => {
811 documents.push(";".to_doc())
812 }
813 Position::Statement
814 | Position::Expression(_)
815 | Position::Tail
816 | Position::Assign(_) => {}
817 }
818 }
819
820 documents.to_doc().force_break()
821 }
822
823 pub(crate) fn expression_flattening_blocks(
824 &mut self,
825 expression: &'a TypedExpr,
826 ) -> Document<'a> {
827 if let TypedExpr::Block { statements, .. } = expression {
828 self.statements(statements)
829 } else {
830 self.expression(expression)
831 }
832 }
833
834 fn block(&mut self, statements: &'a Vec1<TypedStatement>) -> Document<'a> {
835 if statements.len() == 1 {
836 match statements.first() {
837 Statement::Expression(expression) => return self.child_expression(expression),
838
839 Statement::Assignment(assignment) => match &assignment.kind {
840 AssignmentKind::Let | AssignmentKind::Generated => {
841 return self.child_expression(&assignment.value);
842 }
843 // We can't just return the right-hand side of a `let assert`
844 // assignment; we still need to check that the pattern matches.
845 AssignmentKind::Assert { .. } => {}
846 },
847
848 Statement::Use(use_) => return self.child_expression(&use_.call),
849
850 // Similar to `let assert`, we can't immediately return the value
851 // that is asserted; we have to actually perform the assertion.
852 Statement::Assert(_) => {}
853 }
854 }
855 match &self.scope_position {
856 Position::Tail | Position::Assign(_) | Position::Statement => {
857 self.block_document(statements)
858 }
859 Position::Expression(Ordering::Strict) => self
860 .immediately_invoked_function_expression(statements, |this, statements| {
861 this.statements(statements)
862 }),
863 Position::Expression(Ordering::Loose) => self.wrap_block(|this| {
864 // Save previous scope
865 let current_scope_vars = this.current_scope_vars.clone();
866
867 let document = this.block_document(statements);
868
869 // Restore previous state
870 this.current_scope_vars = current_scope_vars;
871
872 document
873 }),
874 }
875 }
876
877 fn block_document(&mut self, statements: &'a Vec1<TypedStatement>) -> Document<'a> {
878 let statements = self.statements(statements);
879 docvec!["{", docvec![line(), statements].nest(INDENT), line(), "}"]
880 }
881
882 fn statements(&mut self, statements: &'a [TypedStatement]) -> Document<'a> {
883 // If there are any statements that need to be printed at statement level, that's
884 // for an outer scope so we don't want to print them inside this one.
885 let statement_level = std::mem::take(&mut self.statement_level);
886 let count = statements.len();
887 let mut documents = Vec::with_capacity(count * 3);
888 for (i, statement) in statements.iter().enumerate() {
889 if i + 1 < count {
890 let function_position =
891 std::mem::replace(&mut self.function_position, Position::Statement);
892 let scope_position =
893 std::mem::replace(&mut self.scope_position, Position::Statement);
894
895 documents.push(self.statement(statement));
896
897 self.function_position = function_position;
898 self.scope_position = scope_position;
899
900 if requires_semicolon(statement) {
901 documents.push(";".to_doc());
902 }
903 documents.push(line());
904 } else {
905 documents.push(self.statement(statement));
906 }
907
908 // If we've generated code for a statement that always throws, we
909 // can skip code generation for all the following ones.
910 if self.let_assert_always_panics {
911 self.let_assert_always_panics = false;
912 break;
913 }
914 }
915 self.statement_level = statement_level;
916 if count == 1 {
917 documents.to_doc()
918 } else {
919 documents.to_doc().force_break()
920 }
921 }
922
923 fn simple_variable_assignment(
924 &mut self,
925 name: &'a EcoString,
926 value: &'a TypedExpr,
927 location: SrcSpan,
928 ) -> Document<'a> {
929 // Subject must be rendered before the variable for variable numbering
930 let subject =
931 self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(value));
932 let js_name = self.next_local_var(name);
933 let assignment = docvec![
934 self.source_map_tracker(location.start),
935 "let ",
936 js_name.clone(),
937 " = ",
938 subject,
939 ";"
940 ];
941 let assignment = match &self.scope_position {
942 Position::Expression(_) | Position::Statement => assignment,
943 Position::Tail => docvec![assignment, line(), "return ", js_name, ";"],
944 Position::Assign(block_variable) => docvec![
945 assignment,
946 line(),
947 block_variable.clone(),
948 " = ",
949 js_name,
950 ";"
951 ],
952 };
953
954 assignment.force_break()
955 }
956
957 fn assignment(&mut self, assignment: &'a TypedAssignment) -> Document<'a> {
958 let TypedAssignment {
959 pattern,
960 kind,
961 value,
962 compiled_case,
963 location,
964 annotation: _,
965 } = assignment;
966
967 // In case the pattern is just a variable, we special case it to
968 // generate just a simple assignment instead of using the decision tree
969 // for the code generation step.
970 if let TypedPattern::Variable { name, .. } = pattern {
971 return self.simple_variable_assignment(name, value, *location);
972 }
973
974 docvec![
975 self.source_map_tracker(location.start),
976 decision::let_(compiled_case, value, kind, self, pattern)
977 ]
978 }
979
980 fn assert(&mut self, assert: &'a TypedAssert) -> Document<'a> {
981 let TypedAssert {
982 location,
983 value,
984 message,
985 } = assert;
986
987 let message = match message {
988 Some(message) => {
989 self.not_in_tail_position(Some(Ordering::Strict), |this| this.expression(message))
990 }
991 None => string("Assertion failed."),
992 };
993
994 let check = self.not_in_tail_position(Some(Ordering::Loose), |this| {
995 this.assert_check(value, &message, *location)
996 });
997
998 match &self.scope_position {
999 Position::Expression(_) | Position::Statement => check,
1000 Position::Tail | Position::Assign(_) => {
1001 docvec![check, line(), self.wrap_return("undefined".to_doc())]
1002 }
1003 }
1004 }
1005
1006 fn assert_check(
1007 &mut self,
1008 subject: &'a TypedExpr,
1009 message: &Document<'a>,
1010 location: SrcSpan,
1011 ) -> Document<'a> {
1012 let (subject_document, mut fields) = match subject {
1013 TypedExpr::Call { fun, arguments, .. } => {
1014 let argument_variables = arguments
1015 .iter()
1016 .map(|element| {
1017 self.not_in_tail_position(Some(Ordering::Strict), |this| {
1018 this.assign_to_variable(&element.value)
1019 })
1020 })
1021 .collect_vec();
1022 (
1023 self.call_with_doc_arguments(fun, argument_variables.clone()),
1024 vec![
1025 ("kind", string("function_call")),
1026 (
1027 "arguments",
1028 array(argument_variables.into_iter().zip(arguments).map(
1029 |(variable, argument)| {
1030 self.asserted_expression(
1031 AssertExpression::from_expression(&argument.value),
1032 Some(variable),
1033 argument.location(),
1034 )
1035 },
1036 )),
1037 ),
1038 ],
1039 )
1040 }
1041
1042 TypedExpr::BinOp {
1043 operator,
1044 left,
1045 right,
1046 ..
1047 } => {
1048 match operator {
1049 BinOp::And => return self.assert_and(left, right, message, location),
1050 BinOp::Or => return self.assert_or(left, right, message, location),
1051 BinOp::Eq
1052 | BinOp::NotEq
1053 | BinOp::LtInt
1054 | BinOp::LtEqInt
1055 | BinOp::LtFloat
1056 | BinOp::LtEqFloat
1057 | BinOp::GtEqInt
1058 | BinOp::GtInt
1059 | BinOp::GtEqFloat
1060 | BinOp::GtFloat
1061 | BinOp::AddInt
1062 | BinOp::AddFloat
1063 | BinOp::SubInt
1064 | BinOp::SubFloat
1065 | BinOp::MultInt
1066 | BinOp::MultFloat
1067 | BinOp::DivInt
1068 | BinOp::DivFloat
1069 | BinOp::RemainderInt
1070 | BinOp::Concatenate => {}
1071 }
1072
1073 let left_document = self.not_in_tail_position(Some(Ordering::Loose), |this| {
1074 this.assign_to_variable(left)
1075 });
1076 let right_document = self.not_in_tail_position(Some(Ordering::Loose), |this| {
1077 this.assign_to_variable(right)
1078 });
1079
1080 (
1081 self.bin_op_with_doc_operands(
1082 *operator,
1083 left_document.clone(),
1084 right_document.clone(),
1085 &left.type_(),
1086 )
1087 .surround("(", ")"),
1088 vec![
1089 ("kind", string("binary_operator")),
1090 ("operator", string(operator.name())),
1091 (
1092 "left",
1093 self.asserted_expression(
1094 AssertExpression::from_expression(left),
1095 Some(left_document),
1096 left.location(),
1097 ),
1098 ),
1099 (
1100 "right",
1101 self.asserted_expression(
1102 AssertExpression::from_expression(right),
1103 Some(right_document),
1104 right.location(),
1105 ),
1106 ),
1107 ],
1108 )
1109 }
1110
1111 TypedExpr::Int { .. }
1112 | TypedExpr::Float { .. }
1113 | TypedExpr::String { .. }
1114 | TypedExpr::Block { .. }
1115 | TypedExpr::Pipeline { .. }
1116 | TypedExpr::Var { .. }
1117 | TypedExpr::Fn { .. }
1118 | TypedExpr::List { .. }
1119 | TypedExpr::Case { .. }
1120 | TypedExpr::RecordAccess { .. }
1121 | TypedExpr::PositionalAccess { .. }
1122 | TypedExpr::ModuleSelect { .. }
1123 | TypedExpr::Tuple { .. }
1124 | TypedExpr::TupleIndex { .. }
1125 | TypedExpr::Todo { .. }
1126 | TypedExpr::Panic { .. }
1127 | TypedExpr::Echo { .. }
1128 | TypedExpr::BitArray { .. }
1129 | TypedExpr::RecordUpdate { .. }
1130 | TypedExpr::NegateBool { .. }
1131 | TypedExpr::NegateInt { .. }
1132 | TypedExpr::Invalid { .. } => (
1133 self.wrap_expression(subject),
1134 vec![
1135 ("kind", string("expression")),
1136 (
1137 "expression",
1138 self.asserted_expression(
1139 AssertExpression::from_expression(subject),
1140 Some("false".to_doc()),
1141 subject.location(),
1142 ),
1143 ),
1144 ],
1145 ),
1146 };
1147
1148 fields.push(("start", location.start.to_doc()));
1149 fields.push(("end", subject.location().end.to_doc()));
1150 fields.push(("expression_start", subject.location().start.to_doc()));
1151
1152 docvec![
1153 self.source_map_tracker(location.start),
1154 "if (",
1155 docvec!["!", subject_document].nest(INDENT),
1156 break_("", ""),
1157 ") {",
1158 docvec![
1159 line(),
1160 self.throw_error("assert", message, location, fields),
1161 ]
1162 .nest(INDENT),
1163 line(),
1164 "}",
1165 ]
1166 .group()
1167 }
1168
1169 fn negate_bool_expression(&mut self, value: &'a TypedExpr) -> Document<'a> {
1170 match value {
1171 TypedExpr::BinOp {
1172 operator,
1173 left,
1174 right,
1175 ..
1176 } => match operator {
1177 BinOp::And => self.print_bin_op(left, right, "||"),
1178 BinOp::Or => self.print_bin_op(left, right, "&&"),
1179 BinOp::Eq => self.equal(left, right, false),
1180 BinOp::NotEq => self.equal(left, right, true),
1181 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(left, right, ">="),
1182 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(left, right, ">"),
1183 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(left, right, "<="),
1184 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(left, right, "<"),
1185 BinOp::AddInt
1186 | BinOp::AddFloat
1187 | BinOp::SubInt
1188 | BinOp::SubFloat
1189 | BinOp::MultInt
1190 | BinOp::MultFloat
1191 | BinOp::DivInt
1192 | BinOp::DivFloat
1193 | BinOp::RemainderInt
1194 | BinOp::Concatenate => unreachable!("type checking should make this impossible"),
1195 },
1196 TypedExpr::NegateBool { value, .. } => self.wrap_expression(value),
1197 TypedExpr::Int { .. }
1198 | TypedExpr::Float { .. }
1199 | TypedExpr::String { .. }
1200 | TypedExpr::Block { .. }
1201 | TypedExpr::Pipeline { .. }
1202 | TypedExpr::Var { .. }
1203 | TypedExpr::Fn { .. }
1204 | TypedExpr::List { .. }
1205 | TypedExpr::Call { .. }
1206 | TypedExpr::Case { .. }
1207 | TypedExpr::RecordAccess { .. }
1208 | TypedExpr::PositionalAccess { .. }
1209 | TypedExpr::ModuleSelect { .. }
1210 | TypedExpr::Tuple { .. }
1211 | TypedExpr::TupleIndex { .. }
1212 | TypedExpr::Todo { .. }
1213 | TypedExpr::Panic { .. }
1214 | TypedExpr::Echo { .. }
1215 | TypedExpr::BitArray { .. }
1216 | TypedExpr::RecordUpdate { .. }
1217 | TypedExpr::NegateInt { .. }
1218 | TypedExpr::Invalid { .. } => docvec!["!", self.wrap_expression(value)],
1219 }
1220 }
1221
1222 /// In Gleam, the `&&` operator is short-circuiting, meaning that we can't
1223 /// pre-evaluate both sides of it, and use them in the exception that is
1224 /// thrown.
1225 /// Instead, we need to implement this short-circuiting logic ourself.
1226 ///
1227 /// If we short-circuit, we must leave the second expression unevaluated,
1228 /// and signal that using the `unevaluated` variant, as detailed in the
1229 /// exception format. For the first expression, we know it must be `false`,
1230 /// otherwise we would have continued by evaluating the second expression.
1231 ///
1232 /// Similarly, if we do evaluate the second expression and fail, we know
1233 /// that the first expression must have evaluated to `true`, and the second
1234 /// to `false`. This way, we avoid needing to evaluate either expression
1235 /// twice.
1236 ///
1237 /// The generated code then looks something like this:
1238 /// ```javascript
1239 /// if (expr1) {
1240 /// if (!expr2) {
1241 /// <throw exception>
1242 /// }
1243 /// } else {
1244 /// <throw exception>
1245 /// }
1246 /// ```
1247 ///
1248 fn assert_and(
1249 &mut self,
1250 left: &'a TypedExpr,
1251 right: &'a TypedExpr,
1252 message: &Document<'a>,
1253 location: SrcSpan,
1254 ) -> Document<'a> {
1255 let left_kind = AssertExpression::from_expression(left);
1256 let right_kind = AssertExpression::from_expression(right);
1257
1258 let fields_if_short_circuiting = vec![
1259 ("kind", string("binary_operator")),
1260 ("operator", string("&&")),
1261 (
1262 "left",
1263 self.asserted_expression(left_kind, Some("false".to_doc()), left.location()),
1264 ),
1265 (
1266 "right",
1267 self.asserted_expression(AssertExpression::Unevaluated, None, right.location()),
1268 ),
1269 ("start", location.start.to_doc()),
1270 ("end", right.location().end.to_doc()),
1271 ("expression_start", left.location().start.to_doc()),
1272 ];
1273
1274 let fields = vec![
1275 ("kind", string("binary_operator")),
1276 ("operator", string("&&")),
1277 (
1278 "left",
1279 self.asserted_expression(left_kind, Some("true".to_doc()), left.location()),
1280 ),
1281 (
1282 "right",
1283 self.asserted_expression(right_kind, Some("false".to_doc()), right.location()),
1284 ),
1285 ("start", location.start.to_doc()),
1286 ("end", right.location().end.to_doc()),
1287 ("expression_start", left.location().start.to_doc()),
1288 ];
1289
1290 let left_value =
1291 self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(left));
1292
1293 let right_value = self.not_in_tail_position(Some(Ordering::Strict), |this| {
1294 this.negate_bool_expression(right)
1295 });
1296
1297 let right_check = docvec![
1298 line(),
1299 "if (",
1300 right_value.nest(INDENT),
1301 ") {",
1302 docvec![
1303 line(),
1304 self.throw_error("assert", message, location, fields)
1305 ]
1306 .nest(INDENT),
1307 line(),
1308 "}",
1309 ];
1310
1311 docvec![
1312 self.source_map_tracker(location.start),
1313 "if (",
1314 left_value.nest(INDENT),
1315 ") {",
1316 right_check.nest(INDENT),
1317 line(),
1318 "} else {",
1319 docvec![
1320 line(),
1321 self.throw_error("assert", message, location, fields_if_short_circuiting)
1322 ]
1323 .nest(INDENT),
1324 line(),
1325 "}"
1326 ]
1327 }
1328
1329 /// Similar to `&&`, `||` is also short-circuiting in Gleam. However, if `||`
1330 /// short-circuits, that's because the first expression evaluated to `true`,
1331 /// meaning the whole assertion succeeds. This allows us to directly use the
1332 /// `||` operator in JavaScript.
1333 ///
1334 /// The only difference is that due to the nature of `||`, if the assertion fails,
1335 /// we know that both sides must have evaluated to `false`, so we don't
1336 /// need to store the values of them in variables beforehand.
1337 fn assert_or(
1338 &mut self,
1339 left: &'a TypedExpr,
1340 right: &'a TypedExpr,
1341 message: &Document<'a>,
1342 location: SrcSpan,
1343 ) -> Document<'a> {
1344 let fields = vec![
1345 ("kind", string("binary_operator")),
1346 ("operator", string("||")),
1347 (
1348 "left",
1349 self.asserted_expression(
1350 AssertExpression::from_expression(left),
1351 Some("false".to_doc()),
1352 left.location(),
1353 ),
1354 ),
1355 (
1356 "right",
1357 self.asserted_expression(
1358 AssertExpression::from_expression(right),
1359 Some("false".to_doc()),
1360 right.location(),
1361 ),
1362 ),
1363 ("start", location.start.to_doc()),
1364 ("end", right.location().end.to_doc()),
1365 ("expression_start", left.location().start.to_doc()),
1366 ];
1367
1368 let left_value =
1369 self.not_in_tail_position(Some(Ordering::Loose), |this| this.child_expression(left));
1370
1371 let right_value =
1372 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1373
1374 docvec![
1375 line(),
1376 self.source_map_tracker(location.start),
1377 "if (",
1378 docvec!["!(", left_value, " || ", right_value, ")"].nest(INDENT),
1379 ") {",
1380 docvec![
1381 line(),
1382 self.throw_error("assert", message, location, fields)
1383 ]
1384 .nest(INDENT),
1385 line(),
1386 "}",
1387 ]
1388 }
1389
1390 fn assign_to_variable(&mut self, value: &'a TypedExpr) -> Document<'a> {
1391 if let TypedExpr::Var { .. } = value {
1392 self.expression(value)
1393 } else {
1394 let value = self.wrap_expression(value);
1395 let variable = self.next_local_var(&ASSIGNMENT_VAR.into());
1396 let assignment = docvec!["let ", variable.clone(), " = ", value, ";"];
1397 self.statement_level.push(assignment);
1398 variable.to_doc()
1399 }
1400 }
1401
1402 fn asserted_expression(
1403 &mut self,
1404 kind: AssertExpression,
1405 value: Option<Document<'a>>,
1406 location: SrcSpan,
1407 ) -> Document<'a> {
1408 let kind = match kind {
1409 AssertExpression::Literal => string("literal"),
1410 AssertExpression::Expression => string("expression"),
1411 AssertExpression::Unevaluated => string("unevaluated"),
1412 };
1413
1414 let start = location.start.to_doc();
1415 let end = location.end.to_doc();
1416 let items = if let Some(value) = value {
1417 vec![
1418 ("kind", kind),
1419 ("value", value),
1420 ("start", start),
1421 ("end", end),
1422 ]
1423 } else {
1424 vec![("kind", kind), ("start", start), ("end", end)]
1425 };
1426
1427 wrap_object(
1428 items
1429 .into_iter()
1430 .map(|(key, value)| (key.to_doc(), Some(value))),
1431 )
1432 }
1433
1434 fn tuple(&mut self, elements: &'a [TypedExpr]) -> Document<'a> {
1435 self.not_in_tail_position(Some(Ordering::Strict), |this| {
1436 array(elements.iter().map(|element| this.wrap_expression(element)))
1437 })
1438 }
1439
1440 fn call(&mut self, fun: &'a TypedExpr, arguments: &'a [TypedCallArg]) -> Document<'a> {
1441 let arguments = arguments
1442 .iter()
1443 .map(|element| {
1444 self.not_in_tail_position(Some(Ordering::Strict), |this| {
1445 this.wrap_expression(&element.value)
1446 })
1447 })
1448 .collect_vec();
1449
1450 self.call_with_doc_arguments(fun, arguments)
1451 }
1452
1453 fn call_with_doc_arguments(
1454 &mut self,
1455 fun: &'a TypedExpr,
1456 arguments: Vec<Document<'a>>,
1457 ) -> Document<'a> {
1458 match fun {
1459 // Qualified record construction
1460 TypedExpr::ModuleSelect {
1461 constructor: ModuleValueConstructor::Record { name, .. },
1462 module_alias,
1463 ..
1464 } => self.wrap_return(construct_record(Some(module_alias), name, arguments)),
1465
1466 // Record construction
1467 TypedExpr::Var {
1468 constructor:
1469 ValueConstructor {
1470 variant: ValueConstructorVariant::Record { .. },
1471 type_,
1472 ..
1473 },
1474 name,
1475 ..
1476 } => {
1477 if type_.is_result_constructor() {
1478 if name == "Ok" {
1479 self.tracker.ok_used = true;
1480 } else if name == "Error" {
1481 self.tracker.error_used = true;
1482 }
1483 }
1484 self.wrap_return(construct_record(None, name, arguments))
1485 }
1486
1487 // Tail call optimisation. If we are calling the current function
1488 // and we are in tail position we can avoid creating a new stack
1489 // frame, enabling recursion with constant memory usage.
1490 TypedExpr::Var { name, .. }
1491 if self.function_name == *name
1492 && self.current_function.can_recurse()
1493 && self.function_position.is_tail()
1494 && self.current_scope_vars.get(name) == Some(&0) =>
1495 {
1496 let mut docs = Vec::with_capacity(arguments.len() * 4);
1497 // Record that tail recursion is happening so that we know to
1498 // render the loop at the top level of the function.
1499 self.tail_recursion_used = true;
1500
1501 for (i, (element, argument)) in arguments
1502 .into_iter()
1503 .zip(&self.function_arguments)
1504 .enumerate()
1505 {
1506 if i != 0 {
1507 docs.push(line());
1508 }
1509 // Create an assignment for each variable created by the function arguments
1510 if let Some(name) = argument {
1511 docs.push("loop$".to_doc());
1512 docs.push(name.to_doc());
1513 docs.push(" = ".to_doc());
1514 }
1515 // Render the value given to the function. Even if it is not
1516 // assigned we still render it because the expression may
1517 // have some side effects.
1518 docs.push(element);
1519 docs.push(";".to_doc());
1520 }
1521 docs.to_doc()
1522 }
1523
1524 TypedExpr::Int { .. }
1525 | TypedExpr::Float { .. }
1526 | TypedExpr::String { .. }
1527 | TypedExpr::Block { .. }
1528 | TypedExpr::Pipeline { .. }
1529 | TypedExpr::Var { .. }
1530 | TypedExpr::Fn { .. }
1531 | TypedExpr::List { .. }
1532 | TypedExpr::Call { .. }
1533 | TypedExpr::BinOp { .. }
1534 | TypedExpr::Case { .. }
1535 | TypedExpr::RecordAccess { .. }
1536 | TypedExpr::PositionalAccess { .. }
1537 | TypedExpr::ModuleSelect { .. }
1538 | TypedExpr::Tuple { .. }
1539 | TypedExpr::TupleIndex { .. }
1540 | TypedExpr::Todo { .. }
1541 | TypedExpr::Panic { .. }
1542 | TypedExpr::Echo { .. }
1543 | TypedExpr::BitArray { .. }
1544 | TypedExpr::RecordUpdate { .. }
1545 | TypedExpr::NegateBool { .. }
1546 | TypedExpr::NegateInt { .. }
1547 | TypedExpr::Invalid { .. } => {
1548 let fun = self.not_in_tail_position(None, |this| -> Document<'_> {
1549 let is_fn_literal = matches!(fun, TypedExpr::Fn { .. });
1550 let fun = this.wrap_expression(fun);
1551 if is_fn_literal {
1552 docvec!["(", fun, ")"]
1553 } else {
1554 fun
1555 }
1556 });
1557 let arguments = call_arguments(arguments);
1558 self.wrap_return(docvec![fun, arguments])
1559 }
1560 }
1561 }
1562
1563 fn fn_(
1564 &mut self,
1565 arguments: &'a [TypedArg],
1566 body: &'a [TypedStatement],
1567 kind: &FunctionLiteralKind,
1568 ) -> Document<'a> {
1569 // New function, this is now the tail position
1570 let function_position = std::mem::replace(&mut self.function_position, Position::Tail);
1571 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail);
1572
1573 // And there's a new scope
1574 let scope = self.current_scope_vars.clone();
1575 for name in arguments.iter().flat_map(Arg::get_variable_name) {
1576 let _ = self.current_scope_vars.insert(name.clone(), 0);
1577 }
1578
1579 // This is a new function so track that so that we don't
1580 // mistakenly trigger tail call optimisation
1581 let mut current_function = CurrentFunction::Anonymous;
1582 std::mem::swap(&mut self.current_function, &mut current_function);
1583
1584 // Generate the function body
1585 let result = self.statements(body);
1586
1587 // Reset function name, scope, and tail position tracking
1588 self.function_position = function_position;
1589 self.scope_position = scope_position;
1590 self.current_scope_vars = scope;
1591 std::mem::swap(&mut self.current_function, &mut current_function);
1592
1593 let mut docs = docvec![];
1594
1595 // If the function is a use then we need to add a source map tracker
1596 // before the result to denote that the function is created by the use
1597 if let FunctionLiteralKind::Use { location } = kind {
1598 docs = docs.append(self.source_map_tracker(location.start));
1599 }
1600 docs = docs.append(fun_arguments(arguments, false));
1601 docs = docs.append(" => {".to_doc());
1602 docs = docs.append(break_("", " "));
1603 docs = docs.append(result);
1604
1605 docvec![docs.nest(INDENT).append(break_("", " ")).group(), "}",]
1606 }
1607
1608 fn record_access(&mut self, record: &'a TypedExpr, label: &'a str) -> Document<'a> {
1609 self.not_in_tail_position(None, |this| {
1610 let record = this.wrap_expression(record);
1611 docvec![record, ".", maybe_escape_property(label)]
1612 })
1613 }
1614
1615 fn positional_access(&mut self, record: &'a TypedExpr, index: u64) -> Document<'a> {
1616 self.not_in_tail_position(None, |this| {
1617 let record = this.wrap_expression(record);
1618 docvec![record, "[", index, "]"]
1619 })
1620 }
1621
1622 fn record_update(
1623 &mut self,
1624 updated_record_assigned_name: &'a Option<EcoString>,
1625 updated_record: &'a TypedExpr,
1626 constructor: &'a TypedExpr,
1627 arguments: &'a [TypedCallArg],
1628 ) -> Document<'a> {
1629 match updated_record_assigned_name.as_ref() {
1630 Some(name) => {
1631 docvec![
1632 self.not_in_tail_position(None, |this| this.simple_variable_assignment(
1633 name,
1634 updated_record,
1635 updated_record.location(),
1636 )),
1637 line(),
1638 self.call(constructor, arguments),
1639 ]
1640 }
1641 None => self.call(constructor, arguments),
1642 }
1643 }
1644
1645 fn tuple_index(&mut self, tuple: &'a TypedExpr, index: u64) -> Document<'a> {
1646 self.not_in_tail_position(None, |this| {
1647 let tuple = this.wrap_expression(tuple);
1648 docvec![tuple, eco_format!("[{index}]")]
1649 })
1650 }
1651
1652 fn bin_op(
1653 &mut self,
1654 name: &'a BinOp,
1655 left: &'a TypedExpr,
1656 right: &'a TypedExpr,
1657 ) -> Document<'a> {
1658 match name {
1659 BinOp::And => self.print_bin_op(left, right, "&&"),
1660 BinOp::Or => self.print_bin_op(left, right, "||"),
1661 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(left, right, "<"),
1662 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(left, right, "<="),
1663 BinOp::Eq => self.equal(left, right, true),
1664 BinOp::NotEq => self.equal(left, right, false),
1665 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(left, right, ">"),
1666 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(left, right, ">="),
1667 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => {
1668 self.print_bin_op(left, right, "+")
1669 }
1670 BinOp::SubInt | BinOp::SubFloat => self.print_bin_op(left, right, "-"),
1671 BinOp::MultInt | BinOp::MultFloat => self.print_bin_op(left, right, "*"),
1672 BinOp::RemainderInt => self.remainder_int(left, right),
1673 BinOp::DivInt => self.div_int(left, right),
1674 BinOp::DivFloat => self.div_float(left, right),
1675 }
1676 }
1677
1678 fn div_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> {
1679 let left_doc =
1680 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left));
1681 let right_doc =
1682 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1683
1684 // If we have a constant value divided by zero then it's safe to replace
1685 // it directly with 0.
1686 if left.is_literal() && right.is_zero_compile_time_number() {
1687 "0".to_doc()
1688 } else if right.is_non_zero_compile_time_number() {
1689 let division = if let TypedExpr::BinOp { .. } = left {
1690 docvec![left_doc.surround("(", ")"), " / ", right_doc]
1691 } else {
1692 docvec![left_doc, " / ", right_doc]
1693 };
1694 docvec!["globalThis.Math.trunc", wrap_arguments([division])]
1695 } else {
1696 self.tracker.int_division_used = true;
1697 docvec!["divideInt", wrap_arguments([left_doc, right_doc])]
1698 }
1699 }
1700
1701 fn remainder_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> {
1702 let left_doc =
1703 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left));
1704 let right_doc =
1705 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1706
1707 // If we have a constant value divided by zero then it's safe to replace
1708 // it directly with 0.
1709 if left.is_literal() && right.is_zero_compile_time_number() {
1710 "0".to_doc()
1711 } else if right.is_non_zero_compile_time_number() {
1712 if let TypedExpr::BinOp { .. } = left {
1713 docvec![left_doc.surround("(", ")"), " % ", right_doc]
1714 } else {
1715 docvec![left_doc, " % ", right_doc]
1716 }
1717 } else {
1718 self.tracker.int_remainder_used = true;
1719 docvec!["remainderInt", wrap_arguments([left_doc, right_doc])]
1720 }
1721 }
1722
1723 fn div_float(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> {
1724 let left_doc =
1725 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left));
1726 let right_doc =
1727 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1728
1729 // If we have a constant value divided by zero then it's safe to replace
1730 // it directly with 0.
1731 if left.is_literal() && right.is_zero_compile_time_number() {
1732 "0.0".to_doc()
1733 } else if right.is_non_zero_compile_time_number() {
1734 if let TypedExpr::BinOp { .. } = left {
1735 docvec![left_doc.surround("(", ")"), " / ", right_doc]
1736 } else {
1737 docvec![left_doc, " / ", right_doc]
1738 }
1739 } else {
1740 self.tracker.float_division_used = true;
1741 docvec!["divideFloat", wrap_arguments([left_doc, right_doc])]
1742 }
1743 }
1744
1745 fn equal(
1746 &mut self,
1747 left: &'a TypedExpr,
1748 right: &'a TypedExpr,
1749 should_be_equal: bool,
1750 ) -> Document<'a> {
1751 // If it is a simple scalar type then we can use JS' reference identity
1752 if is_js_scalar(left.type_()) {
1753 let left_doc = self
1754 .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left));
1755 let right_doc = self
1756 .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1757 let operator = if should_be_equal { " === " } else { " !== " };
1758 return docvec![left_doc, operator, right_doc];
1759 }
1760
1761 // For comparison with singleton custom types, ie, one with no fields.
1762 // If you have some code like this
1763 // ```gleam
1764 // pub type Wibble {
1765 // Wibble
1766 // Wobble
1767 // }
1768
1769 // pub fn is_wibble(w: Wibble) -> Bool {
1770 // w == Wibble
1771 // }
1772 // ```
1773 // Instead of `isEqual(w, new Wibble())`, generate `w instanceof Wibble`
1774 // because the first approach needs to construct a new Wibble, and then call the isEqual function,
1775 // which supports any shape of data, and so does a lot of extra logic which isn't necessary.
1776
1777 if let Some(doc) = self.singleton_variant_equality(left, right, should_be_equal) {
1778 return doc;
1779 }
1780
1781 if let Some(doc) = self.singleton_variant_equality(right, left, should_be_equal) {
1782 return doc;
1783 }
1784
1785 // Other types must be compared using structural equality
1786 let left =
1787 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left));
1788 let right =
1789 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right));
1790
1791 self.prelude_equal_call(should_be_equal, left, right)
1792 }
1793
1794 fn singleton_variant_equality(
1795 &mut self,
1796 left: &'a TypedExpr,
1797 right: &'a TypedExpr,
1798 should_be_equal: bool,
1799 ) -> Option<Document<'a>> {
1800 match right {
1801 TypedExpr::Var {
1802 constructor:
1803 ValueConstructor {
1804 variant: ValueConstructorVariant::Record { arity: 0, name, .. },
1805 ..
1806 },
1807 ..
1808 } => {
1809 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
1810 this.wrap_expression(left)
1811 });
1812 Some(self.singleton_equal(left_doc, None, name, should_be_equal))
1813 }
1814 TypedExpr::ModuleSelect {
1815 module_alias,
1816 constructor: ModuleValueConstructor::Record { arity: 0, name, .. },
1817 ..
1818 } => {
1819 let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| {
1820 this.wrap_expression(left)
1821 });
1822 Some(self.singleton_equal(left_doc, Some(module_alias), name, should_be_equal))
1823 }
1824 TypedExpr::Int { .. }
1825 | TypedExpr::Float { .. }
1826 | TypedExpr::String { .. }
1827 | TypedExpr::Block { .. }
1828 | TypedExpr::Pipeline { .. }
1829 | TypedExpr::Var { .. }
1830 | TypedExpr::Fn { .. }
1831 | TypedExpr::List { .. }
1832 | TypedExpr::Call { .. }
1833 | TypedExpr::BinOp { .. }
1834 | TypedExpr::Case { .. }
1835 | TypedExpr::RecordAccess { .. }
1836 | TypedExpr::PositionalAccess { .. }
1837 | TypedExpr::ModuleSelect { .. }
1838 | TypedExpr::Tuple { .. }
1839 | TypedExpr::TupleIndex { .. }
1840 | TypedExpr::Todo { .. }
1841 | TypedExpr::Panic { .. }
1842 | TypedExpr::Echo { .. }
1843 | TypedExpr::BitArray { .. }
1844 | TypedExpr::RecordUpdate { .. }
1845 | TypedExpr::NegateBool { .. }
1846 | TypedExpr::NegateInt { .. }
1847 | TypedExpr::Invalid { .. } => None,
1848 }
1849 }
1850
1851 fn singleton_equal(
1852 &self,
1853 value: Document<'a>,
1854 module: Option<&'a str>,
1855 name: &'a str,
1856 should_be_equal: bool,
1857 ) -> Document<'a> {
1858 let record = if let Some(module) = module {
1859 docvec!["$", module, ".", name]
1860 } else {
1861 name.to_doc()
1862 };
1863
1864 if should_be_equal {
1865 docvec![value, " instanceof ", record]
1866 } else {
1867 docvec!["!(", value, " instanceof ", record, ")"]
1868 }
1869 }
1870
1871 fn equal_with_doc_operands(
1872 &mut self,
1873 left: Document<'a>,
1874 right: Document<'a>,
1875 type_: Arc<Type>,
1876 should_be_equal: bool,
1877 ) -> Document<'a> {
1878 // If it is a simple scalar type then we can use JS' reference identity
1879 if is_js_scalar(type_) {
1880 let operator = if should_be_equal { " === " } else { " !== " };
1881 return docvec![left, operator, right];
1882 }
1883
1884 // Other types must be compared using structural equality
1885 self.prelude_equal_call(should_be_equal, left, right)
1886 }
1887
1888 pub(super) fn prelude_equal_call(
1889 &mut self,
1890 should_be_equal: bool,
1891 left: Document<'a>,
1892 right: Document<'a>,
1893 ) -> Document<'a> {
1894 // Record that we need to import the prelude's isEqual function into the module
1895 self.tracker.object_equality_used = true;
1896 // Construct the call
1897 let arguments = wrap_arguments([left, right]);
1898 let operator = if should_be_equal {
1899 "isEqual"
1900 } else {
1901 "!isEqual"
1902 };
1903 docvec![operator, arguments]
1904 }
1905
1906 fn print_bin_op(
1907 &mut self,
1908 left: &'a TypedExpr,
1909 right: &'a TypedExpr,
1910 op: &'a str,
1911 ) -> Document<'a> {
1912 let left =
1913 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left));
1914 let right =
1915 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right));
1916 docvec![left, " ", op, " ", right]
1917 }
1918
1919 pub(super) fn bin_op_with_doc_operands(
1920 &mut self,
1921 name: BinOp,
1922 left: Document<'a>,
1923 right: Document<'a>,
1924 type_: &Arc<Type>,
1925 ) -> Document<'a> {
1926 match name {
1927 BinOp::And => docvec![left, " && ", right],
1928 BinOp::Or => docvec![left, " || ", right],
1929 BinOp::LtInt | BinOp::LtFloat => docvec![left, " < ", right],
1930 BinOp::LtEqInt | BinOp::LtEqFloat => docvec![left, " <= ", right],
1931 BinOp::Eq => self.equal_with_doc_operands(left, right, type_.clone(), true),
1932 BinOp::NotEq => self.equal_with_doc_operands(left, right, type_.clone(), false),
1933 BinOp::GtInt | BinOp::GtFloat => docvec![left, " > ", right],
1934 BinOp::GtEqInt | BinOp::GtEqFloat => docvec![left, " >= ", right],
1935 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => {
1936 docvec![left, " + ", right]
1937 }
1938 BinOp::SubInt | BinOp::SubFloat => docvec![left, " - ", right],
1939 BinOp::MultInt | BinOp::MultFloat => docvec![left, " * ", right],
1940 BinOp::RemainderInt => {
1941 self.tracker.int_remainder_used = true;
1942 docvec!["remainderInt", wrap_arguments([left, right])]
1943 }
1944 BinOp::DivInt => {
1945 self.tracker.int_division_used = true;
1946 docvec!["divideInt", wrap_arguments([left, right])]
1947 }
1948 BinOp::DivFloat => {
1949 self.tracker.float_division_used = true;
1950 docvec!["divideFloat", wrap_arguments([left, right])]
1951 }
1952 }
1953 }
1954
1955 fn todo(&mut self, message: Option<&'a TypedExpr>, location: &'a SrcSpan) -> Document<'a> {
1956 let message = match message {
1957 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)),
1958 None => string("`todo` expression evaluated. This code has not yet been implemented."),
1959 };
1960 self.throw_error("todo", &message, *location, vec![])
1961 }
1962
1963 fn panic(&mut self, location: &'a SrcSpan, message: Option<&'a TypedExpr>) -> Document<'a> {
1964 let message = match message {
1965 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)),
1966 None => string("`panic` expression evaluated."),
1967 };
1968 self.throw_error("panic", &message, *location, vec![])
1969 }
1970
1971 pub(crate) fn throw_error<Fields>(
1972 &mut self,
1973 error_name: &'a str,
1974 message: &Document<'a>,
1975 location: SrcSpan,
1976 fields: Fields,
1977 ) -> Document<'a>
1978 where
1979 Fields: IntoIterator<Item = (&'a str, Document<'a>)>,
1980 {
1981 self.tracker.make_error_used = true;
1982 let module = self.module_name.clone().to_doc().surround('"', '"');
1983 let function = self.function_name.clone().to_doc().surround("\"", "\"");
1984 let line = self.line_numbers.line_number(location.start).to_doc();
1985 let fields = wrap_object(fields.into_iter().map(|(k, v)| (k.to_doc(), Some(v))));
1986
1987 docvec![
1988 self.source_map_tracker(location.start),
1989 "throw makeError",
1990 wrap_arguments([
1991 string(error_name),
1992 "FILEPATH".to_doc(),
1993 module,
1994 line,
1995 function,
1996 message.clone(),
1997 fields
1998 ]),
1999 ]
2000 }
2001
2002 fn module_select(
2003 &mut self,
2004 module: &'a str,
2005 label: &'a EcoString,
2006 constructor: &'a ModuleValueConstructor,
2007 ) -> Document<'a> {
2008 match constructor {
2009 ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. } => {
2010 docvec!["$", module, ".", maybe_escape_identifier(label)]
2011 }
2012
2013 ModuleValueConstructor::Record {
2014 name, arity, type_, ..
2015 } => record_constructor(type_.clone(), Some(module), name, *arity, self.tracker),
2016 }
2017 }
2018
2019 fn echo(
2020 &mut self,
2021 expression: Document<'a>,
2022 message: Option<&'a TypedExpr>,
2023 location: &'a SrcSpan,
2024 ) -> Document<'a> {
2025 self.tracker.echo_used = true;
2026
2027 let message = match message {
2028 Some(message) => self
2029 .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(message)),
2030 None => "undefined".to_doc(),
2031 };
2032
2033 let echo_arguments = call_arguments(vec![
2034 expression,
2035 message,
2036 self.src_path.clone().to_doc(),
2037 self.line_numbers.line_number(location.start).to_doc(),
2038 ]);
2039 self.wrap_return(docvec!["echo", echo_arguments])
2040 }
2041
2042 pub(crate) fn constant_expression(
2043 &mut self,
2044 context: Context,
2045 expression: &'a TypedConstant,
2046 ) -> Document<'a> {
2047 match expression {
2048 Constant::Int { value, .. } => int(value),
2049 Constant::Float { value, .. } => float(value),
2050 Constant::String { value, .. } => string(value),
2051 Constant::Tuple { elements, .. } => array(
2052 elements
2053 .iter()
2054 .map(|element| self.constant_expression(context, element)),
2055 ),
2056
2057 Constant::List { elements, tail, .. } => {
2058 self.tracker.list_used = true;
2059 let list = match tail {
2060 // There's no tail in the list, we join all the elements and
2061 // call it a day.
2062 None => list(
2063 elements
2064 .iter()
2065 .map(|element| self.constant_expression(context, element)),
2066 ),
2067
2068 Some(tail) => match tail.list_elements() {
2069 // There's a tail in the list whose elements are all
2070 // known at compile time. In this case we replace the
2071 // tail with those elements and create a single flat
2072 // list.
2073 Some(tail_elements) => list(
2074 elements
2075 .iter()
2076 .chain(tail_elements)
2077 .map(|element| self.constant_expression(context, element)),
2078 ),
2079 // There's a tail in the list but we can't really tell
2080 // what its elements are at compile time. This means we
2081 // have to prepend to this list.
2082 None => {
2083 self.tracker.prepend_used = true;
2084 let tail = self.constant_expression(context, tail);
2085 prepend(
2086 elements
2087 .iter()
2088 .map(|element| self.constant_expression(context, element)),
2089 tail,
2090 )
2091 }
2092 },
2093 };
2094 match context {
2095 Context::Constant => docvec!["/* @__PURE__ */ ", list],
2096 Context::Guard => list,
2097 }
2098 }
2099
2100 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => {
2101 "true".to_doc()
2102 }
2103 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => {
2104 "false".to_doc()
2105 }
2106 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(),
2107
2108 Constant::Record {
2109 arguments,
2110 module,
2111 name,
2112 type_,
2113 ..
2114 } => {
2115 let tag = expression
2116 .constant_record_tag()
2117 .expect("record without inferred constructor made it to code generation");
2118
2119 if module.is_none() && type_.is_result() {
2120 if tag == "Ok" {
2121 self.tracker.ok_used = true;
2122 } else {
2123 self.tracker.error_used = true;
2124 }
2125 }
2126
2127 // If there's no arguments and the type is a function that takes
2128 // arguments then this is the constructor being referenced, not the
2129 // function being called.
2130 if let Some(arity) = type_.fn_arity()
2131 && arguments.is_none()
2132 && arity != 0
2133 {
2134 let arity = arity as u16;
2135 return record_constructor(type_.clone(), None, name, arity, self.tracker);
2136 }
2137
2138 // Otherwise we're always constructing a record! Even if there's
2139 // no argument list:
2140 // ```gleam
2141 // pub type Wibble { Wibble }
2142 // pub const wibble = Wibble // <- here we're constructing the record!
2143 // ```
2144 //
2145 // Record updates are fully expanded during type checking, so we
2146 // just handle arguments
2147 let field_values = arguments
2148 .iter()
2149 .flatten()
2150 .map(|argument| self.constant_expression(context, &argument.value))
2151 .collect_vec();
2152
2153 let constructor = construct_record(
2154 module.as_ref().map(|(module, _)| module.as_str()),
2155 name,
2156 field_values,
2157 );
2158 match context {
2159 Context::Constant => docvec!["/* @__PURE__ */ ", constructor],
2160 Context::Guard => constructor,
2161 }
2162 }
2163 Constant::BitArray { segments, .. } => {
2164 let bit_array = self.constant_bit_array(segments, context);
2165 match context {
2166 Context::Constant => docvec!["/* @__PURE__ */ ", bit_array],
2167 Context::Guard => bit_array,
2168 }
2169 }
2170
2171 Constant::Var { name, module, .. } => {
2172 match (module, context) {
2173 (None, Context::Guard) => self.local_var(name).to_doc(),
2174 (None, Context::Constant) => maybe_escape_identifier(name).to_doc(),
2175 (Some((module, _)), _) => {
2176 // JS keywords can be accessed here, but we must escape anyway
2177 // as we escape when exporting such names in the first place,
2178 // and the imported name has to match the exported name.
2179 docvec!["$", module, ".", maybe_escape_identifier(name)]
2180 }
2181 }
2182 }
2183
2184 Constant::StringConcatenation { left, right, .. } => {
2185 let left = self.constant_expression(context, left);
2186 let right = self.constant_expression(context, right);
2187 docvec![left, " + ", right]
2188 }
2189
2190 Constant::RecordUpdate { .. } => {
2191 panic!("record updates should not reach code generation")
2192 }
2193 Constant::Todo { .. } => {
2194 panic!("todo constants should not reach code generation")
2195 }
2196 Constant::Invalid { .. } => {
2197 panic!("invalid constants should not reach code generation")
2198 }
2199 }
2200 }
2201
2202 fn constant_bit_array(
2203 &mut self,
2204 segments: &'a [TypedConstantBitArraySegment],
2205 context: Context,
2206 ) -> Document<'a> {
2207 self.tracker.bit_array_literal_used = true;
2208 let segments_array = array(segments.iter().map(|segment| {
2209 let value = match context {
2210 Context::Constant => self.constant_expression(context, &segment.value),
2211 Context::Guard => self.guard_constant_expression(&segment.value),
2212 };
2213
2214 let details = self.constant_bit_array_segment_details(segment, context);
2215
2216 match details.type_ {
2217 BitArraySegmentType::BitArray => {
2218 if segment.size().is_some() {
2219 self.tracker.bit_array_slice_used = true;
2220 docvec!["bitArraySlice(", value, ", 0, ", details.size, ")"]
2221 } else {
2222 value
2223 }
2224 }
2225 BitArraySegmentType::Int => match (details.size_value, segment.value.as_ref()) {
2226 (Some(size_value), Constant::Int { int_value, .. })
2227 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into()
2228 && (&size_value % BigInt::from(8) == BigInt::ZERO) =>
2229 {
2230 let bytes = bit_array_segment_int_value_to_bytes(
2231 int_value.clone(),
2232 size_value,
2233 segment.endianness(),
2234 );
2235
2236 u8_slice(&bytes)
2237 }
2238
2239 (Some(size_value), _) if size_value == 8.into() => value,
2240
2241 (Some(size_value), _) if size_value <= 0.into() => nil(),
2242
2243 _ => {
2244 self.tracker.sized_integer_segment_used = true;
2245 let size = details.size;
2246 let is_big = bool(segment.endianness().is_big());
2247 docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]
2248 }
2249 },
2250 BitArraySegmentType::Float => {
2251 self.tracker.float_bit_array_segment_used = true;
2252 let size = details.size;
2253 let is_big = bool(details.endianness.is_big());
2254 docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]
2255 }
2256 BitArraySegmentType::String(StringEncoding::Utf8) => {
2257 self.tracker.string_bit_array_segment_used = true;
2258 docvec!["stringBits(", value, ")"]
2259 }
2260 BitArraySegmentType::String(StringEncoding::Utf16) => {
2261 self.tracker.string_utf16_bit_array_segment_used = true;
2262 let is_big = bool(details.endianness.is_big());
2263 docvec!["stringToUtf16(", value, ", ", is_big, ")"]
2264 }
2265 BitArraySegmentType::String(StringEncoding::Utf32) => {
2266 self.tracker.string_utf32_bit_array_segment_used = true;
2267 let is_big = bool(details.endianness.is_big());
2268 docvec!["stringToUtf32(", value, ", ", is_big, ")"]
2269 }
2270 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => {
2271 self.tracker.codepoint_bit_array_segment_used = true;
2272 docvec!["codepointBits(", value, ")"]
2273 }
2274 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => {
2275 self.tracker.codepoint_utf16_bit_array_segment_used = true;
2276 let is_big = bool(details.endianness.is_big());
2277 docvec!["codepointToUtf16(", value, ", ", is_big, ")"]
2278 }
2279 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => {
2280 self.tracker.codepoint_utf32_bit_array_segment_used = true;
2281 let is_big = bool(details.endianness.is_big());
2282 docvec!["codepointToUtf32(", value, ", ", is_big, ")"]
2283 }
2284 }
2285 }));
2286
2287 docvec!["toBitArray(", segments_array, ")"]
2288 }
2289
2290 fn constant_bit_array_segment_details(
2291 &mut self,
2292 segment: &'a TypedConstantBitArraySegment,
2293 context: Context,
2294 ) -> BitArraySegmentDetails<'a> {
2295 let size = segment.size();
2296 let unit = segment.unit();
2297 let (size_value, size) = match size {
2298 Some(Constant::Int { int_value, .. }) => {
2299 let size_value = int_value * unit;
2300 let size = eco_format!("{}", size_value).to_doc();
2301 (Some(size_value), size)
2302 }
2303
2304 Some(size) => {
2305 let mut size = match context {
2306 Context::Constant => self.constant_expression(context, size),
2307 Context::Guard => self.guard_constant_expression(size),
2308 };
2309 if unit != 1 {
2310 size = size.group().append(" * ".to_doc().append(unit.to_doc()));
2311 }
2312
2313 (None, size)
2314 }
2315
2316 None => {
2317 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 };
2318 (Some(BigInt::from(size_value)), docvec![size_value])
2319 }
2320 };
2321
2322 let type_ = BitArraySegmentType::from_segment(segment);
2323
2324 BitArraySegmentDetails {
2325 type_,
2326 size,
2327 size_value,
2328 endianness: segment.endianness(),
2329 }
2330 }
2331
2332 pub(crate) fn guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> {
2333 match guard {
2334 ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"),
2335
2336 ClauseGuard::Block { value, .. } => self.guard(value).surround("(", ")"),
2337
2338 ClauseGuard::BinaryOperator {
2339 left,
2340 right,
2341 operator,
2342 ..
2343 } => {
2344 let left_document = self.wrapped_guard(left);
2345 let right_document = self.wrapped_guard(right);
2346
2347 let operator = match operator {
2348 BinOp::Eq if is_js_scalar(left.type_()) => "===",
2349 BinOp::NotEq if is_js_scalar(left.type_()) => "!==",
2350 BinOp::Eq | BinOp::NotEq => {
2351 let should_be_equal = *operator == BinOp::Eq;
2352
2353 // Handle singleton equality optimization for guards
2354 if let Some(doc) =
2355 self.singleton_variant_guard_equality(left, right, should_be_equal)
2356 {
2357 return doc;
2358 }
2359
2360 if let Some(doc) =
2361 self.singleton_variant_guard_equality(right, left, should_be_equal)
2362 {
2363 return doc;
2364 }
2365
2366 let left_doc = self.guard(left);
2367 let right_doc = self.guard(right);
2368 return self.prelude_equal_call(should_be_equal, left_doc, right_doc);
2369 }
2370
2371 BinOp::GtFloat | BinOp::GtInt => ">",
2372 BinOp::GtEqFloat | BinOp::GtEqInt => ">=",
2373 BinOp::LtFloat | BinOp::LtInt => "<",
2374 BinOp::LtEqFloat | BinOp::LtEqInt => "<=",
2375
2376 BinOp::AddFloat | BinOp::AddInt | BinOp::Concatenate => "+",
2377 BinOp::SubFloat | BinOp::SubInt => "-",
2378 BinOp::MultFloat | BinOp::MultInt => "*",
2379
2380 BinOp::DivFloat => {
2381 self.tracker.float_division_used = true;
2382 return docvec![
2383 "divideFloat",
2384 wrap_arguments([left_document, right_document])
2385 ];
2386 }
2387
2388 BinOp::DivInt => {
2389 self.tracker.int_division_used = true;
2390 return docvec![
2391 "divideInt",
2392 wrap_arguments([left_document, right_document])
2393 ];
2394 }
2395
2396 BinOp::RemainderInt => {
2397 self.tracker.int_remainder_used = true;
2398 return docvec![
2399 "remainderInt",
2400 wrap_arguments([left_document, right_document])
2401 ];
2402 }
2403
2404 BinOp::And => "&&",
2405 BinOp::Or => "||",
2406 };
2407
2408 docvec![left_document, " ", operator, " ", right_document]
2409 }
2410
2411 ClauseGuard::Var { name, .. } => self.local_var(name).to_doc(),
2412
2413 ClauseGuard::TupleIndex { tuple, index, .. } => {
2414 docvec![self.guard(tuple,), "[", index, "]"]
2415 }
2416
2417 ClauseGuard::FieldAccess {
2418 label, container, ..
2419 } => docvec![self.guard(container), ".", maybe_escape_property(label)],
2420
2421 ClauseGuard::ModuleSelect {
2422 module_alias,
2423 label,
2424 ..
2425 } => docvec!["$", module_alias, ".", label],
2426
2427 ClauseGuard::Not { expression, .. } => docvec!["!", self.guard(expression,)],
2428
2429 ClauseGuard::Constant(constant) => self.guard_constant_expression(constant),
2430 }
2431 }
2432
2433 fn singleton_variant_guard_equality(
2434 &mut self,
2435 left: &'a TypedClauseGuard,
2436 right: &'a TypedClauseGuard,
2437 should_be_equal: bool,
2438 ) -> Option<Document<'a>> {
2439 if let ClauseGuard::Constant(Constant::Record {
2440 record_constructor: Some(constructor),
2441 module,
2442 name,
2443 ..
2444 }) = right
2445 && let ValueConstructorVariant::Record { arity: 0, .. } = constructor.variant
2446 {
2447 let left_doc = self.guard(left);
2448 return Some(self.singleton_equal(
2449 left_doc,
2450 module.as_ref().map(|(module, _)| module.as_str()),
2451 name,
2452 should_be_equal,
2453 ));
2454 }
2455 None
2456 }
2457
2458 fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> {
2459 match guard {
2460 ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"),
2461 ClauseGuard::Var { .. }
2462 | ClauseGuard::TupleIndex { .. }
2463 | ClauseGuard::Constant(_)
2464 | ClauseGuard::Not { .. }
2465 | ClauseGuard::FieldAccess { .. }
2466 | ClauseGuard::Block { .. } => self.guard(guard),
2467
2468 ClauseGuard::BinaryOperator { .. } | ClauseGuard::ModuleSelect { .. } => {
2469 docvec!["(", self.guard(guard), ")"]
2470 }
2471 }
2472 }
2473
2474 fn guard_constant_expression(&mut self, expression: &'a TypedConstant) -> Document<'a> {
2475 match expression {
2476 Constant::Tuple { elements, .. } => array(
2477 elements
2478 .iter()
2479 .map(|element| self.guard_constant_expression(element)),
2480 ),
2481
2482 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => {
2483 "true".to_doc()
2484 }
2485 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => {
2486 "false".to_doc()
2487 }
2488 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(),
2489
2490 Constant::BitArray { segments, .. } => {
2491 self.constant_bit_array(segments, Context::Guard)
2492 }
2493
2494 Constant::Var { name, .. } => self.local_var(name).to_doc(),
2495
2496 Constant::Record { .. }
2497 | Constant::Int { .. }
2498 | Constant::Float { .. }
2499 | Constant::String { .. }
2500 | Constant::List { .. }
2501 | Constant::RecordUpdate { .. }
2502 | Constant::StringConcatenation { .. }
2503 | Constant::Todo { .. }
2504 | Constant::Invalid { .. } => self.constant_expression(Context::Guard, expression),
2505 }
2506 }
2507
2508 pub fn source_map_tracker(&mut self, start_index: u32) -> Document<'a> {
2509 create_cursor_position_observer(&self.source_map_builder, self.line_numbers, start_index)
2510 }
2511}
2512
2513#[derive(Clone, Copy)]
2514enum AssertExpression {
2515 Literal,
2516 Expression,
2517 Unevaluated,
2518}
2519
2520impl AssertExpression {
2521 fn from_expression(expression: &TypedExpr) -> Self {
2522 if expression.is_literal() {
2523 Self::Literal
2524 } else {
2525 Self::Expression
2526 }
2527 }
2528}
2529
2530pub fn int(value: &str) -> Document<'_> {
2531 eco_string_int(value.into())
2532}
2533
2534pub fn eco_string_int<'a>(value: EcoString) -> Document<'a> {
2535 let mut out = EcoString::with_capacity(value.len());
2536
2537 if value.starts_with('-') {
2538 out.push('-');
2539 } else if value.starts_with('+') {
2540 out.push('+');
2541 };
2542 let value = value.trim_start_matches(['+', '-'].as_ref());
2543
2544 let value = if value.starts_with("0x") {
2545 out.push_str("0x");
2546 value.trim_start_matches("0x")
2547 } else if value.starts_with("0o") {
2548 out.push_str("0o");
2549 value.trim_start_matches("0o")
2550 } else if value.starts_with("0b") {
2551 out.push_str("0b");
2552 value.trim_start_matches("0b")
2553 } else {
2554 value
2555 };
2556
2557 let value = value.trim_start_matches(['0', '_']);
2558 if value.is_empty() {
2559 out.push('0');
2560 }
2561
2562 out.push_str(value);
2563
2564 out.to_doc()
2565}
2566
2567pub fn float(value: &str) -> Document<'_> {
2568 let mut out = EcoString::with_capacity(value.len());
2569
2570 if value.starts_with('-') {
2571 out.push('-');
2572 } else if value.starts_with('+') {
2573 out.push('+');
2574 };
2575 let value = value.trim_start_matches(['+', '-'].as_ref());
2576
2577 let value = value.trim_start_matches(['0', '_']);
2578 if value.starts_with(['.', 'e', 'E']) {
2579 out.push('0');
2580 }
2581 out.push_str(value);
2582
2583 out.to_doc()
2584}
2585
2586pub fn float_from_value(value: f64) -> Document<'static> {
2587 if value.is_infinite() {
2588 if value.is_sign_positive() {
2589 "Infinity".to_doc()
2590 } else {
2591 "-Infinity".to_doc()
2592 }
2593 } else if value.is_nan() {
2594 // NOTE: this case is probably unnecessary, as this function is only
2595 // invoked with `LiteralFloatValue` values, which cannot be nan.
2596 "NaN".to_doc()
2597 } else {
2598 value.to_doc()
2599 }
2600}
2601
2602/// The context where the constant expression is used, it might be inside a
2603/// function call, or in the definition of another constant.
2604///
2605/// Based on the context we might want to annotate pure function calls as
2606/// "@__PURE__".
2607///
2608#[derive(Debug, Clone, Copy)]
2609pub enum Context {
2610 Constant,
2611 Guard,
2612}
2613
2614#[derive(Debug)]
2615struct BitArraySegmentDetails<'a> {
2616 type_: BitArraySegmentType,
2617 size: Document<'a>,
2618 /// The size of the bit array segment stored as a BigInt.
2619 /// This has a value when the segment's size is known at compile time.
2620 size_value: Option<BigInt>,
2621 endianness: Endianness,
2622}
2623
2624#[derive(Debug, Clone, Copy)]
2625enum BitArraySegmentType {
2626 BitArray,
2627 Int,
2628 Float,
2629 String(StringEncoding),
2630 UtfCodepoint(StringEncoding),
2631}
2632
2633impl BitArraySegmentType {
2634 fn from_segment<Value>(segment: &BitArraySegment<Value, Arc<Type>>) -> Self {
2635 if segment.type_.is_int() {
2636 BitArraySegmentType::Int
2637 } else if segment.type_.is_float() {
2638 BitArraySegmentType::Float
2639 } else if segment.type_.is_bit_array() {
2640 BitArraySegmentType::BitArray
2641 } else if segment.type_.is_string() {
2642 let encoding = if segment.has_utf16_option() {
2643 StringEncoding::Utf16
2644 } else if segment.has_utf32_option() {
2645 StringEncoding::Utf32
2646 } else {
2647 StringEncoding::Utf8
2648 };
2649 BitArraySegmentType::String(encoding)
2650 } else if segment.type_.is_utf_codepoint() {
2651 let encoding = if segment.has_utf16_codepoint_option() {
2652 StringEncoding::Utf16
2653 } else if segment.has_utf32_codepoint_option() {
2654 StringEncoding::Utf32
2655 } else {
2656 StringEncoding::Utf8
2657 };
2658 BitArraySegmentType::UtfCodepoint(encoding)
2659 } else {
2660 panic!(
2661 "Invalid bit array segment type reached code generation: {:?}",
2662 segment.type_
2663 );
2664 }
2665 }
2666}
2667
2668pub fn string<'a>(value: &'a str) -> Document<'a> {
2669 if value.contains('\n') {
2670 EcoString::from(value.replace('\n', r"\n"))
2671 .to_doc()
2672 .surround("\"", "\"")
2673 } else {
2674 value.to_doc().surround("\"", "\"")
2675 }
2676}
2677
2678pub(crate) fn array<'a, Elements: IntoIterator<Item = Document<'a>>>(
2679 elements: Elements,
2680) -> Document<'a> {
2681 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", ")).collect_vec();
2682 if elements.is_empty() {
2683 // Do not add a trailing comma since that adds an 'undefined' element
2684 "[]".to_doc()
2685 } else {
2686 docvec![
2687 "[",
2688 docvec![break_("", ""), elements].nest(INDENT),
2689 break_(",", ""),
2690 "]"
2691 ]
2692 .group()
2693 }
2694}
2695
2696pub(crate) fn list<'a, I: IntoIterator<Item = Document<'a>>>(elements: I) -> Document<'a>
2697where
2698 I::IntoIter: DoubleEndedIterator,
2699{
2700 let array = array(elements);
2701 docvec!["toList(", array, ")"]
2702}
2703
2704fn prepend<'a, I: IntoIterator<Item = Document<'a>>>(
2705 elements: I,
2706 tail: Document<'a>,
2707) -> Document<'a>
2708where
2709 I::IntoIter: DoubleEndedIterator + ExactSizeIterator,
2710{
2711 elements.into_iter().rev().fold(tail, |tail, element| {
2712 let arguments = call_arguments([element, tail]);
2713 docvec!["listPrepend", arguments]
2714 })
2715}
2716
2717fn call_arguments<'a, Elements: IntoIterator<Item = Document<'a>>>(
2718 elements: Elements,
2719) -> Document<'a> {
2720 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", "))
2721 .collect_vec()
2722 .to_doc();
2723 if elements.is_empty() {
2724 return "()".to_doc();
2725 }
2726 docvec![
2727 "(",
2728 docvec![break_("", ""), elements].nest(INDENT),
2729 break_(",", ""),
2730 ")"
2731 ]
2732 .group()
2733}
2734
2735pub(crate) fn construct_record<'a>(
2736 module: Option<&'a str>,
2737 name: &'a str,
2738 arguments: impl IntoIterator<Item = Document<'a>>,
2739) -> Document<'a> {
2740 let mut any_arguments = false;
2741 let arguments = join(
2742 arguments.into_iter().inspect(|_| {
2743 any_arguments = true;
2744 }),
2745 break_(",", ", "),
2746 );
2747 let arguments = docvec![break_("", ""), arguments].nest(INDENT);
2748 let name = if let Some(module) = module {
2749 docvec!["$", module, ".", name]
2750 } else {
2751 name.to_doc()
2752 };
2753 if any_arguments {
2754 docvec!["new ", name, "(", arguments, break_(",", ""), ")"].group()
2755 } else {
2756 docvec!["new ", name, "()"]
2757 }
2758}
2759
2760impl TypedExpr {
2761 fn handles_own_return(&self) -> bool {
2762 match self {
2763 TypedExpr::Todo { .. }
2764 | TypedExpr::Call { .. }
2765 | TypedExpr::Case { .. }
2766 | TypedExpr::Panic { .. }
2767 | TypedExpr::Block { .. }
2768 | TypedExpr::Echo { .. }
2769 | TypedExpr::Pipeline { .. }
2770 | TypedExpr::RecordUpdate { .. } => true,
2771
2772 TypedExpr::Int { .. }
2773 | TypedExpr::Float { .. }
2774 | TypedExpr::String { .. }
2775 | TypedExpr::Var { .. }
2776 | TypedExpr::Fn { .. }
2777 | TypedExpr::List { .. }
2778 | TypedExpr::BinOp { .. }
2779 | TypedExpr::RecordAccess { .. }
2780 | TypedExpr::PositionalAccess { .. }
2781 | TypedExpr::ModuleSelect { .. }
2782 | TypedExpr::Tuple { .. }
2783 | TypedExpr::TupleIndex { .. }
2784 | TypedExpr::BitArray { .. }
2785 | TypedExpr::NegateBool { .. }
2786 | TypedExpr::NegateInt { .. }
2787 | TypedExpr::Invalid { .. } => false,
2788 }
2789 }
2790}
2791
2792impl BinOp {
2793 fn is_operator_to_wrap(&self) -> bool {
2794 match self {
2795 BinOp::And
2796 | BinOp::Or
2797 | BinOp::Eq
2798 | BinOp::NotEq
2799 | BinOp::LtInt
2800 | BinOp::LtEqInt
2801 | BinOp::LtFloat
2802 | BinOp::LtEqFloat
2803 | BinOp::GtEqInt
2804 | BinOp::GtInt
2805 | BinOp::GtEqFloat
2806 | BinOp::GtFloat
2807 | BinOp::AddInt
2808 | BinOp::AddFloat
2809 | BinOp::SubInt
2810 | BinOp::SubFloat
2811 | BinOp::MultFloat
2812 | BinOp::DivInt
2813 | BinOp::DivFloat
2814 | BinOp::RemainderInt
2815 | BinOp::Concatenate => true,
2816 BinOp::MultInt => false,
2817 }
2818 }
2819}
2820
2821pub fn is_js_scalar(t: Arc<Type>) -> bool {
2822 t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string()
2823}
2824
2825fn requires_semicolon(statement: &TypedStatement) -> bool {
2826 match statement {
2827 Statement::Expression(expression) => expression_requires_semicolon(expression),
2828
2829 Statement::Assignment(_) => false,
2830 Statement::Use(_) => false,
2831 Statement::Assert(_) => false,
2832 }
2833}
2834
2835fn expression_requires_semicolon(expression: &TypedExpr) -> bool {
2836 match expression {
2837 TypedExpr::Int { .. }
2838 | TypedExpr::Fn { .. }
2839 | TypedExpr::Var { .. }
2840 | TypedExpr::List { .. }
2841 | TypedExpr::Call { .. }
2842 | TypedExpr::Echo { .. }
2843 | TypedExpr::Float { .. }
2844 | TypedExpr::String { .. }
2845 | TypedExpr::BinOp { .. }
2846 | TypedExpr::Tuple { .. }
2847 | TypedExpr::NegateInt { .. }
2848 | TypedExpr::BitArray { .. }
2849 | TypedExpr::TupleIndex { .. }
2850 | TypedExpr::NegateBool { .. }
2851 | TypedExpr::RecordAccess { .. }
2852 | TypedExpr::PositionalAccess { .. }
2853 | TypedExpr::ModuleSelect { .. }
2854 | TypedExpr::Block { .. } => true,
2855
2856 TypedExpr::Todo { .. }
2857 | TypedExpr::Case { .. }
2858 | TypedExpr::Panic { .. }
2859 | TypedExpr::Pipeline { .. }
2860 | TypedExpr::RecordUpdate { .. }
2861 | TypedExpr::Invalid { .. } => false,
2862 }
2863}
2864
2865/// Wrap a document in an immediately invoked function expression
2866fn immediately_invoked_function_expression_document(document: Document<'_>) -> Document<'_> {
2867 docvec![
2868 docvec!["(() => {", break_("", " "), document].nest(INDENT),
2869 break_("", " "),
2870 "})()",
2871 ]
2872 .group()
2873}
2874
2875pub(crate) fn record_constructor<'a>(
2876 type_: Arc<Type>,
2877 qualifier: Option<&'a str>,
2878 name: &'a str,
2879 arity: u16,
2880 tracker: &mut UsageTracker,
2881) -> Document<'a> {
2882 if qualifier.is_none() && type_.is_result_constructor() {
2883 if name == "Ok" {
2884 tracker.ok_used = true;
2885 } else if name == "Error" {
2886 tracker.error_used = true;
2887 }
2888 }
2889 if type_.is_bool() && name == "True" {
2890 "true".to_doc()
2891 } else if type_.is_bool() {
2892 "false".to_doc()
2893 } else if type_.is_nil() {
2894 "undefined".to_doc()
2895 } else if arity == 0 {
2896 match qualifier {
2897 Some(module) => docvec!["new $", module, ".", name, "()"],
2898 None => docvec!["new ", name, "()"],
2899 }
2900 } else {
2901 let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc());
2902 let body = docvec![
2903 "return ",
2904 construct_record(qualifier, name, vars.clone()),
2905 ";"
2906 ];
2907 docvec![
2908 docvec![wrap_arguments(vars), " => {", break_("", " "), body]
2909 .nest(INDENT)
2910 .append(break_("", " "))
2911 .group(),
2912 "}",
2913 ]
2914 }
2915}
2916
2917fn u8_slice<'a>(bytes: &[u8]) -> Document<'a> {
2918 let s: EcoString = bytes
2919 .iter()
2920 .map(u8::to_string)
2921 .collect::<Vec<_>>()
2922 .join(", ")
2923 .into();
2924
2925 docvec![s]
2926}