Fork of daniellemaywood.uk/gleam — Wasm codegen work
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}