Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / inline.rs
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 &parameters, 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 &parameters, 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(&parameters, 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}