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