Fork of daniellemaywood.uk/gleam — Wasm codegen work
57 kB
1639 lines
1//! This module implements the function inlining optimisation. This allows
2//! function calls to be inlined at the callsite, and replaced with the contents
3//! of the function which is being called.
4//!
5//! Function inlining is useful for two main reasons:
6//! - It removes the overhead of calling other functions and jumping around
7//! execution too much
8//! - It removes the barrier of the function call between the code around the
9//! call, and the code inside the called function.
10//!
11//! For example, the following Gleam code make heavy use of `use` sugar and higher
12//! order functions:
13//!
14//! ```gleam
15//! pub fn try_sum(list: List(Result(String, Nil)), sum: Int) -> Result(Int, Nil) {
16//! use <- bool.guard(when: sum >= 1000, return: Ok(sum))
17//! case list {
18//! [] -> Ok(sum)
19//! [first, ..rest] -> {
20//! use number <- result.try(int.parse(first))
21//! try_sum(rest, sum + number)
22//! }
23//! }
24//! }
25//! ```
26//!
27//! This can make the code easier to read, but it normally would have a performance
28//! cost. There are two called functions, and two implicit anonymous functions.
29//! This function is also not tail recursive, as it uses higher order functions
30//! inside its body.
31//!
32//! However, with function inlining, the above code can be optimised to:
33//!
34//! ```gleam
35//! pub fn try_sum(list: List(Result(String, Nil)), sum: Int) -> Result(Int, Nil) {
36//! case sum >= 1000 {
37//! True -> Ok(sum)
38//! False -> case list {
39//! [] -> Ok(sum)
40//! [first, ..rest] -> {
41//! case int.parse(first) {
42//! Ok(number) -> try_sum(rest, sum + number)
43//! Error(error) -> Error(error)
44//! }
45//! }
46//! }
47//! }
48//! }
49//! ```
50//!
51//! Which now has no extra function calls, and is tail recursive!
52//!
53//! The process of function inlining is quite simple really. It is implemented
54//! using an AST folder, which traverses each node of the AST, and potentially
55//! alters it as it goes.
56//!
57//! Every time we encounter a function call, we decide whether or not we can
58//! inline it. For now, the criteria for inlining is very simple, although a
59//! more complex heuristic-based approach will likely be implemented in the
60//! future. For now though, a function can be inlined if:
61//! It is a standard library function within the hardcoded list - which can be
62//! found in the `inline_function` function - or, it is an anonymous function.
63//!
64//! Inlining anonymous functions allows us to:
65//! - Remove calls to parameters of higher-order functions once those higher-
66//! order functions have been inlined. For example, the following example using
67//! `result.map`:
68//! ```gleam
69//! result.map(Ok(10), fn(x) { x + 1 })
70//! ```
71//!
72//! Without inlining of anonymous function would be turned into:
73//! ```gleam
74//! case Ok(10) {
75//! Ok(value) -> Ok(fn(x) { x + 1 }(value))
76//! Error(error) -> Error(error)
77//! }
78//! ```
79//!
80//! However if we inline anonymous functions also, we remove every call, and
81//! so it becomes:
82//!
83//! ```gleam
84//! case Ok(10) {
85//! Ok(value) -> Ok(value + 1)
86//! Error(error) -> Error(error)
87//! }
88//! ```
89//!
90//! - Remove calls to anonymous functions in pipelines. Sometimes, an anonymous
91//! function is used in a pipeline, which can sometimes be the result of an
92//! expanded function capture. For example:
93//!
94//! ```gleam
95//! "10" |> int.parse |> result.unwrap(0) |> fn(x) { x * x } |> something_else
96//! ```
97//!
98//! This can now be desugared to:
99//! ```gleam
100//! let _pipe1 = "10"
101//! let _pipe2 = int.parse(_pipe1)
102//! let _pipe3 = result.unwrap(_pipe2, 0)
103//! let _pipe4 = _pipe3 * _pipe3
104//! something_else(_pipe4)
105//! ```
106//!
107//! See documentation of individual functions to explain better how the process
108//! works.
109//!
110
111use std::{collections::HashMap, sync::Arc};
112
113use ecow::EcoString;
114use itertools::Itertools;
115use vec1::Vec1;
116
117use crate::{
118 STDLIB_PACKAGE_NAME,
119 analyse::Inferred,
120 ast::{
121 ArgNames, Assert, Assignment, AssignmentKind, BitArrayOption, BitArraySegment, CallArg,
122 Definition, FunctionLiteralKind, PipelineAssignmentKind, Publicity, SrcSpan, Statement,
123 TypedArg, TypedAssert, TypedAssignment, TypedClause, TypedDefinition, TypedExpr,
124 TypedExprBitArraySegment, TypedFunction, TypedModule, TypedPattern,
125 TypedPipelineAssignment, TypedStatement, TypedUse,
126 },
127 exhaustiveness::CompiledCase,
128 type_::{
129 self, Deprecation, ModuleInterface, ModuleValueConstructor, PRELUDE_MODULE_NAME,
130 PatternConstructor, Type, TypedCallArg, ValueConstructorVariant, collapse_links,
131 error::VariableOrigin,
132 expression::{Implementations, Purity},
133 },
134};
135
136/// Perform function inlining across an entire module, applying it to each
137/// individual function.
138pub fn module(
139 mut module: TypedModule,
140 modules: &im::HashMap<EcoString, ModuleInterface>,
141) -> TypedModule {
142 let mut inliner = Inliner::new(modules);
143
144 module.definitions = module
145 .definitions
146 .into_iter()
147 .map(|definition| inliner.definition(definition))
148 .collect();
149 module
150}
151
152struct Inliner<'a> {
153 /// Importable modules, containing information about functions which can be
154 /// inlined
155 modules: &'a im::HashMap<EcoString, ModuleInterface>,
156 /// Any variables which can be inlined. This is used when inlining the body
157 /// of function calls. Let's look at an example inlinable function:
158 /// ```gleam
159 /// pub fn add(a, b) {
160 /// a + b
161 /// }
162 /// ```
163 /// If it is called - `add(1, 2)` - it can be inlined to the following:
164 /// ```gleam
165 /// {
166 /// let a = 1
167 /// let b = 2
168 /// a + b
169 /// }
170 /// ```
171 ///
172 /// However, this can be inlined further. Since `a` and `b` are only used
173 /// once each in the body, the whole expression can be reduced to `1 + 2`.
174 ///
175 /// In the above example, this variable would contain `{a: 1, b: 2}`,
176 /// indicating the names of the variables to be inlined, as well as the
177 /// values to replace them with.
178 inline_variables: HashMap<EcoString, TypedExpr>,
179}
180
181impl Inliner<'_> {
182 fn new(modules: &im::HashMap<EcoString, ModuleInterface>) -> Inliner<'_> {
183 Inliner {
184 modules,
185 inline_variables: HashMap::new(),
186 }
187 }
188
189 /// Perform inlining over a single definition. This only does anything for
190 /// function definitions as none of the other definitions can contain call
191 /// expressions to be inlined.
192 fn definition(&mut self, definition: TypedDefinition) -> TypedDefinition {
193 match definition {
194 Definition::Function(function_ast) => Definition::Function(self.function(function_ast)),
195 Definition::TypeAlias(_)
196 | Definition::CustomType(_)
197 | Definition::Import(_)
198 | Definition::ModuleConstant(_) => definition,
199 }
200 }
201
202 fn function(&mut self, mut function: TypedFunction) -> TypedFunction {
203 function.body = function.body.mapped(|statement| self.statement(statement));
204 function
205 }
206
207 fn statement(&mut self, statement: TypedStatement) -> TypedStatement {
208 match statement {
209 Statement::Expression(expression_ast) => {
210 Statement::Expression(self.expression(expression_ast))
211 }
212 Statement::Assignment(assignment_ast) => {
213 Statement::Assignment(Box::new(self.assignment(*assignment_ast)))
214 }
215 Statement::Use(use_ast) => Statement::Use(self.use_(use_ast)),
216 Statement::Assert(assert_ast) => Statement::Assert(self.assert(assert_ast)),
217 }
218 }
219
220 fn assert(&mut self, assert: TypedAssert) -> TypedAssert {
221 let Assert {
222 location,
223 value,
224 message,
225 } = assert;
226
227 Assert {
228 location,
229 value: self.expression(value),
230 message: message.map(|expression| self.expression(expression)),
231 }
232 }
233
234 fn use_(&mut self, mut use_: TypedUse) -> TypedUse {
235 use_.call = self.boxed_expression(use_.call);
236 use_
237 }
238
239 fn assignment(&mut self, assignment: TypedAssignment) -> TypedAssignment {
240 let Assignment {
241 location,
242 value,
243 pattern,
244 kind,
245 annotation,
246 compiled_case,
247 } = assignment;
248
249 Assignment {
250 location,
251 value: self.expression(value),
252 pattern,
253 kind: self.assignment_kind(kind),
254 annotation,
255 compiled_case,
256 }
257 }
258
259 fn assignment_kind(&mut self, kind: AssignmentKind<TypedExpr>) -> AssignmentKind<TypedExpr> {
260 match kind {
261 AssignmentKind::Let | AssignmentKind::Generated => kind,
262 AssignmentKind::Assert {
263 location,
264 assert_keyword_start,
265 message,
266 } => AssignmentKind::Assert {
267 location,
268 assert_keyword_start,
269 message: message.map(|expression| self.expression(expression)),
270 },
271 }
272 }
273
274 fn boxed_expression(&mut self, boxed: Box<TypedExpr>) -> Box<TypedExpr> {
275 Box::new(self.expression(*boxed))
276 }
277
278 fn expressions(&mut self, expressions: Vec<TypedExpr>) -> Vec<TypedExpr> {
279 expressions
280 .into_iter()
281 .map(|expression| self.expression(expression))
282 .collect()
283 }
284
285 /// Perform inlining over an expression. This function is recursive, as
286 /// expressions can be deeply nested. Most expressions just recursively
287 /// call this function on each of their component parts, but some have
288 /// special handling.
289 fn expression(&mut self, expression: TypedExpr) -> TypedExpr {
290 match expression {
291 TypedExpr::Int { .. }
292 | TypedExpr::Float { .. }
293 | TypedExpr::String { .. }
294 | TypedExpr::Fn { .. }
295 | TypedExpr::ModuleSelect { .. }
296 | TypedExpr::Invalid { .. } => expression,
297
298 TypedExpr::Var {
299 ref constructor,
300 ref name,
301 ..
302 } => match &constructor.variant {
303 // If this variable can be inlined, replace it with its value.
304 // See the `inline_variables` documentation for an explanation.
305 ValueConstructorVariant::LocalVariable { .. } => {
306 // We remove the variable as inlined variables can only be
307 // inlined once.
308 match self.inline_variables.remove(name) {
309 Some(expression) => expression,
310 None => expression,
311 }
312 }
313 ValueConstructorVariant::ModuleConstant { .. }
314 | ValueConstructorVariant::LocalConstant { .. }
315 | ValueConstructorVariant::ModuleFn { .. }
316 | ValueConstructorVariant::Record { .. } => expression,
317 },
318
319 TypedExpr::Block {
320 location,
321 statements,
322 } => TypedExpr::Block {
323 location,
324 statements: statements.mapped(|statement| self.statement(statement)),
325 },
326
327 TypedExpr::NegateBool { location, value } => TypedExpr::NegateBool {
328 location,
329 value: self.boxed_expression(value),
330 },
331
332 TypedExpr::NegateInt { location, value } => TypedExpr::NegateInt {
333 location,
334 value: self.boxed_expression(value),
335 },
336
337 TypedExpr::Pipeline {
338 location,
339 first_value,
340 assignments,
341 finally,
342 finally_kind,
343 } => self.pipeline(location, first_value, assignments, finally, finally_kind),
344
345 TypedExpr::List {
346 location,
347 type_,
348 elements,
349 tail,
350 } => TypedExpr::List {
351 location,
352 type_,
353 elements: self.expressions(elements),
354 tail: tail.map(|boxed_expression| self.boxed_expression(boxed_expression)),
355 },
356
357 TypedExpr::Call {
358 location,
359 type_,
360 fun,
361 args,
362 } => self.call(location, type_, fun, args),
363
364 TypedExpr::BinOp {
365 location,
366 type_,
367 name,
368 name_location,
369 left,
370 right,
371 } => TypedExpr::BinOp {
372 location,
373 type_,
374 name,
375 name_location,
376 left: self.boxed_expression(left),
377 right: self.boxed_expression(right),
378 },
379
380 TypedExpr::Case {
381 location,
382 type_,
383 subjects,
384 clauses,
385 compiled_case,
386 } => self.case(location, type_, subjects, clauses, compiled_case),
387
388 TypedExpr::RecordAccess {
389 location,
390 field_start,
391 type_,
392 label,
393 index,
394 record,
395 } => TypedExpr::RecordAccess {
396 location,
397 field_start,
398 type_,
399 label,
400 index,
401 record: self.boxed_expression(record),
402 },
403
404 TypedExpr::Tuple {
405 location,
406 type_,
407 elements,
408 } => TypedExpr::Tuple {
409 location,
410 type_,
411 elements: self.expressions(elements),
412 },
413
414 TypedExpr::TupleIndex {
415 location,
416 type_,
417 index,
418 tuple,
419 } => TypedExpr::TupleIndex {
420 location,
421 type_,
422 index,
423 tuple: self.boxed_expression(tuple),
424 },
425
426 TypedExpr::Todo {
427 location,
428 message,
429 kind,
430 type_,
431 } => TypedExpr::Todo {
432 location,
433 message: message.map(|boxed_expression| self.boxed_expression(boxed_expression)),
434 kind,
435 type_,
436 },
437
438 TypedExpr::Panic {
439 location,
440 message,
441 type_,
442 } => TypedExpr::Panic {
443 location,
444 message: message.map(|boxed_expression| self.boxed_expression(boxed_expression)),
445 type_,
446 },
447
448 TypedExpr::Echo {
449 location,
450 type_,
451 expression,
452 } => TypedExpr::Echo {
453 location,
454 expression: expression
455 .map(|boxed_expression| self.boxed_expression(boxed_expression)),
456 type_,
457 },
458
459 TypedExpr::BitArray {
460 location,
461 type_,
462 segments,
463 } => self.bit_array(location, type_, segments),
464
465 TypedExpr::RecordUpdate {
466 location,
467 type_,
468 record,
469 constructor,
470 args,
471 } => TypedExpr::RecordUpdate {
472 location,
473 type_,
474 record: Box::new(self.assignment(*record)),
475 constructor: self.boxed_expression(constructor),
476 args,
477 },
478 }
479 }
480
481 /// Where the magic happens. First, we check the left-hand side of the call
482 /// so see if it's something we can inline. If not, we continue to walk the
483 /// tree like all the other expressions do. If it can be inlined, we follow
484 /// a three-step process:
485 ///
486 /// - Inlining: Here, we replace the reference to the function with an
487 /// anonymous function with the same contents. If the left-hand side is
488 /// already an anonymous function, we skip this step.
489 ///
490 /// - Beta reduction: The call to the anonymous function it transformed into
491 /// a block with assignments for each argument at the beginning
492 ///
493 /// - Optimisation: We then recursively optimise the block. This allows us
494 /// to, for example, inline anonymous functions passed to higher-order
495 /// functions.
496 ///
497 /// Here is an example of inlining `result.map`:
498 ///
499 /// Initial code:
500 /// ```gleam
501 /// let x = Ok(10)
502 /// result.map(x, fn(x) {
503 /// let y = x + 4
504 /// int.to_string(y)
505 /// })
506 /// ```
507 ///
508 /// After inlining:
509 /// ```gleam
510 /// let x = Ok(10)
511 /// fn(result, function) {
512 /// case result {
513 /// Ok(value) -> Ok(function(value))
514 /// Error(error) -> Error(error)
515 /// }
516 /// }(x, fn(x) {
517 /// let y = x + 4
518 /// int.to_string(y)
519 /// })
520 /// ```
521 ///
522 /// After beta reduction:
523 /// ```gleam
524 /// let x = Ok(10)
525 /// {
526 /// let result = x
527 /// let function = fn(x) {
528 /// let y = x + 4
529 /// int.to_string(y)
530 /// }
531 /// case result {
532 /// Ok(value) -> Ok(function(value))
533 /// Error(error) -> Error(error)
534 /// }
535 /// }
536 /// ```
537 ///
538 /// And finally, after the final optimising pass, where this inlining process
539 /// is repeated:
540 /// ```gleam
541 /// let x = Ok(10)
542 /// case x {
543 /// Ok(value) -> Ok({
544 /// let y = x + 4
545 /// int.to_string(y)
546 /// })
547 /// Error(error) -> Error(error)
548 /// }
549 /// ```
550 ///
551 fn call(
552 &mut self,
553 location: SrcSpan,
554 type_: Arc<Type>,
555 function: Box<TypedExpr>,
556 arguments: Vec<TypedCallArg>,
557 ) -> TypedExpr {
558 let arguments = arguments
559 .into_iter()
560 .map(
561 |TypedCallArg {
562 label,
563 location,
564 value,
565 implicit,
566 }| TypedCallArg {
567 label,
568 location,
569 value: self.expression(value),
570 implicit,
571 },
572 )
573 .collect();
574
575 // First, we traverse the left-hand side of this call. If this is called
576 // inside another inlined function, this could potentially inline an
577 // argument, allowing further inlining.
578 let function = self.expression(*function);
579
580 // If the left-hand side is in a block for some reason, for example
581 // `{ fn(x) { x + 1 } }(10)`, we still want to be able to inline it.
582 let function = expand_block(function);
583
584 let function = match function {
585 TypedExpr::Var {
586 ref constructor,
587 ref name,
588 ..
589 } => match &constructor.variant {
590 ValueConstructorVariant::ModuleFn { module, .. } => {
591 // If the function is in the list of inlinable functions in
592 // the module it belongs to, we can inline it!
593 if let Some(function) = self
594 .modules
595 .get(module)
596 .and_then(|module| module.inline_functions.get(name))
597 {
598 // First, we do the actual inlining, by converting it to
599 // an anonymous function.
600 let TypedExpr::Fn {
601 args: parameters,
602 body,
603 ..
604 } = function.to_anonymous_function()
605 else {
606 unreachable!("to_anonymous_function always returns TypedExpr::fn");
607 };
608 // Then, we perform beta reduction, inlining the call to
609 // the anonymous function.
610 return self.inline_anonymous_function_call(
611 ¶meters,
612 arguments,
613 body,
614 &function.inlinable_parameters,
615 );
616 } else {
617 function
618 }
619 }
620 // We cannot inline local variables or constants, as we do not
621 // have enough information to inline them. Records are not actually
622 // function calls, so they also cannot be inlined.
623 ValueConstructorVariant::LocalVariable { .. }
624 | ValueConstructorVariant::ModuleConstant { .. }
625 | ValueConstructorVariant::LocalConstant { .. }
626 | ValueConstructorVariant::Record { .. } => function,
627 },
628 TypedExpr::ModuleSelect {
629 ref constructor,
630 label: ref name,
631 ref module_name,
632 ..
633 } => match constructor {
634 // We use the same logic here as for `TypedExpr::Var` above.
635 ModuleValueConstructor::Fn { .. } => {
636 if let Some(function) = self
637 .modules
638 .get(module_name)
639 .and_then(|module| module.inline_functions.get(name))
640 {
641 let TypedExpr::Fn {
642 args: parameters,
643 body,
644 ..
645 } = function.to_anonymous_function()
646 else {
647 unreachable!("to_anonymous_function always returns TypedExpr::fn");
648 };
649 return self.inline_anonymous_function_call(
650 ¶meters,
651 arguments,
652 body,
653 &function.inlinable_parameters,
654 );
655 } else {
656 function
657 }
658 }
659 ModuleValueConstructor::Record { .. } | ModuleValueConstructor::Constant { .. } => {
660 function
661 }
662 },
663 // Direct calls to anonymous functions can always be inlined
664 TypedExpr::Fn {
665 args: parameters,
666 body,
667 ..
668 } => {
669 return self.inline_anonymous_function_call(¶meters, arguments, body, &[]);
670 }
671 TypedExpr::Int { .. }
672 | TypedExpr::Float { .. }
673 | TypedExpr::String { .. }
674 | TypedExpr::Block { .. }
675 | TypedExpr::Pipeline { .. }
676 | TypedExpr::List { .. }
677 | TypedExpr::Call { .. }
678 | TypedExpr::BinOp { .. }
679 | TypedExpr::Case { .. }
680 | TypedExpr::RecordAccess { .. }
681 | TypedExpr::Tuple { .. }
682 | TypedExpr::TupleIndex { .. }
683 | TypedExpr::Todo { .. }
684 | TypedExpr::Panic { .. }
685 | TypedExpr::Echo { .. }
686 | TypedExpr::BitArray { .. }
687 | TypedExpr::RecordUpdate { .. }
688 | TypedExpr::NegateBool { .. }
689 | TypedExpr::NegateInt { .. }
690 | TypedExpr::Invalid { .. } => function,
691 };
692
693 TypedExpr::Call {
694 location,
695 type_,
696 fun: Box::new(function),
697 args: arguments,
698 }
699 }
700
701 /// Turn a call to an anonymous function into a block with assignments.
702 fn inline_anonymous_function_call(
703 &mut self,
704 parameters: &[TypedArg],
705 arguments: Vec<TypedCallArg>,
706 body: Vec1<TypedStatement>,
707 inlinable_parameters: &[EcoString],
708 ) -> TypedExpr {
709 // Arguments to this call that can be inlined, and do not need an assignment.
710 let mut inline = HashMap::new();
711
712 // We start by collecting all the assignments for parameters which cannot
713 // be inlined.
714 let mut statements = parameters
715 .iter()
716 .zip(arguments)
717 .filter_map(|(parameter, argument)| {
718 let name = parameter.get_variable_name().cloned().unwrap_or("_".into());
719
720 // An argument can be inlined if it is only used once (stored in
721 // the `InlineFunction` structure), and it is pure. Sometime impure
722 // arguments can be inlined, but for simplicity we avoid inlining
723 // all impure arguments for now. This heuristic can be improved
724 // later.
725 if inlinable_parameters.contains(&name)
726 && argument.value.is_pure_value_constructor()
727 {
728 _ = inline.insert(name, argument.value);
729 return None;
730 }
731
732 let type_ = argument.value.type_();
733
734 // Otherwise, we make an assignment which assigns the value of
735 // the argument to the correct parameter name.
736 Some(Statement::Assignment(Box::new(Assignment {
737 location: BLANK_LOCATION,
738 value: argument.value,
739 pattern: TypedPattern::Variable {
740 location: BLANK_LOCATION,
741 name: name.clone(),
742 type_: type_.clone(),
743 origin: VariableOrigin::generated(),
744 },
745 kind: AssignmentKind::Generated,
746 compiled_case: CompiledCase::simple_variable_assignment(name, type_),
747 annotation: None,
748 })))
749 })
750 .collect_vec();
751
752 // If we are performing inlining within an already inlined function, there
753 // might be inlinable variables in the outer scope. However, these cannot be
754 // inlined inside a nested function, so they are saved and restored afterwards.
755 let inline_variables = std::mem::replace(&mut self.inline_variables, inline);
756
757 // Perform inlining on each of the statements in this function's body,
758 // potentially inlining parameters and function calls inside this function.
759 statements.extend(body.into_iter().map(|statement| self.statement(statement)));
760
761 self.inline_variables = inline_variables;
762
763 TypedExpr::Block {
764 location: BLANK_LOCATION,
765 statements: statements
766 .try_into()
767 .expect("Type checking ensures there is at least one statement"),
768 }
769 }
770
771 fn pipeline(
772 &mut self,
773 location: SrcSpan,
774 first_value: TypedPipelineAssignment,
775 assignments: Vec<(TypedPipelineAssignment, PipelineAssignmentKind)>,
776 finally: Box<TypedExpr>,
777 finally_kind: PipelineAssignmentKind,
778 ) -> TypedExpr {
779 let first_value = self.pipeline_assignment(first_value);
780 let assignments = assignments
781 .into_iter()
782 .map(|(assignment, kind)| (self.pipeline_assignment(assignment), kind))
783 .collect();
784 let finally = self.boxed_expression(finally);
785
786 TypedExpr::Pipeline {
787 location,
788 first_value,
789 assignments,
790 finally,
791 finally_kind,
792 }
793 }
794
795 fn pipeline_assignment(
796 &mut self,
797 assignment: TypedPipelineAssignment,
798 ) -> TypedPipelineAssignment {
799 let TypedPipelineAssignment {
800 location,
801 name,
802 value,
803 } = assignment;
804
805 TypedPipelineAssignment {
806 location,
807 name,
808 value: self.boxed_expression(value),
809 }
810 }
811
812 fn bit_array(
813 &mut self,
814 location: SrcSpan,
815 type_: Arc<Type>,
816 segments: Vec<TypedExprBitArraySegment>,
817 ) -> TypedExpr {
818 let segments = segments
819 .into_iter()
820 .map(
821 |BitArraySegment {
822 location,
823 value,
824 options,
825 type_,
826 }| BitArraySegment {
827 location,
828 value: self.boxed_expression(value),
829 options: options
830 .into_iter()
831 .map(|bit_array_option| self.bit_array_option(bit_array_option))
832 .collect(),
833 type_,
834 },
835 )
836 .collect();
837
838 TypedExpr::BitArray {
839 location,
840 type_,
841 segments,
842 }
843 }
844
845 fn bit_array_option(&mut self, option: BitArrayOption<TypedExpr>) -> BitArrayOption<TypedExpr> {
846 match option {
847 BitArrayOption::Bytes { .. }
848 | BitArrayOption::Int { .. }
849 | BitArrayOption::Float { .. }
850 | BitArrayOption::Bits { .. }
851 | BitArrayOption::Utf8 { .. }
852 | BitArrayOption::Utf16 { .. }
853 | BitArrayOption::Utf32 { .. }
854 | BitArrayOption::Utf8Codepoint { .. }
855 | BitArrayOption::Utf16Codepoint { .. }
856 | BitArrayOption::Utf32Codepoint { .. }
857 | BitArrayOption::Signed { .. }
858 | BitArrayOption::Unsigned { .. }
859 | BitArrayOption::Big { .. }
860 | BitArrayOption::Little { .. }
861 | BitArrayOption::Native { .. }
862 | BitArrayOption::Unit { .. } => option,
863 BitArrayOption::Size {
864 location,
865 value,
866 short_form,
867 } => BitArrayOption::Size {
868 location,
869 value: self.boxed_expression(value),
870 short_form,
871 },
872 }
873 }
874
875 fn case(
876 &mut self,
877 location: SrcSpan,
878 type_: Arc<Type>,
879 subjects: Vec<TypedExpr>,
880 clauses: Vec<TypedClause>,
881 compiled_case: CompiledCase,
882 ) -> TypedExpr {
883 let subjects = self.expressions(subjects);
884 let clauses = clauses
885 .into_iter()
886 .map(
887 |TypedClause {
888 location,
889 pattern,
890 alternative_patterns,
891 guard,
892 then,
893 }| TypedClause {
894 location,
895 pattern,
896 alternative_patterns,
897 guard,
898 then: self.expression(then),
899 },
900 )
901 .collect();
902
903 TypedExpr::Case {
904 location,
905 type_,
906 subjects,
907 clauses,
908 compiled_case,
909 }
910 }
911}
912
913/// Removes any blocks which are acting as brackets (they hold a single expression)
914fn expand_block(expression: TypedExpr) -> TypedExpr {
915 match expression {
916 TypedExpr::Block {
917 location,
918 statements,
919 } if statements.len() == 1 => {
920 let first = statements
921 .into_iter()
922 .next()
923 .expect("Vec1 always has a first element");
924
925 match first {
926 // If this is several blocks inside each other, we want to
927 // expand them all.
928 Statement::Expression(inner) => expand_block(inner),
929 Statement::Assignment(_) | Statement::Use(_) | Statement::Assert(_) => {
930 TypedExpr::Block {
931 location,
932 statements: Vec1::new(first),
933 }
934 }
935 }
936 }
937 _ => expression,
938 }
939}
940
941/// Converts a function from the Gleam AST into a special "inlinable function",
942/// which is a simplified version, containing just enough information for us to
943/// perform inlining, while keeping the cache files to a minimum size.
944///
945/// This function also determines whether a function is inlinable. Currently this
946/// just checks it against a list of stdlib functions we want to prioritise
947/// inlining, but later it will be changed to a more complicated heuristic.
948///
949pub fn function_to_inlinable(
950 package: &str,
951 module: &str,
952 function: &TypedFunction,
953) -> Option<InlinableFunction> {
954 let (_, name) = function.name.as_ref()?;
955
956 if !is_inlinable(package, module, name) {
957 return None;
958 }
959
960 let parameters = function
961 .arguments
962 .iter()
963 .map(|argument| match &argument.names {
964 ArgNames::Discard { name, .. } | ArgNames::Named { name, .. } => InlinableParameter {
965 label: None,
966 name: name.clone(),
967 },
968 ArgNames::LabelledDiscard { label, name, .. }
969 | ArgNames::NamedLabelled { label, name, .. } => InlinableParameter {
970 label: Some(label.clone()),
971 name: name.clone(),
972 },
973 })
974 .collect();
975
976 let mut converter = FunctionToInlinable::new(&function.arguments);
977
978 let body = function
979 .body
980 .iter()
981 .map(|statement| converter.statement(statement))
982 .collect::<Option<_>>()?;
983
984 // Figure out which parameters can be inlined within the body of this function.
985 // When we inline a function, we convert it to a block with assignments for
986 // the parameters. Then, if those parameters contain no side effects and are
987 // only referenced once within the body, they can be inlined into the place
988 // they are referenced.
989 //
990 // This code checks for the parameters which are used exactly once, which
991 // can then be inlined. We can't inline parameters which are never used, as
992 // there is nowhere to inline them to, so it doesn't make sense. We still
993 // need to evaluate them though, as there could be side effects caused by
994 // the values passed to them.
995 let inlinable_parameters = converter
996 .parameter_references
997 .into_iter()
998 .filter_map(|((name, _), used)| used.then_some(name))
999 .collect();
1000
1001 Some(InlinableFunction {
1002 parameters,
1003 body,
1004 inlinable_parameters,
1005 })
1006}
1007
1008/// The heuristic to determine whether a function is inlinable. For now, this
1009/// just checks against a list of standard library functions.
1010fn is_inlinable(package: &str, module: &str, name: &str) -> bool {
1011 // For now we only offer inlining of standard library functions
1012 if package != STDLIB_PACKAGE_NAME {
1013 return false;
1014 }
1015
1016 match (module, name) {
1017 // These are the functions which we currently inline
1018 ("gleam/bool", "guard") => true,
1019 ("gleam/bool", "lazy_guard") => true,
1020 ("gleam/result", "try") => true,
1021 ("gleam/result", "then") => true,
1022 ("gleam/result", "map") => true,
1023 _ => false,
1024 }
1025}
1026
1027/// Holds state for converting a `TypedFunction` into an `InlinableFunction`.
1028struct FunctionToInlinable {
1029 /// A map of parameters to a boolean of whether they have been used. Since
1030 /// Gleam has variable shadowing, we must also store the definition location
1031 /// of each parameter to ensure that it is not a variable shadowing the parameter
1032 /// name.
1033 /// If a parameter is used more than once, it is removed from the map, so it
1034 /// is no longer tracked as an inlinable parameter.
1035 parameter_references: HashMap<(EcoString, SrcSpan), bool>,
1036}
1037
1038impl FunctionToInlinable {
1039 fn new(arguments: &[TypedArg]) -> Self {
1040 let parameter_references = arguments
1041 .iter()
1042 .filter_map(|argument| {
1043 let (name, location) = match &argument.names {
1044 ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => return None,
1045 ArgNames::Named { name, location } => (name.clone(), *location),
1046 ArgNames::NamedLabelled {
1047 name,
1048 name_location,
1049 ..
1050 } => (name.clone(), *name_location),
1051 };
1052
1053 Some(((name, location), false))
1054 })
1055 .collect();
1056
1057 Self {
1058 parameter_references,
1059 }
1060 }
1061
1062 fn statement(&mut self, statement: &TypedStatement) -> Option<InlinableExpression> {
1063 match statement {
1064 Statement::Expression(expression) => self.expression(expression),
1065 Statement::Assignment(_) | Statement::Use(_) | Statement::Assert(_) => None,
1066 }
1067 }
1068
1069 /// Converts an expression to an `InlinableExpression`. We only convert a
1070 /// small subset of the AST for now, enough to compile our desired inlinable
1071 /// stdlib functions. Anything else returns `None`, indicating a function
1072 /// cannot be inlined.
1073 fn expression(&mut self, expression: &TypedExpr) -> Option<InlinableExpression> {
1074 match expression {
1075 TypedExpr::Case {
1076 subjects,
1077 clauses,
1078 compiled_case,
1079 type_,
1080 ..
1081 } => {
1082 let subjects = subjects
1083 .iter()
1084 .map(|expression| self.expression(expression))
1085 .collect::<Option<_>>()?;
1086 let clauses = clauses
1087 .iter()
1088 .map(|clause| self.clause(clause))
1089 .collect::<Option<_>>()?;
1090
1091 Some(InlinableExpression::Case {
1092 subjects,
1093 clauses,
1094 compiled_case: compiled_case.clone(),
1095 type_: self.type_(type_),
1096 })
1097 }
1098 TypedExpr::Var {
1099 constructor, name, ..
1100 } => {
1101 match &constructor.variant {
1102 ValueConstructorVariant::LocalVariable { location, .. } => {
1103 let key = (name.clone(), *location);
1104 match self.parameter_references.get_mut(&key) {
1105 Some(true) => {
1106 _ = self.parameter_references.remove(&key);
1107 }
1108 Some(usage) => *usage = true,
1109 None => {}
1110 }
1111 }
1112 ValueConstructorVariant::ModuleConstant { .. }
1113 | ValueConstructorVariant::LocalConstant { .. }
1114 | ValueConstructorVariant::ModuleFn { .. }
1115 | ValueConstructorVariant::Record { .. } => {}
1116 }
1117
1118 Some(InlinableExpression::Variable {
1119 name: name.clone(),
1120 constructor: self.value_constructor(constructor)?,
1121 type_: self.type_(&constructor.type_),
1122 })
1123 }
1124 TypedExpr::Call {
1125 fun, args, type_, ..
1126 } => {
1127 let function = self.expression(fun)?;
1128 let arguments = args
1129 .iter()
1130 .map(|argument| {
1131 Some(InlinableArgument {
1132 label: argument.label.clone(),
1133 value: self.expression(&argument.value)?,
1134 })
1135 })
1136 .collect::<Option<_>>()?;
1137
1138 Some(InlinableExpression::Call {
1139 function: Box::new(function),
1140 arguments,
1141 type_: self.type_(type_),
1142 })
1143 }
1144
1145 TypedExpr::Int { .. }
1146 | TypedExpr::Float { .. }
1147 | TypedExpr::String { .. }
1148 | TypedExpr::Block { .. }
1149 | TypedExpr::Pipeline { .. }
1150 | TypedExpr::Fn { .. }
1151 | TypedExpr::List { .. }
1152 | TypedExpr::BinOp { .. }
1153 | TypedExpr::RecordAccess { .. }
1154 | TypedExpr::ModuleSelect { .. }
1155 | TypedExpr::Tuple { .. }
1156 | TypedExpr::TupleIndex { .. }
1157 | TypedExpr::Todo { .. }
1158 | TypedExpr::Panic { .. }
1159 | TypedExpr::Echo { .. }
1160 | TypedExpr::BitArray { .. }
1161 | TypedExpr::RecordUpdate { .. }
1162 | TypedExpr::NegateBool { .. }
1163 | TypedExpr::NegateInt { .. }
1164 | TypedExpr::Invalid { .. } => None,
1165 }
1166 }
1167
1168 fn type_(&self, type_: &Arc<Type>) -> InlinableType {
1169 match collapse_links(type_.clone()).as_ref() {
1170 Type::Fn { args, return_ } => InlinableType::Function {
1171 arguments: args.iter().map(|argument| self.type_(argument)).collect(),
1172 return_: Box::new(self.type_(return_)),
1173 },
1174 Type::Named {
1175 module, name, args, ..
1176 } if module == PRELUDE_MODULE_NAME => self.prelude_type(name, args),
1177 Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => InlinableType::Other,
1178 }
1179 }
1180
1181 fn prelude_type(&self, name: &str, arguments: &[Arc<Type>]) -> InlinableType {
1182 match (name, arguments) {
1183 ("BitArray", _) => InlinableType::BitArray,
1184 ("Bool", _) => InlinableType::Bool,
1185 ("Float", _) => InlinableType::Float,
1186 ("Int", _) => InlinableType::Int,
1187 ("List", [element]) => InlinableType::List(Box::new(self.type_(element))),
1188 ("Nil", _) => InlinableType::Nil,
1189 ("Result", [ok, error]) => InlinableType::Result {
1190 ok: Box::new(self.type_(ok)),
1191 error: Box::new(self.type_(error)),
1192 },
1193 ("String", _) => InlinableType::String,
1194 ("UtfCodepoint", _) => InlinableType::UtfCodepoint,
1195 _ => InlinableType::Other,
1196 }
1197 }
1198
1199 fn value_constructor(
1200 &mut self,
1201 constructor: &type_::ValueConstructor,
1202 ) -> Option<InlinableValueConstructor> {
1203 match &constructor.variant {
1204 ValueConstructorVariant::LocalVariable { .. } => {
1205 Some(InlinableValueConstructor::LocalVariable)
1206 }
1207 ValueConstructorVariant::ModuleConstant { .. }
1208 | ValueConstructorVariant::LocalConstant { .. } => None,
1209 ValueConstructorVariant::ModuleFn { name, module, .. } => {
1210 Some(InlinableValueConstructor::Function {
1211 name: name.clone(),
1212 module: module.clone(),
1213 })
1214 }
1215 ValueConstructorVariant::Record { name, module, .. } => {
1216 Some(InlinableValueConstructor::Record {
1217 name: name.clone(),
1218 module: module.clone(),
1219 })
1220 }
1221 }
1222 }
1223
1224 fn clause(&mut self, clause: &TypedClause) -> Option<InlinableClause> {
1225 let pattern = clause
1226 .pattern
1227 .iter()
1228 .map(Self::pattern)
1229 .collect::<Option<_>>()?;
1230 let body = self.expression(&clause.then)?;
1231 Some(InlinableClause { pattern, body })
1232 }
1233
1234 fn pattern(pattern: &TypedPattern) -> Option<InlinablePattern> {
1235 match pattern {
1236 TypedPattern::Variable { name, .. } => {
1237 Some(InlinablePattern::Variable { name: name.clone() })
1238 }
1239 TypedPattern::Constructor {
1240 name,
1241 arguments,
1242 constructor: Inferred::Known(inferred),
1243 ..
1244 } => {
1245 let arguments = arguments
1246 .iter()
1247 .map(|argument| {
1248 Some(InlinableArgument {
1249 label: argument.label.clone(),
1250 value: Self::pattern(&argument.value)?,
1251 })
1252 })
1253 .collect::<Option<_>>()?;
1254
1255 Some(InlinablePattern::Constructor {
1256 name: name.clone(),
1257 module: inferred.module.clone(),
1258 arguments,
1259 })
1260 }
1261
1262 TypedPattern::Constructor {
1263 constructor: Inferred::Unknown,
1264 ..
1265 } => None,
1266
1267 TypedPattern::Int { .. }
1268 | TypedPattern::Float { .. }
1269 | TypedPattern::String { .. }
1270 | TypedPattern::VarUsage { .. }
1271 | TypedPattern::Assign { .. }
1272 | TypedPattern::Discard { .. }
1273 | TypedPattern::List { .. }
1274 | TypedPattern::Tuple { .. }
1275 | TypedPattern::BitArray { .. }
1276 | TypedPattern::StringPrefix { .. }
1277 | TypedPattern::Invalid { .. } => None,
1278 }
1279 }
1280}
1281
1282/// A simplified version of a `TypedFunction`.
1283#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1284pub struct InlinableFunction {
1285 pub parameters: Vec<InlinableParameter>,
1286 pub body: Vec<InlinableExpression>,
1287 /// A list of parameters which are only referenced once and can therefore
1288 /// be inlined within the body of this function.
1289 pub inlinable_parameters: Vec<EcoString>,
1290}
1291
1292/// Location information is not stored for inlinable functions, to reduce cache
1293/// size. The only reason we should need location information is for generating
1294/// code for panicking keywords, like `panic` or `todo`.
1295///
1296/// Those are not supported yet, and when they are they will likely require some
1297/// more thought as to how they are implemented, as inlining a function completely
1298/// changes its location in the codebase.
1299const BLANK_LOCATION: SrcSpan = SrcSpan { start: 0, end: 0 };
1300
1301impl InlinableFunction {
1302 /// Converts an `InlinableFunction` to an anonymous function, which can then
1303 /// be inlined within another function.
1304 fn to_anonymous_function(&self) -> TypedExpr {
1305 let parameters = self
1306 .parameters
1307 .iter()
1308 .map(|parameter| parameter.to_typed_arg())
1309 .collect();
1310
1311 let body = self
1312 .body
1313 .iter()
1314 .map(|ast| Statement::Expression(ast.to_expression()))
1315 .collect_vec();
1316
1317 TypedExpr::Fn {
1318 location: BLANK_LOCATION,
1319 type_: unknown_type(),
1320 kind: FunctionLiteralKind::Anonymous {
1321 head: BLANK_LOCATION,
1322 },
1323 args: parameters,
1324 body: body
1325 .try_into()
1326 .expect("Type-checking ensured that the body has at least 1 statement"),
1327 return_annotation: None,
1328 purity: Purity::Unknown,
1329 }
1330 }
1331}
1332
1333#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1334pub enum InlinableExpression {
1335 Case {
1336 subjects: Vec<InlinableExpression>,
1337 clauses: Vec<InlinableClause>,
1338 compiled_case: CompiledCase,
1339 type_: InlinableType,
1340 },
1341
1342 Variable {
1343 name: EcoString,
1344 constructor: InlinableValueConstructor,
1345 type_: InlinableType,
1346 },
1347
1348 Call {
1349 function: Box<InlinableExpression>,
1350 arguments: Vec<InlinableArgument<InlinableExpression>>,
1351 type_: InlinableType,
1352 },
1353}
1354
1355impl InlinableExpression {
1356 fn to_expression(&self) -> TypedExpr {
1357 match self {
1358 InlinableExpression::Case {
1359 subjects,
1360 clauses,
1361 compiled_case,
1362 type_,
1363 } => TypedExpr::Case {
1364 location: BLANK_LOCATION,
1365 type_: type_.to_type(),
1366 subjects: subjects
1367 .iter()
1368 .map(|subject| subject.to_expression())
1369 .collect(),
1370 clauses: clauses
1371 .iter()
1372 .map(|clause| clause.to_typed_clause())
1373 .collect(),
1374 compiled_case: compiled_case.clone(),
1375 },
1376 InlinableExpression::Variable {
1377 name,
1378 constructor,
1379 type_,
1380 } => TypedExpr::Var {
1381 location: BLANK_LOCATION,
1382 constructor: constructor.to_value_constructor(type_.to_type()),
1383 name: name.clone(),
1384 },
1385 InlinableExpression::Call {
1386 function,
1387 arguments,
1388 type_,
1389 } => TypedExpr::Call {
1390 location: BLANK_LOCATION,
1391 type_: type_.to_type(),
1392 fun: Box::new(function.to_expression()),
1393 args: arguments
1394 .iter()
1395 .map(|argument| argument.to_call_arg(Self::to_expression))
1396 .collect(),
1397 },
1398 }
1399 }
1400}
1401
1402#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1403pub struct InlinableClause {
1404 pub pattern: Vec<InlinablePattern>,
1405 pub body: InlinableExpression,
1406}
1407
1408impl InlinableClause {
1409 fn to_typed_clause(&self) -> TypedClause {
1410 TypedClause {
1411 location: BLANK_LOCATION,
1412 pattern: self
1413 .pattern
1414 .iter()
1415 .map(|pattern| pattern.to_typed_pattern())
1416 .collect(),
1417 alternative_patterns: Vec::new(),
1418 guard: None,
1419 then: self.body.to_expression(),
1420 }
1421 }
1422}
1423
1424#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1425pub enum InlinablePattern {
1426 Constructor {
1427 name: EcoString,
1428 module: EcoString,
1429 arguments: Vec<InlinableArgument<InlinablePattern>>,
1430 },
1431
1432 Variable {
1433 name: EcoString,
1434 },
1435}
1436
1437impl InlinablePattern {
1438 fn to_typed_pattern(&self) -> TypedPattern {
1439 match self {
1440 InlinablePattern::Constructor {
1441 name,
1442 module,
1443 arguments,
1444 } => TypedPattern::Constructor {
1445 location: BLANK_LOCATION,
1446 name_location: BLANK_LOCATION,
1447 name: name.clone(),
1448 arguments: arguments
1449 .iter()
1450 .map(|argument| argument.to_call_arg(Self::to_typed_pattern))
1451 .collect(),
1452 module: None,
1453 constructor: Inferred::Known(PatternConstructor {
1454 name: name.clone(),
1455 field_map: None,
1456 documentation: None,
1457 module: module.clone(),
1458 location: BLANK_LOCATION,
1459 constructor_index: 0,
1460 }),
1461 spread: None,
1462 type_: unknown_type(),
1463 },
1464 InlinablePattern::Variable { name } => TypedPattern::Variable {
1465 location: BLANK_LOCATION,
1466 name: name.clone(),
1467 type_: unknown_type(),
1468 origin: VariableOrigin::generated(),
1469 },
1470 }
1471 }
1472}
1473
1474#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1475pub enum InlinableValueConstructor {
1476 LocalVariable,
1477 Function { name: EcoString, module: EcoString },
1478 Record { name: EcoString, module: EcoString },
1479}
1480
1481impl InlinableValueConstructor {
1482 fn to_value_constructor(&self, type_: Arc<Type>) -> type_::ValueConstructor {
1483 let variant = match self {
1484 InlinableValueConstructor::LocalVariable => ValueConstructorVariant::LocalVariable {
1485 location: BLANK_LOCATION,
1486 origin: VariableOrigin::generated(),
1487 },
1488 InlinableValueConstructor::Function { name, module } => {
1489 ValueConstructorVariant::ModuleFn {
1490 name: name.clone(),
1491 field_map: None,
1492 module: module.clone(),
1493 arity: 0,
1494 location: BLANK_LOCATION,
1495 documentation: None,
1496 implementations: Implementations::supporting_all(),
1497 external_erlang: None,
1498 external_javascript: None,
1499 purity: Purity::Unknown,
1500 }
1501 }
1502 InlinableValueConstructor::Record { name, module } => ValueConstructorVariant::Record {
1503 name: name.clone(),
1504 arity: 0,
1505 field_map: None,
1506 location: BLANK_LOCATION,
1507 module: module.clone(),
1508 variants_count: 0,
1509 variant_index: 0,
1510 documentation: None,
1511 },
1512 };
1513 type_::ValueConstructor {
1514 publicity: Publicity::Private,
1515 deprecation: Deprecation::NotDeprecated,
1516 variant,
1517 type_,
1518 }
1519 }
1520}
1521
1522#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1523pub struct InlinableArgument<T> {
1524 pub label: Option<EcoString>,
1525 pub value: T,
1526}
1527
1528impl<T> InlinableArgument<T> {
1529 fn to_call_arg<U, F>(&self, convert_value: F) -> CallArg<U>
1530 where
1531 F: FnOnce(&T) -> U,
1532 {
1533 CallArg {
1534 label: self.label.clone(),
1535 location: BLANK_LOCATION,
1536 value: convert_value(&self.value),
1537 implicit: None,
1538 }
1539 }
1540}
1541
1542#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1543pub struct InlinableParameter {
1544 pub label: Option<EcoString>,
1545 pub name: EcoString,
1546}
1547
1548impl InlinableParameter {
1549 fn to_typed_arg(&self) -> TypedArg {
1550 let is_discard = self.name.starts_with('_');
1551
1552 let names = match &self.label {
1553 Some(label) if is_discard => ArgNames::LabelledDiscard {
1554 label: label.clone(),
1555 label_location: BLANK_LOCATION,
1556 name: self.name.clone(),
1557 name_location: BLANK_LOCATION,
1558 },
1559 Some(label) => ArgNames::NamedLabelled {
1560 label: label.clone(),
1561 label_location: BLANK_LOCATION,
1562 name: self.name.clone(),
1563 name_location: BLANK_LOCATION,
1564 },
1565 None if is_discard => ArgNames::Discard {
1566 name: self.name.clone(),
1567 location: BLANK_LOCATION,
1568 },
1569 None => ArgNames::Named {
1570 name: self.name.clone(),
1571 location: BLANK_LOCATION,
1572 },
1573 };
1574
1575 TypedArg {
1576 names,
1577 location: BLANK_LOCATION,
1578 annotation: None,
1579 type_: unknown_type(),
1580 }
1581 }
1582}
1583
1584/// A simplified version of `Type`, which only cares about prelude types. Code
1585/// generation needs this type information, as some prelude types are handled
1586/// specially in certain cases. Custom type don't matter though, so they all get
1587/// reduced into a single value, which decreases cache size.
1588#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1589pub enum InlinableType {
1590 BitArray,
1591 Bool,
1592 Float,
1593 Int,
1594 List(Box<InlinableType>),
1595 Nil,
1596 Result {
1597 ok: Box<InlinableType>,
1598 error: Box<InlinableType>,
1599 },
1600 String,
1601 UtfCodepoint,
1602
1603 Function {
1604 arguments: Vec<InlinableType>,
1605 return_: Box<InlinableType>,
1606 },
1607
1608 Other,
1609}
1610
1611fn unknown_type() -> Arc<Type> {
1612 type_::generic_var(0)
1613}
1614
1615impl InlinableType {
1616 fn to_type(&self) -> Arc<Type> {
1617 match self {
1618 InlinableType::BitArray => type_::bit_array(),
1619 InlinableType::Bool => type_::bool(),
1620 InlinableType::Float => type_::float(),
1621 InlinableType::Int => type_::int(),
1622 InlinableType::List(element) => type_::list(element.to_type()),
1623 InlinableType::Nil => type_::nil(),
1624 InlinableType::Result { ok, error } => type_::result(ok.to_type(), error.to_type()),
1625 InlinableType::String => type_::string(),
1626 InlinableType::UtfCodepoint => type_::utf_codepoint(),
1627
1628 InlinableType::Function { arguments, return_ } => type_::fn_(
1629 arguments.iter().map(Self::to_type).collect(),
1630 return_.to_type(),
1631 ),
1632
1633 // Code generation doesn't care about custom types at all, only
1634 // prelude types are handled specially, so we treat custom types as
1635 // opaque generic type variables.
1636 InlinableType::Other => unknown_type(),
1637 }
1638 }
1639}