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