use std::{collections::HashMap, ops::Deref}; use ecow::{EcoString, eco_format}; use num_bigint::BigInt; use vec1::Vec1; use crate::{ cranelift::mir::{ast::*, visit::Visit}, exhaustiveness, parse, type_, }; pub mod ast; pub mod visit; struct FlattenBlockPass; impl<'mir> Visit<'mir> for FlattenBlockPass { fn visit_expression(&mut self, expression: &'mir mut Expression) { visit::visit_expression(self, expression); if let Expression::Block(inner) = expression && inner.len() == 1 { *expression = inner.pop().expect("block should've been poppable"); } } fn visit_expression_block(&mut self, expressions: &'mir mut Vec) { visit::visit_expression_block(self, expressions); *expressions = std::mem::take(expressions) .into_iter() .flat_map(|expr| match expr { Expression::Block(inner) => inner, _ => vec![expr], }) .collect(); } } struct RemoveUnusedPass; impl<'mir> Visit<'mir> for RemoveUnusedPass { fn visit_expression_block(&mut self, expressions: &'mir mut Vec) { visit::visit_expression_block(self, expressions); let last_index = expressions.len() - 1; *expressions = std::mem::take(expressions) .into_iter() .enumerate() .filter_map(|(index, expr)| match expr { Expression::Var { .. } | Expression::FunctionRef { .. } | Expression::Int { .. } | Expression::Float { .. } | Expression::Bool { .. } | Expression::String { .. } if index != last_index => { None } _ => Some(expr), }) .collect(); } } #[derive(Debug, Default, PartialEq, Eq)] pub struct Translator { module: Module, } impl Translator { pub fn translate(mut self, module: &crate::ast::TypedModule) -> Module { for definition in &module.definitions { match definition { crate::ast::Definition::Function(function) => { self.module .functions .push(Self::translate_function(function)); } crate::ast::Definition::TypeAlias(_type_alias) => todo!(), crate::ast::Definition::CustomType(_custom_type) => {} crate::ast::Definition::Import(_import) => todo!(), crate::ast::Definition::ModuleConstant(_module_constant) => todo!(), } } self.module } fn translate_function(function: &crate::ast::TypedFunction) -> Function { let (_, name) = function.name.clone().expect("function should have a name"); let return_type = Self::translate_type(&function.return_type); let mut translator = BodyTranslator::default(); let arguments: Vec<_> = Iterator::collect(function.arguments.iter().map(|argument| { argument .get_variable_name() .map(|name| translator.make_var(name.clone())) })); let parameters = Iterator::collect(function.arguments.iter().zip(arguments.iter()).map( |(argument, name)| { let type_ = Self::translate_type(&argument.type_); FunctionParameter { name: name.clone().map(|name| name.name), type_, } }, )); let mut body = translator.translate(&function.body); FlattenBlockPass.visit_expression(&mut body); RemoveUnusedPass.visit_expression(&mut body); Function { name, body, return_type, parameters, } } fn translate_type(type_: &type_::Type) -> Type { match type_ { type_::Type::Named { publicity: _, package: _, module, name, arguments, inferred_variant: _, } => match (module.as_str(), name.as_str()) { ("gleam", "Int") => Type::Int, ("gleam", "Float") => Type::Float, ("gleam", "Bool") => Type::Bool, ("gleam", "String") => Type::String, ("gleam", "List") => { let list_type = Self::translate_type(&arguments[0]); Type::List(Box::new(list_type)) } (_, _) => Type::Struct { elements: arguments .iter() .map(|type_| Self::translate_type(type_)) .collect(), }, }, type_::Type::Fn { arguments: _, return_: _, } => todo!(), type_::Type::Var { type_ } => match type_.borrow().deref() { type_::TypeVar::Link { type_ } => Self::translate_type(type_), type_::TypeVar::Unbound { id: _ } | type_::TypeVar::Generic { id: _ } => { Type::Generic } }, type_::Type::Tuple { elements: _ } => todo!(), } } } #[derive(Debug, Default)] pub struct BodyTranslator { variables: HashMap, variable_count: usize, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum DecisionKind { Case, Let, } impl BodyTranslator { fn make_tmp_var(&mut self) -> Var { self.make_var("_tmp".into()) } fn make_var(&mut self, name: EcoString) -> Var { self.variable_count += 1; let entry = eco_format!("{}${}", name.clone(), self.variable_count); _ = self.variables.insert(name.clone(), entry.clone()); Var { name: entry } } fn get_var(&mut self, name: &EcoString) -> Var { let variable = self .variables .get(name) .cloned() .unwrap_or_else(|| panic!("variable '{name}' not found")); Var { name: variable } } fn in_var_scope(&mut self, func: impl Fn(&mut Self) -> T) -> T { let snapshot = self.variables.clone(); let result = func(self); self.variables = snapshot; result } fn translate(mut self, body: &Vec1) -> Expression { self.translate_statements(body) } fn translate_statements(&mut self, statements: &[crate::ast::TypedStatement]) -> Expression { Expression::Block(Iterator::collect( statements.iter().map(|stmt| self.translate_statement(stmt)), )) } fn translate_statement(&mut self, statement: &crate::ast::TypedStatement) -> Expression { match statement { crate::ast::Statement::Expression(expression) => self.translate_expression(expression), crate::ast::Statement::Assignment(assignment) => self.translate_assignment(assignment), crate::ast::Statement::Use(_) => todo!(), crate::ast::Statement::Assert(_) => todo!(), } } fn translate_expression(&mut self, expression: &crate::ast::TypedExpr) -> Expression { match expression { crate::ast::TypedExpr::Int { location: _, type_: _, value: _, int_value, } => Expression::Int { value: int_value.clone(), }, crate::ast::TypedExpr::Float { location: _, type_: _, value, } => Expression::Float { value: value.clone(), }, crate::ast::TypedExpr::String { location: _, type_: _, value, } => Expression::String { value: value.clone(), }, crate::ast::TypedExpr::Block { location: _, statements, } => self.in_var_scope(|this| this.translate_statements(statements)), crate::ast::TypedExpr::Pipeline { location: _, first_value, assignments, finally, finally_kind: _, } => { let mut block = vec![]; let value = self.translate_expression(&first_value.value); block.push(Expression::Set { name: self.make_var(first_value.name.clone()), value: Box::new(value), }); for (assignment, _) in assignments { let value = self.translate_expression(&assignment.value); block.push(Expression::Set { name: self.make_var(assignment.name.clone()), value: Box::new(value), }); } block.push(self.translate_expression(finally)); Expression::Block(block) } crate::ast::TypedExpr::Var { location: _, constructor, name, } => match &constructor.variant { type_::ValueConstructorVariant::LocalVariable { location: _, origin: _, } => Expression::Var(self.get_var(name)), type_::ValueConstructorVariant::ModuleConstant { documentation: _, location: _, module: _, name: _, literal: _, implementations: _, } => todo!(), type_::ValueConstructorVariant::LocalConstant { literal: _ } => todo!(), type_::ValueConstructorVariant::ModuleFn { name, field_map: _, module, arity, location: _, documentation: _, implementations: _, external_erlang: _, external_javascript: _, external_cranelift: _, purity: _, } => Expression::FunctionRef { module: module.clone(), name: name.clone(), arity: *arity, }, type_::ValueConstructorVariant::Record { name, arity, field_map: _, location: _, module, variants_count: _, variant_index: _, documentation: _, } => match (module.as_str(), name.as_str()) { ("gleam", "True") => Expression::Bool { value: true }, ("gleam", "False") => Expression::Bool { value: false }, (_, _) => Expression::FunctionRef { module: module.clone(), name: name.clone(), arity: usize::from(*arity), }, }, }, crate::ast::TypedExpr::Fn { location: _, type_: _, kind: _, arguments: _, body: _, return_annotation: _, purity: _, } => todo!(), crate::ast::TypedExpr::List { location: _, type_: _, elements, tail, } => { let items = elements .iter() .map(|element| self.translate_expression(element)) .collect(); let tail = tail .as_ref() .map(|tail| Box::new(self.translate_expression(tail))); Expression::List { items, tail } } crate::ast::TypedExpr::Call { location: _, type_: _, fun, arguments, } => { let target = self.translate_expression(fun); let args = arguments .iter() .map(|arg| self.translate_expression(&arg.value)) .collect(); Expression::Call { target: Box::new(target), args, } } crate::ast::TypedExpr::BinOp { location: _, type_: _, name, name_location: _, left, right, } => { let lhs = Box::new(self.translate_expression(left)); let rhs = Box::new(self.translate_expression(right)); match name { crate::ast::BinOp::And => Expression::If { cond: lhs, then: rhs, else_: Box::new(Expression::Bool { value: false }), }, crate::ast::BinOp::Or => Expression::If { cond: lhs, then: Box::new(Expression::Bool { value: true }), else_: rhs, }, crate::ast::BinOp::Eq => Expression::Equals { lhs, rhs }, crate::ast::BinOp::NotEq => Expression::NotEquals { lhs, rhs }, crate::ast::BinOp::LtInt => Expression::IntLt { lhs, rhs }, crate::ast::BinOp::LtEqInt => Expression::IntLtEq { lhs, rhs }, crate::ast::BinOp::LtFloat => Expression::FloatLt { lhs, rhs }, crate::ast::BinOp::LtEqFloat => Expression::FloatLtEq { lhs, rhs }, crate::ast::BinOp::GtEqInt => Expression::IntGtEq { lhs, rhs }, crate::ast::BinOp::GtInt => Expression::IntGt { lhs, rhs }, crate::ast::BinOp::GtEqFloat => Expression::FloatGtEq { lhs, rhs }, crate::ast::BinOp::GtFloat => Expression::FloatGt { lhs, rhs }, crate::ast::BinOp::AddInt => Expression::IntAdd { lhs, rhs }, crate::ast::BinOp::AddFloat => Expression::FloatAdd { lhs, rhs }, crate::ast::BinOp::SubInt => Expression::IntSub { lhs, rhs }, crate::ast::BinOp::SubFloat => Expression::FloatSub { lhs, rhs }, crate::ast::BinOp::MultInt => Expression::IntMul { lhs, rhs }, crate::ast::BinOp::MultFloat => Expression::FloatMul { lhs, rhs }, crate::ast::BinOp::DivInt => Expression::IntDiv { lhs, rhs }, crate::ast::BinOp::DivFloat => Expression::FloatDiv { lhs, rhs }, crate::ast::BinOp::RemainderInt => Expression::IntRem { lhs, rhs }, crate::ast::BinOp::Concatenate => Expression::StringConcat { lhs, rhs }, } } crate::ast::TypedExpr::Case { location: _, type_: _, subjects, clauses, compiled_case, } => { let mut block = vec![]; let subject_vars: Vec<_> = Iterator::collect(subjects.iter().map(|subject| { let name = self.make_tmp_var(); let value = self.translate_expression(subject); block.push(Expression::Set { name: name.clone(), value: Box::new(value), }); return name; })); block.push(self.translate_decision( DecisionKind::Case, &subject_vars, clauses, &compiled_case.tree, )); Expression::Block(block) } crate::ast::TypedExpr::RecordAccess { location: _, field_start: _, type_: _, label: _, index, record, } => Expression::StructAccess { value: Box::new(self.translate_expression(record)), index: *index, }, crate::ast::TypedExpr::ModuleSelect { location: _, field_start: _, type_: _, label: _, module_name: _, module_alias: _, constructor: _, } => todo!(), crate::ast::TypedExpr::Tuple { location: _, type_: _, elements, } => Expression::Struct { tag: None, items: elements .iter() .map(|element| self.translate_expression(element)) .collect(), }, crate::ast::TypedExpr::TupleIndex { location: _, type_: _, index, tuple, } => Expression::StructAccess { value: Box::new(self.translate_expression(tuple)), index: *index, }, crate::ast::TypedExpr::Todo { location: _, message: _, kind: _, type_: _, } => todo!(), crate::ast::TypedExpr::Panic { location: _, message: _, type_: _, } => todo!(), crate::ast::TypedExpr::Echo { location: _, type_: _, expression: _, message: _, } => todo!(), crate::ast::TypedExpr::BitArray { location: _, type_: _, segments: _, } => todo!(), crate::ast::TypedExpr::RecordUpdate { location: _, type_: _, record_assignment: _, constructor: _, arguments: _, } => todo!(), crate::ast::TypedExpr::NegateBool { location: _, value } => Expression::Equals { lhs: Box::new(self.translate_expression(value)), rhs: Box::new(Expression::Bool { value: false }), }, crate::ast::TypedExpr::NegateInt { location: _, value } => Expression::IntSub { lhs: Box::new(Expression::Int { value: BigInt::from(0), }), rhs: Box::new(self.translate_expression(value)), }, crate::ast::TypedExpr::Invalid { location: _, type_: _, } => todo!(), } } fn translate_assignment(&mut self, assignment: &crate::ast::TypedAssignment) -> Expression { let mut block = vec![]; let value_var = self.make_tmp_var(); let value = self.translate_expression(&assignment.value); block.push(Expression::Set { name: value_var.clone(), value: Box::new(value), }); block.push(self.translate_decision( DecisionKind::Let, &[value_var.clone()], &[], &assignment.compiled_case.tree, )); block.push(Expression::Var(value_var)); Expression::Block(block) } fn translate_decision( &mut self, kind: DecisionKind, subjects: &[Var], clauses: &[crate::ast::TypedClause], decision: &exhaustiveness::Decision, ) -> Expression { match decision { exhaustiveness::Decision::Run { body } => { let mut block = vec![]; for (binding, value) in &body.bindings { let binding = self.make_var(binding.clone()); block.push(self.translate_binding(subjects, binding.clone(), value)); } if kind == DecisionKind::Case { block.push(self.translate_expression(&clauses[body.clause_index].then)); } Expression::Block(block) } exhaustiveness::Decision::Guard { guard, if_true, if_false, } => { let mut block = vec![]; let guard = clauses[*guard] .guard .as_ref() .expect("expected clause to have a guard"); let referenced_in_guard = guard.referenced_variables(); let (guard_bindings, if_true_bindings): (Vec<_>, Vec<_>) = if_true .bindings .iter() .partition(|(binding, _)| referenced_in_guard.contains(binding)); for (binding, value) in guard_bindings { let binding = self.make_var(binding.clone()); block.push(self.translate_binding(subjects, binding, value)); } let cond = self.translate_guard(guard); let if_true = { let mut block = vec![]; for (binding, value) in if_true_bindings { let binding = self.make_var(binding.clone()); block.push(self.translate_binding(subjects, binding.clone(), value)); } block.push(self.translate_expression(&clauses[if_true.clause_index].then)); block }; let if_false = self.translate_decision(kind, subjects, clauses, if_false); block.push(Expression::If { cond: Box::new(cond), then: Box::new(Expression::Block(if_true)), else_: Box::new(if_false), }); Expression::Block(block) } exhaustiveness::Decision::Switch { var, choices, fallback, fallback_check: _, } => { let fallback = self.translate_decision(kind, subjects, clauses, fallback); DoubleEndedIterator::rfold(choices.iter(), fallback, |fallback, (check, then)| { Expression::If { cond: Box::new(self.translate_runtime_check( check, Expression::Var(subjects[var.id].clone()), )), then: Box::new(self.translate_decision(kind, subjects, clauses, then)), else_: Box::new(fallback), } }) } exhaustiveness::Decision::Fail => todo!(), } } fn translate_guard(&mut self, guard: &crate::ast::TypedClauseGuard) -> Expression { match guard { crate::ast::ClauseGuard::Block { location: _, value } => self.translate_guard(value), crate::ast::ClauseGuard::Equals { location: _, left, right, } => { let lhs = self.translate_guard(left); let rhs = self.translate_guard(right); Expression::Equals { lhs: Box::new(lhs), rhs: Box::new(rhs), } } crate::ast::ClauseGuard::NotEquals { location: _, left, right, } => Expression::NotEquals { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::GtInt { location: _, left, right, } => Expression::IntGt { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::GtEqInt { location: _, left, right, } => Expression::IntGtEq { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::LtInt { location: _, left, right, } => Expression::IntLt { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::LtEqInt { location: _, left, right, } => Expression::IntLtEq { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::GtFloat { location: _, left, right, } => Expression::FloatGt { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::GtEqFloat { location: _, left, right, } => Expression::FloatGtEq { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::LtFloat { location: _, left, right, } => Expression::FloatLt { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::LtEqFloat { location: _, left, right, } => Expression::FloatLtEq { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::AddInt { location: _, left, right, } => Expression::IntAdd { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::AddFloat { location: _, left, right, } => Expression::FloatAdd { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::SubInt { location: _, left, right, } => Expression::IntSub { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::SubFloat { location: _, left, right, } => Expression::FloatSub { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::MultInt { location: _, left, right, } => Expression::IntMul { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::MultFloat { location: _, left, right, } => Expression::FloatMul { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::DivInt { location: _, left, right, } => Expression::IntDiv { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::DivFloat { location: _, left, right, } => Expression::FloatDiv { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::RemainderInt { location: _, left, right, } => Expression::IntRem { lhs: Box::new(self.translate_guard(left)), rhs: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::Or { location: _, left, right, } => Expression::If { cond: Box::new(self.translate_guard(left)), then: Box::new(Expression::Bool { value: true }), else_: Box::new(self.translate_guard(right)), }, crate::ast::ClauseGuard::And { location: _, left, right, } => Expression::If { cond: Box::new(self.translate_guard(left)), then: Box::new(self.translate_guard(right)), else_: Box::new(Expression::Bool { value: false }), }, crate::ast::ClauseGuard::Not { location: _, expression, } => Expression::Equals { lhs: Box::new(self.translate_guard(expression)), rhs: Box::new(Expression::Bool { value: false }), }, crate::ast::ClauseGuard::Var { location: _, type_: _, name, definition_location: _, } => Expression::Var(self.get_var(name)), crate::ast::ClauseGuard::TupleIndex { location: _, index, type_: _, tuple, } => Expression::StructAccess { value: Box::new(self.translate_guard(tuple)), index: *index, }, crate::ast::ClauseGuard::FieldAccess { label_location: _, index, label: _, type_: _, container, } => Expression::StructAccess { value: Box::new(self.translate_guard(container)), index: index.expect("field access expected an index"), }, crate::ast::ClauseGuard::ModuleSelect { location: _, type_: _, label: _, module_name: _, module_alias: _, literal: _, } => todo!(), crate::ast::ClauseGuard::Constant(constant) => self.translate_constant(constant), } } fn translate_constant(&mut self, constant: &crate::ast::TypedConstant) -> Expression { match constant { crate::ast::Constant::Int { location: _, value: _, int_value, } => Expression::Int { value: int_value.clone(), }, crate::ast::Constant::Float { location: _, value } => Expression::Float { value: value.clone(), }, crate::ast::Constant::String { location: _, value } => Expression::String { value: value.clone(), }, crate::ast::Constant::Tuple { location: _, elements, } => Expression::Struct { tag: None, items: elements .iter() .map(|element| self.translate_constant(element)) .collect(), }, crate::ast::Constant::List { location: _, elements, type_: _, } => Expression::List { items: elements .iter() .map(|element| self.translate_constant(element)) .collect(), tail: None, }, crate::ast::Constant::Record { location: _, module: _, name: _, arguments: _, tag: _, type_: _, field_map: _, record_constructor: _, } => todo!(), crate::ast::Constant::BitArray { location: _, segments: _, } => todo!(), crate::ast::Constant::Var { location: _, module: _, name: _, constructor: _, type_: _, } => todo!(), crate::ast::Constant::StringConcatenation { location: _, left, right, } => Expression::StringConcat { lhs: Box::new(self.translate_constant(left)), rhs: Box::new(self.translate_constant(right)), }, crate::ast::Constant::Invalid { location: _, type_: _, } => todo!(), } } fn translate_binding( &mut self, subjects: &[Var], binding: Var, value: &exhaustiveness::BoundValue, ) -> Expression { let value = match value { exhaustiveness::BoundValue::Variable(variable) => { Expression::Var(subjects[variable.id].clone()) } exhaustiveness::BoundValue::LiteralString(_eco_string) => todo!(), exhaustiveness::BoundValue::LiteralInt(big_int) => Expression::Int { value: big_int.clone(), }, exhaustiveness::BoundValue::LiteralFloat(_eco_string) => todo!(), exhaustiveness::BoundValue::BitArraySlice { bit_array: _, read_action: _, } => todo!(), }; Expression::Set { name: binding, value: Box::new(value), } } fn translate_runtime_check( &mut self, check: &exhaustiveness::RuntimeCheck, against: Expression, ) -> Expression { match check { exhaustiveness::RuntimeCheck::Int { value } => { let value = parse::parse_int_value(&value).expect("unable to parse integer value"); Expression::Equals { lhs: Box::new(against), rhs: Box::new(Expression::Int { value }), } } exhaustiveness::RuntimeCheck::Float { value: _ } => todo!(), exhaustiveness::RuntimeCheck::String { value: _ } => todo!(), exhaustiveness::RuntimeCheck::StringPrefix { prefix: _, rest: _ } => todo!(), exhaustiveness::RuntimeCheck::Tuple { size: _, elements: _, } => todo!(), exhaustiveness::RuntimeCheck::BitArray { test: _ } => todo!(), exhaustiveness::RuntimeCheck::Variant { match_: _, index: _, labels: _, fields: _, } => todo!(), exhaustiveness::RuntimeCheck::NonEmptyList { first: _, rest: _ } => todo!(), exhaustiveness::RuntimeCheck::EmptyList => todo!(), } } } #[cfg(test)] mod tests { use std::collections::{HashMap, HashSet}; use super::*; use crate::analyse::TargetSupport; use crate::build::{Origin, Target}; use crate::config::PackageConfig; use crate::line_numbers::LineNumbers; use crate::parse::parse_module; use crate::type_::PRELUDE_MODULE_NAME; use crate::uid::UniqueIdGenerator; use crate::warning::{TypeWarningEmitter, WarningEmitter}; use camino::Utf8PathBuf; fn compile_module(src: &str) -> crate::ast::TypedModule { use crate::type_::build_prelude; let parsed = parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null()) .expect("syntax error"); let ast = parsed.module; let ids = UniqueIdGenerator::new(); let mut config = PackageConfig::default(); config.name = "thepackage".into(); let mut modules = im::HashMap::new(); let _ = modules.insert(PRELUDE_MODULE_NAME.into(), build_prelude(&ids)); let line_numbers = LineNumbers::new(src); crate::analyse::ModuleAnalyzerConstructor::<()> { target: Target::Erlang, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &HashMap::new(), dev_dependencies: &HashSet::new(), target_support: TargetSupport::Enforced, package_config: &config, } .infer_module(ast, line_numbers, "".into()) .expect("should successfully infer") } fn translate(src: &str) -> Module { let typed_module = compile_module(src); Translator::default().translate(&typed_module) } #[test] fn translates_samples() { insta::assert_debug_snapshot!(translate( r#"pub fn add(lhs: Int, rhs: Int) -> Int { lhs + rhs }"# )); insta::assert_debug_snapshot!(translate( r#"pub fn some_case(n: Int) -> Int { case n { 0 -> 2 1 -> 4 _ -> 6 } }"# )); insta::assert_debug_snapshot!(translate( r#"pub fn some_case(n: Int) -> Int { case n { y if y == 0 -> 2 1 -> 4 _ -> 6 } }"# )); insta::assert_debug_snapshot!(translate( r#"pub fn fib(n: Int) -> Int { case n { 0 -> 0 1 -> 1 _ -> fib(n - 1) + fib(n - 2) } }"# )); insta::assert_debug_snapshot!(translate( r#"pub fn maths() { let _ = 1 + 0 let _ = 2 - 1 let _ = 3 * 2 let _ = 4 / 3 let _ = 5 % 4 let _ = 1.0 +. 0.0 let _ = 2.0 -. 1.0 let _ = 3.0 *. 2.0 let _ = 4.0 /. 3.0 let _ = 1 == 2 && 2 == 4 let _ = 1 == 2 || 2 == 4 }"# )); insta::assert_debug_snapshot!(translate( r#"pub fn booleans() { let _ = True let _ = False let _ = True == False let _ = False != True let _ = 1 > 2 let _ = 1 >= 2 let _ = 1 < 2 let _ = 1 <= 2 let _ = 1.0 >. 2.0 let _ = 1.0 >=. 2.0 let _ = 1.0 <. 2.0 let _ = 1.0 <=. 2.0 }"# )); insta::assert_debug_snapshot!(translate( r#"pub fn guard_operations(n: Int, f: Float) -> Int { case n, f { x, y if x == 0 -> 1 x, y if x != 1 -> 2 x, y if x > 2 -> 3 x, y if x >= 3 -> 4 x, y if x < 4 -> 5 x, y if x <= 5 -> 6 x, y if y >. 1.0 -> 7 x, y if y >=. 2.0 -> 8 x, y if y <. 3.0 -> 9 x, y if y <=. 4.0 -> 10 x, y if x > 0 && x < 10 -> 11 x, y if x < 0 || x > 100 -> 12 x, y if !{ x == 0 } -> 13 x, y if x + 1 > 5 -> 14 x, y if x - 1 < 0 -> 15 x, y if x * 2 == 10 -> 16 x, y if x / 2 == 1 -> 17 x, y if y +. 1.0 >. 5.0 -> 18 x, y if y -. 1.0 <. 0.0 -> 19 x, y if y *. 2.0 == 4.0 -> 20 x, y if y /. 2.0 >=. 1.0 -> 21 x, y if x % 2 == 0 -> 22 _, _ -> 0 } }"# )); insta::assert_debug_snapshot!(translate( r#"pub fn strings() { let _ = "hello" let _ = "hello, " <> "world!" }"# )); insta::assert_debug_snapshot!(translate( r#"pub fn tuples() { let a = #(1, 2, 3) let _ = a.1 }"# )); insta::assert_debug_snapshot!(translate( r#"pub type Wibble { Wibble(a: Int, b: Int) } pub fn types(w: Wibble) { let x = Wibble(a: 10, b: 20) w.a + x.b }"# )); insta::assert_debug_snapshot!(translate( r#"pub fn add(lhs: Int, rhs: Int) -> Int { lhs + rhs } pub fn pipe() { 10 |> add(20) |> add(30) }"# )); insta::assert_debug_snapshot!(translate( r#"pub fn pipe() { [1, 2, 3, ..[4, 5, 6]] }"# )); } }