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

Configure Feed

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

gleam / compiler-core / src / wasm / mir.rs
57 kB 1569 lines
1use std::{collections::HashMap, ops::Deref}; 2 3use ecow::{EcoString, eco_format}; 4use num_bigint::BigInt; 5 6use crate::{ 7 wasm::mir::{ast::*, visit::Visit}, 8 exhaustiveness, type_, 9}; 10 11pub mod ast; 12pub mod lower; 13pub mod visit; 14 15struct FlattenBlockPass; 16 17impl<'mir, T> Visit<'mir, T> for FlattenBlockPass { 18 fn visit_expression(&mut self, expression: &'mir mut Expression<T>) { 19 visit::visit_expression(self, expression); 20 21 if let Expression::Block(inner) = expression 22 && inner.len() == 1 23 { 24 *expression = inner.pop().expect("block should've been poppable"); 25 } 26 } 27 28 fn visit_expression_block(&mut self, expressions: &'mir mut Vec<Expression<T>>) { 29 visit::visit_expression_block(self, expressions); 30 31 *expressions = std::mem::take(expressions) 32 .into_iter() 33 .flat_map(|expr| match expr { 34 Expression::Block(inner) => inner, 35 _ => vec![expr], 36 }) 37 .collect(); 38 } 39} 40 41struct RemoveUnusedPass; 42 43impl<'mir, T> Visit<'mir, T> for RemoveUnusedPass { 44 fn visit_expression_block(&mut self, expressions: &'mir mut Vec<Expression<T>>) { 45 visit::visit_expression_block(self, expressions); 46 47 let last_index = expressions.len() - 1; 48 49 *expressions = std::mem::take(expressions) 50 .into_iter() 51 .enumerate() 52 .filter_map(|(index, expr)| match expr { 53 Expression::Var { .. } 54 | Expression::FunctionRef { .. } 55 | Expression::Int { .. } 56 | Expression::Float { .. } 57 | Expression::Bool { .. } 58 | Expression::String { .. } 59 if index != last_index => 60 { 61 None 62 } 63 64 _ => Some(expr), 65 }) 66 .collect(); 67 } 68} 69 70#[derive(Default)] 71struct RemoveUselessSetPass { 72 replacements: HashMap<EcoString, EcoString>, 73} 74 75impl<'mir, T> Visit<'mir, T> for RemoveUselessSetPass { 76 fn visit_expression(&mut self, expression: &'mir mut Expression<T>) { 77 visit::visit_expression(self, expression); 78 79 if let Expression::Set { name, value } = expression { 80 if let Expression::Var(var) = value.as_ref() { 81 _ = self 82 .replacements 83 .insert(name.name.clone(), var.name.clone()); 84 85 _ = var; 86 _ = name; 87 } 88 } 89 } 90 91 fn visit_expression_block(&mut self, expressions: &'mir mut Vec<Expression<T>>) { 92 visit::visit_expression_block(self, expressions); 93 94 *expressions = std::mem::take(expressions) 95 .into_iter() 96 .filter_map(|expr| match expr { 97 Expression::Set { name, value: _ } 98 if self.replacements.contains_key(&name.name) => 99 { 100 None 101 } 102 _ => Some(expr), 103 }) 104 .collect(); 105 } 106 107 fn visit_expression_var(&mut self, var: &'mir mut Var<T>) { 108 if let Some(replacement) = self.replacements.get(&var.name) { 109 var.name = replacement.clone(); 110 } 111 } 112} 113 114#[derive(Debug)] 115pub struct Translator<'ast> { 116 module: &'ast crate::ast::TypedModule, 117} 118 119impl<'ast> Translator<'ast> { 120 pub fn new(module: &'ast crate::ast::TypedModule) -> Self { 121 Self { module } 122 } 123 124 pub fn translate(self) -> Module<IncompleteType> { 125 let mut module = Module { 126 name: self.module.name.clone(), 127 functions: vec![], 128 }; 129 130 for function in &self.module.definitions.functions { 131 module.functions.push(self.translate_function(function)); 132 } 133 for custom_type in &self.module.definitions.custom_types { 134 module 135 .functions 136 .append(&mut self.translate_custom_type(custom_type)); 137 } 138 // Type aliases, imports, and module constants are not lowered to MIR. 139 140 FlattenBlockPass.visit_module(&mut module); 141 RemoveUnusedPass.visit_module(&mut module); 142 RemoveUselessSetPass::default().visit_module(&mut module); 143 144 module 145 } 146 147 fn translate_custom_type( 148 &self, 149 custom_type: &crate::ast::TypedCustomType, 150 ) -> Vec<Function<IncompleteType>> { 151 let mut functions = vec![]; 152 153 for (index, constructor) in custom_type.constructors.iter().enumerate() { 154 let parameters = constructor 155 .arguments 156 .iter() 157 .enumerate() 158 .map(|(index, argument)| FunctionParameter { 159 name: Some(eco_format!("_{index}")), 160 type_: Self::translate_type(&argument.type_), 161 }) 162 .collect(); 163 164 let field_types: Vec<_> = constructor 165 .arguments 166 .iter() 167 .map(|argument| Self::translate_type(&argument.type_)) 168 .collect(); 169 let struct_type = IncompleteType::Struct { 170 elements: field_types.clone(), 171 }; 172 173 functions.push(Function::<IncompleteType> { 174 name: constructor.name.clone(), 175 return_type: struct_type.clone(), 176 parameters, 177 body: Some(Expression::Block(vec![Expression::<IncompleteType>::Struct { 178 tag: index as u32, 179 items: constructor 180 .arguments 181 .iter() 182 .enumerate() 183 .map(|(index, argument)| { 184 Expression::Var(Var { 185 name: eco_format!("_{index}"), 186 type_: Self::translate_type(&argument.type_), 187 }) 188 }) 189 .collect(), 190 type_: struct_type, 191 }])), 192 external_wasm: None, 193 }); 194 } 195 196 functions 197 } 198 199 fn translate_function(&self, function: &crate::ast::TypedFunction) -> Function<IncompleteType> { 200 let (_, name) = function.name.clone().expect("function should have a name"); 201 202 let return_type = Self::translate_type(&function.return_type); 203 204 let mut translator = BodyTranslator::default(); 205 let arguments: Vec<_> = Iterator::collect(function.arguments.iter().map(|argument| { 206 argument.get_variable_name().map(|name| { 207 translator.make_var(name.clone(), Self::translate_type(&argument.type_)) 208 }) 209 })); 210 211 let parameters = Iterator::collect(function.arguments.iter().zip(arguments.iter()).map( 212 |(argument, name)| { 213 let type_ = Self::translate_type(&argument.type_); 214 215 FunctionParameter { 216 name: name.clone().map(|name| name.name), 217 type_, 218 } 219 }, 220 )); 221 222 let external_wasm = function 223 .external_wasm 224 .as_ref() 225 .map(|(module, name, _)| (module.clone(), name.clone())); 226 let body = if external_wasm.is_some() && function.body.is_empty() { 227 None 228 } else { 229 Some(translator.translate(&function.body)) 230 }; 231 232 Function { 233 name, 234 body, 235 return_type, 236 parameters, 237 external_wasm, 238 } 239 } 240 241 fn translate_type(type_: &type_::Type) -> IncompleteType { 242 match type_ { 243 type_::Type::Named { 244 publicity: _, 245 package: _, 246 module, 247 name, 248 arguments, 249 inferred_variant: _, 250 } => match (module.as_str(), name.as_str()) { 251 ("gleam", "Int") => IncompleteType::Int, 252 ("gleam", "Float") => IncompleteType::Float, 253 ("gleam", "Bool") => IncompleteType::Bool, 254 ("gleam", "String") => IncompleteType::String, 255 ("gleam", "List") => { 256 let list_type = Self::translate_type(&arguments[0]); 257 258 IncompleteType::List(Box::new(list_type)) 259 } 260 (_, _) => IncompleteType::Struct { 261 elements: arguments 262 .iter() 263 .map(|type_| Self::translate_type(type_)) 264 .collect(), 265 }, 266 }, 267 type_::Type::Fn { arguments, return_ } => IncompleteType::Func { 268 arguments: arguments 269 .iter() 270 .map(|arg| Self::translate_type(arg)) 271 .collect(), 272 returns: Box::new(Self::translate_type(return_)), 273 }, 274 type_::Type::Var { type_ } => match type_.borrow().deref() { 275 type_::TypeVar::Link { type_ } => Self::translate_type(type_), 276 type_::TypeVar::Unbound { id } | type_::TypeVar::Generic { id } => { 277 IncompleteType::Generic { id: *id } 278 } 279 }, 280 type_::Type::Tuple { elements } => IncompleteType::Struct { 281 elements: elements 282 .iter() 283 .map(|elem| Self::translate_type(elem)) 284 .collect(), 285 }, 286 } 287 } 288} 289 290#[derive(Debug, Default)] 291pub struct BodyTranslator { 292 variables: HashMap<EcoString, Var<IncompleteType>>, 293 variable_count: usize, 294 295 decision_map: HashMap<usize, Var<IncompleteType>>, 296} 297 298#[derive(Debug, Clone, Copy, PartialEq, Eq)] 299enum DecisionKind { 300 Case, 301 Let, 302} 303 304impl BodyTranslator { 305 fn make_tmp_var(&mut self, type_: IncompleteType) -> Var<IncompleteType> { 306 self.make_var("_tmp".into(), type_) 307 } 308 309 fn make_var(&mut self, name: EcoString, type_: IncompleteType) -> Var<IncompleteType> { 310 self.variable_count += 1; 311 312 let entry = eco_format!("{}${}", name.clone(), self.variable_count); 313 let var = Var { 314 name: entry, 315 type_: type_, 316 }; 317 318 _ = self.variables.insert(name.clone(), var.clone()); 319 320 var 321 } 322 323 fn get_var(&mut self, name: &EcoString) -> Var<IncompleteType> { 324 self.variables 325 .get(name) 326 .cloned() 327 .unwrap_or_else(|| panic!("variable '{name}' not found")) 328 } 329 330 fn in_var_scope<T>(&mut self, func: impl Fn(&mut Self) -> T) -> T { 331 let snapshot = self.variables.clone(); 332 let result = func(self); 333 self.variables = snapshot; 334 result 335 } 336 337 fn in_decision_scope<T>(&mut self, func: impl Fn(&mut Self) -> T) -> T { 338 let snapshot = self.decision_map.clone(); 339 let result = func(self); 340 self.decision_map = snapshot; 341 result 342 } 343 344 fn translate(mut self, body: &[crate::ast::TypedStatement]) -> Expression<IncompleteType> { 345 self.translate_statements(body) 346 } 347 348 fn translate_statements( 349 &mut self, 350 statements: &[crate::ast::TypedStatement], 351 ) -> Expression<IncompleteType> { 352 Expression::Block(Iterator::collect( 353 statements.iter().map(|stmt| self.translate_statement(stmt)), 354 )) 355 } 356 357 fn translate_statement( 358 &mut self, 359 statement: &crate::ast::TypedStatement, 360 ) -> Expression<IncompleteType> { 361 match statement { 362 crate::ast::Statement::Expression(expression) => self.translate_expression(expression), 363 crate::ast::Statement::Assignment(assignment) => self.translate_assignment(assignment), 364 crate::ast::Statement::Use(_) => todo!(), 365 crate::ast::Statement::Assert(_) => todo!(), 366 } 367 } 368 369 fn translate_expression( 370 &mut self, 371 expression: &crate::ast::TypedExpr, 372 ) -> Expression<IncompleteType> { 373 match expression { 374 crate::ast::TypedExpr::Int { 375 location: _, 376 type_: _, 377 value: _, 378 int_value, 379 } => Expression::Int { 380 value: int_value.clone(), 381 }, 382 crate::ast::TypedExpr::Float { 383 location: _, 384 type_: _, 385 value, 386 float_value: _, 387 } => Expression::Float { 388 value: value.clone(), 389 }, 390 crate::ast::TypedExpr::String { 391 location: _, 392 type_: _, 393 value, 394 } => Expression::String { 395 value: crate::strings::convert_string_escape_chars(value), 396 }, 397 crate::ast::TypedExpr::Block { 398 location: _, 399 statements, 400 } => self.in_var_scope(|this| this.translate_statements(statements)), 401 crate::ast::TypedExpr::Pipeline { 402 location: _, 403 first_value, 404 assignments, 405 finally, 406 finally_kind: _, 407 } => { 408 let mut block = vec![]; 409 410 let value = self.translate_expression(&first_value.value); 411 block.push(Expression::Set { 412 name: self.make_var( 413 first_value.name.clone(), 414 Translator::translate_type(&first_value.type_()), 415 ), 416 value: Box::new(value), 417 }); 418 419 for (assignment, _) in assignments { 420 let value = self.translate_expression(&assignment.value); 421 block.push(Expression::Set { 422 name: self.make_var( 423 assignment.name.clone(), 424 Translator::translate_type(&assignment.value.type_()), 425 ), 426 value: Box::new(value), 427 }); 428 } 429 430 block.push(self.translate_expression(finally)); 431 432 Expression::Block(block) 433 } 434 crate::ast::TypedExpr::Var { 435 location: _, 436 constructor, 437 name, 438 } => match &constructor.variant { 439 type_::ValueConstructorVariant::LocalVariable { 440 location: _, 441 origin: _, 442 } => Expression::Var(self.get_var(name)), 443 type_::ValueConstructorVariant::ModuleConstant { 444 documentation: _, 445 location: _, 446 module: _, 447 name: _, 448 literal: _, 449 implementations: _, 450 } => todo!(), 451 type_::ValueConstructorVariant::ModuleFn { 452 name, 453 field_map: _, 454 module, 455 arity, 456 location: _, 457 documentation: _, 458 implementations: _, 459 external_erlang: _, 460 external_javascript: _, 461 external_wasm: _, 462 purity: _, 463 } => Expression::FunctionRef { 464 module: module.clone(), 465 name: name.clone(), 466 arity: *arity, 467 type_: Translator::translate_type(&constructor.type_), 468 }, 469 type_::ValueConstructorVariant::Record { 470 name, 471 arity, 472 field_map: _, 473 location: _, 474 module, 475 variants_count: _, 476 variant_index, 477 documentation: _, 478 } => match (module.as_str(), name.as_str()) { 479 ("gleam", "True") => Expression::Bool { value: true }, 480 ("gleam", "False") => Expression::Bool { value: false }, 481 // Zero-arity constructors are values, not functions. 482 (_, _) if *arity == 0 => Expression::Struct { 483 tag: u32::from(*variant_index), 484 items: vec![], 485 type_: Translator::translate_type(&constructor.type_), 486 }, 487 (_, _) => Expression::FunctionRef { 488 module: module.clone(), 489 name: name.clone(), 490 arity: usize::from(*arity), 491 type_: Translator::translate_type(&constructor.type_), 492 }, 493 }, 494 }, 495 crate::ast::TypedExpr::Fn { 496 location: _, 497 type_: _, 498 kind: _, 499 arguments: _, 500 body: _, 501 return_annotation: _, 502 purity: _, 503 } => todo!(), 504 crate::ast::TypedExpr::List { 505 location: _, 506 type_, 507 elements, 508 tail, 509 } => { 510 let items = elements 511 .iter() 512 .map(|element| self.translate_expression(element)) 513 .collect(); 514 515 let tail = tail 516 .as_ref() 517 .map(|tail| Box::new(self.translate_expression(tail))); 518 519 Expression::List { 520 items, 521 tail, 522 type_: Translator::translate_type(type_), 523 } 524 } 525 crate::ast::TypedExpr::Call { 526 location: _, 527 type_, 528 fun, 529 arguments, 530 open_parenthesis: _, 531 } => { 532 let args: Vec<_> = arguments 533 .iter() 534 .map(|arg| self.translate_expression(&arg.value)) 535 .collect(); 536 537 // Record constructors (including prelude `Ok`/`Error`/`Some`/`None`) 538 // become heap structs directly — the prelude is not monomorphised 539 // into the package as a Wasm module. 540 if let crate::ast::TypedExpr::Var { 541 constructor: 542 type_::ValueConstructor { 543 variant: 544 type_::ValueConstructorVariant::Record { 545 variant_index, .. 546 }, 547 .. 548 }, 549 .. 550 } = fun.as_ref() 551 { 552 return Expression::Struct { 553 tag: u32::from(*variant_index), 554 items: args, 555 type_: Translator::translate_type(type_), 556 }; 557 } 558 559 let target = self.translate_expression(fun); 560 Expression::Call { 561 target: Box::new(target), 562 args, 563 type_: Translator::translate_type(type_), 564 } 565 } 566 crate::ast::TypedExpr::BinOp { 567 location: _, 568 type_: _, 569 operator: name, 570 operator_start: _, 571 left, 572 right, 573 } => { 574 let lhs = Box::new(self.translate_expression(left)); 575 let rhs = Box::new(self.translate_expression(right)); 576 577 match name { 578 crate::ast::BinOp::And => Expression::If { 579 cond: lhs, 580 then: rhs, 581 else_: Box::new(Expression::Bool { value: false }), 582 }, 583 crate::ast::BinOp::Or => Expression::If { 584 cond: lhs, 585 then: Box::new(Expression::Bool { value: true }), 586 else_: rhs, 587 }, 588 crate::ast::BinOp::Eq => Expression::Equals { lhs, rhs }, 589 crate::ast::BinOp::NotEq => Expression::NotEquals { lhs, rhs }, 590 crate::ast::BinOp::LtInt => Expression::IntLt { lhs, rhs }, 591 crate::ast::BinOp::LtEqInt => Expression::IntLtEq { lhs, rhs }, 592 crate::ast::BinOp::LtFloat => Expression::FloatLt { lhs, rhs }, 593 crate::ast::BinOp::LtEqFloat => Expression::FloatLtEq { lhs, rhs }, 594 crate::ast::BinOp::GtEqInt => Expression::IntGtEq { lhs, rhs }, 595 crate::ast::BinOp::GtInt => Expression::IntGt { lhs, rhs }, 596 crate::ast::BinOp::GtEqFloat => Expression::FloatGtEq { lhs, rhs }, 597 crate::ast::BinOp::GtFloat => Expression::FloatGt { lhs, rhs }, 598 crate::ast::BinOp::AddInt => Expression::IntAdd { lhs, rhs }, 599 crate::ast::BinOp::AddFloat => Expression::FloatAdd { lhs, rhs }, 600 crate::ast::BinOp::SubInt => Expression::IntSub { lhs, rhs }, 601 crate::ast::BinOp::SubFloat => Expression::FloatSub { lhs, rhs }, 602 crate::ast::BinOp::MultInt => Expression::IntMul { lhs, rhs }, 603 crate::ast::BinOp::MultFloat => Expression::FloatMul { lhs, rhs }, 604 crate::ast::BinOp::DivInt => Expression::IntDiv { lhs, rhs }, 605 crate::ast::BinOp::DivFloat => Expression::FloatDiv { lhs, rhs }, 606 crate::ast::BinOp::RemainderInt => Expression::IntRem { lhs, rhs }, 607 crate::ast::BinOp::Concatenate => Expression::StringConcat { lhs, rhs }, 608 } 609 } 610 crate::ast::TypedExpr::Case { 611 location: _, 612 type_: _, 613 subjects, 614 clauses, 615 compiled_case, 616 } => self.in_decision_scope(move |this| { 617 let mut block = vec![]; 618 619 for (index, subject) in subjects.iter().enumerate() { 620 let name = this.make_tmp_var(Translator::translate_type(&subject.type_())); 621 let value = this.translate_expression(subject); 622 623 // Map the exhaustiveness subject variable id → MIR local so 624 // decision-tree switches look up the correct value. 625 let subject_variable = &compiled_case.subject_variables[index]; 626 _ = this 627 .decision_map 628 .insert(subject_variable.id, name.clone()); 629 630 block.push(Expression::Set { 631 name: name.clone(), 632 value: Box::new(value), 633 }); 634 } 635 636 block.push(this.translate_decision( 637 DecisionKind::Case, 638 clauses, 639 &compiled_case.tree, 640 )); 641 642 Expression::Block(block) 643 }), 644 crate::ast::TypedExpr::RecordAccess { 645 location: _, 646 field_start: _, 647 type_, 648 label: _, 649 index, 650 record, 651 documentation: _, 652 } => Expression::StructAccess { 653 value: Box::new(self.translate_expression(record)), 654 index: *index, 655 type_: Translator::translate_type(type_), 656 }, 657 crate::ast::TypedExpr::ModuleSelect { 658 location: _, 659 field_start: _, 660 type_, 661 label: _, 662 module_name, 663 module_alias: _, 664 constructor, 665 } => match constructor { 666 type_::ModuleValueConstructor::Fn { module, name, .. } => { 667 Expression::FunctionRef { 668 module: module.clone(), 669 name: name.clone(), 670 arity: match type_.as_ref() { 671 type_::Type::Fn { arguments, .. } => arguments.len(), 672 _ => 0, 673 }, 674 type_: Translator::translate_type(type_), 675 } 676 } 677 type_::ModuleValueConstructor::Constant { literal, .. } => { 678 self.translate_constant(literal) 679 } 680 type_::ModuleValueConstructor::Record { name, arity, .. } => { 681 match (module_name.as_str(), name.as_str()) { 682 ("gleam", "True") => Expression::Bool { value: true }, 683 ("gleam", "False") => Expression::Bool { value: false }, 684 (_, _) => Expression::FunctionRef { 685 module: module_name.clone(), 686 name: name.clone(), 687 arity: usize::from(*arity), 688 type_: Translator::translate_type(type_), 689 }, 690 } 691 } 692 }, 693 crate::ast::TypedExpr::Tuple { 694 location: _, 695 type_, 696 elements, 697 } => Expression::Tuple { 698 items: elements 699 .iter() 700 .map(|element| self.translate_expression(element)) 701 .collect(), 702 type_: Translator::translate_type(type_), 703 }, 704 crate::ast::TypedExpr::TupleIndex { 705 location: _, 706 type_, 707 index, 708 tuple, 709 } => Expression::TupleAccess { 710 value: Box::new(self.translate_expression(tuple)), 711 index: *index, 712 type_: Translator::translate_type(type_), 713 }, 714 crate::ast::TypedExpr::Todo { 715 location: _, 716 message: _, 717 kind: _, 718 type_: _, 719 } => todo!(), 720 crate::ast::TypedExpr::Panic { 721 location: _, 722 message: _, 723 type_: _, 724 } => todo!(), 725 crate::ast::TypedExpr::Echo { 726 location: _, 727 type_: _, 728 expression: _, 729 message: _, 730 } => todo!(), 731 crate::ast::TypedExpr::BitArray { 732 location: _, 733 type_: _, 734 segments: _, 735 } => todo!(), 736 crate::ast::TypedExpr::RecordUpdate { 737 location: _, 738 spread_start: _, 739 type_, 740 updated_record, 741 updated_record_assigned_name, 742 constructor, 743 arguments, 744 } => { 745 let mut block = vec![]; 746 747 // If the record value itself needs to be bound before the 748 // update (non-variable base), define it first. 749 if let Some(name) = updated_record_assigned_name { 750 let value = self.translate_expression(updated_record); 751 let var = self.make_var(name.clone(), value.type_().clone()); 752 block.push(Expression::Set { 753 name: var, 754 value: Box::new(value), 755 }); 756 } 757 758 // A record update is simply a constructor call with filled args. 759 let constructor = self.translate_expression(constructor); 760 let arguments = arguments 761 .iter() 762 .map(|argument| self.translate_expression(&argument.value)) 763 .collect(); 764 765 block.push(Expression::Call { 766 target: Box::new(constructor), 767 args: arguments, 768 type_: Translator::translate_type(type_), 769 }); 770 771 Expression::Block(block) 772 } 773 crate::ast::TypedExpr::PositionalAccess { .. } => todo!(), 774 crate::ast::TypedExpr::NegateBool { location: _, value } => Expression::Equals { 775 lhs: Box::new(self.translate_expression(value)), 776 rhs: Box::new(Expression::Bool { value: false }), 777 }, 778 crate::ast::TypedExpr::NegateInt { location: _, value } => Expression::IntSub { 779 lhs: Box::new(Expression::Int { 780 value: BigInt::from(0), 781 }), 782 rhs: Box::new(self.translate_expression(value)), 783 }, 784 crate::ast::TypedExpr::Invalid { 785 location: _, 786 type_: _, 787 extra_information: _, 788 } => todo!(), 789 } 790 } 791 792 fn translate_assignment( 793 &mut self, 794 assignment: &crate::ast::TypedAssignment, 795 ) -> Expression<IncompleteType> { 796 self.in_decision_scope(|this| { 797 let mut block = vec![]; 798 799 let value_var = 800 this.make_tmp_var(Translator::translate_type(&assignment.value.type_())); 801 let value = this.translate_expression(&assignment.value); 802 803 block.push(Expression::Set { 804 name: value_var.clone(), 805 value: Box::new(value), 806 }); 807 808 if let Some(subject_variable) = assignment.compiled_case.subject_variables.first() { 809 _ = this 810 .decision_map 811 .insert(subject_variable.id, value_var.clone()); 812 } else { 813 _ = this.decision_map.insert(0, value_var.clone()); 814 } 815 816 block.push(this.translate_decision( 817 DecisionKind::Let, 818 &[], 819 &assignment.compiled_case.tree, 820 )); 821 822 block.push(Expression::Var(value_var)); 823 824 Expression::Block(block) 825 }) 826 } 827 828 fn translate_bound_value_type(&mut self, value: &exhaustiveness::BoundValue) -> IncompleteType { 829 match value { 830 exhaustiveness::BoundValue::Variable(variable) => { 831 Translator::translate_type(&variable.type_) 832 } 833 exhaustiveness::BoundValue::LiteralString(_) => IncompleteType::String, 834 exhaustiveness::BoundValue::LiteralInt(_) => IncompleteType::Int, 835 exhaustiveness::BoundValue::LiteralFloat(_) => IncompleteType::Float, 836 exhaustiveness::BoundValue::BitArraySlice { 837 bit_array: _, 838 read_action: _, 839 } => todo!(), 840 exhaustiveness::BoundValue::StringSlice { 841 subject: _, 842 prefix: _, 843 } => IncompleteType::String, 844 } 845 } 846 847 fn translate_decision( 848 &mut self, 849 kind: DecisionKind, 850 clauses: &[crate::ast::TypedClause], 851 decision: &exhaustiveness::Decision, 852 ) -> Expression<IncompleteType> { 853 match decision { 854 exhaustiveness::Decision::Run { body } => { 855 let mut block = vec![]; 856 857 for (binding, value) in &body.bindings { 858 let binding_type = self.translate_bound_value_type(value); 859 let binding = self.make_var(binding.clone(), binding_type); 860 861 block.push(self.translate_binding(binding.clone(), value)); 862 } 863 864 if kind == DecisionKind::Case { 865 block.push(self.translate_expression(&clauses[body.clause_index].then)); 866 } 867 868 Expression::Block(block) 869 } 870 exhaustiveness::Decision::Guard { 871 guard, 872 if_true, 873 if_false, 874 } => { 875 let mut block = vec![]; 876 877 let guard = clauses[*guard] 878 .guard 879 .as_ref() 880 .expect("expected clause to have a guard"); 881 882 let referenced_in_guard = guard.referenced_variables(); 883 884 let (guard_bindings, if_true_bindings): (Vec<_>, Vec<_>) = if_true 885 .bindings 886 .iter() 887 .partition(|(binding, _)| referenced_in_guard.contains(binding)); 888 889 for (binding, value) in guard_bindings { 890 let binding_type = self.translate_bound_value_type(value); 891 let binding = self.make_var(binding.clone(), binding_type); 892 893 block.push(self.translate_binding(binding, value)); 894 } 895 896 let cond = self.translate_guard(guard); 897 898 let if_true = { 899 let mut block = vec![]; 900 901 for (binding, value) in if_true_bindings { 902 let binding_type = self.translate_bound_value_type(value); 903 let binding = self.make_var(binding.clone(), binding_type); 904 905 block.push(self.translate_binding(binding.clone(), value)); 906 } 907 908 block.push(self.translate_expression(&clauses[if_true.clause_index].then)); 909 block 910 }; 911 912 let if_false = self.translate_decision(kind, clauses, if_false); 913 914 block.push(Expression::If { 915 cond: Box::new(cond), 916 then: Box::new(Expression::Block(if_true)), 917 else_: Box::new(if_false), 918 }); 919 920 Expression::Block(block) 921 } 922 exhaustiveness::Decision::Switch { 923 var, 924 choices, 925 fallback, 926 fallback_check, 927 } => { 928 let fallback = 929 self.translate_fallback(kind, var, clauses, fallback, fallback_check); 930 931 DoubleEndedIterator::rfold(choices.iter(), fallback, |fallback, (check, then)| { 932 Expression::If { 933 cond: Box::new( 934 self.translate_runtime_check( 935 check, 936 Expression::Var( 937 self.decision_map 938 .get(&var.id) 939 .cloned() 940 .expect("expected variable to be defined"), 941 ), 942 ), 943 ), 944 then: Box::new(self.translate_decision(kind, clauses, then)), 945 else_: Box::new(fallback), 946 } 947 }) 948 } 949 exhaustiveness::Decision::Fail => todo!(), 950 } 951 } 952 953 fn translate_fallback( 954 &mut self, 955 kind: DecisionKind, 956 var: &exhaustiveness::Variable, 957 clauses: &[crate::ast::TypedClause], 958 decision: &exhaustiveness::Decision, 959 check: &exhaustiveness::FallbackCheck, 960 ) -> Expression<IncompleteType> { 961 match check { 962 exhaustiveness::FallbackCheck::InfiniteCatchAll 963 | exhaustiveness::FallbackCheck::CatchAll { .. } => { 964 self.translate_decision(kind, clauses, decision) 965 } 966 exhaustiveness::FallbackCheck::RuntimeCheck { check } => { 967 let cond = self.translate_runtime_check( 968 check, 969 Expression::Var( 970 self.decision_map 971 .get(&var.id) 972 .cloned() 973 .expect("expected variable to be defined"), 974 ), 975 ); 976 977 let then = self.translate_decision(kind, clauses, decision); 978 979 Expression::If { 980 cond: Box::new(cond), 981 then: Box::new(then.clone()), 982 else_: Box::new(Expression::Panic { 983 type_: then.type_().clone(), 984 }), 985 } 986 } 987 } 988 } 989 990 fn translate_guard( 991 &mut self, 992 guard: &crate::ast::TypedClauseGuard, 993 ) -> Expression<IncompleteType> { 994 match guard { 995 crate::ast::ClauseGuard::Block { location: _, value } => self.translate_guard(value), 996 crate::ast::ClauseGuard::BinaryOperator { 997 location: _, 998 operator, 999 operator_start: _, 1000 left, 1001 right, 1002 } => { 1003 let lhs = Box::new(self.translate_guard(left)); 1004 let rhs = Box::new(self.translate_guard(right)); 1005 match operator { 1006 crate::ast::BinOp::And => Expression::If { 1007 cond: lhs, 1008 then: rhs, 1009 else_: Box::new(Expression::Bool { value: false }), 1010 }, 1011 crate::ast::BinOp::Or => Expression::If { 1012 cond: lhs, 1013 then: Box::new(Expression::Bool { value: true }), 1014 else_: rhs, 1015 }, 1016 crate::ast::BinOp::Eq => Expression::Equals { lhs, rhs }, 1017 crate::ast::BinOp::NotEq => Expression::NotEquals { lhs, rhs }, 1018 crate::ast::BinOp::LtInt => Expression::IntLt { lhs, rhs }, 1019 crate::ast::BinOp::LtEqInt => Expression::IntLtEq { lhs, rhs }, 1020 crate::ast::BinOp::LtFloat => Expression::FloatLt { lhs, rhs }, 1021 crate::ast::BinOp::LtEqFloat => Expression::FloatLtEq { lhs, rhs }, 1022 crate::ast::BinOp::GtInt => Expression::IntGt { lhs, rhs }, 1023 crate::ast::BinOp::GtEqInt => Expression::IntGtEq { lhs, rhs }, 1024 crate::ast::BinOp::GtFloat => Expression::FloatGt { lhs, rhs }, 1025 crate::ast::BinOp::GtEqFloat => Expression::FloatGtEq { lhs, rhs }, 1026 crate::ast::BinOp::AddInt => Expression::IntAdd { lhs, rhs }, 1027 crate::ast::BinOp::AddFloat => Expression::FloatAdd { lhs, rhs }, 1028 crate::ast::BinOp::SubInt => Expression::IntSub { lhs, rhs }, 1029 crate::ast::BinOp::SubFloat => Expression::FloatSub { lhs, rhs }, 1030 crate::ast::BinOp::MultInt => Expression::IntMul { lhs, rhs }, 1031 crate::ast::BinOp::MultFloat => Expression::FloatMul { lhs, rhs }, 1032 crate::ast::BinOp::DivInt => Expression::IntDiv { lhs, rhs }, 1033 crate::ast::BinOp::DivFloat => Expression::FloatDiv { lhs, rhs }, 1034 crate::ast::BinOp::RemainderInt => Expression::IntRem { lhs, rhs }, 1035 crate::ast::BinOp::Concatenate => Expression::StringConcat { lhs, rhs }, 1036 } 1037 } 1038 crate::ast::ClauseGuard::Not { 1039 location: _, 1040 expression, 1041 } => Expression::Equals { 1042 lhs: Box::new(self.translate_guard(expression)), 1043 rhs: Box::new(Expression::Bool { value: false }), 1044 }, 1045 crate::ast::ClauseGuard::Var { 1046 location: _, 1047 type_: _, 1048 name, 1049 definition_location: _, 1050 origin: _, 1051 } => Expression::Var(self.get_var(name)), 1052 crate::ast::ClauseGuard::TupleIndex { 1053 location: _, 1054 index, 1055 type_, 1056 tuple, 1057 } => Expression::StructAccess { 1058 value: Box::new(self.translate_guard(tuple)), 1059 index: *index, 1060 type_: Translator::translate_type(type_), 1061 }, 1062 crate::ast::ClauseGuard::FieldAccess { 1063 label_location: _, 1064 index, 1065 label: _, 1066 type_, 1067 container, 1068 } => Expression::StructAccess { 1069 value: Box::new(self.translate_guard(container)), 1070 index: index.expect("field access expected an index"), 1071 type_: Translator::translate_type(type_), 1072 }, 1073 crate::ast::ClauseGuard::ModuleSelect { 1074 location: _, 1075 field_start: _, 1076 definition_location: _, 1077 type_: _, 1078 label: _, 1079 module_name: _, 1080 module_alias: _, 1081 literal: _, 1082 } => todo!(), 1083 crate::ast::ClauseGuard::Constant(constant) => self.translate_constant(constant), 1084 crate::ast::ClauseGuard::Invalid { .. } => { 1085 unreachable!("invalid guard made it to code generation") 1086 } 1087 } 1088 } 1089 1090 fn translate_constant( 1091 &mut self, 1092 constant: &crate::ast::TypedConstant, 1093 ) -> Expression<IncompleteType> { 1094 match constant { 1095 crate::ast::Constant::Int { 1096 location: _, 1097 value: _, 1098 int_value, 1099 } => Expression::Int { 1100 value: int_value.clone(), 1101 }, 1102 crate::ast::Constant::Float { 1103 location: _, 1104 value, 1105 float_value: _, 1106 } => Expression::Float { 1107 value: value.clone(), 1108 }, 1109 crate::ast::Constant::String { location: _, value } => Expression::String { 1110 value: crate::strings::convert_string_escape_chars(value), 1111 }, 1112 crate::ast::Constant::Tuple { 1113 location: _, 1114 elements, 1115 type_, 1116 } => Expression::Tuple { 1117 items: elements 1118 .iter() 1119 .map(|element| self.translate_constant(element)) 1120 .collect(), 1121 type_: Translator::translate_type(type_), 1122 }, 1123 crate::ast::Constant::List { 1124 location: _, 1125 elements, 1126 type_, 1127 tail: _, 1128 } => Expression::List { 1129 items: elements 1130 .iter() 1131 .map(|element| self.translate_constant(element)) 1132 .collect(), 1133 tail: None, 1134 type_: Translator::translate_type(type_), 1135 }, 1136 crate::ast::Constant::Record { 1137 location: _, 1138 module: _, 1139 name: _, 1140 arguments: _, 1141 type_: _, 1142 field_map: _, 1143 record_constructor: _, 1144 } => todo!(), 1145 crate::ast::Constant::RecordUpdate { .. } => todo!(), 1146 crate::ast::Constant::BitArray { 1147 location: _, 1148 segments: _, 1149 } => todo!(), 1150 crate::ast::Constant::Var { 1151 location: _, 1152 module: _, 1153 name: _, 1154 constructor: _, 1155 type_: _, 1156 } => todo!(), 1157 crate::ast::Constant::StringConcatenation { 1158 location: _, 1159 left, 1160 right, 1161 } => Expression::StringConcat { 1162 lhs: Box::new(self.translate_constant(left)), 1163 rhs: Box::new(self.translate_constant(right)), 1164 }, 1165 crate::ast::Constant::Invalid { 1166 location: _, 1167 type_: _, 1168 extra_information: _, 1169 } => todo!(), 1170 crate::ast::Constant::Todo { 1171 location: _, 1172 type_: _, 1173 message: _, 1174 } => todo!(), 1175 } 1176 } 1177 1178 fn translate_binding( 1179 &mut self, 1180 binding: Var<IncompleteType>, 1181 value: &exhaustiveness::BoundValue, 1182 ) -> Expression<IncompleteType> { 1183 let value = match value { 1184 exhaustiveness::BoundValue::Variable(variable) => Expression::Var( 1185 self.decision_map 1186 .get(&variable.id) 1187 .cloned() 1188 .expect("expected variable to be defined"), 1189 ), 1190 exhaustiveness::BoundValue::LiteralString(eco_string) => Expression::String { 1191 value: eco_string.clone(), 1192 }, 1193 exhaustiveness::BoundValue::LiteralInt(big_int) => Expression::Int { 1194 value: big_int.clone(), 1195 }, 1196 exhaustiveness::BoundValue::LiteralFloat(eco_string) => Expression::Float { 1197 value: eco_string.clone(), 1198 }, 1199 exhaustiveness::BoundValue::BitArraySlice { 1200 bit_array: _, 1201 read_action: _, 1202 } => todo!(), 1203 exhaustiveness::BoundValue::StringSlice { 1204 subject: _, 1205 prefix: _, 1206 } => todo!(), 1207 }; 1208 1209 Expression::Set { 1210 name: binding, 1211 value: Box::new(value), 1212 } 1213 } 1214 1215 fn translate_runtime_check( 1216 &mut self, 1217 check: &exhaustiveness::RuntimeCheck, 1218 against: Expression<IncompleteType>, 1219 ) -> Expression<IncompleteType> { 1220 match check { 1221 exhaustiveness::RuntimeCheck::Int { int_value } => Expression::Equals { 1222 lhs: Box::new(against), 1223 rhs: Box::new(Expression::Int { 1224 value: int_value.clone(), 1225 }), 1226 }, 1227 exhaustiveness::RuntimeCheck::Float { float_value } => Expression::Equals { 1228 lhs: Box::new(against), 1229 rhs: Box::new(Expression::Float { 1230 value: eco_format!("{}", float_value.value()), 1231 }), 1232 }, 1233 exhaustiveness::RuntimeCheck::String { value } => Expression::Equals { 1234 lhs: Box::new(against), 1235 rhs: Box::new(Expression::String { 1236 value: value.clone(), 1237 }), 1238 }, 1239 exhaustiveness::RuntimeCheck::StringPrefix { prefix: _, rest: _ } => todo!(), 1240 exhaustiveness::RuntimeCheck::Tuple { 1241 size: _, 1242 elements: _, 1243 } => todo!(), 1244 exhaustiveness::RuntimeCheck::BitArray { test: _ } => todo!(), 1245 exhaustiveness::RuntimeCheck::Variant { 1246 match_: _, 1247 index, 1248 labels: _, 1249 fields, 1250 } => { 1251 // Prelude `Bool` is a two-variant custom type (`True` = 0, `False` = 1) 1252 // but the Wasm MIR represents booleans as a dedicated `Bool` value. 1253 if fields.is_empty() && against.type_() == IncompleteType::Bool { 1254 return Expression::Equals { 1255 lhs: Box::new(against), 1256 rhs: Box::new(Expression::Bool { 1257 // variant_index 0 is `True` 1258 value: *index == 0, 1259 }), 1260 }; 1261 } 1262 1263 let mut block = vec![]; 1264 1265 for (index, field) in fields.iter().enumerate() { 1266 let var_type = Translator::translate_type(&field.type_); 1267 let var = self.make_tmp_var(var_type.clone()); 1268 1269 _ = self.decision_map.insert(field.id, var.clone()); 1270 1271 block.push(Expression::Set { 1272 name: var, 1273 value: Box::new(Expression::StructAccess { 1274 value: Box::new(against.clone()), 1275 index: index as u64, 1276 type_: var_type, 1277 }), 1278 }); 1279 } 1280 1281 block.push(Expression::Equals { 1282 lhs: Box::new(Expression::Int { 1283 value: BigInt::from(*index), 1284 }), 1285 rhs: Box::new(Expression::StructTag { 1286 value: Box::new(against), 1287 }), 1288 }); 1289 1290 Expression::Block(block) 1291 } 1292 exhaustiveness::RuntimeCheck::NonEmptyList { first, rest } => { 1293 let mut block = vec![]; 1294 let list_type = against.type_(); 1295 1296 let first_type = Translator::translate_type(&first.type_); 1297 let first_var = self.make_tmp_var(first_type.clone()); 1298 _ = self.decision_map.insert(first.id, first_var.clone()); 1299 block.push(Expression::Set { 1300 name: first_var, 1301 value: Box::new(Expression::StructAccess { 1302 value: Box::new(against.clone()), 1303 index: 0, 1304 type_: first_type, 1305 }), 1306 }); 1307 1308 let rest_type = Translator::translate_type(&rest.type_); 1309 let rest_var = self.make_tmp_var(rest_type.clone()); 1310 _ = self.decision_map.insert(rest.id, rest_var.clone()); 1311 block.push(Expression::Set { 1312 name: rest_var, 1313 value: Box::new(Expression::StructAccess { 1314 value: Box::new(against.clone()), 1315 index: 1, 1316 type_: rest_type, 1317 }), 1318 }); 1319 1320 // Non-empty lists are non-null pointers. 1321 block.push(Expression::NotEquals { 1322 lhs: Box::new(against), 1323 rhs: Box::new(Expression::List { 1324 items: vec![], 1325 tail: None, 1326 type_: list_type, 1327 }), 1328 }); 1329 1330 Expression::Block(block) 1331 } 1332 exhaustiveness::RuntimeCheck::EmptyList => { 1333 // Empty lists are the null pointer; compare against the empty list 1334 // literal so both sides share `List` type (i32 in Wasm). 1335 let list_type = against.type_(); 1336 Expression::Equals { 1337 lhs: Box::new(against), 1338 rhs: Box::new(Expression::List { 1339 items: vec![], 1340 tail: None, 1341 type_: list_type, 1342 }), 1343 } 1344 } 1345 } 1346 } 1347} 1348 1349#[cfg(test)] 1350mod tests { 1351 use std::collections::{HashMap, HashSet}; 1352 1353 use super::*; 1354 use crate::analyse::TargetSupport; 1355 use crate::build::{Origin, Target}; 1356 use crate::config::PackageConfig; 1357 use crate::line_numbers::LineNumbers; 1358 use crate::parse::parse_module; 1359 use crate::type_::PRELUDE_MODULE_NAME; 1360 use crate::uid::UniqueIdGenerator; 1361 use crate::warning::{TypeWarningEmitter, WarningEmitter}; 1362 use camino::Utf8PathBuf; 1363 1364 fn compile_module(src: &str) -> crate::ast::TypedModule { 1365 use crate::type_::build_prelude; 1366 let parsed = parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null()) 1367 .expect("syntax error"); 1368 let ast = parsed.module; 1369 let ids = UniqueIdGenerator::new(); 1370 let mut config = PackageConfig::default(); 1371 config.name = "thepackage".into(); 1372 let mut modules = im::HashMap::new(); 1373 let _ = modules.insert(PRELUDE_MODULE_NAME.into(), build_prelude(&ids)); 1374 let line_numbers = LineNumbers::new(src); 1375 1376 crate::analyse::ModuleAnalyzerConstructor::<()> { 1377 target: Target::Erlang, 1378 ids: &ids, 1379 origin: Origin::Src, 1380 importable_modules: &modules, 1381 warnings: &TypeWarningEmitter::null(), 1382 direct_dependencies: &HashMap::new(), 1383 dev_dependencies: &HashSet::new(), 1384 target_support: TargetSupport::Enforced, 1385 package_config: &config, 1386 } 1387 .infer_module(ast, line_numbers, "".into()) 1388 .expect("should successfully infer") 1389 } 1390 1391 fn translate(src: &str) -> Module<IncompleteType> { 1392 let typed_module = compile_module(src); 1393 1394 Translator::new(&typed_module).translate() 1395 } 1396 1397 #[test] 1398 fn translates_samples() { 1399 insta::assert_debug_snapshot!(translate( 1400 r#"pub fn add(lhs: Int, rhs: Int) -> Int { 1401 lhs + rhs 1402 }"# 1403 )); 1404 1405 insta::assert_debug_snapshot!(translate( 1406 r#"pub fn some_case(n: Int) -> Int { 1407 case n { 1408 0 -> 2 1409 1 -> 4 1410 _ -> 6 1411 } 1412 }"# 1413 )); 1414 1415 insta::assert_debug_snapshot!(translate( 1416 r#"pub fn some_case(n: Int) -> Int { 1417 case n { 1418 y if y == 0 -> 2 1419 1 -> 4 1420 _ -> 6 1421 } 1422 }"# 1423 )); 1424 1425 insta::assert_debug_snapshot!(translate( 1426 r#"pub fn fib(n: Int) -> Int { 1427 case n { 1428 0 -> 0 1429 1 -> 1 1430 _ -> fib(n - 1) + fib(n - 2) 1431 } 1432 }"# 1433 )); 1434 1435 insta::assert_debug_snapshot!(translate( 1436 r#"pub fn maths() { 1437 let _ = 1 + 0 1438 let _ = 2 - 1 1439 let _ = 3 * 2 1440 let _ = 4 / 3 1441 let _ = 5 % 4 1442 1443 let _ = 1.0 +. 0.0 1444 let _ = 2.0 -. 1.0 1445 let _ = 3.0 *. 2.0 1446 let _ = 4.0 /. 3.0 1447 1448 let _ = 1 == 2 && 2 == 4 1449 let _ = 1 == 2 || 2 == 4 1450 }"# 1451 )); 1452 1453 insta::assert_debug_snapshot!(translate( 1454 r#"pub fn booleans() { 1455 let _ = True 1456 let _ = False 1457 1458 let _ = True == False 1459 let _ = False != True 1460 1461 let _ = 1 > 2 1462 let _ = 1 >= 2 1463 let _ = 1 < 2 1464 let _ = 1 <= 2 1465 1466 let _ = 1.0 >. 2.0 1467 let _ = 1.0 >=. 2.0 1468 let _ = 1.0 <. 2.0 1469 let _ = 1.0 <=. 2.0 1470 }"# 1471 )); 1472 1473 insta::assert_debug_snapshot!(translate( 1474 r#"pub fn guard_operations(n: Int, f: Float) -> Int { 1475 case n, f { 1476 x, y if x == 0 -> 1 1477 x, y if x != 1 -> 2 1478 x, y if x > 2 -> 3 1479 x, y if x >= 3 -> 4 1480 x, y if x < 4 -> 5 1481 x, y if x <= 5 -> 6 1482 x, y if y >. 1.0 -> 7 1483 x, y if y >=. 2.0 -> 8 1484 x, y if y <. 3.0 -> 9 1485 x, y if y <=. 4.0 -> 10 1486 x, y if x > 0 && x < 10 -> 11 1487 x, y if x < 0 || x > 100 -> 12 1488 x, y if !{ x == 0 } -> 13 1489 x, y if x + 1 > 5 -> 14 1490 x, y if x - 1 < 0 -> 15 1491 x, y if x * 2 == 10 -> 16 1492 x, y if x / 2 == 1 -> 17 1493 x, y if y +. 1.0 >. 5.0 -> 18 1494 x, y if y -. 1.0 <. 0.0 -> 19 1495 x, y if y *. 2.0 == 4.0 -> 20 1496 x, y if y /. 2.0 >=. 1.0 -> 21 1497 x, y if x % 2 == 0 -> 22 1498 _, _ -> 0 1499 } 1500 }"# 1501 )); 1502 1503 insta::assert_debug_snapshot!(translate( 1504 r#"pub fn strings() { 1505 let _ = "hello" 1506 let _ = "hello, " <> "world!" 1507 }"# 1508 )); 1509 1510 insta::assert_debug_snapshot!(translate( 1511 r#"pub fn tuples() { 1512 let a = #(1, 2, 3) 1513 let _ = a.1 1514 }"# 1515 )); 1516 1517 insta::assert_debug_snapshot!(translate( 1518 r#"pub type Wibble { 1519 Wibble(a: Int, b: Int) 1520 } 1521 1522 pub fn types(w: Wibble) { 1523 let x = Wibble(a: 10, b: 20) 1524 1525 w.a + x.b 1526 }"# 1527 )); 1528 1529 insta::assert_debug_snapshot!(translate( 1530 r#"pub fn add(lhs: Int, rhs: Int) -> Int { 1531 lhs + rhs 1532 } 1533 1534 pub fn pipe() { 1535 10 |> add(20) |> add(30) 1536 }"# 1537 )); 1538 1539 insta::assert_debug_snapshot!(translate( 1540 r#"pub fn pipe() { 1541 [1, 2, 3, ..[4, 5, 6]] 1542 }"# 1543 )); 1544 1545 insta::assert_debug_snapshot!(translate( 1546 r#"pub type Wibble { 1547 Wibble 1548 Wobble 1549 } 1550 1551 pub fn check(wibble: Wibble) { 1552 case wibble { 1553 Wibble -> 0 1554 Wobble -> 1 1555 } 1556 }"# 1557 )); 1558 1559 insta::assert_debug_snapshot!(translate( 1560 r#"pub type Wibble { 1561 Wibble(x: Int, y: Int, z: Int) 1562 } 1563 1564 pub fn record_update(wibble: Wibble) { 1565 Wibble(..wibble, y: 20) 1566 }"# 1567 )); 1568 } 1569}