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