Fork of daniellemaywood.uk/gleam — Wasm codegen work
32 kB
1783 lines
1use crate::ast::SrcSpan;
2use crate::parse::error::{
3 InvalidUnicodeEscapeError, LexicalError, LexicalErrorType, ParseError, ParseErrorType,
4};
5use crate::parse::lexer::make_tokenizer;
6use crate::parse::token::Token;
7use crate::warning::WarningEmitter;
8use camino::Utf8PathBuf;
9
10use ecow::EcoString;
11use itertools::Itertools;
12use pretty_assertions::assert_eq;
13
14macro_rules! assert_error {
15 ($src:expr, $error:expr $(,)?) => {
16 let result = crate::parse::parse_statement_sequence($src).expect_err("should not parse");
17 assert_eq!(($src, $error), ($src, result),);
18 };
19 ($src:expr) => {
20 let error = $crate::parse::tests::expect_error($src);
21 let output = format!("----- SOURCE CODE\n{}\n\n----- ERROR\n{}", $src, error);
22 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
23 };
24}
25
26macro_rules! assert_module_error {
27 ($src:expr) => {
28 let error = $crate::parse::tests::expect_module_error($src);
29 let output = format!("----- SOURCE CODE\n{}\n\n----- ERROR\n{}", $src, error);
30 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
31 };
32}
33
34macro_rules! assert_parse_module {
35 ($src:expr) => {
36 let result = crate::parse::parse_module(
37 camino::Utf8PathBuf::from("test/path"),
38 $src,
39 &crate::warning::WarningEmitter::null(),
40 )
41 .expect("should parse");
42 insta::assert_snapshot!(insta::internals::AutoName, &format!("{:#?}", result), $src);
43 };
44}
45
46macro_rules! assert_parse {
47 ($src:expr) => {
48 let result = crate::parse::parse_statement_sequence($src).expect("should parse");
49 insta::assert_snapshot!(insta::internals::AutoName, &format!("{:#?}", result), $src);
50 };
51}
52
53pub fn expect_module_error(src: &str) -> String {
54 let result =
55 crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null())
56 .expect_err("should not parse");
57 let error = crate::error::Error::Parse {
58 src: src.into(),
59 path: Utf8PathBuf::from("/src/parse/error.gleam"),
60 error: result,
61 };
62 error.pretty_string()
63}
64
65pub fn expect_error(src: &str) -> String {
66 let result = crate::parse::parse_statement_sequence(src).expect_err("should not parse");
67 let error = crate::error::Error::Parse {
68 src: src.into(),
69 path: Utf8PathBuf::from("/src/parse/error.gleam"),
70 error: result,
71 };
72 error.pretty_string()
73}
74
75#[test]
76fn int_tests() {
77 // bad binary digit
78 assert_error!(
79 "0b012",
80 ParseError {
81 error: ParseErrorType::LexError {
82 error: LexicalError {
83 error: LexicalErrorType::DigitOutOfRadix,
84 location: SrcSpan { start: 4, end: 4 },
85 }
86 },
87 location: SrcSpan { start: 4, end: 4 },
88 }
89 );
90 // bad octal digit
91 assert_error!(
92 "0o12345678",
93 ParseError {
94 error: ParseErrorType::LexError {
95 error: LexicalError {
96 error: LexicalErrorType::DigitOutOfRadix,
97 location: SrcSpan { start: 9, end: 9 },
98 }
99 },
100 location: SrcSpan { start: 9, end: 9 },
101 }
102 );
103 // no int value
104 assert_error!(
105 "0x",
106 ParseError {
107 error: ParseErrorType::LexError {
108 error: LexicalError {
109 error: LexicalErrorType::RadixIntNoValue,
110 location: SrcSpan { start: 1, end: 1 },
111 }
112 },
113 location: SrcSpan { start: 1, end: 1 },
114 }
115 );
116 // trailing underscore
117 assert_error!(
118 "1_000_",
119 ParseError {
120 error: ParseErrorType::LexError {
121 error: LexicalError {
122 error: LexicalErrorType::NumTrailingUnderscore,
123 location: SrcSpan { start: 5, end: 5 },
124 }
125 },
126 location: SrcSpan { start: 5, end: 5 },
127 }
128 );
129}
130
131#[test]
132fn string_bad_character_escape() {
133 assert_error!(
134 r#""\g""#,
135 ParseError {
136 error: ParseErrorType::LexError {
137 error: LexicalError {
138 error: LexicalErrorType::BadStringEscape,
139 location: SrcSpan { start: 1, end: 2 },
140 }
141 },
142 location: SrcSpan { start: 1, end: 2 },
143 }
144 );
145}
146
147#[test]
148fn string_bad_character_escape_leading_backslash() {
149 assert_error!(
150 r#""\\\g""#,
151 ParseError {
152 error: ParseErrorType::LexError {
153 error: LexicalError {
154 error: LexicalErrorType::BadStringEscape,
155 location: SrcSpan { start: 3, end: 4 },
156 }
157 },
158 location: SrcSpan { start: 3, end: 4 },
159 }
160 );
161}
162
163#[test]
164fn string_freestanding_unicode_escape_sequence() {
165 assert_error!(
166 r#""\u""#,
167 ParseError {
168 error: ParseErrorType::LexError {
169 error: LexicalError {
170 error: LexicalErrorType::InvalidUnicodeEscape(
171 InvalidUnicodeEscapeError::MissingOpeningBrace,
172 ),
173 location: SrcSpan { start: 2, end: 3 },
174 }
175 },
176 location: SrcSpan { start: 2, end: 3 },
177 }
178 );
179}
180
181#[test]
182fn string_unicode_escape_sequence_no_braces() {
183 assert_error!(
184 r#""\u65""#,
185 ParseError {
186 error: ParseErrorType::LexError {
187 error: LexicalError {
188 error: LexicalErrorType::InvalidUnicodeEscape(
189 InvalidUnicodeEscapeError::MissingOpeningBrace,
190 ),
191 location: SrcSpan { start: 2, end: 3 },
192 }
193 },
194 location: SrcSpan { start: 2, end: 3 },
195 }
196 );
197}
198
199#[test]
200fn string_unicode_escape_sequence_invalid_hex() {
201 assert_error!(
202 r#""\u{z}""#,
203 ParseError {
204 error: ParseErrorType::LexError {
205 error: LexicalError {
206 error: LexicalErrorType::InvalidUnicodeEscape(
207 InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace,
208 ),
209 location: SrcSpan { start: 4, end: 5 },
210 }
211 },
212 location: SrcSpan { start: 4, end: 5 },
213 }
214 );
215}
216
217#[test]
218fn string_unclosed_unicode_escape_sequence() {
219 assert_error!(
220 r#""\u{039a""#,
221 ParseError {
222 error: ParseErrorType::LexError {
223 error: LexicalError {
224 error: LexicalErrorType::InvalidUnicodeEscape(
225 InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace,
226 ),
227 location: SrcSpan { start: 8, end: 9 },
228 }
229 },
230 location: SrcSpan { start: 8, end: 9 },
231 }
232 );
233}
234
235#[test]
236fn string_empty_unicode_escape_sequence() {
237 assert_error!(
238 r#""\u{}""#,
239 ParseError {
240 error: ParseErrorType::LexError {
241 error: LexicalError {
242 error: LexicalErrorType::InvalidUnicodeEscape(
243 InvalidUnicodeEscapeError::InvalidNumberOfHexDigits,
244 ),
245 location: SrcSpan { start: 1, end: 5 },
246 }
247 },
248 location: SrcSpan { start: 1, end: 5 },
249 }
250 );
251}
252
253#[test]
254fn string_overlong_unicode_escape_sequence() {
255 assert_error!(
256 r#""\u{0011f601}""#,
257 ParseError {
258 error: ParseErrorType::LexError {
259 error: LexicalError {
260 error: LexicalErrorType::InvalidUnicodeEscape(
261 InvalidUnicodeEscapeError::InvalidNumberOfHexDigits,
262 ),
263 location: SrcSpan { start: 1, end: 13 },
264 }
265 },
266 location: SrcSpan { start: 1, end: 13 },
267 }
268 );
269}
270
271#[test]
272fn string_invalid_unicode_escape_sequence() {
273 assert_error!(
274 r#""\u{110000}""#,
275 ParseError {
276 error: ParseErrorType::LexError {
277 error: LexicalError {
278 error: LexicalErrorType::InvalidUnicodeEscape(
279 InvalidUnicodeEscapeError::InvalidCodepoint,
280 ),
281 location: SrcSpan { start: 1, end: 11 },
282 }
283 },
284 location: SrcSpan { start: 1, end: 11 },
285 }
286 );
287}
288
289#[test]
290fn bit_array() {
291 // non int value in bit array unit option
292 assert_error!(
293 "let x = <<1:unit(0)>> x",
294 ParseError {
295 error: ParseErrorType::InvalidBitArrayUnit,
296 location: SrcSpan { start: 17, end: 18 }
297 }
298 );
299}
300
301#[test]
302fn bit_array1() {
303 assert_error!(
304 "let x = <<1:unit(257)>> x",
305 ParseError {
306 error: ParseErrorType::InvalidBitArrayUnit,
307 location: SrcSpan { start: 17, end: 20 }
308 }
309 );
310}
311
312#[test]
313fn bit_array2() {
314 // patterns cannot be nested
315 assert_error!(
316 "case <<>> { <<<<1>>:bits>> -> 1 }",
317 ParseError {
318 error: ParseErrorType::NestedBitArrayPattern,
319 location: SrcSpan { start: 14, end: 19 }
320 }
321 );
322}
323
324// https://github.com/gleam-lang/gleam/issues/3125
325#[test]
326fn triple_equals() {
327 assert_error!(
328 "let wobble:Int = 32
329 wobble === 42",
330 ParseError {
331 error: ParseErrorType::LexError {
332 error: LexicalError {
333 error: LexicalErrorType::InvalidTripleEqual,
334 location: SrcSpan { start: 35, end: 38 },
335 }
336 },
337 location: SrcSpan { start: 35, end: 38 },
338 }
339 );
340}
341
342#[test]
343fn triple_equals_with_whitespace() {
344 assert_error!(
345 "let wobble:Int = 32
346 wobble == = 42",
347 ParseError {
348 error: ParseErrorType::OpNakedRight,
349 location: SrcSpan { start: 35, end: 37 },
350 }
351 );
352}
353
354// https://github.com/gleam-lang/gleam/issues/1231
355#[test]
356fn pointless_spread() {
357 assert_error!(
358 "let xs = [] [..xs]",
359 ParseError {
360 error: ParseErrorType::ListSpreadWithoutElements,
361 location: SrcSpan { start: 12, end: 18 },
362 }
363 );
364}
365
366// https://github.com/gleam-lang/gleam/issues/1358
367#[test]
368fn lowcase_bool_in_pattern() {
369 assert_error!(
370 "case 42 > 42 { true -> 1; false -> 2; }",
371 ParseError {
372 error: ParseErrorType::LowcaseBooleanPattern,
373 location: SrcSpan { start: 15, end: 19 },
374 }
375 );
376}
377
378// https://github.com/gleam-lang/gleam/issues/1613
379#[test]
380fn anonymous_function_labeled_arguments() {
381 assert_error!(
382 "let anon_subtract = fn (minuend a: Int, subtrahend b: Int) -> Int {
383 a - b
384}",
385 ParseError {
386 location: SrcSpan { start: 24, end: 31 },
387 error: ParseErrorType::UnexpectedLabel
388 }
389 );
390}
391
392#[test]
393fn no_let_binding() {
394 assert_error!(
395 "wibble = 32",
396 ParseError {
397 location: SrcSpan { start: 7, end: 8 },
398 error: ParseErrorType::NoLetBinding
399 }
400 );
401}
402
403#[test]
404fn no_let_binding1() {
405 assert_error!(
406 "wibble:Int = 32",
407 ParseError {
408 location: SrcSpan { start: 6, end: 7 },
409 error: ParseErrorType::NoLetBinding
410 }
411 );
412}
413
414#[test]
415fn no_let_binding2() {
416 assert_error!(
417 "let wobble:Int = 32
418 wobble = 42",
419 ParseError {
420 location: SrcSpan { start: 35, end: 36 },
421 error: ParseErrorType::NoLetBinding
422 }
423 );
424}
425
426#[test]
427fn no_let_binding3() {
428 assert_error!(
429 "[x] = [2]",
430 ParseError {
431 location: SrcSpan { start: 4, end: 5 },
432 error: ParseErrorType::NoLetBinding
433 }
434 );
435}
436
437#[test]
438fn with_let_binding3() {
439 // The same with `let assert` must parse:
440 assert_parse!("let assert [x] = [2]");
441}
442
443#[test]
444fn with_let_binding3_and_annotation() {
445 assert_parse!("let assert [x]: List(Int) = [2]");
446}
447
448#[test]
449fn no_eq_after_binding() {
450 assert_error!(
451 "let wibble",
452 ParseError {
453 location: SrcSpan { start: 4, end: 10 },
454 error: ParseErrorType::ExpectedEqual
455 }
456 );
457}
458
459#[test]
460fn no_eq_after_binding1() {
461 assert_error!(
462 "let wibble
463 wibble = 4",
464 ParseError {
465 location: SrcSpan { start: 4, end: 10 },
466 error: ParseErrorType::ExpectedEqual
467 }
468 );
469}
470
471#[test]
472fn echo_followed_by_expression_ends_where_expression_ends() {
473 assert_parse!("echo wibble");
474}
475
476#[test]
477fn echo_with_no_expressions_after_it() {
478 assert_parse!("echo");
479}
480
481#[test]
482fn echo_with_block() {
483 assert_parse!("echo { 1 + 1 }");
484}
485
486#[test]
487fn echo_has_lower_precedence_than_binop() {
488 assert_parse!("echo 1 + 1");
489}
490
491#[test]
492fn echo_in_a_pipeline() {
493 assert_parse!("[] |> echo |> wibble");
494}
495
496#[test]
497fn echo_has_lower_precedence_than_pipeline() {
498 assert_parse!("echo wibble |> wobble |> woo");
499}
500
501#[test]
502fn echo_cannot_have_an_expression_in_a_pipeline() {
503 // So this is actually two pipelines!
504 assert_parse!("[] |> echo fun |> wibble");
505}
506
507#[test]
508fn panic_with_echo() {
509 assert_parse!("panic as echo \"string\"");
510}
511
512#[test]
513fn echo_with_panic() {
514 assert_parse!("echo panic as \"a\"");
515}
516
517#[test]
518fn repeated_echos() {
519 assert_parse!("echo echo echo 1");
520}
521
522#[test]
523fn echo_at_start_of_pipeline_wraps_the_whole_thing() {
524 assert_parse!("echo 1 |> wibble |> wobble");
525}
526
527#[test]
528fn no_let_binding_snapshot_1() {
529 assert_error!("wibble = 4");
530}
531
532#[test]
533fn no_let_binding_snapshot_2() {
534 assert_error!("wibble:Int = 4");
535}
536
537#[test]
538fn no_let_binding_snapshot_3() {
539 assert_error!(
540 "let wobble:Int = 32
541 wobble = 42"
542 );
543}
544
545#[test]
546fn no_eq_after_binding_snapshot_1() {
547 assert_error!("let wibble");
548}
549#[test]
550fn no_eq_after_binding_snapshot_2() {
551 assert_error!(
552 "let wibble
553 wibble = 4"
554 );
555}
556
557#[test]
558fn discard_left_hand_side_of_concat_pattern() {
559 assert_error!(
560 r#"
561 case "" {
562 _ <> rest -> rest
563 }
564 "#
565 );
566}
567
568#[test]
569fn assign_left_hand_side_of_concat_pattern() {
570 assert_error!(
571 r#"
572 case "" {
573 first <> rest -> rest
574 }
575 "#
576 );
577}
578
579// https://github.com/gleam-lang/gleam/issues/1890
580#[test]
581fn valueless_list_spread_expression() {
582 assert_error!(r#"let x = [1, 2, 3, ..]"#);
583}
584
585// https://github.com/gleam-lang/gleam/issues/2035
586#[test]
587fn semicolons() {
588 assert_error!(r#"{ 2 + 3; - -5; }"#);
589}
590
591#[test]
592fn bare_expression() {
593 assert_parse!(r#"1"#);
594}
595
596// https://github.com/gleam-lang/gleam/issues/1991
597#[test]
598fn block_of_one() {
599 assert_parse!(r#"{ 1 }"#);
600}
601
602// https://github.com/gleam-lang/gleam/issues/1991
603#[test]
604fn block_of_two() {
605 assert_parse!(r#"{ 1 2 }"#);
606}
607
608// https://github.com/gleam-lang/gleam/issues/1991
609#[test]
610fn nested_block() {
611 assert_parse!(r#"{ 1 { 1.0 2.0 } 3 }"#);
612}
613
614// https://github.com/gleam-lang/gleam/issues/1831
615#[test]
616fn argument_scope() {
617 assert_error!(
618 "
6191 + let a = 5
620a
621"
622 );
623}
624
625#[test]
626fn multiple_external_for_same_project_erlang() {
627 assert_module_error!(
628 r#"
629@external(erlang, "one", "two")
630@external(erlang, "three", "four")
631pub fn one(x: Int) -> Int {
632 todo
633}
634"#
635 );
636}
637
638#[test]
639fn multiple_external_for_same_project_javascript() {
640 assert_module_error!(
641 r#"
642@external(javascript, "one", "two")
643@external(javascript, "three", "four")
644pub fn one(x: Int) -> Int {
645 todo
646}
647"#
648 );
649}
650
651#[test]
652fn unknown_external_target() {
653 assert_module_error!(
654 r#"
655@external(erl, "one", "two")
656pub fn one(x: Int) -> Int {
657 todo
658}"#
659 );
660}
661
662#[test]
663fn unknown_target() {
664 assert_module_error!(
665 r#"
666@target(abc)
667pub fn one() {}"#
668 );
669}
670
671#[test]
672fn missing_target() {
673 assert_module_error!(
674 r#"
675@target()
676pub fn one() {}"#
677 );
678}
679
680#[test]
681fn missing_target_and_bracket() {
682 assert_module_error!(
683 r#"
684@target(
685pub fn one() {}"#
686 );
687}
688
689#[test]
690fn unknown_attribute() {
691 assert_module_error!(
692 r#"@go_faster()
693pub fn main() { 1 }"#
694 );
695}
696
697#[test]
698fn incomplete_function() {
699 assert_error!("fn()");
700}
701
702#[test]
703fn multiple_deprecation_attributes() {
704 assert_module_error!(
705 r#"
706@deprecated("1")
707@deprecated("2")
708pub fn main() -> Nil {
709 Nil
710}
711"#
712 );
713}
714
715#[test]
716fn deprecation_without_message() {
717 assert_module_error!(
718 r#"
719@deprecated()
720pub fn main() -> Nil {
721 Nil
722}
723"#
724 );
725}
726
727#[test]
728fn multiple_internal_attributes() {
729 assert_module_error!(
730 r#"
731@internal
732@internal
733pub fn main() -> Nil {
734 Nil
735}
736"#
737 );
738}
739
740#[test]
741fn attributes_with_no_definition() {
742 assert_module_error!(
743 r#"
744@deprecated("1")
745@target(erlang)
746"#
747 );
748}
749
750#[test]
751fn external_attribute_with_non_fn_definition() {
752 assert_module_error!(
753 r#"
754@external(erlang, "module", "fun")
755pub type Fun
756"#
757 );
758}
759
760#[test]
761fn attributes_with_improper_definition() {
762 assert_module_error!(
763 r#"
764@deprecated("1")
765@external(erlang, "module", "fun")
766"#
767 );
768}
769
770#[test]
771fn const_with_function_call() {
772 assert_module_error!(
773 r#"
774pub fn wibble() { 123 }
775const wib: Int = wibble()
776"#
777 );
778}
779
780#[test]
781fn const_with_function_call_with_args() {
782 assert_module_error!(
783 r#"
784pub fn wibble() { 123 }
785const wib: Int = wibble(1, "wobble")
786"#
787 );
788}
789
790#[test]
791fn import_type() {
792 assert_parse_module!(r#"import wibble.{type Wobble, Wobble, type Wabble}"#);
793}
794
795#[test]
796fn reserved_auto() {
797 assert_module_error!(r#"const auto = 1"#);
798}
799
800#[test]
801fn reserved_delegate() {
802 assert_module_error!(r#"const delegate = 1"#);
803}
804
805#[test]
806fn reserved_derive() {
807 assert_module_error!(r#"const derive = 1"#);
808}
809
810#[test]
811fn reserved_else() {
812 assert_module_error!(r#"const else = 1"#);
813}
814
815#[test]
816fn reserved_implement() {
817 assert_module_error!(r#"const implement = 1"#);
818}
819
820#[test]
821fn reserved_macro() {
822 assert_module_error!(r#"const macro = 1"#);
823}
824
825#[test]
826fn reserved_test() {
827 assert_module_error!(r#"const test = 1"#);
828}
829
830#[test]
831fn reserved_echo() {
832 assert_module_error!(r#"const echo = 1"#);
833}
834
835#[test]
836fn capture_with_name() {
837 assert_module_error!(
838 r#"
839pub fn main() {
840 add(_name, 1)
841}
842
843fn add(x, y) {
844 x + y
845}
846"#
847 );
848}
849
850#[test]
851fn list_spread_with_no_tail_in_the_middle_of_a_list() {
852 assert_module_error!(
853 r#"
854pub fn main() -> Nil {
855 let xs = [1, 2, 3]
856 [1, 2, .., 3 + 3, 4]
857}
858"#
859 );
860}
861
862#[test]
863fn list_spread_followed_by_extra_items() {
864 assert_module_error!(
865 r#"
866pub fn main() -> Nil {
867 let xs = [1, 2, 3]
868 [1, 2, ..xs, 3 + 3, 4]
869}
870"#
871 );
872}
873
874#[test]
875fn list_spread_as_first_item_followed_by_other_items() {
876 assert_module_error!(
877 r#"
878pub fn main() -> Nil {
879 let xs = [1, 2, 3]
880 [..xs, 3 + 3, 4]
881}
882"#
883 );
884}
885
886// Tests for nested tuples and structs in tuples
887// https://github.com/gleam-lang/gleam/issues/1980
888
889#[test]
890fn nested_tuples() {
891 assert_parse!(
892 r#"
893let tup = #(#(5, 6))
894{tup.0}.1
895"#
896 );
897}
898
899#[test]
900fn nested_tuples_no_block() {
901 assert_parse!(
902 r#"
903let tup = #(#(5, 6))
904tup.0.1
905"#
906 );
907}
908
909#[test]
910fn deeply_nested_tuples() {
911 assert_parse!(
912 r#"
913let tup = #(#(#(#(4))))
914{{{tup.0}.0}.0}.0
915"#
916 );
917}
918
919#[test]
920fn deeply_nested_tuples_no_block() {
921 assert_parse!(
922 r#"
923let tup = #(#(#(#(4))))
924tup.0.0.0.0
925"#
926 );
927}
928
929#[test]
930fn inner_single_quote_parses() {
931 assert_parse!(
932 r#"
933let a = "inner 'quotes'"
934"#
935 );
936}
937
938#[test]
939fn string_single_char_suggestion() {
940 assert_module_error!(
941 "
942 pub fn main() {
943 let a = 'example'
944 }
945 "
946 );
947}
948
949#[test]
950fn private_internal_const() {
951 assert_module_error!(
952 "
953@internal
954const wibble = 1
955"
956 );
957}
958
959#[test]
960fn private_internal_type_alias() {
961 assert_module_error!(
962 "
963@internal
964type Alias = Int
965"
966 );
967}
968
969#[test]
970fn private_internal_function() {
971 assert_module_error!(
972 "
973@internal
974fn wibble() { todo }
975"
976 );
977}
978
979#[test]
980fn private_internal_type() {
981 assert_module_error!(
982 "
983@internal
984type Wibble {
985 Wibble
986}
987"
988 );
989}
990
991#[test]
992fn wrong_record_access_pattern() {
993 assert_module_error!(
994 "
995pub fn main() {
996 case wibble {
997 wibble.thing -> 1
998 }
999}
1000"
1001 );
1002}
1003
1004#[test]
1005fn tuple_invalid_expr() {
1006 assert_module_error!(
1007 "
1008fn main() {
1009 #(1, 2, const)
1010}
1011"
1012 );
1013}
1014
1015#[test]
1016fn bit_array_invalid_segment() {
1017 assert_module_error!(
1018 "
1019fn main() {
1020 <<72, 101, 108, 108, 111, 44, 32, 74, 111, 101, const>>
1021}
1022"
1023 );
1024}
1025
1026#[test]
1027fn case_invalid_expression() {
1028 assert_module_error!(
1029 "
1030fn main() {
1031 case 1, type {
1032 _, _ -> 0
1033 }
1034}
1035"
1036 );
1037}
1038
1039#[test]
1040fn case_invalid_case_pattern() {
1041 assert_module_error!(
1042 "
1043fn main() {
1044 case 1 {
1045 -> -> 0
1046 }
1047}
1048"
1049 );
1050}
1051
1052#[test]
1053fn use_invalid_assignments() {
1054 assert_module_error!(
1055 "
1056fn main() {
1057 use fn <- result.try(get_username())
1058}
1059"
1060 );
1061}
1062
1063#[test]
1064fn assignment_pattern_invalid_tuple() {
1065 assert_module_error!(
1066 "
1067fn main() {
1068 let #(a, case, c) = #(1, 2, 3)
1069}
1070"
1071 );
1072}
1073
1074#[test]
1075fn assignment_pattern_invalid_bit_segment() {
1076 assert_module_error!(
1077 "
1078fn main() {
1079 let <<b1, pub>> = <<24, 3>>
1080}
1081"
1082 );
1083}
1084
1085#[test]
1086fn case_list_pattern_after_spread() {
1087 assert_module_error!(
1088 "
1089fn main() {
1090 case somelist {
1091 [..rest, last] -> 1
1092 _ -> 2
1093 }
1094}
1095"
1096 );
1097}
1098
1099#[test]
1100fn type_invalid_constructor() {
1101 assert_module_error!(
1102 "
1103type A {
1104 A(String)
1105 type
1106}
1107"
1108 );
1109}
1110
1111// Tests whether diagnostic presents an example of how to formulate a proper
1112// record constructor based off a common user error pattern.
1113// https://github.com/gleam-lang/gleam/issues/3324
1114
1115#[test]
1116fn type_invalid_record_constructor() {
1117 assert_module_error!(
1118 "
1119pub type User {
1120 name: String,
1121}
1122"
1123 );
1124}
1125
1126#[test]
1127fn type_invalid_record_constructor_without_field_type() {
1128 assert_module_error!(
1129 "
1130pub opaque type User {
1131 name
1132}
1133"
1134 );
1135}
1136
1137#[test]
1138fn type_invalid_record_constructor_invalid_field_type() {
1139 assert_module_error!(
1140 r#"
1141type User {
1142 name: "Test User",
1143}
1144"#
1145 );
1146}
1147
1148#[test]
1149fn type_invalid_type_name() {
1150 assert_module_error!(
1151 "
1152type A(a, type) {
1153 A
1154}
1155"
1156 );
1157}
1158
1159#[test]
1160fn type_invalid_constructor_arg() {
1161 assert_module_error!(
1162 "
1163type A {
1164 A(type: String)
1165}
1166"
1167 );
1168}
1169
1170#[test]
1171fn type_invalid_record() {
1172 assert_module_error!(
1173 "
1174type A {
1175 One
1176 Two
1177 3
1178}
1179"
1180 );
1181}
1182
1183#[test]
1184fn function_type_invalid_param_type() {
1185 assert_module_error!(
1186 "
1187fn f(g: fn(Int, 1) -> Int) -> Int {
1188 g(0, 1)
1189}
1190"
1191 );
1192}
1193
1194#[test]
1195fn function_invalid_signature() {
1196 assert_module_error!(
1197 r#"
1198fn f(a, "b") -> String {
1199 a <> b
1200}
1201"#
1202 );
1203}
1204
1205#[test]
1206fn const_invalid_tuple() {
1207 assert_module_error!(
1208 "
1209const a = #(1, 2, <-)
1210"
1211 );
1212}
1213
1214#[test]
1215fn const_invalid_list() {
1216 assert_module_error!(
1217 "
1218const a = [1, 2, <-]
1219"
1220 );
1221}
1222
1223#[test]
1224fn const_invalid_bit_array_segment() {
1225 assert_module_error!(
1226 "
1227const a = <<1, 2, <->>
1228"
1229 );
1230}
1231
1232#[test]
1233fn const_invalid_record_constructor() {
1234 assert_module_error!(
1235 "
1236type A {
1237 A(String, Int)
1238}
1239const a = A(\"a\", let)
1240"
1241 );
1242}
1243
1244// record access should parse even if there is no label written
1245#[test]
1246fn record_access_no_label() {
1247 assert_parse_module!(
1248 "
1249type Wibble {
1250 Wibble(wibble: String)
1251}
1252
1253fn wobble() {
1254 Wibble(\"a\").
1255}
1256"
1257 );
1258}
1259
1260#[test]
1261fn newline_tokens() {
1262 assert_eq!(
1263 make_tokenizer("1\n\n2\n").collect_vec(),
1264 [
1265 Ok((
1266 0,
1267 Token::Int {
1268 value: "1".into(),
1269 int_value: 1.into()
1270 },
1271 1
1272 )),
1273 Ok((1, Token::NewLine, 2)),
1274 Ok((2, Token::NewLine, 3)),
1275 Ok((
1276 3,
1277 Token::Int {
1278 value: "2".into(),
1279 int_value: 2.into()
1280 },
1281 4
1282 )),
1283 Ok((4, Token::NewLine, 5))
1284 ]
1285 );
1286}
1287
1288// https://github.com/gleam-lang/gleam/issues/1756
1289#[test]
1290fn arithmetic_in_guards() {
1291 assert_parse!(
1292 "
1293case 2, 3 {
1294 x, y if x + y == 1 -> True
1295}"
1296 );
1297}
1298
1299#[test]
1300fn const_string_concat() {
1301 assert_parse_module!(
1302 "
1303const cute = \"cute\"
1304const cute_bee = cute <> \"bee\"
1305"
1306 );
1307}
1308
1309#[test]
1310fn const_string_concat_naked_right() {
1311 assert_module_error!(
1312 "
1313const no_cute_bee = \"cute\" <>
1314"
1315 );
1316}
1317
1318#[test]
1319fn function_call_in_case_clause_guard() {
1320 assert_error!(
1321 r#"
1322let my_string = "hello"
1323case my_string {
1324 _ if length(my_string) > 2 -> io.debug("doesn't work')
1325}"#
1326 );
1327}
1328
1329#[test]
1330fn dot_access_function_call_in_case_clause_guard() {
1331 assert_error!(
1332 r#"
1333let my_string = "hello"
1334case my_string {
1335 _ if string.length(my_string) > 2 -> io.debug("doesn't work')
1336}"#
1337 );
1338}
1339
1340#[test]
1341fn invalid_left_paren_in_case_clause_guard() {
1342 assert_error!(
1343 r#"
1344let my_string = "hello"
1345case my_string {
1346 _ if string.length( > 2 -> io.debug("doesn't work')
1347}"#
1348 );
1349}
1350
1351#[test]
1352fn invalid_label_shorthand() {
1353 assert_module_error!(
1354 "
1355pub fn main() {
1356 wibble(:)
1357}
1358"
1359 );
1360}
1361
1362#[test]
1363fn invalid_label_shorthand_2() {
1364 assert_module_error!(
1365 "
1366pub fn main() {
1367 wibble(:,)
1368}
1369"
1370 );
1371}
1372
1373#[test]
1374fn invalid_label_shorthand_3() {
1375 assert_module_error!(
1376 "
1377pub fn main() {
1378 wibble(:arg)
1379}
1380"
1381 );
1382}
1383
1384#[test]
1385fn invalid_label_shorthand_4() {
1386 assert_module_error!(
1387 "
1388pub fn main() {
1389 wibble(arg::)
1390}
1391"
1392 );
1393}
1394
1395#[test]
1396fn invalid_label_shorthand_5() {
1397 assert_module_error!(
1398 "
1399pub fn main() {
1400 wibble(arg::arg)
1401}
1402"
1403 );
1404}
1405
1406#[test]
1407fn invalid_pattern_label_shorthand() {
1408 assert_module_error!(
1409 "
1410pub fn main() {
1411 let Wibble(:) = todo
1412}
1413"
1414 );
1415}
1416
1417#[test]
1418fn invalid_pattern_label_shorthand_2() {
1419 assert_module_error!(
1420 "
1421pub fn main() {
1422 let Wibble(:arg) = todo
1423}
1424"
1425 );
1426}
1427
1428#[test]
1429fn invalid_pattern_label_shorthand_3() {
1430 assert_module_error!(
1431 "
1432pub fn main() {
1433 let Wibble(arg::) = todo
1434}
1435"
1436 );
1437}
1438
1439#[test]
1440fn invalid_pattern_label_shorthand_4() {
1441 assert_module_error!(
1442 "
1443pub fn main() {
1444 let Wibble(arg: arg:) = todo
1445}
1446"
1447 );
1448}
1449
1450#[test]
1451fn invalid_pattern_label_shorthand_5() {
1452 assert_module_error!(
1453 "
1454pub fn main() {
1455 let Wibble(arg1: arg2:) = todo
1456}
1457"
1458 );
1459}
1460
1461fn first_parsed_docstring(src: &str) -> EcoString {
1462 let parsed =
1463 crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null())
1464 .expect("should parse");
1465
1466 parsed
1467 .module
1468 .definitions
1469 .first()
1470 .expect("parsed a definition")
1471 .definition
1472 .get_doc()
1473 .expect("definition without doc")
1474}
1475
1476#[test]
1477fn doc_comment_before_comment_is_not_attached_to_following_function() {
1478 assert_eq!(
1479 first_parsed_docstring(
1480 r#"
1481 /// Not included!
1482 // pub fn call()
1483
1484 /// Doc!
1485 pub fn wibble() {}
1486"#
1487 ),
1488 " Doc!\n"
1489 )
1490}
1491
1492#[test]
1493fn doc_comment_before_comment_is_not_attached_to_following_type() {
1494 assert_eq!(
1495 first_parsed_docstring(
1496 r#"
1497 /// Not included!
1498 // pub fn call()
1499
1500 /// Doc!
1501 pub type Wibble
1502"#
1503 ),
1504 " Doc!\n"
1505 )
1506}
1507
1508#[test]
1509fn doc_comment_before_comment_is_not_attached_to_following_type_alias() {
1510 assert_eq!(
1511 first_parsed_docstring(
1512 r#"
1513 /// Not included!
1514 // pub fn call()
1515
1516 /// Doc!
1517 pub type Wibble = Int
1518"#
1519 ),
1520 " Doc!\n"
1521 )
1522}
1523
1524#[test]
1525fn doc_comment_before_comment_is_not_attached_to_following_constant() {
1526 assert_eq!(
1527 first_parsed_docstring(
1528 r#"
1529 /// Not included!
1530 // pub fn call()
1531
1532 /// Doc!
1533 pub const wibble = 1
1534"#
1535 ),
1536 " Doc!\n"
1537 );
1538}
1539
1540#[test]
1541fn non_module_level_function_with_a_name() {
1542 assert_module_error!(
1543 r#"
1544pub fn main() {
1545 fn my() { 1 }
1546}
1547"#
1548 );
1549}
1550
1551#[test]
1552fn error_message_on_variable_starting_with_underscore() {
1553 // https://github.com/gleam-lang/gleam/issues/3504
1554 assert_module_error!(
1555 "
1556 pub fn main() {
1557 let val = _func_starting_with_underscore(1)
1558 }"
1559 );
1560}
1561
1562#[test]
1563fn non_module_level_function_with_not_a_name() {
1564 assert_module_error!(
1565 r#"
1566pub fn main() {
1567 fn @() { 1 } // wrong token and not a name
1568}
1569"#
1570 );
1571}
1572
1573#[test]
1574fn error_message_on_variable_starting_with_underscore2() {
1575 // https://github.com/gleam-lang/gleam/issues/3504
1576 assert_module_error!(
1577 "
1578 pub fn main() {
1579 case 1 {
1580 1 -> _with_underscore(1)
1581 }
1582 }"
1583 );
1584}
1585
1586#[test]
1587fn function_inside_a_type() {
1588 assert_module_error!(
1589 r#"
1590type Wibble {
1591 fn wobble() {}
1592}
1593"#
1594 );
1595}
1596
1597#[test]
1598fn pub_function_inside_a_type() {
1599 assert_module_error!(
1600 r#"
1601type Wibble {
1602 pub fn wobble() {}
1603}
1604"#
1605 );
1606}
1607
1608#[test]
1609fn if_like_expression() {
1610 assert_module_error!(
1611 r#"
1612pub fn main() {
1613 let a = if wibble {
1614 wobble
1615 }
1616}
1617"#
1618 );
1619}
1620
1621// https://github.com/gleam-lang/gleam/issues/3730
1622#[test]
1623fn missing_constructor_arguments() {
1624 assert_module_error!(
1625 "
1626pub type A {
1627 A(Int)
1628}
1629
1630const a = A()
1631"
1632 );
1633}
1634
1635// https://github.com/gleam-lang/gleam/issues/3796
1636#[test]
1637fn missing_type_constructor_arguments_in_type_definition() {
1638 assert_module_error!(
1639 "
1640pub type A() {
1641 A(Int)
1642}
1643"
1644 );
1645}
1646
1647#[test]
1648fn missing_type_constructor_arguments_in_type_annotation_1() {
1649 assert_module_error!("pub fn main() -> Int() {}");
1650}
1651
1652#[test]
1653fn missing_type_constructor_arguments_in_type_annotation_2() {
1654 assert_module_error!(
1655 "pub fn main() {
1656 let a: Int() = todo
1657}"
1658 );
1659}
1660
1661#[test]
1662fn tuple_without_hash() {
1663 assert_module_error!(
1664 r#"
1665pub fn main() {
1666 let triple = (1, 2.2, "three")
1667 io.debug(triple)
1668 let (a, *, *) = triple
1669 io.debug(a)
1670 io.debug(triple.1)
1671}
1672"#
1673 );
1674}
1675
1676#[test]
1677fn deprecation_attribute_on_type_variant() {
1678 assert_parse_module!(
1679 r#"
1680type Wibble {
1681 @deprecated("1")
1682 Wibble1
1683 Wibble2
1684}
1685"#
1686 );
1687}
1688
1689#[test]
1690
1691fn float_empty_exponent() {
1692 assert_error!("1.32e");
1693}
1694
1695#[test]
1696fn multiple_deprecation_attribute_on_type_variant() {
1697 assert_module_error!(
1698 r#"
1699type Wibble {
1700 @deprecated("1")
1701 @deprecated("2")
1702 Wibble1
1703 Wibble2
1704}
1705"#
1706 );
1707}
1708
1709#[test]
1710fn target_attribute_on_type_variant() {
1711 assert_module_error!(
1712 r#"
1713type Wibble {
1714 @target(erlang)
1715 Wibble2
1716}
1717"#
1718 );
1719}
1720
1721#[test]
1722fn internal_attribute_on_type_variant() {
1723 assert_module_error!(
1724 r#"
1725type Wibble {
1726 @internal
1727 Wibble1
1728}
1729"#
1730 );
1731}
1732
1733#[test]
1734fn external_attribute_on_type_variant() {
1735 assert_module_error!(
1736 r#"
1737type Wibble {
1738 @external(erlang, "one", "two")
1739 Wibble1
1740}
1741"#
1742 );
1743}
1744
1745#[test]
1746fn multiple_unsupported_attributes_on_type_variant() {
1747 assert_module_error!(
1748 r#"
1749type Wibble {
1750 @external(erlang, "one", "two")
1751 @target(erlang)
1752 @internal
1753 Wibble1
1754}
1755"#
1756 );
1757}
1758
1759#[test]
1760// https://github.com/gleam-lang/gleam/issues/3870
1761fn nested_tuple_access_after_function() {
1762 assert_parse!("tuple().0.1");
1763}
1764
1765#[test]
1766fn case_expression_without_body() {
1767 assert_parse!("case a");
1768}
1769
1770#[test]
1771fn assert_statement() {
1772 assert_parse!("assert 10 != 11");
1773}
1774
1775#[test]
1776fn assert_statement_without_expression() {
1777 assert_error!("assert");
1778}
1779
1780#[test]
1781fn assert_statement_followed_by_statement() {
1782 assert_error!("assert let a = 10");
1783}