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