1 use std::sync::Arc;
2 
3 use super::{pattern::CompiledPattern, *};
4 use crate::{
5     ast::*,
6     line_numbers::LineNumbers,
7     pretty::*,
8     type_::{HasType, ModuleValueConstructor, Type, ValueConstructor, ValueConstructorVariant},
9 };
10 
11 #[derive(Debug)]
12 pub(crate) struct Generator<'module> {
13     module_name: &'module [String],
14     line_numbers: &'module LineNumbers,
15     function_name: Option<&'module str>,
16     function_arguments: Vec<Option<&'module str>>,
17     current_scope_vars: im::HashMap<String, usize>,
18     pub tail_position: bool,
19     // We register whether these features are used within an expression so that
20     // the module generator can output a suitable function if it is needed.
21     pub tracker: &'module mut UsageTracker,
22     // We track whether tail call recusion is used so that we can render a loop
23     // at the top level of the function to use in place of pushing new stack
24     // frames.
25     pub tail_recursion_used: bool,
26 }
27 
28 impl<'module> Generator<'module> {
29     #[allow(clippy::too_many_arguments)] // TODO: FIXME
new( module_name: &'module [String], line_numbers: &'module LineNumbers, function_name: &'module str, function_arguments: Vec<Option<&'module str>>, tracker: &'module mut UsageTracker, mut current_scope_vars: im::HashMap<String, usize>, ) -> Self30     pub fn new(
31         module_name: &'module [String],
32         line_numbers: &'module LineNumbers,
33         function_name: &'module str,
34         function_arguments: Vec<Option<&'module str>>,
35         tracker: &'module mut UsageTracker,
36         mut current_scope_vars: im::HashMap<String, usize>,
37     ) -> Self {
38         for &name in function_arguments.iter().flatten() {
39             let _ = current_scope_vars.insert(name.to_string(), 0);
40         }
41         Self {
42             tracker,
43             module_name,
44             line_numbers,
45             function_name: Some(function_name),
46             function_arguments,
47             tail_recursion_used: false,
48             current_scope_vars,
49             tail_position: true,
50         }
51     }
52 
local_var<'a>(&mut self, name: &'a str) -> Document<'a>53     pub fn local_var<'a>(&mut self, name: &'a str) -> Document<'a> {
54         match self.current_scope_vars.get(name) {
55             None => {
56                 let _ = self.current_scope_vars.insert(name.to_string(), 0);
57                 maybe_escape_identifier_doc(name)
58             }
59             Some(0) => maybe_escape_identifier_doc(name),
60             Some(n) if name == "$" => Document::String(format!("${}", n)),
61             Some(n) => Document::String(format!("{}${}", name, n)),
62         }
63     }
64 
next_local_var<'a>(&mut self, name: &'a str) -> Document<'a>65     pub fn next_local_var<'a>(&mut self, name: &'a str) -> Document<'a> {
66         let next = self.current_scope_vars.get(name).map_or(0, |i| i + 1);
67         let _ = self.current_scope_vars.insert(name.to_string(), next);
68         self.local_var(name)
69     }
70 
function_body<'a>( &mut self, expression: &'a TypedExpr, args: &'a [TypedArg], ) -> Output<'a>71     pub fn function_body<'a>(
72         &mut self,
73         expression: &'a TypedExpr,
74         args: &'a [TypedArg],
75     ) -> Output<'a> {
76         let body = self.expression(expression)?;
77         if self.tail_recursion_used {
78             self.tail_call_loop(body, args)
79         } else {
80             Ok(body)
81         }
82     }
83 
tail_call_loop<'a>(&mut self, body: Document<'a>, args: &'a [TypedArg]) -> Output<'a>84     fn tail_call_loop<'a>(&mut self, body: Document<'a>, args: &'a [TypedArg]) -> Output<'a> {
85         let loop_assignments = concat(args.iter().flat_map(Arg::get_variable_name).map(|name| {
86             let var = maybe_escape_identifier_doc(name);
87             docvec!["let ", var, " = loop$", name, ";", line()]
88         }));
89         Ok(docvec!(
90             loop_assignments,
91             "while (true) {",
92             docvec!(line(), body).nest(INDENT),
93             line(),
94             "}"
95         ))
96     }
97 
expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a>98     pub fn expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a> {
99         let document = match expression {
100             TypedExpr::String { value, .. } => Ok(string(value)),
101 
102             TypedExpr::Int { value, .. } => Ok(int(value)),
103             TypedExpr::Float { value, .. } => Ok(float(value)),
104 
105             TypedExpr::List { elements, tail, .. } => {
106                 self.tracker.list_used = true;
107                 self.not_in_tail_position(|gen| {
108                     let tail = match tail {
109                         Some(tail) => Some(gen.wrap_expression(tail)?),
110                         None => None,
111                     };
112                     list(elements.iter().map(|e| gen.wrap_expression(e)), tail)
113                 })
114             }
115 
116             TypedExpr::Tuple { elems, .. } => self.tuple(elems),
117             TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index),
118 
119             TypedExpr::Case {
120                 location,
121                 subjects,
122                 clauses,
123                 ..
124             } => self.case(*location, subjects, clauses),
125 
126             TypedExpr::Call { fun, args, .. } => self.call(fun, args),
127             TypedExpr::Fn { args, body, .. } => self.fn_(args, body),
128 
129             TypedExpr::RecordAccess { record, label, .. } => self.record_access(record, label),
130             TypedExpr::RecordUpdate { spread, args, .. } => self.record_update(spread, args),
131 
132             TypedExpr::Var {
133                 name, constructor, ..
134             } => Ok(self.variable(name, constructor)),
135 
136             TypedExpr::Sequence { expressions, .. } => self.sequence(expressions),
137 
138             TypedExpr::Assignment { value, pattern, .. } => self.assignment(value, pattern),
139 
140             TypedExpr::Try {
141                 value,
142                 then,
143                 pattern,
144                 ..
145             } => self.try_(value, pattern, then),
146 
147             TypedExpr::BinOp {
148                 name, left, right, ..
149             } => self.bin_op(name, left, right),
150 
151             TypedExpr::Todo {
152                 label, location, ..
153             } => Ok(self.todo(label, location)),
154 
155             TypedExpr::BitString { segments, .. } => self.bit_string(segments),
156 
157             TypedExpr::ModuleSelect {
158                 module_alias,
159                 label,
160                 constructor,
161                 ..
162             } => Ok(self.module_select(module_alias, label, constructor)),
163         }?;
164         Ok(if expression.handles_own_return() {
165             document
166         } else {
167             self.wrap_return(document)
168         })
169     }
170 
bit_string<'a>(&mut self, segments: &'a [TypedExprBitStringSegment]) -> Output<'a>171     fn bit_string<'a>(&mut self, segments: &'a [TypedExprBitStringSegment]) -> Output<'a> {
172         self.tracker.bit_string_literal_used = true;
173 
174         use BitStringSegmentOption as Opt;
175 
176         // Collect all the values used in segments.
177         let segments_array = array(segments.iter().map(|segment| {
178             let value = self.not_in_tail_position(|gen| gen.wrap_expression(&segment.value))?;
179             match segment.options.as_slice() {
180                 // Ints
181                 [] => Ok(value),
182 
183                 // UTF8 strings
184                 [Opt::Utf8 { .. }] => {
185                     self.tracker.string_bit_string_segment_used = true;
186                     Ok(docvec!["stringBits(", value, ")"])
187                 }
188 
189                 // UTF8 codepoints
190                 [Opt::Utf8Codepoint { .. }] => {
191                     self.tracker.codepoint_bit_string_segment_used = true;
192                     Ok(docvec!["codepointBits(", value, ")"])
193                 }
194 
195                 // Bit strings
196                 [Opt::BitString { .. }] => Ok(docvec![value, ".buffer"]),
197 
198                 // Anything else
199                 _ => Err(Error::Unsupported {
200                     feature: "This bit string segment option".to_string(),
201                     location: segment.location,
202                 }),
203             }
204         }))?;
205 
206         Ok(docvec!["toBitString(", segments_array, ")"])
207     }
208 
wrap_return<'a>(&self, document: Document<'a>) -> Document<'a>209     pub fn wrap_return<'a>(&self, document: Document<'a>) -> Document<'a> {
210         if self.tail_position {
211             docvec!["return ", document, ";"]
212         } else {
213             document
214         }
215     }
216 
not_in_tail_position<'a, CompileFn>(&mut self, compile: CompileFn) -> Output<'a> where CompileFn: Fn(&mut Self) -> Output<'a>,217     pub fn not_in_tail_position<'a, CompileFn>(&mut self, compile: CompileFn) -> Output<'a>
218     where
219         CompileFn: Fn(&mut Self) -> Output<'a>,
220     {
221         let tail = self.tail_position;
222         self.tail_position = false;
223         let result = compile(self);
224         self.tail_position = tail;
225         result
226     }
227 
228     /// Wrap an expression in an immediately involked function expression if
229     /// required due to being a JS statement
wrap_expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a>230     pub fn wrap_expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a> {
231         match expression {
232             TypedExpr::Todo { .. }
233             | TypedExpr::Case { .. }
234             | TypedExpr::Sequence { .. }
235             | TypedExpr::Assignment { .. }
236             | TypedExpr::Try { .. } => self.immediately_involked_function_expression(expression),
237             _ => self.expression(expression),
238         }
239     }
240 
241     /// Wrap an expression in an immediately involked function expression if
242     /// required due to being a JS statement, or in parens if required due to
243     /// being an operator
binop_child_expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a>244     pub fn binop_child_expression<'a>(&mut self, expression: &'a TypedExpr) -> Output<'a> {
245         match expression {
246             TypedExpr::BinOp { name, .. } if name.is_operator_to_wrap() => {
247                 Ok(docvec!("(", self.expression(expression)?, ")"))
248             }
249             TypedExpr::Case { .. }
250             | TypedExpr::Sequence { .. }
251             | TypedExpr::Assignment { .. }
252             | TypedExpr::Try { .. } => self.immediately_involked_function_expression(expression),
253             _ => self.expression(expression),
254         }
255     }
256 
257     /// Wrap an expression in an immediately involked function expression
immediately_involked_function_expression<'a>( &mut self, expression: &'a TypedExpr, ) -> Output<'a>258     fn immediately_involked_function_expression<'a>(
259         &mut self,
260         expression: &'a TypedExpr,
261     ) -> Output<'a> {
262         let tail = self.tail_position;
263         self.tail_position = true;
264         let current_scope_vars = self.current_scope_vars.clone();
265         let result = self.expression(expression);
266         self.tail_position = tail;
267         self.current_scope_vars = current_scope_vars;
268         Ok(self.immediately_involked_function_expression_document(result?))
269     }
270 
271     /// Wrap a document in an immediately involked function expression
immediately_involked_function_expression_document<'a>( &mut self, document: Document<'a>, ) -> Document<'a>272     fn immediately_involked_function_expression_document<'a>(
273         &mut self,
274         document: Document<'a>,
275     ) -> Document<'a> {
276         docvec!(
277             docvec!("(() => {", break_("", " "), document).nest(INDENT),
278             break_("", " "),
279             "})()",
280         )
281         .group()
282     }
283 
variable<'a>(&mut self, name: &'a str, constructor: &'a ValueConstructor) -> Document<'a>284     fn variable<'a>(&mut self, name: &'a str, constructor: &'a ValueConstructor) -> Document<'a> {
285         match &constructor.variant {
286             ValueConstructorVariant::Record { arity, .. } => {
287                 self.record_constructor(constructor.type_.clone(), None, name, *arity)
288             }
289             ValueConstructorVariant::ModuleFn { .. }
290             | ValueConstructorVariant::ModuleConstant { .. }
291             | ValueConstructorVariant::LocalVariable => self.local_var(name),
292         }
293     }
294 
record_constructor<'a>( &mut self, type_: Arc<Type>, qualifier: Option<&'a str>, name: &'a str, arity: usize, ) -> Document<'a>295     fn record_constructor<'a>(
296         &mut self,
297         type_: Arc<Type>,
298         qualifier: Option<&'a str>,
299         name: &'a str,
300         arity: usize,
301     ) -> Document<'a> {
302         if qualifier.is_none() && type_.is_result_constructor() {
303             if name == "Ok" {
304                 self.tracker.ok_used = true;
305             } else if name == "Error" {
306                 self.tracker.error_used = true;
307             }
308         }
309         if type_.is_bool() && name == "True" {
310             "true".to_doc()
311         } else if type_.is_bool() {
312             "false".to_doc()
313         } else if type_.is_nil() {
314             "undefined".to_doc()
315         } else if arity == 0 {
316             match qualifier {
317                 Some(module) => docvec!["new $", module, ".", name, "()"],
318                 None => docvec!["new ", name, "()"],
319             }
320         } else {
321             let vars = (0..arity).map(|i| Document::String(format!("var{}", i)));
322             let body = docvec![
323                 "return ",
324                 construct_record(qualifier, name, vars.clone()),
325                 ";"
326             ];
327             docvec!(
328                 docvec!(wrap_args(vars), " => {", break_("", " "), body)
329                     .nest(INDENT)
330                     .append(break_("", " "))
331                     .group(),
332                 "}",
333             )
334         }
335     }
336 
sequence<'a>(&mut self, expressions: &'a [TypedExpr]) -> Output<'a>337     fn sequence<'a>(&mut self, expressions: &'a [TypedExpr]) -> Output<'a> {
338         let count = expressions.len();
339         let mut documents = Vec::with_capacity(count * 3);
340         documents.push(force_break());
341         for (i, expression) in expressions.iter().enumerate() {
342             if i + 1 < count {
343                 documents.push(self.not_in_tail_position(|gen| gen.expression(expression))?);
344                 if !matches!(
345                     expression,
346                     TypedExpr::Assignment { .. } | TypedExpr::Case { .. }
347                 ) {
348                     documents.push(";".to_doc());
349                 }
350                 documents.push(line());
351             } else {
352                 documents.push(self.expression(expression)?);
353             }
354         }
355         Ok(documents.to_doc())
356     }
357 
try_<'a>( &mut self, subject: &'a TypedExpr, pattern: &'a TypedPattern, then: &'a TypedExpr, ) -> Output<'a>358     fn try_<'a>(
359         &mut self,
360         subject: &'a TypedExpr,
361         pattern: &'a TypedPattern,
362         then: &'a TypedExpr,
363     ) -> Output<'a> {
364         let mut docs = vec![force_break()];
365 
366         // If the subject is not a variable then we will need to save it to a
367         // variable to prevent any side effects from rendering the same
368         // expression twice.
369         let subject_doc = if let TypedExpr::Var { name, .. } = subject {
370             self.local_var(name)
371         } else {
372             let subject = self.not_in_tail_position(|gen| gen.wrap_expression(subject))?;
373             let name = self.next_local_var(pattern::ASSIGNMENT_VAR);
374             docs.push("let ".to_doc());
375             docs.push(name.clone());
376             docs.push(" = ".to_doc());
377             docs.push(subject);
378             docs.push(";".to_doc());
379             docs.push(line());
380             name
381         };
382 
383         // We return early if the subject is an error
384         docs.push("if (!".to_doc());
385         docs.push(subject_doc.clone());
386         docs.push(r#".isOk()) return "#.to_doc());
387         docs.push(subject_doc.clone());
388         docs.push(";".to_doc());
389 
390         match pattern {
391             // Assign the inner value to a variable if it used
392             TypedPattern::Var { name, .. } => {
393                 docs.push(line());
394                 docs.push("let ".to_doc());
395                 docs.push(self.next_local_var(name));
396                 docs.push(" = ".to_doc());
397                 docs.push(subject_doc);
398                 docs.push("[0];".to_doc());
399                 docs.push(lines(2));
400             }
401 
402             TypedPattern::Discard { .. } => {
403                 docs.push(lines(1));
404             }
405 
406             // TODO: At time of writing to support patterns in trys we will need
407             // to adapt the `assignment` method to take a Document as a value
408             // and pass the subject document into that also rather than letting
409             // the pattern generator determine what it should be.
410             pattern => {
411                 let subject = subject_doc.append("[0]");
412                 let mut pattern_generator = pattern::Generator::new(self);
413                 pattern_generator.traverse_pattern(&subject, pattern)?;
414                 let compiled = pattern_generator.take_compiled();
415                 docs.push(line());
416                 docs.push(self.pattern_into_assignment_doc(
417                     compiled,
418                     subject,
419                     pattern.location(),
420                 )?);
421                 docs.push(lines(2));
422             }
423         }
424 
425         // Lastly, whatever comes next
426         docs.push(self.expression(then)?);
427 
428         Ok(docs.to_doc())
429     }
430 
assignment<'a>(&mut self, value: &'a TypedExpr, pattern: &'a TypedPattern) -> Output<'a>431     fn assignment<'a>(&mut self, value: &'a TypedExpr, pattern: &'a TypedPattern) -> Output<'a> {
432         // If it is a simple assignment to a variable we can generate a normal
433         // JS assignment
434         if let TypedPattern::Var { name, .. } = pattern {
435             // Subject must be rendered before the variable for variable numbering
436             let subject = self.not_in_tail_position(|gen| gen.wrap_expression(value))?;
437             let name = self.next_local_var(name);
438             return Ok(if self.tail_position {
439                 docvec![
440                     force_break(),
441                     "let ",
442                     name.clone(),
443                     " = ",
444                     subject,
445                     ";",
446                     line(),
447                     "return ",
448                     name,
449                     ";"
450                 ]
451             } else {
452                 docvec![force_break(), "let ", name, " = ", subject, ";"]
453             });
454         }
455 
456         // Otherwise we need to compile the patterns
457         let (subject, subject_assignment) = pattern::assign_subject(self, value);
458         // Value needs to be rendered before traversing pattern to have correctly incremented variables.
459         let value = self.not_in_tail_position(|gen| gen.wrap_expression(value))?;
460         let mut pattern_generator = pattern::Generator::new(self);
461         pattern_generator.traverse_pattern(&subject, pattern)?;
462         let compiled = pattern_generator.take_compiled();
463 
464         // If we are in tail position we can return value being assigned
465         let afterwards = if self.tail_position {
466             line()
467                 .append("return ")
468                 .append(subject_assignment.clone().unwrap_or_else(|| value.clone()))
469                 .append(";")
470         } else {
471             nil()
472         };
473 
474         // If there is a subject name given create a variable to hold it for
475         // use in patterns
476         let doc = match subject_assignment {
477             Some(name) => {
478                 let compiled =
479                     self.pattern_into_assignment_doc(compiled, subject, pattern.location())?;
480                 docvec!("let ", name, " = ", value, ";", line(), compiled)
481             }
482             None => self.pattern_into_assignment_doc(compiled, subject, pattern.location())?,
483         };
484 
485         Ok(docvec!(force_break(), doc.append(afterwards)))
486     }
487 
case<'a>( &mut self, location: SrcSpan, subject_values: &'a [TypedExpr], clauses: &'a [TypedClause], ) -> Output<'a>488     fn case<'a>(
489         &mut self,
490         location: SrcSpan,
491         subject_values: &'a [TypedExpr],
492         clauses: &'a [TypedClause],
493     ) -> Output<'a> {
494         let mut possibility_of_no_match = true;
495 
496         let (subjects, subject_assignments): (Vec<_>, Vec<_>) =
497             pattern::assign_subjects(self, subject_values)
498                 .into_iter()
499                 .unzip();
500         let mut gen = pattern::Generator::new(self);
501 
502         let mut doc = nil();
503 
504         // We wish to be able to know whether this is the first or clause being
505         // processed, so record the index number. We use this instead of
506         // `Iterator.enumerate` because we are using a nested for loop.
507         let mut clause_number = 0;
508         let total_patterns: usize = clauses
509             .iter()
510             .map(|c| c.alternative_patterns.len())
511             .sum::<usize>()
512             + clauses.len();
513 
514         // A case has many clauses `pattern -> consequence`
515         for clause in clauses {
516             let multipattern = std::iter::once(&clause.pattern);
517             let multipatterns = multipattern.chain(&clause.alternative_patterns);
518 
519             // A clause can have many patterns `pattern, pattern ->...`
520             for multipatterns in multipatterns {
521                 let scope = gen.expression_generator.current_scope_vars.clone();
522                 let mut compiled = gen.generate(&subjects, multipatterns, clause.guard.as_ref())?;
523                 let consequence = gen.expression_generator.expression(&clause.then)?;
524 
525                 // We've seen one more clause
526                 clause_number += 1;
527 
528                 // Reset the scope now that this clause has finished, causing the
529                 // variables to go out of scope.
530                 gen.expression_generator.current_scope_vars = scope;
531 
532                 // If the pattern assigns any variables we need to render assignments
533                 let body = if compiled.has_assignments() {
534                     let assignments = gen
535                         .expression_generator
536                         .pattern_take_assignments_doc(&mut compiled);
537                     docvec!(assignments, line(), consequence)
538                 } else {
539                     consequence
540                 };
541 
542                 let is_final_clause = clause_number == total_patterns;
543                 let is_first_clause = clause_number == 1;
544                 let is_only_clause = is_final_clause && is_first_clause;
545                 let is_catch_all = !compiled.has_checks() && clause.guard.is_none();
546 
547                 if is_catch_all {
548                     possibility_of_no_match = false;
549                 }
550 
551                 doc = if is_only_clause && is_catch_all {
552                     // If this is the only clause and there are no checks then we can
553                     // render just the body as the case does nothing
554                     doc.append(body)
555                 } else if is_final_clause && is_catch_all {
556                     // If this is the final clause and there are no checks then we can
557                     // render `else` instead of `else if (...)`
558                     doc.append(" else {")
559                         .append(docvec!(line(), body).nest(INDENT))
560                         .append(line())
561                         .append("}")
562                 } else {
563                     doc.append(if is_first_clause {
564                         "if ("
565                     } else {
566                         " else if ("
567                     })
568                     .append(
569                         gen.expression_generator
570                             .pattern_take_checks_doc(&mut compiled, true),
571                     )
572                     .append(") {")
573                     .append(docvec!(line(), body).nest(INDENT))
574                     .append(line())
575                     .append("}")
576                 };
577             }
578         }
579 
580         if possibility_of_no_match {
581             // Lastly append an error if no clause matches.
582             // We can remove this when we get exhaustiveness checking.
583             doc = doc
584                 .append(" else {")
585                 .append(
586                     docvec!(line(), self.case_no_match(location, subjects.into_iter())?)
587                         .nest(INDENT),
588                 )
589                 .append(line())
590                 .append("}")
591         }
592 
593         // If there is a subject name given create a variable to hold it for
594         // use in patterns
595         let subject_assignments: Vec<_> = subject_assignments
596             .into_iter()
597             .zip(subject_values)
598             .flat_map(|(assignment_name, value)| assignment_name.map(|name| (name, value)))
599             .map(|(name, value)| {
600                 let value = self.not_in_tail_position(|gen| gen.wrap_expression(value))?;
601                 Ok(docvec!("let ", name, " = ", value, ";", line()))
602             })
603             .try_collect()?;
604 
605         Ok(docvec![force_break(), subject_assignments, doc])
606     }
607 
case_no_match<'a, Subjects>(&mut self, location: SrcSpan, subjects: Subjects) -> Output<'a> where Subjects: IntoIterator<Item = Document<'a>>,608     fn case_no_match<'a, Subjects>(&mut self, location: SrcSpan, subjects: Subjects) -> Output<'a>
609     where
610         Subjects: IntoIterator<Item = Document<'a>>,
611     {
612         Ok(self.throw_error(
613             "case_no_match",
614             "No case clause matched",
615             location,
616             [("values", array(subjects.into_iter().map(Ok))?)],
617         ))
618     }
619 
assignment_no_match<'a>(&mut self, location: SrcSpan, subject: Document<'a>) -> Output<'a>620     fn assignment_no_match<'a>(&mut self, location: SrcSpan, subject: Document<'a>) -> Output<'a> {
621         Ok(self.throw_error(
622             "assignment_no_match",
623             "Assignment pattern did not much",
624             location,
625             [("value", subject)],
626         ))
627     }
628 
tuple<'a>(&mut self, elements: &'a [TypedExpr]) -> Output<'a>629     fn tuple<'a>(&mut self, elements: &'a [TypedExpr]) -> Output<'a> {
630         self.not_in_tail_position(|gen| {
631             array(elements.iter().map(|element| gen.wrap_expression(element)))
632         })
633     }
634 
call<'a>(&mut self, fun: &'a TypedExpr, arguments: &'a [CallArg<TypedExpr>]) -> Output<'a>635     fn call<'a>(&mut self, fun: &'a TypedExpr, arguments: &'a [CallArg<TypedExpr>]) -> Output<'a> {
636         let tail = self.tail_position;
637         self.tail_position = false;
638         let arguments = arguments
639             .iter()
640             .map(|element| self.wrap_expression(&element.value))
641             .try_collect()?;
642         self.tail_position = tail;
643         self.call_with_doc_args(fun, arguments)
644     }
645 
call_with_doc_args<'a>( &mut self, fun: &'a TypedExpr, arguments: Vec<Document<'a>>, ) -> Output<'a>646     fn call_with_doc_args<'a>(
647         &mut self,
648         fun: &'a TypedExpr,
649         arguments: Vec<Document<'a>>,
650     ) -> Output<'a> {
651         match fun {
652             // Qualified record construction
653             TypedExpr::ModuleSelect {
654                 constructor: ModuleValueConstructor::Record { name, .. },
655                 module_alias,
656                 ..
657             } => Ok(self.wrap_return(construct_record(Some(module_alias), name, arguments))),
658 
659             // Record construction
660             TypedExpr::Var {
661                 constructor:
662                     ValueConstructor {
663                         variant: ValueConstructorVariant::Record { .. },
664                         type_,
665                         ..
666                     },
667                 name,
668                 ..
669             } => {
670                 if type_.is_result_constructor() {
671                     if name == "Ok" {
672                         self.tracker.ok_used = true;
673                     } else if name == "Error" {
674                         self.tracker.error_used = true;
675                     }
676                 }
677                 Ok(self.wrap_return(construct_record(None, name, arguments)))
678             }
679 
680             // Tail call optimisation. If we are calling the current function
681             // and we are in tail position we can avoid creating a new stack
682             // frame, enabling recursion with constant memory usage.
683             TypedExpr::Var { name, .. }
684                 if self.function_name == Some(name)
685                     && self.tail_position
686                     && self.current_scope_vars.get(name) == Some(&0) =>
687             {
688                 let mut docs = Vec::with_capacity(arguments.len() * 4);
689                 // Record that tail recursion is happening so that we know to
690                 // render the loop at the top level of the function.
691                 self.tail_recursion_used = true;
692 
693                 for (i, (element, argument)) in arguments
694                     .into_iter()
695                     .zip(self.function_arguments.clone())
696                     .enumerate()
697                 {
698                     if i != 0 {
699                         docs.push(line());
700                     }
701                     // Create an assignment for each variable created by the function arguments
702                     if let Some(name) = argument {
703                         docs.push(Document::String(maybe_escape_identifier_string(name)));
704                         docs.push(" = ".to_doc());
705                     }
706                     // Render the value given to the function. Even if it is not
707                     // assigned we still render it because the expression may
708                     // have some side effects.
709                     docs.push(element);
710                     docs.push(";".to_doc());
711                 }
712                 Ok(docs.to_doc())
713             }
714 
715             _ => {
716                 let fun = self.not_in_tail_position(|gen| {
717                     let is_fn_literal = matches!(fun, TypedExpr::Fn { .. });
718                     let fun = gen.expression(fun)?;
719                     if is_fn_literal {
720                         Ok(docvec!("(", fun, ")"))
721                     } else {
722                         Ok(fun)
723                     }
724                 })?;
725                 let arguments = call_arguments(arguments.into_iter().map(Ok))?;
726                 Ok(self.wrap_return(docvec![fun, arguments]))
727             }
728         }
729     }
730 
fn_<'a>(&mut self, arguments: &'a [TypedArg], body: &'a TypedExpr) -> Output<'a>731     fn fn_<'a>(&mut self, arguments: &'a [TypedArg], body: &'a TypedExpr) -> Output<'a> {
732         // New function, this is now the tail position
733         let tail = self.tail_position;
734         self.tail_position = true;
735         // And there's a new scope
736         let scope = self.current_scope_vars.clone();
737         for name in arguments.iter().flat_map(Arg::get_variable_name) {
738             let _ = self.current_scope_vars.remove(name);
739         }
740 
741         // This is a new function so unset the recorded name so that we don't
742         // mistakenly trigger tail call optimisation
743         let mut name = None;
744         std::mem::swap(&mut self.function_name, &mut name);
745 
746         // Generate the function body
747         let result = self.expression(body);
748 
749         // Reset function name, scope, and tail position tracking
750         self.tail_position = tail;
751         self.current_scope_vars = scope;
752         std::mem::swap(&mut self.function_name, &mut name);
753 
754         Ok(docvec!(
755             docvec!(
756                 fun_args(arguments, false),
757                 " => {",
758                 break_("", " "),
759                 result?
760             )
761             .nest(INDENT)
762             .append(break_("", " "))
763             .group(),
764             "}",
765         ))
766     }
767 
record_access<'a>(&mut self, record: &'a TypedExpr, label: &'a str) -> Output<'a>768     fn record_access<'a>(&mut self, record: &'a TypedExpr, label: &'a str) -> Output<'a> {
769         self.not_in_tail_position(|gen| {
770             let record = gen.wrap_expression(record)?;
771             Ok(docvec![record, ".", label])
772         })
773     }
774 
record_update<'a>( &mut self, record: &'a TypedExpr, updates: &'a [TypedRecordUpdateArg], ) -> Output<'a>775     fn record_update<'a>(
776         &mut self,
777         record: &'a TypedExpr,
778         updates: &'a [TypedRecordUpdateArg],
779     ) -> Output<'a> {
780         self.not_in_tail_position(|gen| {
781             let record = gen.wrap_expression(record)?;
782             let fields = updates
783                 .iter()
784                 .map(|TypedRecordUpdateArg { label, value, .. }| {
785                     (label.to_doc(), gen.wrap_expression(value))
786                 });
787             let object = try_wrap_object(fields)?;
788             Ok(docvec![record, ".withFields(", object, ")"])
789         })
790     }
791 
tuple_index<'a>(&mut self, tuple: &'a TypedExpr, index: u64) -> Output<'a>792     fn tuple_index<'a>(&mut self, tuple: &'a TypedExpr, index: u64) -> Output<'a> {
793         self.not_in_tail_position(|gen| {
794             let tuple = gen.wrap_expression(tuple)?;
795             Ok(docvec![tuple, Document::String(format!("[{}]", index))])
796         })
797     }
798 
bin_op<'a>( &mut self, name: &'a BinOp, left: &'a TypedExpr, right: &'a TypedExpr, ) -> Output<'a>799     fn bin_op<'a>(
800         &mut self,
801         name: &'a BinOp,
802         left: &'a TypedExpr,
803         right: &'a TypedExpr,
804     ) -> Output<'a> {
805         match name {
806             BinOp::And => self.print_bin_op(left, right, "&&"),
807             BinOp::Or => self.print_bin_op(left, right, "||"),
808             BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(left, right, "<"),
809             BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(left, right, "<="),
810             BinOp::Eq => self.equal(left, right, true),
811             BinOp::NotEq => self.equal(left, right, false),
812             BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(left, right, ">"),
813             BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(left, right, ">="),
814             BinOp::AddInt | BinOp::AddFloat => self.print_bin_op(left, right, "+"),
815             BinOp::SubInt | BinOp::SubFloat => self.print_bin_op(left, right, "-"),
816             BinOp::MultInt => self.mult_int(left, right),
817             BinOp::MultFloat => self.print_bin_op(left, right, "*"),
818             BinOp::ModuloInt => self.print_bin_op(left, right, "%"),
819             BinOp::DivInt => self.div_int(left, right),
820             BinOp::DivFloat => self.div_float(left, right),
821         }
822     }
823 
mult_int<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a>824     fn mult_int<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> {
825         let left = self.not_in_tail_position(|gen| gen.expression(left))?;
826         let right = self.not_in_tail_position(|gen| gen.expression(right))?;
827         Ok(docvec!("Math.imul", wrap_args([left, right])))
828     }
829 
div_int<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a>830     fn div_int<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> {
831         let left = self.not_in_tail_position(|gen| gen.expression(left))?;
832         let right = self.not_in_tail_position(|gen| gen.expression(right))?;
833         self.tracker.int_division_used = true;
834         Ok(docvec!("divideInt", wrap_args([left, right])))
835     }
836 
div_float<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a>837     fn div_float<'a>(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> {
838         let left = self.not_in_tail_position(|gen| gen.expression(left))?;
839         let right = self.not_in_tail_position(|gen| gen.expression(right))?;
840         self.tracker.float_division_used = true;
841         Ok(docvec!("divideFloat", wrap_args([left, right])))
842     }
843 
equal<'a>( &mut self, left: &'a TypedExpr, right: &'a TypedExpr, should_be_equal: bool, ) -> Output<'a>844     fn equal<'a>(
845         &mut self,
846         left: &'a TypedExpr,
847         right: &'a TypedExpr,
848         should_be_equal: bool,
849     ) -> Output<'a> {
850         // If it is a simple scalar type then we can use JS' reference identity
851         if is_js_scalar(left.type_()) {
852             let left_doc = self.not_in_tail_position(|gen| gen.binop_child_expression(left))?;
853             let right_doc = self.not_in_tail_position(|gen| gen.binop_child_expression(right))?;
854             let operator = if should_be_equal { " === " } else { " !== " };
855             return Ok(docvec!(left_doc, operator, right_doc));
856         }
857 
858         // Other types must be compared using structural equality
859         let left = self.not_in_tail_position(|gen| gen.wrap_expression(left))?;
860         let right = self.not_in_tail_position(|gen| gen.wrap_expression(right))?;
861         Ok(self.prelude_equal_call(should_be_equal, left, right))
862     }
863 
prelude_equal_call<'a>( &mut self, should_be_equal: bool, left: Document<'a>, right: Document<'a>, ) -> Document<'a>864     pub(super) fn prelude_equal_call<'a>(
865         &mut self,
866         should_be_equal: bool,
867         left: Document<'a>,
868         right: Document<'a>,
869     ) -> Document<'a> {
870         // Record that we need to import the prelude's isEqual function into the module
871         self.tracker.object_equality_used = true;
872         // Construct the call
873         let args = wrap_args([left, right]);
874         let operator = if should_be_equal {
875             "isEqual"
876         } else {
877             "!isEqual"
878         };
879         docvec!(operator, args)
880     }
881 
print_bin_op<'a>( &mut self, left: &'a TypedExpr, right: &'a TypedExpr, op: &'a str, ) -> Output<'a>882     fn print_bin_op<'a>(
883         &mut self,
884         left: &'a TypedExpr,
885         right: &'a TypedExpr,
886         op: &'a str,
887     ) -> Output<'a> {
888         let left = self.not_in_tail_position(|gen| gen.binop_child_expression(left))?;
889         let right = self.not_in_tail_position(|gen| gen.binop_child_expression(right))?;
890         Ok(docvec!(left, " ", op, " ", right))
891     }
892 
todo<'a>(&mut self, message: &'a Option<String>, location: &'a SrcSpan) -> Document<'a>893     fn todo<'a>(&mut self, message: &'a Option<String>, location: &'a SrcSpan) -> Document<'a> {
894         let tail_position = self.tail_position;
895         self.tail_position = false;
896 
897         let message = message
898             .as_deref()
899             .unwrap_or("This has not yet been implemented");
900         let doc = self.throw_error("todo", message, *location, vec![]);
901 
902         // Reset tail position so later values are returned as needed. i.e.
903         // following clauses in a case expression.
904         self.tail_position = tail_position;
905 
906         doc
907     }
908 
throw_error<'a, Fields>( &mut self, error_name: &'a str, message: &'a str, location: SrcSpan, fields: Fields, ) -> Document<'a> where Fields: IntoIterator<Item = (&'a str, Document<'a>)>,909     fn throw_error<'a, Fields>(
910         &mut self,
911         error_name: &'a str,
912         message: &'a str,
913         location: SrcSpan,
914         fields: Fields,
915     ) -> Document<'a>
916     where
917         Fields: IntoIterator<Item = (&'a str, Document<'a>)>,
918     {
919         self.tracker.throw_error_used = true;
920         let module = Document::String(self.module_name.join("/")).surround('"', '"');
921         // TODO switch to use `string(self.function_name)`
922         // This will require resolving the
923         // difference in lifetimes 'module and 'a.
924         let function = Document::String(self.function_name.unwrap_or_default().to_string())
925             .surround("\"", "\"");
926         let line = self.line_numbers.line_number(location.start).to_doc();
927         let fields = wrap_object(fields.into_iter().map(|(k, v)| (k.to_doc(), Some(v))));
928         docvec![
929             "throwError",
930             wrap_args([
931                 string(error_name),
932                 module,
933                 line,
934                 function,
935                 string(message),
936                 fields
937             ]),
938             ";"
939         ]
940     }
941 
module_select<'a>( &mut self, module: &'a str, label: &'a str, constructor: &'a ModuleValueConstructor, ) -> Document<'a>942     fn module_select<'a>(
943         &mut self,
944         module: &'a str,
945         label: &'a str,
946         constructor: &'a ModuleValueConstructor,
947     ) -> Document<'a> {
948         match constructor {
949             ModuleValueConstructor::Fn | ModuleValueConstructor::Constant { .. } => {
950                 docvec!["$", module, ".", maybe_escape_identifier_doc(label)]
951             }
952 
953             ModuleValueConstructor::Record {
954                 name, arity, type_, ..
955             } => self.record_constructor(type_.clone(), Some(module), name, *arity),
956         }
957     }
958 
pattern_into_assignment_doc<'a>( &mut self, compiled_pattern: CompiledPattern<'a>, subject: Document<'a>, location: SrcSpan, ) -> Output<'a>959     fn pattern_into_assignment_doc<'a>(
960         &mut self,
961         compiled_pattern: CompiledPattern<'a>,
962         subject: Document<'a>,
963         location: SrcSpan,
964     ) -> Output<'a> {
965         if compiled_pattern.checks.is_empty() {
966             return Ok(Self::pattern_assignments_doc(compiled_pattern.assignments));
967         }
968         if compiled_pattern.assignments.is_empty() {
969             return self.pattern_checks_or_throw_doc(compiled_pattern.checks, subject, location);
970         }
971 
972         Ok(docvec![
973             self.pattern_checks_or_throw_doc(compiled_pattern.checks, subject, location)?,
974             line(),
975             Self::pattern_assignments_doc(compiled_pattern.assignments)
976         ])
977     }
978 
pattern_checks_or_throw_doc<'a>( &mut self, checks: Vec<pattern::Check<'a>>, subject: Document<'a>, location: SrcSpan, ) -> Output<'a>979     fn pattern_checks_or_throw_doc<'a>(
980         &mut self,
981         checks: Vec<pattern::Check<'a>>,
982         subject: Document<'a>,
983         location: SrcSpan,
984     ) -> Output<'a> {
985         let checks = self.pattern_checks_doc(checks, false);
986         Ok(docvec![
987             "if (",
988             docvec![break_("", ""), checks].nest(INDENT),
989             break_("", ""),
990             ") {",
991             docvec![line(), self.assignment_no_match(location, subject)?].nest(INDENT),
992             line(),
993             "}",
994         ]
995         .group())
996     }
997 
pattern_assignments_doc(assignments: Vec<pattern::Assignment<'_>>) -> Document<'_>998     fn pattern_assignments_doc(assignments: Vec<pattern::Assignment<'_>>) -> Document<'_> {
999         let assignments = assignments.into_iter().map(pattern::Assignment::into_doc);
1000         concat(Itertools::intersperse(assignments, line()))
1001     }
1002 
pattern_take_assignments_doc<'a>( &self, compiled_pattern: &mut CompiledPattern<'a>, ) -> Document<'a>1003     fn pattern_take_assignments_doc<'a>(
1004         &self,
1005         compiled_pattern: &mut CompiledPattern<'a>,
1006     ) -> Document<'a> {
1007         let assignments = std::mem::take(&mut compiled_pattern.assignments);
1008         Self::pattern_assignments_doc(assignments)
1009     }
1010 
pattern_take_checks_doc<'a>( &self, compiled_pattern: &mut CompiledPattern<'a>, match_desired: bool, ) -> Document<'a>1011     fn pattern_take_checks_doc<'a>(
1012         &self,
1013         compiled_pattern: &mut CompiledPattern<'a>,
1014         match_desired: bool,
1015     ) -> Document<'a> {
1016         let checks = std::mem::take(&mut compiled_pattern.checks);
1017         self.pattern_checks_doc(checks, match_desired)
1018     }
1019 
pattern_checks_doc<'a>( &self, checks: Vec<pattern::Check<'a>>, match_desired: bool, ) -> Document<'a>1020     fn pattern_checks_doc<'a>(
1021         &self,
1022         checks: Vec<pattern::Check<'a>>,
1023         match_desired: bool,
1024     ) -> Document<'a> {
1025         if checks.is_empty() {
1026             return "true".to_doc();
1027         };
1028         let operator = if match_desired {
1029             break_(" &&", " && ")
1030         } else {
1031             break_(" ||", " || ")
1032         };
1033 
1034         concat(Itertools::intersperse(
1035             checks
1036                 .into_iter()
1037                 .map(|check| check.into_doc(match_desired)),
1038             operator,
1039         ))
1040     }
1041 }
1042 
int(value: &str) -> Document<'_>1043 pub fn int(value: &str) -> Document<'_> {
1044     value.to_doc()
1045 }
1046 
float(value: &str) -> Document<'_>1047 pub fn float(value: &str) -> Document<'_> {
1048     value.to_doc()
1049 }
1050 
constant_expression<'a>( tracker: &mut UsageTracker, expression: &'a TypedConstant, ) -> Output<'a>1051 pub(crate) fn constant_expression<'a>(
1052     tracker: &mut UsageTracker,
1053     expression: &'a TypedConstant,
1054 ) -> Output<'a> {
1055     match expression {
1056         Constant::Int { value, .. } => Ok(int(value)),
1057         Constant::Float { value, .. } => Ok(float(value)),
1058         Constant::String { value, .. } => Ok(string(value)),
1059         Constant::Tuple { elements, .. } => {
1060             array(elements.iter().map(|e| constant_expression(tracker, e)))
1061         }
1062 
1063         Constant::List { elements, .. } => {
1064             tracker.list_used = true;
1065             list(
1066                 elements.iter().map(|e| constant_expression(tracker, e)),
1067                 None,
1068             )
1069         }
1070 
1071         Constant::Record { typ, name, .. } if typ.is_bool() && name == "True" => {
1072             Ok("true".to_doc())
1073         }
1074         Constant::Record { typ, name, .. } if typ.is_bool() && name == "False" => {
1075             Ok("false".to_doc())
1076         }
1077         Constant::Record { typ, .. } if typ.is_nil() => Ok("undefined".to_doc()),
1078 
1079         Constant::Record {
1080             tag,
1081             typ,
1082             args,
1083             module,
1084             ..
1085         } => {
1086             if typ.is_result() {
1087                 if tag == "Ok" {
1088                     tracker.ok_used = true;
1089                 } else {
1090                     tracker.error_used = true;
1091                 }
1092             }
1093             let field_values: Vec<_> = args
1094                 .iter()
1095                 .map(|arg| constant_expression(tracker, &arg.value))
1096                 .try_collect()?;
1097             Ok(construct_record(module.as_deref(), tag, field_values))
1098         }
1099 
1100         Constant::BitString { location, .. } => Err(Error::Unsupported {
1101             feature: "Bit string syntax".to_string(),
1102             location: *location,
1103         }),
1104     }
1105 }
1106 
string(value: &str) -> Document<'_>1107 pub fn string(value: &str) -> Document<'_> {
1108     if value.contains('\n') {
1109         Document::String(value.replace('\n', r#"\n"#)).surround("\"", "\"")
1110     } else {
1111         value.to_doc().surround("\"", "\"")
1112     }
1113 }
1114 
array<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a>1115 pub fn array<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a> {
1116     let elements = Itertools::intersperse(elements.into_iter(), Ok(break_(",", ", ")))
1117         .collect::<Result<Vec<_>, _>>()?
1118         .to_doc();
1119     Ok(docvec![
1120         "[",
1121         docvec![break_("", ""), elements].nest(INDENT),
1122         break_(",", ""),
1123         "]"
1124     ]
1125     .group())
1126 }
1127 
list<'a, I: IntoIterator<Item = Output<'a>>>( elements: I, tail: Option<Document<'a>>, ) -> Output<'a> where I::IntoIter: DoubleEndedIterator + ExactSizeIterator,1128 fn list<'a, I: IntoIterator<Item = Output<'a>>>(
1129     elements: I,
1130     tail: Option<Document<'a>>,
1131 ) -> Output<'a>
1132 where
1133     I::IntoIter: DoubleEndedIterator + ExactSizeIterator,
1134 {
1135     let array = array(elements);
1136     if let Some(tail) = tail {
1137         let args = [array, Ok(tail)];
1138         Ok(docvec!["toList", call_arguments(args)?])
1139     } else {
1140         Ok(docvec!["toList(", array?, ")"])
1141     }
1142 }
1143 
call_arguments<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a>1144 fn call_arguments<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a> {
1145     let elements = Itertools::intersperse(elements.into_iter(), Ok(break_(",", ", ")))
1146         .collect::<Result<Vec<_>, _>>()?
1147         .to_doc();
1148     Ok(docvec![
1149         "(",
1150         docvec![break_("", ""), elements].nest(INDENT),
1151         break_(",", ""),
1152         ")"
1153     ]
1154     .group())
1155 }
1156 
construct_record<'a>( module: Option<&'a str>, name: &'a str, arguments: impl IntoIterator<Item = Document<'a>>, ) -> Document<'a>1157 fn construct_record<'a>(
1158     module: Option<&'a str>,
1159     name: &'a str,
1160     arguments: impl IntoIterator<Item = Document<'a>>,
1161 ) -> Document<'a> {
1162     let mut any_arguments = false;
1163     let arguments = concat(Itertools::intersperse(
1164         arguments.into_iter().map(|a| {
1165             any_arguments = true;
1166             a
1167         }),
1168         break_(",", ", "),
1169     ));
1170     let arguments = docvec![break_("", ""), arguments].nest(INDENT);
1171     let name = if let Some(module) = module {
1172         docvec!["$", module, ".", name]
1173     } else {
1174         name.to_doc()
1175     };
1176     if any_arguments {
1177         docvec!["new ", name, "(", arguments, break_(",", ""), ")"].group()
1178     } else {
1179         docvec!["new ", name, "()"]
1180     }
1181 }
1182 
1183 impl TypedExpr {
handles_own_return(&self) -> bool1184     fn handles_own_return(&self) -> bool {
1185         matches!(
1186             self,
1187             TypedExpr::Try { .. }
1188                 | TypedExpr::Todo { .. }
1189                 | TypedExpr::Call { .. }
1190                 | TypedExpr::Case { .. }
1191                 | TypedExpr::Sequence { .. }
1192                 | TypedExpr::Assignment { .. }
1193         )
1194     }
1195 }
1196 
1197 impl BinOp {
is_operator_to_wrap(&self) -> bool1198     fn is_operator_to_wrap(&self) -> bool {
1199         match self {
1200             BinOp::And
1201             | BinOp::Or
1202             | BinOp::Eq
1203             | BinOp::NotEq
1204             | BinOp::LtInt
1205             | BinOp::LtEqInt
1206             | BinOp::LtFloat
1207             | BinOp::LtEqFloat
1208             | BinOp::GtEqInt
1209             | BinOp::GtInt
1210             | BinOp::GtEqFloat
1211             | BinOp::GtFloat
1212             | BinOp::AddInt
1213             | BinOp::AddFloat
1214             | BinOp::SubInt
1215             | BinOp::SubFloat
1216             | BinOp::MultFloat
1217             | BinOp::DivInt
1218             | BinOp::DivFloat
1219             | BinOp::ModuloInt => true,
1220             BinOp::MultInt => false,
1221         }
1222     }
1223 }
1224 
is_js_scalar(t: Arc<Type>) -> bool1225 pub fn is_js_scalar(t: Arc<Type>) -> bool {
1226     t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string()
1227 }
1228