// SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2018 The Gleam contributors use std::time::SystemTime; use camino::Utf8PathBuf; use crate::analyse::TargetSupport; use crate::config::PackageConfig; use crate::type_::PRELUDE_MODULE_NAME; use crate::warning::WarningEmitter; use crate::{build, inline}; use crate::{ build::{Origin, Target}, erlang::module, line_numbers::LineNumbers, uid::UniqueIdGenerator, warning::TypeWarningEmitter, }; use camino::Utf8Path; mod assert; mod binops; mod bit_arrays; mod case; mod conditional_compilation; mod consts; mod custom_types; mod documentation; mod echo; mod external_fn; mod functions; mod guards; mod inlining; mod let_assert; mod lists; mod numbers; mod panic; mod patterns; mod pipes; mod records; mod reserved; mod strings; mod todo; mod tuples; mod type_params; mod use_; mod variables; pub fn compile_test_project( src: &str, src_path: &str, dependencies: Vec<(&str, &str, &str)>, ) -> String { let mut modules = im::HashMap::new(); let ids = UniqueIdGenerator::new(); // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = modules.insert( PRELUDE_MODULE_NAME.into(), crate::type_::build_prelude(&ids), ); let mut direct_dependencies = std::collections::HashMap::from_iter(vec![]); for (dep_package, dep_name, dep_src) in dependencies { let mut dep_config = PackageConfig::default(); dep_config.name = dep_package.into(); let parsed = crate::parse::parse_module( Utf8PathBuf::from("test/path"), dep_src, &WarningEmitter::null(), ) .expect("dep syntax error"); let mut ast = parsed.module; ast.name = dep_name.into(); let line_numbers = LineNumbers::new(dep_src); let dep = crate::analyse::ModuleAnalyzerConstructor::<()> { target: Target::Erlang, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &std::collections::HashMap::new(), dev_dependencies: &std::collections::HashSet::new(), target_support: TargetSupport::NotEnforced, package_config: &dep_config, } .infer_module(ast, line_numbers, "".into()) .expect("should successfully infer dep Erlang"); let _ = modules.insert(dep_name.into(), dep.type_info); let _ = direct_dependencies.insert(dep_package.into(), ()); } let path = Utf8PathBuf::from(src_path); let parsed = crate::parse::parse_module(path.clone(), src, &WarningEmitter::null()) .expect("syntax error"); let mut config = PackageConfig::default(); config.name = "thepackage".into(); let mut ast = parsed.module; ast.name = "my/mod".into(); let line_numbers = LineNumbers::new(src); let ast = crate::analyse::ModuleAnalyzerConstructor::<()> { target: Target::Erlang, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &direct_dependencies, dev_dependencies: &std::collections::HashSet::new(), target_support: TargetSupport::NotEnforced, package_config: &config, } .infer_module(ast, line_numbers, path.clone()) .expect("should successfully infer root Erlang"); let ast = inline::module(ast, &modules); // After building everything we still need to attach the module comments, to // do that we're reusing the `attach_doc_and_module_comments` that's used // for the real thing. We just have to make a placeholder module wrapping // the parsed ast and call the function! let mut built_module = build::Module { name: "my/mod".into(), code: src.into(), mtime: SystemTime::UNIX_EPOCH, input_path: path, origin: Origin::Src, ast, extra: parsed.extra, dependencies: vec![], }; let root = Utf8Path::new("/root"); built_module.attach_doc_and_module_comments(); let line_numbers = LineNumbers::new(src); module(&built_module.ast, &line_numbers, root).replace( std::include_str!("../../templates/echo.erl"), "% ...omitted code from `templates/echo.erl`...", ) } #[macro_export] macro_rules! assert_erl { ($(($dep_package:expr, $dep_name:expr, $dep_src:expr)),+, $src:literal $(,)?) => {{ let compiled = $crate::erlang::tests::compile_test_project( $src, "/root/project/test/my/mod.gleam", vec![$(($dep_package, $dep_name, $dep_src)),*], ); let output = format!( "----- SOURCE CODE\n{}\n\n----- COMPILED ERLANG\n{}", $src, compiled ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; ($src:expr $(,)?) => {{ let compiled = $crate::erlang::tests::compile_test_project( $src, "/root/project/test/my/mod.gleam", Vec::new(), ); let output = format!( "----- SOURCE CODE\n{}\n\n----- COMPILED ERLANG\n{}", $src, compiled, ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; } #[test] fn integration_test() { assert_erl!( r#"pub fn go() { let x = #(100000000000000000, #(2000000000, 3000000000000, 40000000000), 50000, 6000000000) x }"# ); } #[test] fn integration_test0_1() { assert_erl!( r#"pub fn go() { let y = 1 let y = 2 y }"# ); } #[test] fn integration_test0_2() { // hex, octal, and binary literals assert_erl!( r#"pub fn go() { let fifteen = 0xF let nine = 0o11 let ten = 0b1010 fifteen }"# ); } #[test] fn integration_test0_3() { assert_erl!( r#"pub fn go() { let y = 1 let y = 2 y }"# ); } #[test] fn integration_test1() { assert_erl!(r#"pub fn t() { True }"#); } #[test] fn integration_test1_1() { assert_erl!( r#"pub type Money { Pound(Int) } pub fn pound(x) { Pound(x) }"# ); } #[test] fn integration_test1_2() { assert_erl!(r#"pub fn loop() { loop() }"#); } #[test] fn integration_test1_4() { assert_erl!( r#"fn inc(x) { x + 1 } pub fn go() { 1 |> inc |> inc |> inc }"# ); } #[test] fn integration_test1_5() { assert_erl!( r#"fn add(x, y) { x + y } pub fn go() { 1 |> add(_, 1) |> add(2, _) |> add(_, 3) }"# ); } #[test] fn integration_test1_6() { assert_erl!( r#"pub fn and(x, y) { x && y } pub fn or(x, y) { x || y } pub fn remainder(x, y) { x % y } pub fn fdiv(x, y) { x /. y } "# ); } #[test] fn integration_test2() { assert_erl!( r#"pub fn second(list) { case list { [x, y] -> y z -> 1 } } pub fn tail(list) { case list { [x, ..xs] -> xs z -> list } } "# ); } #[test] fn integration_test5() { assert_erl!( "pub fn tail(list) { case list { [x, ..] -> x _ -> 0 } }" ); } #[test] fn integration_test6() { assert_erl!(r#"pub fn x() { let x = 1 let x = x + 1 x }"#); } #[test] fn integration_test8() { // Translation of Float-specific BinOp into variable-type Erlang term comparison. assert_erl!(r#"pub fn x() { 1. <. 2.3 }"#); } #[test] fn integration_test9() { // Custom type creation assert_erl!( r#"pub type Pair(x, y) { Pair(x: x, y: y) } pub fn x() { Pair(1, 2) Pair(3., 4.) }"# ); } #[test] fn integration_test10() { assert_erl!( r#"pub type Null { Null } pub fn x() { Null }"# ); } #[test] fn integration_test3() { assert_erl!( r#"pub type Point { Point(x: Int, y: Int) } pub fn y() { fn() { Point }()(4, 6) }"# ); } #[test] fn integration_test11() { assert_erl!( r#"pub type Point { Point(x: Int, y: Int) } pub fn x() { Point(x: 4, y: 6) Point(y: 1, x: 9) }"# ); } #[test] fn integration_test12() { assert_erl!( r#"pub type Point { Point(x: Int, y: Int) } pub fn x(y) { let Point(a, b) = y a }"# ); } //https://github.com/gleam-lang/gleam/issues/1106 #[test] fn integration_test13() { assert_erl!( r#"pub type State{ Start(Int) End(Int) } pub fn build(constructor : fn(Int) -> a) -> a { constructor(1) } pub fn main() { build(End) }"# ); } #[test] fn integration_test16() { assert_erl!( r#"fn go(x xx, y yy) { xx } pub fn x() { go(x: 1, y: 2) go(y: 3, x: 4) }"# ); } #[test] fn integration_test17() { // https://github.com/gleam-lang/gleam/issues/289 assert_erl!( r#" pub type User { User(id: Int, name: String, age: Int) } pub fn create_user(user_id) { User(age: 22, id: user_id, name: "") } "# ); } #[test] fn integration_test18() { assert_erl!(r#"pub fn run() { case 1, 2 { a, b -> a } }"#); } #[test] fn integration_test19() { assert_erl!( r#"pub type X { X(x: Int, y: Float) } pub fn x() { X(x: 1, y: 2.) X(y: 3., x: 4) }"# ); } #[test] fn integration_test20() { assert_erl!( r#" pub fn go(a) { let a = a + 1 a } "# ); } #[test] fn integration_test21() { assert_erl!( r#" pub fn go(a) { let a = 1 a } "# ); } #[test] fn integration_test22() { // https://github.com/gleam-lang/gleam/issues/358 assert_erl!( r#" pub fn factory(f, i) { f(i) } pub type Box { Box(i: Int) } pub fn main() { factory(Box, 0) } "# ); } #[test] fn integration_test23() { // https://github.com/gleam-lang/gleam/issues/384 assert_erl!( r#" pub fn main(args) { case args { _ -> { let a = 1 a } } let a = 2 a } "# ); } #[test] fn binop_parens() { // Parentheses are added for binop subexpressions assert_erl!( r#" pub fn main() { let a = 2 * {3 + 1} / 2 let b = 5 + 3 / 3 * 2 - 6 * 4 b } "# ); } #[test] fn field_access_function_call() { // Parentheses are added when calling functions returned by record access assert_erl!( r#" pub type FnBox { FnBox(f: fn(Int) -> Int) } pub fn main() { let b = FnBox(f: fn(x) { x }) b.f(5) } "# ); } #[test] fn field_access_function_call1() { // Parentheses are added when calling functions returned by tuple access assert_erl!( r#" pub fn main() { let t = #(fn(x) { x }) t.0(5) } "# ); } // https://github.com/gleam-lang/gleam/issues/777 #[test] fn block_assignment() { assert_erl!( r#" pub fn main() { let x = { 1 2 } x } "# ); } #[test] fn recursive_type() { // TODO: we should be able to generalise `id` and we should be // able to handle recursive types. Either of these type features // would make this module type check OK. assert_erl!( r#" fn id(x) { x } pub fn main() { id(id) } "# ); } #[test] fn tuple_access_in_guard() { assert_erl!( r#" pub fn main() { let key = 10 let x = [#(10, 2), #(1, 2)] case x { [first, ..rest] if first.0 == key -> "ok" _ -> "ko" } } "# ); } #[test] fn variable_name_underscores_preserved() { assert_erl!( "pub fn a(name_: String) -> String { let name__ = name_ let name = name__ let one_1 = 1 let one1 = one_1 name }" ); } #[test] fn allowed_string_escapes() { assert_erl!(r#"pub fn a() { "\n" "\r" "\t" "\\" "\"" "\\^" }"#); } // https://github.com/gleam-lang/gleam/issues/1006 #[test] fn keyword_constructors() { assert_erl!("pub type X { Div }"); } // https://github.com/gleam-lang/gleam/issues/1006 #[test] fn keyword_constructors1() { assert_erl!("pub type X { Fun(Int) }"); } #[test] fn discard_in_assert() { assert_erl!( "pub fn x(y) { let assert Ok(_) = y 1 }" ); } // https://github.com/gleam-lang/gleam/issues/1424 #[test] fn operator_pipe_right_hand_side() { assert_erl!( "fn id(x) { x } pub fn bool_expr(x, y) { y || x |> id }" ); } #[test] fn negation() { assert_erl!( "pub fn negate(x) { !x }" ) } #[test] fn negation_block() { assert_erl!( "pub fn negate(x) { !{ 123 x } }" ) } // https://github.com/gleam-lang/gleam/issues/1655 #[test] fn tail_maybe_expr_block() { assert_erl!( "pub fn a() { let fake_tap = fn(x) { x } let b = [99] [ 1, 2, ..b |> fake_tap ] } " ); } // https://github.com/gleam-lang/gleam/issues/1587 #[test] fn guard_variable_rewriting() { assert_erl!( "pub fn main() { case 1.0 { a if a <. 0.0 -> { let a = a a } _ -> 0.0 } } " ) } // https://github.com/gleam-lang/gleam/issues/1816 #[test] fn function_argument_shadowing() { assert_erl!( "pub fn main(a) { Box } pub type Box { Box(Int) } " ) } // https://github.com/gleam-lang/gleam/issues/2156 #[test] fn dynamic() { assert_erl!("pub type Dynamic") } // https://github.com/gleam-lang/gleam/issues/2166 #[test] fn inline_const_pattern_option() { assert_erl!( "pub fn main() { let fifteen = 15 let x = <<5:size(sixteen)>> case x { <<5:size(sixteen)>> -> <<5:size(sixteen)>> <<6:size(fifteen)>> -> <<5:size(fifteen)>> _ -> <<>> } } pub const sixteen = 16" ) } // https://github.com/gleam-lang/gleam/issues/2349 #[test] fn positive_zero() { assert_erl!( " pub fn main() { 0.0 } " ) } // https://github.com/gleam-lang/gleam/issues/3073 #[test] fn scientific_notation() { assert_erl!( " pub fn main() { 1.0e6 1.e6 } " ); } // https://github.com/gleam-lang/gleam/issues/3304 #[test] fn type_named_else() { assert_erl!( " pub type Else { Else } pub fn main() { Else } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn type_named_module_info() { assert_erl!( " pub type ModuleInfo { ModuleInfo } pub fn main() { ModuleInfo } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info() { assert_erl!( " pub fn module_info() { 1 } pub fn main() { module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info_imported() { assert_erl!( ( "some_module", "some_module", " pub fn module_info() { 1 } " ), " import some_module pub fn main() { some_module.module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info_imported_qualified() { assert_erl!( ( "some_module", "some_module", " pub fn module_info() { 1 } " ), " import some_module.{module_info} pub fn main() { module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info() { assert_erl!( " pub const module_info = 1 pub fn main() { module_info } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info_imported() { assert_erl!( ( "some_module", "some_module", " pub const module_info = 1 " ), " import some_module pub fn main() { some_module.module_info } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info_imported_qualified() { assert_erl!( ( "some_module", "some_module", " pub const module_info = 1 " ), " import some_module.{module_info} pub fn main() { module_info } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info_with_function_inside() { assert_erl!( " pub fn function() { 1 } pub const module_info = function pub fn main() { module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info_with_function_inside_imported() { assert_erl!( ( "some_module", "some_module", " pub fn function() { 1 } pub const module_info = function " ), " import some_module pub fn main() { some_module.module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info_with_function_inside_imported_qualified() { assert_erl!( ( "some_module", "some_module", " pub fn function() { 1 } pub const module_info = function " ), " import some_module.{module_info} pub fn main() { module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info_in_constant() { assert_erl!( " pub fn module_info() { 1 } pub const constant = module_info pub fn main() { constant() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info_in_constant_imported() { assert_erl!( ( "some_module", "some_module", " pub fn module_info() { 1 } pub const constant = module_info " ), " import some_module pub fn main() { some_module.constant() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info_in_constant_imported_qualified() { assert_erl!( ( "some_module", "some_module", " pub fn module_info() { 1 } pub const constant = module_info " ), " import some_module.{constant} pub fn main() { constant() } " ); } // https://github.com/gleam-lang/gleam/issues/3648 #[test] fn windows_file_escaping_bug() { let src = "pub fn main() { panic }"; let path = "C:\\root\\project\\test\\my\\mod.gleam"; let output = compile_test_project(src, path, Vec::new()); insta::assert_snapshot!(insta::internals::AutoName, output, src); } // https://github.com/gleam-lang/gleam/issues/3315 #[test] fn bit_pattern_shadowing() { assert_erl!( " pub fn main() { let code = <<\"hello world\":utf8>> let pre = 1 case code { <> -> pre _ -> panic } } " ); } #[test] fn float_division_by_literal_zero() { assert_erl!( " pub fn main() { 1.0 /. 0.0 } " ); } #[test] fn float_division_by_literal_non_zero() { assert_erl!( " pub fn main() { 1.0 /. 2.0 } " ); }