Fork of daniellemaywood.uk/gleam — Wasm codegen work
109 kB
2959 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_empty()
2132 && arity != 0
2133 {
2134 let arity = arity as u16;
2135 return record_constructor(type_.clone(), None, name, arity, self.tracker);
2136 }
2137
2138 // Record updates are fully expanded during type checking, so we just handle arguments
2139 let field_values = arguments
2140 .iter()
2141 .map(|argument| self.constant_expression(context, &argument.value))
2142 .collect_vec();
2143
2144 let constructor = construct_record(
2145 module.as_ref().map(|(module, _)| module.as_str()),
2146 name,
2147 field_values,
2148 );
2149 match context {
2150 Context::Constant => docvec!["/* @__PURE__ */ ", constructor],
2151 Context::Guard => constructor,
2152 }
2153 }
2154 Constant::BitArray { segments, .. } => {
2155 let bit_array = self.constant_bit_array(segments, context);
2156 match context {
2157 Context::Constant => docvec!["/* @__PURE__ */ ", bit_array],
2158 Context::Guard => bit_array,
2159 }
2160 }
2161
2162 Constant::Var { name, module, .. } => {
2163 match (module, context) {
2164 (None, Context::Guard) => self.local_var(name).to_doc(),
2165 (None, Context::Constant) => maybe_escape_identifier(name).to_doc(),
2166 (Some((module, _)), _) => {
2167 // JS keywords can be accessed here, but we must escape anyway
2168 // as we escape when exporting such names in the first place,
2169 // and the imported name has to match the exported name.
2170 docvec!["$", module, ".", maybe_escape_identifier(name)]
2171 }
2172 }
2173 }
2174
2175 Constant::StringConcatenation { left, right, .. } => {
2176 let left = self.constant_expression(context, left);
2177 let right = self.constant_expression(context, right);
2178 docvec![left, " + ", right]
2179 }
2180
2181 Constant::RecordUpdate { .. } => {
2182 panic!("record updates should not reach code generation")
2183 }
2184 Constant::Todo { .. } => {
2185 panic!("todo constants should not reach code generation")
2186 }
2187 Constant::Invalid { .. } => {
2188 panic!("invalid constants should not reach code generation")
2189 }
2190 }
2191 }
2192
2193 fn constant_bit_array(
2194 &mut self,
2195 segments: &'a [TypedConstantBitArraySegment],
2196 context: Context,
2197 ) -> Document<'a> {
2198 self.tracker.bit_array_literal_used = true;
2199 let segments_array = array(segments.iter().map(|segment| {
2200 let value = match context {
2201 Context::Constant => self.constant_expression(context, &segment.value),
2202 Context::Guard => self.guard_constant_expression(&segment.value),
2203 };
2204
2205 let details = self.constant_bit_array_segment_details(segment, context);
2206
2207 match details.type_ {
2208 BitArraySegmentType::BitArray => {
2209 if segment.size().is_some() {
2210 self.tracker.bit_array_slice_used = true;
2211 docvec!["bitArraySlice(", value, ", 0, ", details.size, ")"]
2212 } else {
2213 value
2214 }
2215 }
2216 BitArraySegmentType::Int => match (details.size_value, segment.value.as_ref()) {
2217 (Some(size_value), Constant::Int { int_value, .. })
2218 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into()
2219 && (&size_value % BigInt::from(8) == BigInt::ZERO) =>
2220 {
2221 let bytes = bit_array_segment_int_value_to_bytes(
2222 int_value.clone(),
2223 size_value,
2224 segment.endianness(),
2225 );
2226
2227 u8_slice(&bytes)
2228 }
2229
2230 (Some(size_value), _) if size_value == 8.into() => value,
2231
2232 (Some(size_value), _) if size_value <= 0.into() => nil(),
2233
2234 _ => {
2235 self.tracker.sized_integer_segment_used = true;
2236 let size = details.size;
2237 let is_big = bool(segment.endianness().is_big());
2238 docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]
2239 }
2240 },
2241 BitArraySegmentType::Float => {
2242 self.tracker.float_bit_array_segment_used = true;
2243 let size = details.size;
2244 let is_big = bool(details.endianness.is_big());
2245 docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]
2246 }
2247 BitArraySegmentType::String(StringEncoding::Utf8) => {
2248 self.tracker.string_bit_array_segment_used = true;
2249 docvec!["stringBits(", value, ")"]
2250 }
2251 BitArraySegmentType::String(StringEncoding::Utf16) => {
2252 self.tracker.string_utf16_bit_array_segment_used = true;
2253 let is_big = bool(details.endianness.is_big());
2254 docvec!["stringToUtf16(", value, ", ", is_big, ")"]
2255 }
2256 BitArraySegmentType::String(StringEncoding::Utf32) => {
2257 self.tracker.string_utf32_bit_array_segment_used = true;
2258 let is_big = bool(details.endianness.is_big());
2259 docvec!["stringToUtf32(", value, ", ", is_big, ")"]
2260 }
2261 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => {
2262 self.tracker.codepoint_bit_array_segment_used = true;
2263 docvec!["codepointBits(", value, ")"]
2264 }
2265 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => {
2266 self.tracker.codepoint_utf16_bit_array_segment_used = true;
2267 let is_big = bool(details.endianness.is_big());
2268 docvec!["codepointToUtf16(", value, ", ", is_big, ")"]
2269 }
2270 BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => {
2271 self.tracker.codepoint_utf32_bit_array_segment_used = true;
2272 let is_big = bool(details.endianness.is_big());
2273 docvec!["codepointToUtf32(", value, ", ", is_big, ")"]
2274 }
2275 }
2276 }));
2277
2278 docvec!["toBitArray(", segments_array, ")"]
2279 }
2280
2281 fn constant_bit_array_segment_details(
2282 &mut self,
2283 segment: &'a TypedConstantBitArraySegment,
2284 context: Context,
2285 ) -> BitArraySegmentDetails<'a> {
2286 let size = segment.size();
2287 let unit = segment.unit();
2288 let (size_value, size) = match size {
2289 Some(Constant::Int { int_value, .. }) => {
2290 let size_value = int_value * unit;
2291 let size = eco_format!("{}", size_value).to_doc();
2292 (Some(size_value), size)
2293 }
2294
2295 Some(size) => {
2296 let mut size = match context {
2297 Context::Constant => self.constant_expression(context, size),
2298 Context::Guard => self.guard_constant_expression(size),
2299 };
2300 if unit != 1 {
2301 size = size.group().append(" * ".to_doc().append(unit.to_doc()));
2302 }
2303
2304 (None, size)
2305 }
2306
2307 None => {
2308 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 };
2309 (Some(BigInt::from(size_value)), docvec![size_value])
2310 }
2311 };
2312
2313 let type_ = BitArraySegmentType::from_segment(segment);
2314
2315 BitArraySegmentDetails {
2316 type_,
2317 size,
2318 size_value,
2319 endianness: segment.endianness(),
2320 }
2321 }
2322
2323 pub(crate) fn guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> {
2324 match guard {
2325 ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"),
2326
2327 ClauseGuard::Block { value, .. } => self.guard(value).surround("(", ")"),
2328
2329 ClauseGuard::BinaryOperator {
2330 left,
2331 right,
2332 operator,
2333 ..
2334 } => {
2335 let left_document = self.wrapped_guard(left);
2336 let right_document = self.wrapped_guard(right);
2337
2338 let operator = match operator {
2339 BinOp::Eq if is_js_scalar(left.type_()) => "===",
2340 BinOp::NotEq if is_js_scalar(left.type_()) => "!==",
2341 BinOp::Eq | BinOp::NotEq => {
2342 let should_be_equal = *operator == BinOp::Eq;
2343
2344 // Handle singleton equality optimization for guards
2345 if let Some(doc) =
2346 self.singleton_variant_guard_equality(left, right, should_be_equal)
2347 {
2348 return doc;
2349 }
2350
2351 if let Some(doc) =
2352 self.singleton_variant_guard_equality(right, left, should_be_equal)
2353 {
2354 return doc;
2355 }
2356
2357 let left_doc = self.guard(left);
2358 let right_doc = self.guard(right);
2359 return self.prelude_equal_call(should_be_equal, left_doc, right_doc);
2360 }
2361
2362 BinOp::GtFloat | BinOp::GtInt => ">",
2363 BinOp::GtEqFloat | BinOp::GtEqInt => ">=",
2364 BinOp::LtFloat | BinOp::LtInt => "<",
2365 BinOp::LtEqFloat | BinOp::LtEqInt => "<=",
2366
2367 BinOp::AddFloat | BinOp::AddInt | BinOp::Concatenate => "+",
2368 BinOp::SubFloat | BinOp::SubInt => "-",
2369 BinOp::MultFloat | BinOp::MultInt => "*",
2370
2371 BinOp::DivFloat => {
2372 self.tracker.float_division_used = true;
2373 return docvec![
2374 "divideFloat",
2375 wrap_arguments([left_document, right_document])
2376 ];
2377 }
2378
2379 BinOp::DivInt => {
2380 self.tracker.int_division_used = true;
2381 return docvec![
2382 "divideInt",
2383 wrap_arguments([left_document, right_document])
2384 ];
2385 }
2386
2387 BinOp::RemainderInt => {
2388 self.tracker.int_remainder_used = true;
2389 return docvec![
2390 "remainderInt",
2391 wrap_arguments([left_document, right_document])
2392 ];
2393 }
2394
2395 BinOp::And => "&&",
2396 BinOp::Or => "||",
2397 };
2398
2399 docvec![left_document, " ", operator, " ", right_document]
2400 }
2401
2402 ClauseGuard::Var { name, .. } => self.local_var(name).to_doc(),
2403
2404 ClauseGuard::TupleIndex { tuple, index, .. } => {
2405 docvec![self.guard(tuple,), "[", index, "]"]
2406 }
2407
2408 ClauseGuard::FieldAccess {
2409 label, container, ..
2410 } => docvec![self.guard(container), ".", maybe_escape_property(label)],
2411
2412 ClauseGuard::ModuleSelect {
2413 module_alias,
2414 label,
2415 ..
2416 } => docvec!["$", module_alias, ".", label],
2417
2418 ClauseGuard::Not { expression, .. } => docvec!["!", self.guard(expression,)],
2419
2420 ClauseGuard::Constant(constant) => self.guard_constant_expression(constant),
2421 }
2422 }
2423
2424 fn singleton_variant_guard_equality(
2425 &mut self,
2426 left: &'a TypedClauseGuard,
2427 right: &'a TypedClauseGuard,
2428 should_be_equal: bool,
2429 ) -> Option<Document<'a>> {
2430 if let ClauseGuard::Constant(Constant::Record {
2431 record_constructor: Some(constructor),
2432 module,
2433 name,
2434 ..
2435 }) = right
2436 && let ValueConstructorVariant::Record { arity: 0, .. } = constructor.variant
2437 {
2438 let left_doc = self.guard(left);
2439 return Some(self.singleton_equal(
2440 left_doc,
2441 module.as_ref().map(|(module, _)| module.as_str()),
2442 name,
2443 should_be_equal,
2444 ));
2445 }
2446 None
2447 }
2448
2449 fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> {
2450 match guard {
2451 ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"),
2452 ClauseGuard::Var { .. }
2453 | ClauseGuard::TupleIndex { .. }
2454 | ClauseGuard::Constant(_)
2455 | ClauseGuard::Not { .. }
2456 | ClauseGuard::FieldAccess { .. }
2457 | ClauseGuard::Block { .. } => self.guard(guard),
2458
2459 ClauseGuard::BinaryOperator { .. } | ClauseGuard::ModuleSelect { .. } => {
2460 docvec!["(", self.guard(guard), ")"]
2461 }
2462 }
2463 }
2464
2465 fn guard_constant_expression(&mut self, expression: &'a TypedConstant) -> Document<'a> {
2466 match expression {
2467 Constant::Tuple { elements, .. } => array(
2468 elements
2469 .iter()
2470 .map(|element| self.guard_constant_expression(element)),
2471 ),
2472
2473 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => {
2474 "true".to_doc()
2475 }
2476 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => {
2477 "false".to_doc()
2478 }
2479 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(),
2480
2481 Constant::Record {
2482 arguments,
2483 module,
2484 name,
2485 type_,
2486 ..
2487 } => {
2488 let tag = expression
2489 .constant_record_tag()
2490 .expect("record without inferred constructor made it to code generation");
2491
2492 if module.is_none() && type_.is_result() {
2493 if tag == "Ok" {
2494 self.tracker.ok_used = true;
2495 } else {
2496 self.tracker.error_used = true;
2497 }
2498 }
2499
2500 // If there's no arguments and the type is a function that takes
2501 // arguments then this is the constructor being referenced, not the
2502 // function being called.
2503 if let Some(arity) = type_.fn_arity()
2504 && arguments.is_empty()
2505 && arity != 0
2506 {
2507 let arity = arity as u16;
2508 return record_constructor(type_.clone(), None, name, arity, self.tracker);
2509 }
2510
2511 // Record updates are fully expanded during type checking, so we just
2512 // handle arguments
2513 let field_values = arguments
2514 .iter()
2515 .map(|argument| self.guard_constant_expression(&argument.value))
2516 .collect_vec();
2517 construct_record(
2518 module.as_ref().map(|(module, _)| module.as_str()),
2519 name,
2520 field_values,
2521 )
2522 }
2523
2524 Constant::BitArray { segments, .. } => {
2525 self.constant_bit_array(segments, Context::Guard)
2526 }
2527
2528 Constant::Var { name, .. } => self.local_var(name).to_doc(),
2529
2530 Constant::Int { .. }
2531 | Constant::Float { .. }
2532 | Constant::String { .. }
2533 | Constant::List { .. }
2534 | Constant::RecordUpdate { .. }
2535 | Constant::StringConcatenation { .. }
2536 | Constant::Todo { .. }
2537 | Constant::Invalid { .. } => self.constant_expression(Context::Guard, expression),
2538 }
2539 }
2540
2541 pub fn source_map_tracker(&mut self, start_index: u32) -> Document<'a> {
2542 create_cursor_position_observer(&self.source_map_builder, self.line_numbers, start_index)
2543 }
2544}
2545
2546#[derive(Clone, Copy)]
2547enum AssertExpression {
2548 Literal,
2549 Expression,
2550 Unevaluated,
2551}
2552
2553impl AssertExpression {
2554 fn from_expression(expression: &TypedExpr) -> Self {
2555 if expression.is_literal() {
2556 Self::Literal
2557 } else {
2558 Self::Expression
2559 }
2560 }
2561}
2562
2563pub fn int(value: &str) -> Document<'_> {
2564 eco_string_int(value.into())
2565}
2566
2567pub fn eco_string_int<'a>(value: EcoString) -> Document<'a> {
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 = if value.starts_with("0x") {
2578 out.push_str("0x");
2579 value.trim_start_matches("0x")
2580 } else if value.starts_with("0o") {
2581 out.push_str("0o");
2582 value.trim_start_matches("0o")
2583 } else if value.starts_with("0b") {
2584 out.push_str("0b");
2585 value.trim_start_matches("0b")
2586 } else {
2587 value
2588 };
2589
2590 let value = value.trim_start_matches(['0', '_']);
2591 if value.is_empty() {
2592 out.push('0');
2593 }
2594
2595 out.push_str(value);
2596
2597 out.to_doc()
2598}
2599
2600pub fn float(value: &str) -> Document<'_> {
2601 let mut out = EcoString::with_capacity(value.len());
2602
2603 if value.starts_with('-') {
2604 out.push('-');
2605 } else if value.starts_with('+') {
2606 out.push('+');
2607 };
2608 let value = value.trim_start_matches(['+', '-'].as_ref());
2609
2610 let value = value.trim_start_matches(['0', '_']);
2611 if value.starts_with(['.', 'e', 'E']) {
2612 out.push('0');
2613 }
2614 out.push_str(value);
2615
2616 out.to_doc()
2617}
2618
2619pub fn float_from_value(value: f64) -> Document<'static> {
2620 if value.is_infinite() {
2621 if value.is_sign_positive() {
2622 "Infinity".to_doc()
2623 } else {
2624 "-Infinity".to_doc()
2625 }
2626 } else if value.is_nan() {
2627 // NOTE: this case is probably unnecessary, as this function is only
2628 // invoked with `LiteralFloatValue` values, which cannot be nan.
2629 "NaN".to_doc()
2630 } else {
2631 value.to_doc()
2632 }
2633}
2634
2635/// The context where the constant expression is used, it might be inside a
2636/// function call, or in the definition of another constant.
2637///
2638/// Based on the context we might want to annotate pure function calls as
2639/// "@__PURE__".
2640///
2641#[derive(Debug, Clone, Copy)]
2642pub enum Context {
2643 Constant,
2644 Guard,
2645}
2646
2647#[derive(Debug)]
2648struct BitArraySegmentDetails<'a> {
2649 type_: BitArraySegmentType,
2650 size: Document<'a>,
2651 /// The size of the bit array segment stored as a BigInt.
2652 /// This has a value when the segment's size is known at compile time.
2653 size_value: Option<BigInt>,
2654 endianness: Endianness,
2655}
2656
2657#[derive(Debug, Clone, Copy)]
2658enum BitArraySegmentType {
2659 BitArray,
2660 Int,
2661 Float,
2662 String(StringEncoding),
2663 UtfCodepoint(StringEncoding),
2664}
2665
2666impl BitArraySegmentType {
2667 fn from_segment<Value>(segment: &BitArraySegment<Value, Arc<Type>>) -> Self {
2668 if segment.type_.is_int() {
2669 BitArraySegmentType::Int
2670 } else if segment.type_.is_float() {
2671 BitArraySegmentType::Float
2672 } else if segment.type_.is_bit_array() {
2673 BitArraySegmentType::BitArray
2674 } else if segment.type_.is_string() {
2675 let encoding = if segment.has_utf16_option() {
2676 StringEncoding::Utf16
2677 } else if segment.has_utf32_option() {
2678 StringEncoding::Utf32
2679 } else {
2680 StringEncoding::Utf8
2681 };
2682 BitArraySegmentType::String(encoding)
2683 } else if segment.type_.is_utf_codepoint() {
2684 let encoding = if segment.has_utf16_codepoint_option() {
2685 StringEncoding::Utf16
2686 } else if segment.has_utf32_codepoint_option() {
2687 StringEncoding::Utf32
2688 } else {
2689 StringEncoding::Utf8
2690 };
2691 BitArraySegmentType::UtfCodepoint(encoding)
2692 } else {
2693 panic!(
2694 "Invalid bit array segment type reached code generation: {:?}",
2695 segment.type_
2696 );
2697 }
2698 }
2699}
2700
2701pub fn string<'a>(value: &'a str) -> Document<'a> {
2702 if value.contains('\n') {
2703 EcoString::from(value.replace('\n', r"\n"))
2704 .to_doc()
2705 .surround("\"", "\"")
2706 } else {
2707 value.to_doc().surround("\"", "\"")
2708 }
2709}
2710
2711pub(crate) fn array<'a, Elements: IntoIterator<Item = Document<'a>>>(
2712 elements: Elements,
2713) -> Document<'a> {
2714 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", ")).collect_vec();
2715 if elements.is_empty() {
2716 // Do not add a trailing comma since that adds an 'undefined' element
2717 "[]".to_doc()
2718 } else {
2719 docvec![
2720 "[",
2721 docvec![break_("", ""), elements].nest(INDENT),
2722 break_(",", ""),
2723 "]"
2724 ]
2725 .group()
2726 }
2727}
2728
2729pub(crate) fn list<'a, I: IntoIterator<Item = Document<'a>>>(elements: I) -> Document<'a>
2730where
2731 I::IntoIter: DoubleEndedIterator,
2732{
2733 let array = array(elements);
2734 docvec!["toList(", array, ")"]
2735}
2736
2737fn prepend<'a, I: IntoIterator<Item = Document<'a>>>(
2738 elements: I,
2739 tail: Document<'a>,
2740) -> Document<'a>
2741where
2742 I::IntoIter: DoubleEndedIterator + ExactSizeIterator,
2743{
2744 elements.into_iter().rev().fold(tail, |tail, element| {
2745 let arguments = call_arguments([element, tail]);
2746 docvec!["listPrepend", arguments]
2747 })
2748}
2749
2750fn call_arguments<'a, Elements: IntoIterator<Item = Document<'a>>>(
2751 elements: Elements,
2752) -> Document<'a> {
2753 let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", "))
2754 .collect_vec()
2755 .to_doc();
2756 if elements.is_empty() {
2757 return "()".to_doc();
2758 }
2759 docvec![
2760 "(",
2761 docvec![break_("", ""), elements].nest(INDENT),
2762 break_(",", ""),
2763 ")"
2764 ]
2765 .group()
2766}
2767
2768pub(crate) fn construct_record<'a>(
2769 module: Option<&'a str>,
2770 name: &'a str,
2771 arguments: impl IntoIterator<Item = Document<'a>>,
2772) -> Document<'a> {
2773 let mut any_arguments = false;
2774 let arguments = join(
2775 arguments.into_iter().inspect(|_| {
2776 any_arguments = true;
2777 }),
2778 break_(",", ", "),
2779 );
2780 let arguments = docvec![break_("", ""), arguments].nest(INDENT);
2781 let name = if let Some(module) = module {
2782 docvec!["$", module, ".", name]
2783 } else {
2784 name.to_doc()
2785 };
2786 if any_arguments {
2787 docvec!["new ", name, "(", arguments, break_(",", ""), ")"].group()
2788 } else {
2789 docvec!["new ", name, "()"]
2790 }
2791}
2792
2793impl TypedExpr {
2794 fn handles_own_return(&self) -> bool {
2795 match self {
2796 TypedExpr::Todo { .. }
2797 | TypedExpr::Call { .. }
2798 | TypedExpr::Case { .. }
2799 | TypedExpr::Panic { .. }
2800 | TypedExpr::Block { .. }
2801 | TypedExpr::Echo { .. }
2802 | TypedExpr::Pipeline { .. }
2803 | TypedExpr::RecordUpdate { .. } => true,
2804
2805 TypedExpr::Int { .. }
2806 | TypedExpr::Float { .. }
2807 | TypedExpr::String { .. }
2808 | TypedExpr::Var { .. }
2809 | TypedExpr::Fn { .. }
2810 | TypedExpr::List { .. }
2811 | TypedExpr::BinOp { .. }
2812 | TypedExpr::RecordAccess { .. }
2813 | TypedExpr::PositionalAccess { .. }
2814 | TypedExpr::ModuleSelect { .. }
2815 | TypedExpr::Tuple { .. }
2816 | TypedExpr::TupleIndex { .. }
2817 | TypedExpr::BitArray { .. }
2818 | TypedExpr::NegateBool { .. }
2819 | TypedExpr::NegateInt { .. }
2820 | TypedExpr::Invalid { .. } => false,
2821 }
2822 }
2823}
2824
2825impl BinOp {
2826 fn is_operator_to_wrap(&self) -> bool {
2827 match self {
2828 BinOp::And
2829 | BinOp::Or
2830 | BinOp::Eq
2831 | BinOp::NotEq
2832 | BinOp::LtInt
2833 | BinOp::LtEqInt
2834 | BinOp::LtFloat
2835 | BinOp::LtEqFloat
2836 | BinOp::GtEqInt
2837 | BinOp::GtInt
2838 | BinOp::GtEqFloat
2839 | BinOp::GtFloat
2840 | BinOp::AddInt
2841 | BinOp::AddFloat
2842 | BinOp::SubInt
2843 | BinOp::SubFloat
2844 | BinOp::MultFloat
2845 | BinOp::DivInt
2846 | BinOp::DivFloat
2847 | BinOp::RemainderInt
2848 | BinOp::Concatenate => true,
2849 BinOp::MultInt => false,
2850 }
2851 }
2852}
2853
2854pub fn is_js_scalar(t: Arc<Type>) -> bool {
2855 t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string()
2856}
2857
2858fn requires_semicolon(statement: &TypedStatement) -> bool {
2859 match statement {
2860 Statement::Expression(expression) => expression_requires_semicolon(expression),
2861
2862 Statement::Assignment(_) => false,
2863 Statement::Use(_) => false,
2864 Statement::Assert(_) => false,
2865 }
2866}
2867
2868fn expression_requires_semicolon(expression: &TypedExpr) -> bool {
2869 match expression {
2870 TypedExpr::Int { .. }
2871 | TypedExpr::Fn { .. }
2872 | TypedExpr::Var { .. }
2873 | TypedExpr::List { .. }
2874 | TypedExpr::Call { .. }
2875 | TypedExpr::Echo { .. }
2876 | TypedExpr::Float { .. }
2877 | TypedExpr::String { .. }
2878 | TypedExpr::BinOp { .. }
2879 | TypedExpr::Tuple { .. }
2880 | TypedExpr::NegateInt { .. }
2881 | TypedExpr::BitArray { .. }
2882 | TypedExpr::TupleIndex { .. }
2883 | TypedExpr::NegateBool { .. }
2884 | TypedExpr::RecordAccess { .. }
2885 | TypedExpr::PositionalAccess { .. }
2886 | TypedExpr::ModuleSelect { .. }
2887 | TypedExpr::Block { .. } => true,
2888
2889 TypedExpr::Todo { .. }
2890 | TypedExpr::Case { .. }
2891 | TypedExpr::Panic { .. }
2892 | TypedExpr::Pipeline { .. }
2893 | TypedExpr::RecordUpdate { .. }
2894 | TypedExpr::Invalid { .. } => false,
2895 }
2896}
2897
2898/// Wrap a document in an immediately invoked function expression
2899fn immediately_invoked_function_expression_document(document: Document<'_>) -> Document<'_> {
2900 docvec![
2901 docvec!["(() => {", break_("", " "), document].nest(INDENT),
2902 break_("", " "),
2903 "})()",
2904 ]
2905 .group()
2906}
2907
2908pub(crate) fn record_constructor<'a>(
2909 type_: Arc<Type>,
2910 qualifier: Option<&'a str>,
2911 name: &'a str,
2912 arity: u16,
2913 tracker: &mut UsageTracker,
2914) -> Document<'a> {
2915 if qualifier.is_none() && type_.is_result_constructor() {
2916 if name == "Ok" {
2917 tracker.ok_used = true;
2918 } else if name == "Error" {
2919 tracker.error_used = true;
2920 }
2921 }
2922 if type_.is_bool() && name == "True" {
2923 "true".to_doc()
2924 } else if type_.is_bool() {
2925 "false".to_doc()
2926 } else if type_.is_nil() {
2927 "undefined".to_doc()
2928 } else if arity == 0 {
2929 match qualifier {
2930 Some(module) => docvec!["new $", module, ".", name, "()"],
2931 None => docvec!["new ", name, "()"],
2932 }
2933 } else {
2934 let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc());
2935 let body = docvec![
2936 "return ",
2937 construct_record(qualifier, name, vars.clone()),
2938 ";"
2939 ];
2940 docvec![
2941 docvec![wrap_arguments(vars), " => {", break_("", " "), body]
2942 .nest(INDENT)
2943 .append(break_("", " "))
2944 .group(),
2945 "}",
2946 ]
2947 }
2948}
2949
2950fn u8_slice<'a>(bytes: &[u8]) -> Document<'a> {
2951 let s: EcoString = bytes
2952 .iter()
2953 .map(u8::to_string)
2954 .collect::<Vec<_>>()
2955 .join(", ")
2956 .into();
2957
2958 docvec![s]
2959}