Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / erlang / tests.rs
19 kB 1038 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2018 The Gleam contributors 3 4use std::time::SystemTime; 5 6use camino::Utf8PathBuf; 7 8use crate::analyse::TargetSupport; 9use crate::config::PackageConfig; 10use crate::type_::PRELUDE_MODULE_NAME; 11use crate::warning::WarningEmitter; 12use crate::{build, inline}; 13use crate::{ 14 build::{Origin, Target}, 15 erlang::module, 16 line_numbers::LineNumbers, 17 uid::UniqueIdGenerator, 18 warning::TypeWarningEmitter, 19}; 20use camino::Utf8Path; 21 22mod assert; 23mod bit_arrays; 24mod case; 25mod conditional_compilation; 26mod consts; 27mod custom_types; 28mod documentation; 29mod echo; 30mod external_fn; 31mod functions; 32mod guards; 33mod inlining; 34mod let_assert; 35mod numbers; 36mod panic; 37mod patterns; 38mod pipes; 39mod records; 40mod reserved; 41mod strings; 42mod todo; 43mod type_params; 44mod use_; 45mod variables; 46 47pub fn compile_test_project( 48 src: &str, 49 src_path: &str, 50 dependencies: Vec<(&str, &str, &str)>, 51) -> String { 52 let mut modules = im::HashMap::new(); 53 let ids = UniqueIdGenerator::new(); 54 // DUPE: preludeinsertion 55 // TODO: Currently we do this here and also in the tests. It would be better 56 // to have one place where we create all this required state for use in each 57 // place. 58 let _ = modules.insert( 59 PRELUDE_MODULE_NAME.into(), 60 crate::type_::build_prelude(&ids), 61 ); 62 let mut direct_dependencies = std::collections::HashMap::from_iter(vec![]); 63 for (dep_package, dep_name, dep_src) in dependencies { 64 let mut dep_config = PackageConfig::default(); 65 dep_config.name = dep_package.into(); 66 let parsed = crate::parse::parse_module( 67 Utf8PathBuf::from("test/path"), 68 dep_src, 69 &WarningEmitter::null(), 70 ) 71 .expect("dep syntax error"); 72 let mut ast = parsed.module; 73 ast.name = dep_name.into(); 74 let line_numbers = LineNumbers::new(dep_src); 75 76 let dep = crate::analyse::ModuleAnalyzerConstructor::<()> { 77 target: Target::Erlang, 78 ids: &ids, 79 origin: Origin::Src, 80 importable_modules: &modules, 81 warnings: &TypeWarningEmitter::null(), 82 direct_dependencies: &std::collections::HashMap::new(), 83 dev_dependencies: &std::collections::HashSet::new(), 84 target_support: TargetSupport::NotEnforced, 85 package_config: &dep_config, 86 } 87 .infer_module(ast, line_numbers, "".into()) 88 .expect("should successfully infer dep Erlang"); 89 let _ = modules.insert(dep_name.into(), dep.type_info); 90 let _ = direct_dependencies.insert(dep_package.into(), ()); 91 } 92 let path = Utf8PathBuf::from(src_path); 93 let parsed = crate::parse::parse_module(path.clone(), src, &WarningEmitter::null()) 94 .expect("syntax error"); 95 let mut config = PackageConfig::default(); 96 config.name = "thepackage".into(); 97 let mut ast = parsed.module; 98 ast.name = "my/mod".into(); 99 let line_numbers = LineNumbers::new(src); 100 let ast = crate::analyse::ModuleAnalyzerConstructor::<()> { 101 target: Target::Erlang, 102 ids: &ids, 103 origin: Origin::Src, 104 importable_modules: &modules, 105 warnings: &TypeWarningEmitter::null(), 106 direct_dependencies: &direct_dependencies, 107 dev_dependencies: &std::collections::HashSet::new(), 108 target_support: TargetSupport::NotEnforced, 109 package_config: &config, 110 } 111 .infer_module(ast, line_numbers, path.clone()) 112 .expect("should successfully infer root Erlang"); 113 114 let ast = inline::module(ast, &modules); 115 116 // After building everything we still need to attach the module comments, to 117 // do that we're reusing the `attach_doc_and_module_comments` that's used 118 // for the real thing. We just have to make a placeholder module wrapping 119 // the parsed ast and call the function! 120 let mut built_module = build::Module { 121 name: "my/mod".into(), 122 code: src.into(), 123 mtime: SystemTime::UNIX_EPOCH, 124 input_path: path, 125 origin: Origin::Src, 126 ast, 127 extra: parsed.extra, 128 dependencies: vec![], 129 }; 130 let root = Utf8Path::new("/root"); 131 built_module.attach_doc_and_module_comments(); 132 133 let line_numbers = LineNumbers::new(src); 134 module(&built_module.ast, &line_numbers, root) 135 .unwrap() 136 .replace( 137 std::include_str!("../../templates/echo.erl"), 138 "% ...omitted code from `templates/echo.erl`...", 139 ) 140} 141 142#[macro_export] 143macro_rules! assert_erl { 144 ($(($dep_package:expr, $dep_name:expr, $dep_src:expr)),+, $src:literal $(,)?) => {{ 145 let compiled = $crate::erlang::tests::compile_test_project( 146 $src, 147 "/root/project/test/my/mod.gleam", 148 vec![$(($dep_package, $dep_name, $dep_src)),*], 149 ); 150 let output = format!( 151 "----- SOURCE CODE\n{}\n\n----- COMPILED ERLANG\n{}", 152 $src, compiled 153 ); 154 insta::assert_snapshot!(insta::internals::AutoName, output, $src); 155 }}; 156 157 ($src:expr $(,)?) => {{ 158 let compiled = $crate::erlang::tests::compile_test_project( 159 $src, 160 "/root/project/test/my/mod.gleam", 161 Vec::new(), 162 ); 163 let output = format!( 164 "----- SOURCE CODE\n{}\n\n----- COMPILED ERLANG\n{}", 165 $src, compiled, 166 ); 167 insta::assert_snapshot!(insta::internals::AutoName, output, $src); 168 }}; 169} 170 171#[test] 172fn integration_test() { 173 assert_erl!( 174 r#"pub fn go() { 175let x = #(100000000000000000, #(2000000000, 3000000000000, 40000000000), 50000, 6000000000) 176 x 177}"# 178 ); 179} 180 181#[test] 182fn integration_test0_1() { 183 assert_erl!( 184 r#"pub fn go() { 185 let y = 1 186 let y = 2 187 y 188}"# 189 ); 190} 191 192#[test] 193fn integration_test0_2() { 194 // hex, octal, and binary literals 195 assert_erl!( 196 r#"pub fn go() { 197 let fifteen = 0xF 198 let nine = 0o11 199 let ten = 0b1010 200 fifteen 201}"# 202 ); 203} 204 205#[test] 206fn integration_test0_3() { 207 assert_erl!( 208 r#"pub fn go() { 209 let y = 1 210 let y = 2 211 y 212}"# 213 ); 214} 215 216#[test] 217fn integration_test1() { 218 assert_erl!(r#"pub fn t() { True }"#); 219} 220 221#[test] 222fn integration_test1_1() { 223 assert_erl!( 224 r#"pub type Money { Pound(Int) } 225pub fn pound(x) { Pound(x) }"# 226 ); 227} 228 229#[test] 230fn integration_test1_2() { 231 assert_erl!(r#"pub fn loop() { loop() }"#); 232} 233 234#[test] 235fn integration_test1_4() { 236 assert_erl!( 237 r#"fn inc(x) { x + 1 } 238 pub fn go() { 1 |> inc |> inc |> inc }"# 239 ); 240} 241 242#[test] 243fn integration_test1_5() { 244 assert_erl!( 245 r#"fn add(x, y) { x + y } 246 pub fn go() { 1 |> add(_, 1) |> add(2, _) |> add(_, 3) }"# 247 ); 248} 249 250#[test] 251fn integration_test1_6() { 252 assert_erl!( 253 r#"pub fn and(x, y) { x && y } 254pub fn or(x, y) { x || y } 255pub fn remainder(x, y) { x % y } 256pub fn fdiv(x, y) { x /. y } 257 "# 258 ); 259} 260 261#[test] 262fn integration_test2() { 263 assert_erl!( 264 r#"pub fn second(list) { case list { [x, y] -> y z -> 1 } } 265pub fn tail(list) { case list { [x, ..xs] -> xs z -> list } } 266 "# 267 ); 268} 269 270#[test] 271fn integration_test5() { 272 assert_erl!( 273 "pub fn tail(list) { 274 case list { 275 [x, ..] -> x 276 _ -> 0 277 } 278}" 279 ); 280} 281 282#[test] 283fn integration_test6() { 284 assert_erl!(r#"pub fn x() { let x = 1 let x = x + 1 x }"#); 285} 286 287#[test] 288fn integration_test8() { 289 // Translation of Float-specific BinOp into variable-type Erlang term comparison. 290 assert_erl!(r#"pub fn x() { 1. <. 2.3 }"#); 291} 292 293#[test] 294fn integration_test9() { 295 // Custom type creation 296 assert_erl!( 297 r#"pub type Pair(x, y) { Pair(x: x, y: y) } pub fn x() { Pair(1, 2) Pair(3., 4.) }"# 298 ); 299} 300 301#[test] 302fn integration_test10() { 303 assert_erl!( 304 r#"pub type Null { Null } 305pub fn x() { Null }"# 306 ); 307} 308 309#[test] 310fn integration_test3() { 311 assert_erl!( 312 r#"pub type Point { Point(x: Int, y: Int) } 313pub fn y() { fn() { Point }()(4, 6) }"# 314 ); 315} 316 317#[test] 318fn integration_test11() { 319 assert_erl!( 320 r#"pub type Point { Point(x: Int, y: Int) } 321pub fn x() { Point(x: 4, y: 6) Point(y: 1, x: 9) }"# 322 ); 323} 324 325#[test] 326fn integration_test12() { 327 assert_erl!( 328 r#"pub type Point { Point(x: Int, y: Int) } 329pub fn x(y) { let Point(a, b) = y a }"# 330 ); 331} 332//https://github.com/gleam-lang/gleam/issues/1106 333 334#[test] 335fn integration_test13() { 336 assert_erl!( 337 r#"pub type State{ Start(Int) End(Int) } 338 pub fn build(constructor : fn(Int) -> a) -> a { constructor(1) } 339 pub fn main() { build(End) }"# 340 ); 341} 342 343#[test] 344fn integration_test16() { 345 assert_erl!( 346 r#"fn go(x xx, y yy) { xx } 347pub fn x() { go(x: 1, y: 2) go(y: 3, x: 4) }"# 348 ); 349} 350 351#[test] 352fn integration_test17() { 353 // https://github.com/gleam-lang/gleam/issues/289 354 assert_erl!( 355 r#" 356pub type User { User(id: Int, name: String, age: Int) } 357pub fn create_user(user_id) { User(age: 22, id: user_id, name: "") } 358"# 359 ); 360} 361 362#[test] 363fn integration_test18() { 364 assert_erl!(r#"pub fn run() { case 1, 2 { a, b -> a } }"#); 365} 366 367#[test] 368fn integration_test19() { 369 assert_erl!( 370 r#"pub type X { X(x: Int, y: Float) } 371pub fn x() { X(x: 1, y: 2.) X(y: 3., x: 4) }"# 372 ); 373} 374 375#[test] 376fn integration_test20() { 377 assert_erl!( 378 r#" 379pub fn go(a) { 380 let a = a + 1 381 a 382} 383 384 "# 385 ); 386} 387 388#[test] 389fn integration_test21() { 390 assert_erl!( 391 r#" 392pub fn go(a) { 393 let a = 1 394 a 395} 396 397 "# 398 ); 399} 400 401#[test] 402fn integration_test22() { 403 // https://github.com/gleam-lang/gleam/issues/358 404 assert_erl!( 405 r#" 406pub fn factory(f, i) { 407 f(i) 408} 409 410pub type Box { 411 Box(i: Int) 412} 413 414pub fn main() { 415 factory(Box, 0) 416} 417"# 418 ); 419} 420 421#[test] 422fn integration_test23() { 423 // https://github.com/gleam-lang/gleam/issues/384 424 assert_erl!( 425 r#" 426pub fn main(args) { 427 case args { 428 _ -> { 429 let a = 1 430 a 431 } 432 } 433 let a = 2 434 a 435} 436"# 437 ); 438} 439 440#[test] 441fn binop_parens() { 442 // Parentheses are added for binop subexpressions 443 assert_erl!( 444 r#" 445pub fn main() { 446 let a = 2 * {3 + 1} / 2 447 let b = 5 + 3 / 3 * 2 - 6 * 4 448 b 449} 450"# 451 ); 452} 453 454#[test] 455fn field_access_function_call() { 456 // Parentheses are added when calling functions returned by record access 457 assert_erl!( 458 r#" 459pub type FnBox { 460 FnBox(f: fn(Int) -> Int) 461} 462 463pub fn main() { 464 let b = FnBox(f: fn(x) { x }) 465 b.f(5) 466} 467"# 468 ); 469} 470 471#[test] 472fn field_access_function_call1() { 473 // Parentheses are added when calling functions returned by tuple access 474 assert_erl!( 475 r#" 476pub fn main() { 477 let t = #(fn(x) { x }) 478 479 t.0(5) 480} 481"# 482 ); 483} 484 485// https://github.com/gleam-lang/gleam/issues/777 486#[test] 487fn block_assignment() { 488 assert_erl!( 489 r#" 490pub fn main() { 491 let x = { 492 1 493 2 494 } 495 x 496} 497"# 498 ); 499} 500 501#[test] 502fn recursive_type() { 503 // TODO: we should be able to generalise `id` and we should be 504 // able to handle recursive types. Either of these type features 505 // would make this module type check OK. 506 assert_erl!( 507 r#" 508fn id(x) { 509 x 510} 511 512pub fn main() { 513 id(id) 514} 515"# 516 ); 517} 518 519#[test] 520fn tuple_access_in_guard() { 521 assert_erl!( 522 r#" 523pub fn main() { 524 let key = 10 525 let x = [#(10, 2), #(1, 2)] 526 case x { 527 [first, ..rest] if first.0 == key -> "ok" 528 _ -> "ko" 529 } 530} 531"# 532 ); 533} 534 535#[test] 536fn variable_name_underscores_preserved() { 537 assert_erl!( 538 "pub fn a(name_: String) -> String { 539 let name__ = name_ 540 let name = name__ 541 let one_1 = 1 542 let one1 = one_1 543 name 544}" 545 ); 546} 547 548#[test] 549fn allowed_string_escapes() { 550 assert_erl!(r#"pub fn a() { "\n" "\r" "\t" "\\" "\"" "\\^" }"#); 551} 552 553// https://github.com/gleam-lang/gleam/issues/1006 554#[test] 555fn keyword_constructors() { 556 assert_erl!("pub type X { Div }"); 557} 558 559// https://github.com/gleam-lang/gleam/issues/1006 560#[test] 561fn keyword_constructors1() { 562 assert_erl!("pub type X { Fun(Int) }"); 563} 564 565#[test] 566fn discard_in_assert() { 567 assert_erl!( 568 "pub fn x(y) { 569 let assert Ok(_) = y 570 1 571}" 572 ); 573} 574 575// https://github.com/gleam-lang/gleam/issues/1424 576#[test] 577fn operator_pipe_right_hand_side() { 578 assert_erl!( 579 "fn id(x) { 580 x 581} 582 583pub fn bool_expr(x, y) { 584 y || x |> id 585}" 586 ); 587} 588 589#[test] 590fn negation() { 591 assert_erl!( 592 "pub fn negate(x) { 593 !x 594}" 595 ) 596} 597 598#[test] 599fn negation_block() { 600 assert_erl!( 601 "pub fn negate(x) { 602 !{ 603 123 604 x 605 } 606}" 607 ) 608} 609 610// https://github.com/gleam-lang/gleam/issues/1655 611#[test] 612fn tail_maybe_expr_block() { 613 assert_erl!( 614 "pub fn a() { 615 let fake_tap = fn(x) { x } 616 let b = [99] 617 [ 618 1, 619 2, 620 ..b 621 |> fake_tap 622 ] 623} 624" 625 ); 626} 627 628// https://github.com/gleam-lang/gleam/issues/1587 629#[test] 630fn guard_variable_rewriting() { 631 assert_erl!( 632 "pub fn main() { 633 case 1.0 { 634 a if a <. 0.0 -> { 635 let a = a 636 a 637 } 638 _ -> 0.0 639 } 640} 641" 642 ) 643} 644 645// https://github.com/gleam-lang/gleam/issues/1816 646#[test] 647fn function_argument_shadowing() { 648 assert_erl!( 649 "pub fn main(a) { 650 Box 651} 652 653pub type Box { 654 Box(Int) 655} 656" 657 ) 658} 659 660// https://github.com/gleam-lang/gleam/issues/2156 661#[test] 662fn dynamic() { 663 assert_erl!("pub type Dynamic") 664} 665 666// https://github.com/gleam-lang/gleam/issues/2166 667#[test] 668fn inline_const_pattern_option() { 669 assert_erl!( 670 "pub fn main() { 671 let fifteen = 15 672 let x = <<5:size(sixteen)>> 673 case x { 674 <<5:size(sixteen)>> -> <<5:size(sixteen)>> 675 <<6:size(fifteen)>> -> <<5:size(fifteen)>> 676 _ -> <<>> 677 } 678 } 679 680 pub const sixteen = 16" 681 ) 682} 683 684// https://github.com/gleam-lang/gleam/issues/2349 685#[test] 686fn positive_zero() { 687 assert_erl!( 688 " 689pub fn main() { 690 0.0 691} 692" 693 ) 694} 695 696// https://github.com/gleam-lang/gleam/issues/3073 697#[test] 698fn scientific_notation() { 699 assert_erl!( 700 " 701pub fn main() { 702 1.0e6 703 1.e6 704} 705" 706 ); 707} 708 709// https://github.com/gleam-lang/gleam/issues/3304 710#[test] 711fn type_named_else() { 712 assert_erl!( 713 " 714pub type Else { 715 Else 716} 717 718pub fn main() { 719 Else 720} 721" 722 ); 723} 724 725// https://github.com/gleam-lang/gleam/issues/3382 726#[test] 727fn type_named_module_info() { 728 assert_erl!( 729 " 730pub type ModuleInfo { 731 ModuleInfo 732} 733 734pub fn main() { 735 ModuleInfo 736} 737" 738 ); 739} 740 741// https://github.com/gleam-lang/gleam/issues/3382 742#[test] 743fn function_named_module_info() { 744 assert_erl!( 745 " 746pub fn module_info() { 747 1 748} 749 750pub fn main() { 751 module_info() 752} 753" 754 ); 755} 756 757// https://github.com/gleam-lang/gleam/issues/3382 758#[test] 759fn function_named_module_info_imported() { 760 assert_erl!( 761 ( 762 "some_module", 763 "some_module", 764 " 765pub fn module_info() { 766 1 767} 768 " 769 ), 770 " 771import some_module 772 773pub fn main() { 774 some_module.module_info() 775} 776" 777 ); 778} 779 780// https://github.com/gleam-lang/gleam/issues/3382 781#[test] 782fn function_named_module_info_imported_qualified() { 783 assert_erl!( 784 ( 785 "some_module", 786 "some_module", 787 " 788pub fn module_info() { 789 1 790} 791 " 792 ), 793 " 794import some_module.{module_info} 795 796pub fn main() { 797 module_info() 798} 799" 800 ); 801} 802 803// https://github.com/gleam-lang/gleam/issues/3382 804#[test] 805fn constant_named_module_info() { 806 assert_erl!( 807 " 808pub const module_info = 1 809 810pub fn main() { 811 module_info 812} 813" 814 ); 815} 816 817// https://github.com/gleam-lang/gleam/issues/3382 818#[test] 819fn constant_named_module_info_imported() { 820 assert_erl!( 821 ( 822 "some_module", 823 "some_module", 824 " 825pub const module_info = 1 826 " 827 ), 828 " 829import some_module 830 831pub fn main() { 832 some_module.module_info 833} 834" 835 ); 836} 837 838// https://github.com/gleam-lang/gleam/issues/3382 839#[test] 840fn constant_named_module_info_imported_qualified() { 841 assert_erl!( 842 ( 843 "some_module", 844 "some_module", 845 " 846pub const module_info = 1 847 " 848 ), 849 " 850import some_module.{module_info} 851 852pub fn main() { 853 module_info 854} 855" 856 ); 857} 858 859// https://github.com/gleam-lang/gleam/issues/3382 860#[test] 861fn constant_named_module_info_with_function_inside() { 862 assert_erl!( 863 " 864pub fn function() { 865 1 866} 867 868pub const module_info = function 869 870pub fn main() { 871 module_info() 872} 873" 874 ); 875} 876 877// https://github.com/gleam-lang/gleam/issues/3382 878#[test] 879fn constant_named_module_info_with_function_inside_imported() { 880 assert_erl!( 881 ( 882 "some_module", 883 "some_module", 884 " 885pub fn function() { 886 1 887} 888 889pub const module_info = function 890" 891 ), 892 " 893import some_module 894 895pub fn main() { 896 some_module.module_info() 897} 898" 899 ); 900} 901 902// https://github.com/gleam-lang/gleam/issues/3382 903#[test] 904fn constant_named_module_info_with_function_inside_imported_qualified() { 905 assert_erl!( 906 ( 907 "some_module", 908 "some_module", 909 " 910pub fn function() { 911 1 912} 913 914pub const module_info = function 915" 916 ), 917 " 918import some_module.{module_info} 919 920pub fn main() { 921 module_info() 922} 923" 924 ); 925} 926 927// https://github.com/gleam-lang/gleam/issues/3382 928#[test] 929fn function_named_module_info_in_constant() { 930 assert_erl!( 931 " 932pub fn module_info() { 933 1 934} 935 936pub const constant = module_info 937 938pub fn main() { 939 constant() 940} 941" 942 ); 943} 944 945// https://github.com/gleam-lang/gleam/issues/3382 946#[test] 947fn function_named_module_info_in_constant_imported() { 948 assert_erl!( 949 ( 950 "some_module", 951 "some_module", 952 " 953pub fn module_info() { 954 1 955} 956 957pub const constant = module_info 958 " 959 ), 960 " 961import some_module 962 963pub fn main() { 964 some_module.constant() 965} 966" 967 ); 968} 969 970// https://github.com/gleam-lang/gleam/issues/3382 971#[test] 972fn function_named_module_info_in_constant_imported_qualified() { 973 assert_erl!( 974 ( 975 "some_module", 976 "some_module", 977 " 978pub fn module_info() { 979 1 980} 981 982pub const constant = module_info 983 " 984 ), 985 " 986import some_module.{constant} 987 988pub fn main() { 989 constant() 990} 991" 992 ); 993} 994 995// https://github.com/gleam-lang/gleam/issues/3648 996#[test] 997fn windows_file_escaping_bug() { 998 let src = "pub fn main() { Nil }"; 999 let path = "C:\\root\\project\\test\\my\\mod.gleam"; 1000 let output = compile_test_project(src, path, Vec::new()); 1001 insta::assert_snapshot!(insta::internals::AutoName, output, src); 1002} 1003 1004// https://github.com/gleam-lang/gleam/issues/3315 1005#[test] 1006fn bit_pattern_shadowing() { 1007 assert_erl!( 1008 " 1009pub fn main() { 1010 let code = <<\"hello world\":utf8>> 1011 let pre = 1 1012 case code { 1013 <<pre:bytes-size(pre), _:bytes>> -> pre 1014 _ -> panic 1015 } 1016} " 1017 ); 1018} 1019 1020#[test] 1021fn float_division_by_literal_zero() { 1022 assert_erl!( 1023 " 1024pub fn main() { 1025 1.0 /. 0.0 1026} " 1027 ); 1028} 1029 1030#[test] 1031fn float_division_by_literal_non_zero() { 1032 assert_erl!( 1033 " 1034pub fn main() { 1035 1.0 /. 2.0 1036} " 1037 ); 1038}