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