Fork of daniellemaywood.uk/gleam — Wasm codegen work
46 kB
2501 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2021 The Gleam contributors
3
4use crate::ast::SrcSpan;
5use crate::parse::error::{
6 InvalidUnicodeEscapeError, LexicalError, LexicalErrorType, ParseError, ParseErrorType,
7};
8use crate::parse::lexer::make_tokenizer;
9use crate::parse::token::Token;
10use crate::warning::WarningEmitter;
11use camino::Utf8PathBuf;
12
13use ecow::EcoString;
14use itertools::Itertools;
15use pretty_assertions::assert_eq;
16
17macro_rules! assert_error {
18 ($src:expr, $error:expr $(,)?) => {
19 let result = crate::parse::parse_statement_sequence($src).expect_err("should not parse");
20 assert_eq!(($src, $error), ($src, result),);
21 };
22 ($src:expr) => {
23 let error = $crate::parse::tests::expect_error($src);
24 let output = format!("----- SOURCE CODE\n{}\n\n----- ERROR\n{}", $src, error);
25 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
26 };
27}
28
29macro_rules! assert_module_error {
30 ($src:expr) => {
31 let error = $crate::parse::tests::expect_module_error($src);
32 let output = format!("----- SOURCE CODE\n{}\n\n----- ERROR\n{}", $src, error);
33 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
34 };
35}
36
37macro_rules! assert_parse_module {
38 ($src:expr) => {
39 let result = crate::parse::parse_module(
40 camino::Utf8PathBuf::from("test/path"),
41 $src,
42 &crate::warning::WarningEmitter::null(),
43 )
44 .expect("should parse");
45 insta::assert_snapshot!(insta::internals::AutoName, &format!("{:#?}", result), $src);
46 };
47}
48
49macro_rules! assert_parse {
50 ($src:expr) => {
51 let result = crate::parse::parse_statement_sequence($src).expect("should parse");
52 insta::assert_snapshot!(insta::internals::AutoName, &format!("{:#?}", result), $src);
53 };
54}
55
56pub fn expect_module_error(src: &str) -> String {
57 let result =
58 crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null())
59 .expect_err("should not parse");
60 let error = crate::error::Error::Parse {
61 src: src.into(),
62 path: Utf8PathBuf::from("/src/parse/error.gleam"),
63 error: Box::new(result),
64 };
65 error.pretty_string()
66}
67
68pub fn expect_error(src: &str) -> String {
69 let result = crate::parse::parse_statement_sequence(src).expect_err("should not parse");
70 let error = crate::error::Error::Parse {
71 src: src.into(),
72 path: Utf8PathBuf::from("/src/parse/error.gleam"),
73 error: Box::new(result),
74 };
75 error.pretty_string()
76}
77
78#[test]
79fn int_tests() {
80 // bad binary digit
81 assert_error!(
82 "0b012",
83 ParseError {
84 error: ParseErrorType::LexError {
85 error: LexicalError {
86 error: LexicalErrorType::DigitOutOfRadix,
87 location: SrcSpan { start: 4, end: 4 },
88 }
89 },
90 location: SrcSpan { start: 4, end: 4 },
91 }
92 );
93 // bad octal digit
94 assert_error!(
95 "0o12345678",
96 ParseError {
97 error: ParseErrorType::LexError {
98 error: LexicalError {
99 error: LexicalErrorType::DigitOutOfRadix,
100 location: SrcSpan { start: 9, end: 9 },
101 }
102 },
103 location: SrcSpan { start: 9, end: 9 },
104 }
105 );
106 // no int value
107 assert_error!(
108 "0x",
109 ParseError {
110 error: ParseErrorType::LexError {
111 error: LexicalError {
112 error: LexicalErrorType::RadixIntNoValue,
113 location: SrcSpan { start: 1, end: 1 },
114 }
115 },
116 location: SrcSpan { start: 1, end: 1 },
117 }
118 );
119 // trailing underscore
120 assert_error!(
121 "1_000_",
122 ParseError {
123 error: ParseErrorType::LexError {
124 error: LexicalError {
125 error: LexicalErrorType::NumTrailingUnderscore,
126 location: SrcSpan { start: 5, end: 5 },
127 }
128 },
129 location: SrcSpan { start: 5, end: 5 },
130 }
131 );
132}
133
134#[test]
135fn string_bad_character_escape() {
136 assert_error!(
137 r#""\g""#,
138 ParseError {
139 error: ParseErrorType::LexError {
140 error: LexicalError {
141 error: LexicalErrorType::BadStringEscape,
142 location: SrcSpan { start: 1, end: 2 },
143 }
144 },
145 location: SrcSpan { start: 1, end: 2 },
146 }
147 );
148}
149
150#[test]
151fn string_bad_character_escape_leading_backslash() {
152 assert_error!(
153 r#""\\\g""#,
154 ParseError {
155 error: ParseErrorType::LexError {
156 error: LexicalError {
157 error: LexicalErrorType::BadStringEscape,
158 location: SrcSpan { start: 3, end: 4 },
159 }
160 },
161 location: SrcSpan { start: 3, end: 4 },
162 }
163 );
164}
165
166#[test]
167fn string_freestanding_unicode_escape_sequence() {
168 assert_error!(
169 r#""\u""#,
170 ParseError {
171 error: ParseErrorType::LexError {
172 error: LexicalError {
173 error: LexicalErrorType::InvalidUnicodeEscape(
174 InvalidUnicodeEscapeError::MissingOpeningBrace,
175 ),
176 location: SrcSpan { start: 2, end: 3 },
177 }
178 },
179 location: SrcSpan { start: 2, end: 3 },
180 }
181 );
182}
183
184#[test]
185fn string_unicode_escape_sequence_no_braces() {
186 assert_error!(
187 r#""\u65""#,
188 ParseError {
189 error: ParseErrorType::LexError {
190 error: LexicalError {
191 error: LexicalErrorType::InvalidUnicodeEscape(
192 InvalidUnicodeEscapeError::MissingOpeningBrace,
193 ),
194 location: SrcSpan { start: 2, end: 3 },
195 }
196 },
197 location: SrcSpan { start: 2, end: 3 },
198 }
199 );
200}
201
202#[test]
203fn string_unicode_escape_sequence_invalid_hex() {
204 assert_error!(
205 r#""\u{z}""#,
206 ParseError {
207 error: ParseErrorType::LexError {
208 error: LexicalError {
209 error: LexicalErrorType::InvalidUnicodeEscape(
210 InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace,
211 ),
212 location: SrcSpan { start: 4, end: 5 },
213 }
214 },
215 location: SrcSpan { start: 4, end: 5 },
216 }
217 );
218}
219
220#[test]
221fn string_unclosed_unicode_escape_sequence() {
222 assert_error!(
223 r#""\u{039a""#,
224 ParseError {
225 error: ParseErrorType::LexError {
226 error: LexicalError {
227 error: LexicalErrorType::InvalidUnicodeEscape(
228 InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace,
229 ),
230 location: SrcSpan { start: 8, end: 9 },
231 }
232 },
233 location: SrcSpan { start: 8, end: 9 },
234 }
235 );
236}
237
238#[test]
239fn string_empty_unicode_escape_sequence() {
240 assert_error!(
241 r#""\u{}""#,
242 ParseError {
243 error: ParseErrorType::LexError {
244 error: LexicalError {
245 error: LexicalErrorType::InvalidUnicodeEscape(
246 InvalidUnicodeEscapeError::InvalidNumberOfHexDigits,
247 ),
248 location: SrcSpan { start: 1, end: 5 },
249 }
250 },
251 location: SrcSpan { start: 1, end: 5 },
252 }
253 );
254}
255
256#[test]
257fn string_overlong_unicode_escape_sequence() {
258 assert_error!(
259 r#""\u{0011f601}""#,
260 ParseError {
261 error: ParseErrorType::LexError {
262 error: LexicalError {
263 error: LexicalErrorType::InvalidUnicodeEscape(
264 InvalidUnicodeEscapeError::InvalidNumberOfHexDigits,
265 ),
266 location: SrcSpan { start: 1, end: 13 },
267 }
268 },
269 location: SrcSpan { start: 1, end: 13 },
270 }
271 );
272}
273
274#[test]
275fn string_invalid_unicode_escape_sequence() {
276 assert_error!(
277 r#""\u{110000}""#,
278 ParseError {
279 error: ParseErrorType::LexError {
280 error: LexicalError {
281 error: LexicalErrorType::InvalidUnicodeEscape(
282 InvalidUnicodeEscapeError::InvalidCodepoint,
283 ),
284 location: SrcSpan { start: 1, end: 11 },
285 }
286 },
287 location: SrcSpan { start: 1, end: 11 },
288 }
289 );
290}
291
292#[test]
293fn bit_array() {
294 // non int value in bit array unit option
295 assert_error!(
296 "let x = <<1:unit(0)>> x",
297 ParseError {
298 error: ParseErrorType::InvalidBitArrayUnit,
299 location: SrcSpan { start: 17, end: 18 }
300 }
301 );
302}
303
304#[test]
305fn bit_array1() {
306 assert_error!(
307 "let x = <<1:unit(257)>> x",
308 ParseError {
309 error: ParseErrorType::InvalidBitArrayUnit,
310 location: SrcSpan { start: 17, end: 20 }
311 }
312 );
313}
314
315#[test]
316fn bit_array2() {
317 // patterns cannot be nested
318 assert_error!(
319 "case <<>> { <<<<1>>:bits>> -> 1 }",
320 ParseError {
321 error: ParseErrorType::NestedBitArrayPattern,
322 location: SrcSpan { start: 14, end: 19 }
323 }
324 );
325}
326
327// https://github.com/gleam-lang/gleam/issues/3125
328#[test]
329fn triple_equals() {
330 assert_error!(
331 "let wobble:Int = 32
332 wobble === 42",
333 ParseError {
334 error: ParseErrorType::LexError {
335 error: LexicalError {
336 error: LexicalErrorType::InvalidTripleEqual,
337 location: SrcSpan { start: 35, end: 38 },
338 }
339 },
340 location: SrcSpan { start: 35, end: 38 },
341 }
342 );
343}
344
345#[test]
346fn triple_equals_with_whitespace() {
347 assert_error!(
348 "let wobble:Int = 32
349 wobble == = 42",
350 ParseError {
351 error: ParseErrorType::OpNakedRight,
352 location: SrcSpan { start: 35, end: 37 },
353 }
354 );
355}
356
357// https://github.com/gleam-lang/gleam/issues/1231
358#[test]
359fn pointless_spread() {
360 assert_error!(
361 "let xs = [] [..xs]",
362 ParseError {
363 error: ParseErrorType::ListSpreadWithoutElements,
364 location: SrcSpan { start: 12, end: 18 },
365 }
366 );
367}
368
369// https://github.com/gleam-lang/gleam/issues/1613
370#[test]
371fn anonymous_function_labeled_arguments() {
372 assert_error!(
373 "let anon_subtract = fn (minuend a: Int, subtrahend b: Int) -> Int {
374 a - b
375}",
376 ParseError {
377 location: SrcSpan { start: 24, end: 31 },
378 error: ParseErrorType::UnexpectedLabel
379 }
380 );
381}
382
383#[test]
384fn no_let_binding() {
385 assert_error!(
386 "wibble = 32",
387 ParseError {
388 location: SrcSpan { start: 7, end: 8 },
389 error: ParseErrorType::NoLetBinding
390 }
391 );
392}
393
394#[test]
395fn no_let_binding1() {
396 assert_error!(
397 "wibble:Int = 32",
398 ParseError {
399 location: SrcSpan { start: 6, end: 7 },
400 error: ParseErrorType::NoLetBinding
401 }
402 );
403}
404
405#[test]
406fn no_let_binding2() {
407 assert_error!(
408 "let wobble:Int = 32
409 wobble = 42",
410 ParseError {
411 location: SrcSpan { start: 35, end: 36 },
412 error: ParseErrorType::NoLetBinding
413 }
414 );
415}
416
417#[test]
418fn no_let_binding3() {
419 assert_error!(
420 "[x] = [2]",
421 ParseError {
422 location: SrcSpan { start: 4, end: 5 },
423 error: ParseErrorType::NoLetBinding
424 }
425 );
426}
427
428#[test]
429fn with_let_binding3() {
430 // The same with `let assert` must parse:
431 assert_parse!("let assert [x] = [2]");
432}
433
434#[test]
435fn with_let_binding3_and_annotation() {
436 assert_parse!("let assert [x]: List(Int) = [2]");
437}
438
439#[test]
440fn no_eq_after_binding() {
441 assert_error!(
442 "let wibble",
443 ParseError {
444 location: SrcSpan { start: 4, end: 10 },
445 error: ParseErrorType::ExpectedEqual
446 }
447 );
448}
449
450#[test]
451fn no_eq_after_binding1() {
452 assert_error!(
453 "let wibble
454 wibble = 4",
455 ParseError {
456 location: SrcSpan { start: 4, end: 10 },
457 error: ParseErrorType::ExpectedEqual
458 }
459 );
460}
461
462#[test]
463fn echo_followed_by_expression_ends_where_expression_ends() {
464 assert_parse!("echo wibble");
465}
466
467#[test]
468fn echo_with_simple_expression_1() {
469 assert_parse!("echo wibble as message");
470}
471
472#[test]
473fn echo_with_simple_expression_2() {
474 assert_parse!("echo wibble as \"message\"");
475}
476
477#[test]
478fn echo_with_complex_expression() {
479 assert_parse!("echo wibble as { this <> complex }");
480}
481
482#[test]
483fn echo_with_no_expressions_after_it() {
484 assert_parse!("echo");
485}
486
487#[test]
488fn echo_with_no_expressions_after_it_but_a_message() {
489 assert_parse!("echo as message");
490}
491
492#[test]
493fn echo_with_block() {
494 assert_parse!("echo { 1 + 1 }");
495}
496
497#[test]
498fn echo_has_lower_precedence_than_binop() {
499 assert_parse!("echo 1 + 1");
500}
501
502#[test]
503fn echo_in_a_pipeline() {
504 assert_parse!("[] |> echo |> wibble");
505}
506
507#[test]
508fn echo_has_lower_precedence_than_pipeline() {
509 assert_parse!("echo wibble |> wobble |> woo");
510}
511
512#[test]
513fn echo_cannot_have_an_expression_in_a_pipeline() {
514 // So this is actually two pipelines!
515 assert_parse!("[] |> echo fun |> wibble");
516}
517
518#[test]
519fn panic_with_echo() {
520 assert_parse!("panic as echo \"string\"");
521}
522
523#[test]
524fn panic_with_echo_and_message() {
525 assert_parse!("panic as echo wibble as message");
526}
527
528#[test]
529fn echo_with_panic() {
530 assert_parse!("echo panic as \"a\"");
531}
532
533#[test]
534fn echo_with_panic_and_message() {
535 assert_parse!("echo panic as \"a\"");
536}
537
538#[test]
539fn echo_with_panic_and_messages() {
540 assert_parse!("echo panic as \"a\" as \"b\"");
541}
542
543#[test]
544fn echo_with_assert_and_message_1() {
545 assert_parse!("assert 1 == echo 2 as this_belongs_to_echo");
546}
547
548#[test]
549fn echo_with_assert_and_message_2() {
550 assert_parse!("assert echo True as this_belongs_to_echo");
551}
552
553#[test]
554fn echo_with_assert_and_messages_1() {
555 assert_parse!("assert 1 == echo 2 as this_belongs_to_echo as this_belongs_to_assert");
556}
557
558#[test]
559fn echo_with_assert_and_messages_2() {
560 assert_parse!("assert echo True as this_belongs_to_echo as this_belongs_to_assert");
561}
562
563#[test]
564fn echo_with_assert_and_messages_3() {
565 assert_parse!("assert echo 1 == 2 as this_belongs_to_echo as this_belongs_to_assert");
566}
567
568#[test]
569fn echo_with_let_assert_and_message() {
570 assert_parse!("let assert 1 = echo 2 as this_belongs_to_echo");
571}
572
573#[test]
574fn echo_with_let_assert_and_messages() {
575 assert_parse!("let assert 1 = echo 1 as this_belongs_to_echo as this_belongs_to_assert");
576}
577
578#[test]
579fn repeated_echos() {
580 assert_parse!("echo echo echo 1");
581}
582
583#[test]
584fn echo_at_start_of_pipeline_wraps_the_whole_thing() {
585 assert_parse!("echo 1 |> wibble |> wobble");
586}
587
588#[test]
589fn no_let_binding_snapshot_1() {
590 assert_error!("wibble = 4");
591}
592
593#[test]
594fn no_let_binding_snapshot_2() {
595 assert_error!("wibble:Int = 4");
596}
597
598#[test]
599fn no_let_binding_snapshot_3() {
600 assert_error!(
601 "let wobble:Int = 32
602 wobble = 42"
603 );
604}
605
606#[test]
607fn no_eq_after_binding_snapshot_1() {
608 assert_error!("let wibble");
609}
610#[test]
611fn no_eq_after_binding_snapshot_2() {
612 assert_error!(
613 "let wibble
614 wibble = 4"
615 );
616}
617
618#[test]
619fn discard_left_hand_side_of_concat_pattern() {
620 assert_error!(
621 r#"
622 case "" {
623 _ <> rest -> rest
624 }
625 "#
626 );
627}
628
629#[test]
630fn assign_left_hand_side_of_concat_pattern() {
631 assert_error!(
632 r#"
633 case "" {
634 first <> rest -> rest
635 }
636 "#
637 );
638}
639
640#[test]
641fn discard_infix_and_match_suffix_of_concat_pattern() {
642 assert_error!(
643 r#"case "" {
644 "prefix" <> _ <> "suffix" -> Nil
645}"#
646 );
647}
648
649#[test]
650fn assign_infix_and_match_suffix_of_concat_pattern() {
651 assert_error!(
652 r#"case "" {
653 "prefix" <> infix <> "suffix" -> infix
654}"#
655 );
656}
657
658#[test]
659fn incomplete_suffix_match_in_concat_pattern() {
660 assert_error!(
661 r#"case "" {
662 "prefix" <> wibble <> -> wibble
663}"#
664 );
665}
666
667// https://github.com/gleam-lang/gleam/issues/1890
668#[test]
669fn valueless_list_spread_expression() {
670 assert_error!(r#"let x = [1, 2, 3, ..]"#);
671}
672
673// https://github.com/gleam-lang/gleam/issues/2035
674#[test]
675fn semicolons() {
676 assert_error!(r#"{ 2 + 3; - -5; }"#);
677}
678
679#[test]
680fn bare_expression() {
681 assert_parse!(r#"1"#);
682}
683
684// https://github.com/gleam-lang/gleam/issues/1991
685#[test]
686fn block_of_one() {
687 assert_parse!(r#"{ 1 }"#);
688}
689
690// https://github.com/gleam-lang/gleam/issues/1991
691#[test]
692fn block_of_two() {
693 assert_parse!(r#"{ 1 2 }"#);
694}
695
696// https://github.com/gleam-lang/gleam/issues/1991
697#[test]
698fn nested_block() {
699 assert_parse!(r#"{ 1 { 1.0 2.0 } 3 }"#);
700}
701
702// https://github.com/gleam-lang/gleam/issues/1831
703#[test]
704fn argument_scope() {
705 assert_error!(
706 "
7071 + let a = 5
708a
709"
710 );
711}
712
713#[test]
714fn multiple_external_for_same_project_erlang() {
715 assert_module_error!(
716 r#"
717@external(erlang, "one", "two")
718@external(erlang, "three", "four")
719pub fn one(x: Int) -> Int {
720 todo
721}
722"#
723 );
724}
725
726#[test]
727fn multiple_external_for_same_project_javascript() {
728 assert_module_error!(
729 r#"
730@external(javascript, "one", "two")
731@external(javascript, "three", "four")
732pub fn one(x: Int) -> Int {
733 todo
734}
735"#
736 );
737}
738
739#[test]
740fn unknown_external_target() {
741 assert_module_error!(
742 r#"
743@external(erl, "one", "two")
744pub fn one(x: Int) -> Int {
745 todo
746}"#
747 );
748}
749
750#[test]
751fn unknown_target() {
752 assert_module_error!(
753 r#"
754@target(abc)
755pub fn one() {}"#
756 );
757}
758
759#[test]
760fn missing_target() {
761 assert_module_error!(
762 r#"
763@target()
764pub fn one() {}"#
765 );
766}
767
768#[test]
769fn missing_target_and_bracket() {
770 assert_module_error!(
771 r#"
772@target(
773pub fn one() {}"#
774 );
775}
776
777#[test]
778fn unknown_attribute() {
779 assert_module_error!(
780 r#"@go_faster()
781pub fn main() { 1 }"#
782 );
783}
784
785#[test]
786fn incomplete_function() {
787 assert_error!("fn()");
788}
789
790#[test]
791fn multiple_deprecation_attributes() {
792 assert_module_error!(
793 r#"
794@deprecated("1")
795@deprecated("2")
796pub fn main() -> Nil {
797 Nil
798}
799"#
800 );
801}
802
803#[test]
804fn deprecation_with_no_message() {
805 assert_module_error!(
806 r#"
807@deprecated
808pub fn main() -> Nil {
809 Nil
810}
811"#
812 );
813}
814
815#[test]
816fn deprecation_with_no_message_on_constructor() {
817 assert_module_error!(
818 r#"
819pub type HashAlgorithm {
820 @deprecated
821 Md5
822 Sha224
823 Sha512
824}
825"#
826 );
827}
828
829#[test]
830fn deprecation_without_message() {
831 assert_module_error!(
832 r#"
833@deprecated()
834pub fn main() -> Nil {
835 Nil
836}
837"#
838 );
839}
840
841#[test]
842fn multiple_internal_attributes() {
843 assert_module_error!(
844 r#"
845@internal
846@internal
847pub fn main() -> Nil {
848 Nil
849}
850"#
851 );
852}
853
854#[test]
855fn attributes_with_no_definition() {
856 assert_module_error!(
857 r#"
858@deprecated("1")
859@target(erlang)
860"#
861 );
862}
863
864#[test]
865fn external_attribute_with_custom_type() {
866 assert_parse_module!(
867 r#"
868@external(erlang, "gleam_stdlib", "dict")
869@external(javascript, "./gleam_stdlib.d.ts", "Dict")
870pub type Dict(key, value)
871"#
872 );
873}
874
875#[test]
876fn external_attribute_with_non_fn_definition() {
877 assert_module_error!(
878 r#"
879@external(erlang, "module", "fun")
880pub type Fun = Fun
881"#
882 );
883}
884
885#[test]
886fn attributes_with_improper_definition() {
887 assert_module_error!(
888 r#"
889@deprecated("1")
890@external(erlang, "module", "fun")
891"#
892 );
893}
894
895#[test]
896fn const_with_function_call() {
897 assert_module_error!(
898 r#"
899pub fn wibble() { 123 }
900const wib: Int = wibble()
901"#
902 );
903}
904
905#[test]
906fn const_with_function_call_with_args() {
907 assert_module_error!(
908 r#"
909pub fn wibble() { 123 }
910const wib: Int = wibble(1, "wobble")
911"#
912 );
913}
914
915#[test]
916fn import_type() {
917 assert_parse_module!(r#"import wibble.{type Wobble, Wobble, type Wabble}"#);
918}
919
920#[test]
921fn reserved_auto() {
922 assert_module_error!(r#"const auto = 1"#);
923}
924
925#[test]
926fn reserved_delegate() {
927 assert_module_error!(r#"const delegate = 1"#);
928}
929
930#[test]
931fn reserved_derive() {
932 assert_module_error!(r#"const derive = 1"#);
933}
934
935#[test]
936fn reserved_else() {
937 assert_module_error!(r#"const else = 1"#);
938}
939
940#[test]
941fn reserved_implement() {
942 assert_module_error!(r#"const implement = 1"#);
943}
944
945#[test]
946fn reserved_macro() {
947 assert_module_error!(r#"const macro = 1"#);
948}
949
950#[test]
951fn reserved_test() {
952 assert_module_error!(r#"const test = 1"#);
953}
954
955#[test]
956fn reserved_echo() {
957 assert_module_error!(r#"const echo = 1"#);
958}
959
960#[test]
961fn capture_with_name() {
962 assert_module_error!(
963 r#"
964pub fn main() {
965 add(_name, 1)
966}
967
968fn add(x, y) {
969 x + y
970}
971"#
972 );
973}
974
975#[test]
976fn list_spread_with_no_tail_in_the_middle_of_a_list() {
977 assert_module_error!(
978 r#"
979pub fn main() -> Nil {
980 let xs = [1, 2, 3]
981 [1, 2, .., 3 + 3, 4]
982}
983"#
984 );
985}
986
987#[test]
988fn list_spread_followed_by_extra_items() {
989 assert_module_error!(
990 r#"
991pub fn main() -> Nil {
992 let xs = [1, 2, 3]
993 [1, 2, ..xs, 3 + 3, 4]
994}
995"#
996 );
997}
998
999#[test]
1000fn list_spread_followed_by_extra_item_and_another_spread() {
1001 assert_module_error!(
1002 r#"
1003pub fn main() -> Nil {
1004 let xs = [1, 2, 3]
1005 let ys = [5, 6, 7]
1006 [..xs, 4, ..ys]
1007}
1008"#
1009 );
1010}
1011
1012#[test]
1013fn list_spread_followed_by_other_spread() {
1014 assert_module_error!(
1015 r#"
1016pub fn main() -> Nil {
1017 let xs = [1, 2, 3]
1018 let ys = [5, 6, 7]
1019 [1, ..xs, ..ys]
1020}
1021"#
1022 );
1023}
1024
1025#[test]
1026fn list_spread_as_first_item_followed_by_other_items() {
1027 assert_module_error!(
1028 r#"
1029pub fn main() -> Nil {
1030 let xs = [1, 2, 3]
1031 [..xs, 3 + 3, 4]
1032}
1033"#
1034 );
1035}
1036
1037// Tests for nested tuples and structs in tuples
1038// https://github.com/gleam-lang/gleam/issues/1980
1039
1040#[test]
1041fn nested_tuples() {
1042 assert_parse!(
1043 r#"
1044let tup = #(#(5, 6))
1045{tup.0}.1
1046"#
1047 );
1048}
1049
1050#[test]
1051fn nested_tuples_no_block() {
1052 assert_parse!(
1053 r#"
1054let tup = #(#(5, 6))
1055tup.0.1
1056"#
1057 );
1058}
1059
1060#[test]
1061fn deeply_nested_tuples() {
1062 assert_parse!(
1063 r#"
1064let tup = #(#(#(#(4))))
1065{{{tup.0}.0}.0}.0
1066"#
1067 );
1068}
1069
1070#[test]
1071fn deeply_nested_tuples_no_block() {
1072 assert_parse!(
1073 r#"
1074let tup = #(#(#(#(4))))
1075tup.0.0.0.0
1076"#
1077 );
1078}
1079
1080#[test]
1081fn inner_single_quote_parses() {
1082 assert_parse!(
1083 r#"
1084let a = "inner 'quotes'"
1085"#
1086 );
1087}
1088
1089#[test]
1090fn string_single_char_suggestion() {
1091 assert_module_error!(
1092 "
1093 pub fn main() {
1094 let a = 'example'
1095 }
1096 "
1097 );
1098}
1099
1100#[test]
1101fn private_internal_const() {
1102 assert_module_error!(
1103 "
1104@internal
1105const wibble = 1
1106"
1107 );
1108}
1109
1110#[test]
1111fn private_internal_type_alias() {
1112 assert_module_error!(
1113 "
1114@internal
1115type Alias = Int
1116"
1117 );
1118}
1119
1120#[test]
1121fn private_internal_function() {
1122 assert_module_error!(
1123 "
1124@internal
1125fn wibble() { todo }
1126"
1127 );
1128}
1129
1130#[test]
1131fn private_internal_type() {
1132 assert_module_error!(
1133 "
1134@internal
1135type Wibble {
1136 Wibble
1137}
1138"
1139 );
1140}
1141
1142#[test]
1143fn wrong_record_access_pattern() {
1144 assert_module_error!(
1145 "
1146pub fn main() {
1147 case wibble {
1148 wibble.thing -> 1
1149 }
1150}
1151"
1152 );
1153}
1154
1155#[test]
1156fn tuple_invalid_expr() {
1157 assert_module_error!(
1158 "
1159fn main() {
1160 #(1, 2, const)
1161}
1162"
1163 );
1164}
1165
1166#[test]
1167fn bit_array_invalid_segment() {
1168 assert_module_error!(
1169 "
1170fn main() {
1171 <<72, 101, 108, 108, 111, 44, 32, 74, 111, 101, const>>
1172}
1173"
1174 );
1175}
1176
1177#[test]
1178fn case_invalid_expression() {
1179 assert_module_error!(
1180 "
1181fn main() {
1182 case 1, type {
1183 _, _ -> 0
1184 }
1185}
1186"
1187 );
1188}
1189
1190#[test]
1191fn case_invalid_case_pattern() {
1192 assert_module_error!(
1193 "
1194fn main() {
1195 case 1 {
1196 -> -> 0
1197 }
1198}
1199"
1200 );
1201}
1202
1203#[test]
1204fn case_clause_no_subject() {
1205 assert_module_error!(
1206 "
1207fn main() {
1208 case 1 {
1209 -> 1
1210 _ -> 2
1211 }
1212}
1213"
1214 );
1215}
1216
1217#[test]
1218fn case_alternative_clause_no_subject() {
1219 assert_module_error!(
1220 "
1221fn main() {
1222 case 1 {
1223 1 | -> 1
1224 _ -> 1
1225 }
1226}
1227"
1228 );
1229}
1230
1231#[test]
1232fn use_invalid_assignments() {
1233 assert_module_error!(
1234 "
1235fn main() {
1236 use fn <- result.try(get_username())
1237}
1238"
1239 );
1240}
1241
1242#[test]
1243fn assignment_pattern_invalid_tuple() {
1244 assert_module_error!(
1245 "
1246fn main() {
1247 let #(a, case, c) = #(1, 2, 3)
1248}
1249"
1250 );
1251}
1252
1253#[test]
1254fn assignment_pattern_invalid_bit_segment() {
1255 assert_module_error!(
1256 "
1257fn main() {
1258 let <<b1, pub>> = <<24, 3>>
1259}
1260"
1261 );
1262}
1263
1264#[test]
1265fn case_list_pattern_after_spread() {
1266 assert_module_error!(
1267 "
1268fn main() {
1269 case somelist {
1270 [..rest, last] -> 1
1271 _ -> 2
1272 }
1273}
1274"
1275 );
1276}
1277
1278#[test]
1279fn type_invalid_constructor() {
1280 assert_module_error!(
1281 "
1282type A {
1283 A(String)
1284 type
1285}
1286"
1287 );
1288}
1289
1290// Tests whether diagnostic presents an example of how to formulate a proper
1291// record constructor based off a common user error pattern.
1292// https://github.com/gleam-lang/gleam/issues/3324
1293
1294#[test]
1295fn type_invalid_record_constructor() {
1296 assert_module_error!(
1297 "
1298pub type User {
1299 name: String,
1300}
1301"
1302 );
1303}
1304
1305#[test]
1306fn type_invalid_record_constructor_without_field_type() {
1307 assert_module_error!(
1308 "
1309pub opaque type User {
1310 name
1311}
1312"
1313 );
1314}
1315
1316#[test]
1317fn type_invalid_record_constructor_invalid_field_type() {
1318 assert_module_error!(
1319 r#"
1320type User {
1321 name: "Test User",
1322}
1323"#
1324 );
1325}
1326
1327#[test]
1328fn type_invalid_type_name() {
1329 assert_module_error!(
1330 "
1331type A(a, type) {
1332 A
1333}
1334"
1335 );
1336}
1337
1338#[test]
1339fn type_invalid_constructor_arg() {
1340 assert_module_error!(
1341 "
1342type A {
1343 A(type: String)
1344}
1345"
1346 );
1347}
1348
1349#[test]
1350fn type_invalid_record() {
1351 assert_module_error!(
1352 "
1353type A {
1354 One
1355 Two
1356 3
1357}
1358"
1359 );
1360}
1361
1362#[test]
1363fn function_type_invalid_param_type() {
1364 assert_module_error!(
1365 "
1366fn f(g: fn(Int, 1) -> Int) -> Int {
1367 g(0, 1)
1368}
1369"
1370 );
1371}
1372
1373#[test]
1374fn function_invalid_signature() {
1375 assert_module_error!(
1376 r#"
1377fn f(a, "b") -> String {
1378 a <> b
1379}
1380"#
1381 );
1382}
1383
1384#[test]
1385fn const_invalid_tuple() {
1386 assert_module_error!(
1387 "
1388const a = #(1, 2, <-)
1389"
1390 );
1391}
1392
1393#[test]
1394fn const_invalid_list() {
1395 assert_module_error!(
1396 "
1397const a = [1, 2, <-]
1398"
1399 );
1400}
1401
1402#[test]
1403fn const_invalid_bit_array_segment() {
1404 assert_module_error!(
1405 "
1406const a = <<1, 2, <->>
1407"
1408 );
1409}
1410
1411#[test]
1412fn const_invalid_record_constructor() {
1413 assert_module_error!(
1414 "
1415type A {
1416 A(String, Int)
1417}
1418const a = A(\"a\", let)
1419"
1420 );
1421}
1422
1423// record access should parse even if there is no label written
1424#[test]
1425fn record_access_no_label() {
1426 assert_parse_module!(
1427 "
1428type Wibble {
1429 Wibble(wibble: String)
1430}
1431
1432fn wobble() {
1433 Wibble(\"a\").
1434}
1435"
1436 );
1437}
1438
1439#[test]
1440fn newline_tokens() {
1441 assert_eq!(
1442 make_tokenizer("1\n\n2\n").collect_vec(),
1443 [
1444 Ok((
1445 0,
1446 Token::Int {
1447 value: "1".into(),
1448 int_value: 1.into()
1449 },
1450 1
1451 )),
1452 Ok((1, Token::NewLine, 2)),
1453 Ok((2, Token::NewLine, 3)),
1454 Ok((
1455 3,
1456 Token::Int {
1457 value: "2".into(),
1458 int_value: 2.into()
1459 },
1460 4
1461 )),
1462 Ok((4, Token::NewLine, 5))
1463 ]
1464 );
1465}
1466
1467// https://github.com/gleam-lang/gleam/issues/1756
1468#[test]
1469fn arithmetic_in_guards() {
1470 assert_parse!(
1471 "
1472case 2, 3 {
1473 x, y if x + y == 1 -> True
1474}"
1475 );
1476}
1477
1478#[test]
1479fn const_string_concat() {
1480 assert_parse_module!(
1481 "
1482const cute = \"cute\"
1483const cute_bee = cute <> \"bee\"
1484"
1485 );
1486}
1487
1488#[test]
1489fn const_string_concat_naked_right() {
1490 assert_module_error!(
1491 "
1492const no_cute_bee = \"cute\" <>
1493"
1494 );
1495}
1496
1497#[test]
1498fn function_call_in_case_clause_guard() {
1499 assert_error!(
1500 r#"
1501let my_string = "hello"
1502case my_string {
1503 _ if length(my_string) > 2 -> io.debug("doesn't work')
1504}"#
1505 );
1506}
1507
1508#[test]
1509fn dot_access_function_call_in_case_clause_guard() {
1510 assert_error!(
1511 r#"
1512let my_string = "hello"
1513case my_string {
1514 _ if string.length(my_string) > 2 -> io.debug("doesn't work')
1515}"#
1516 );
1517}
1518
1519#[test]
1520fn invalid_left_paren_in_case_clause_guard() {
1521 assert_error!(
1522 r#"
1523let my_string = "hello"
1524case my_string {
1525 _ if string.length( > 2 -> io.debug("doesn't work')
1526}"#
1527 );
1528}
1529
1530#[test]
1531fn string_concatenation_in_case_clause_guard() {
1532 assert_parse!(
1533 r#"
1534let my_string = "hello "
1535case my_string {
1536 _ if my_string <> "world" == "hello world" -> io.debug("ok")
1537}"#
1538 );
1539}
1540
1541#[test]
1542fn constant_record_with_empty_arguments_is_record() {
1543 assert_parse_module!("pub const wibble = Wibble()");
1544}
1545
1546#[test]
1547fn calling_module_constant_as_constructor() {
1548 assert_module_error!(
1549 "
1550pub type Wibble { Wibble(Int) }
1551pub const wibble = Wibble
1552pub const a = wibble(1)
1553"
1554 );
1555}
1556
1557#[test]
1558fn invalid_label_shorthand() {
1559 assert_module_error!(
1560 "
1561pub fn main() {
1562 wibble(:)
1563}
1564"
1565 );
1566}
1567
1568#[test]
1569fn invalid_label_shorthand_2() {
1570 assert_module_error!(
1571 "
1572pub fn main() {
1573 wibble(:,)
1574}
1575"
1576 );
1577}
1578
1579#[test]
1580fn invalid_label_shorthand_3() {
1581 assert_module_error!(
1582 "
1583pub fn main() {
1584 wibble(:arg)
1585}
1586"
1587 );
1588}
1589
1590#[test]
1591fn invalid_label_shorthand_4() {
1592 assert_module_error!(
1593 "
1594pub fn main() {
1595 wibble(arg::)
1596}
1597"
1598 );
1599}
1600
1601#[test]
1602fn invalid_label_shorthand_5() {
1603 assert_module_error!(
1604 "
1605pub fn main() {
1606 wibble(arg::arg)
1607}
1608"
1609 );
1610}
1611
1612#[test]
1613fn invalid_pattern_label_shorthand() {
1614 assert_module_error!(
1615 "
1616pub fn main() {
1617 let Wibble(:) = todo
1618}
1619"
1620 );
1621}
1622
1623#[test]
1624fn invalid_pattern_label_shorthand_2() {
1625 assert_module_error!(
1626 "
1627pub fn main() {
1628 let Wibble(:arg) = todo
1629}
1630"
1631 );
1632}
1633
1634#[test]
1635fn invalid_pattern_label_shorthand_3() {
1636 assert_module_error!(
1637 "
1638pub fn main() {
1639 let Wibble(arg::) = todo
1640}
1641"
1642 );
1643}
1644
1645#[test]
1646fn invalid_pattern_label_shorthand_4() {
1647 assert_module_error!(
1648 "
1649pub fn main() {
1650 let Wibble(arg: arg:) = todo
1651}
1652"
1653 );
1654}
1655
1656#[test]
1657fn invalid_pattern_label_shorthand_5() {
1658 assert_module_error!(
1659 "
1660pub fn main() {
1661 let Wibble(arg1: arg2:) = todo
1662}
1663"
1664 );
1665}
1666
1667fn first_parsed_docstring(src: &str) -> EcoString {
1668 let parsed =
1669 crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null())
1670 .expect("should parse");
1671
1672 parsed
1673 .module
1674 .definitions
1675 .first()
1676 .expect("parsed a definition")
1677 .definition
1678 .get_doc()
1679 .expect("definition without doc")
1680}
1681
1682#[test]
1683fn doc_comment_before_comment_is_not_attached_to_following_function() {
1684 assert_eq!(
1685 first_parsed_docstring(
1686 r#"
1687 /// Not included!
1688 // pub fn call()
1689
1690 /// Doc!
1691 pub fn wibble() {}
1692"#
1693 ),
1694 " Doc!\n"
1695 )
1696}
1697
1698#[test]
1699fn doc_comment_before_comment_is_not_attached_to_following_type() {
1700 assert_eq!(
1701 first_parsed_docstring(
1702 r#"
1703 /// Not included!
1704 // pub fn call()
1705
1706 /// Doc!
1707 pub type Wibble
1708"#
1709 ),
1710 " Doc!\n"
1711 )
1712}
1713
1714#[test]
1715fn doc_comment_before_comment_is_not_attached_to_following_type_alias() {
1716 assert_eq!(
1717 first_parsed_docstring(
1718 r#"
1719 /// Not included!
1720 // pub fn call()
1721
1722 /// Doc!
1723 pub type Wibble = Int
1724"#
1725 ),
1726 " Doc!\n"
1727 )
1728}
1729
1730#[test]
1731fn doc_comment_before_comment_is_not_attached_to_following_constant() {
1732 assert_eq!(
1733 first_parsed_docstring(
1734 r#"
1735 /// Not included!
1736 // pub fn call()
1737
1738 /// Doc!
1739 pub const wibble = 1
1740"#
1741 ),
1742 " Doc!\n"
1743 );
1744}
1745
1746#[test]
1747fn non_module_level_function_with_a_name() {
1748 assert_module_error!(
1749 r#"
1750pub fn main() {
1751 fn my() { 1 }
1752}
1753"#
1754 );
1755}
1756
1757#[test]
1758fn error_message_on_variable_starting_with_underscore() {
1759 // https://github.com/gleam-lang/gleam/issues/3504
1760 assert_module_error!(
1761 "
1762 pub fn main() {
1763 let val = _func_starting_with_underscore(1)
1764 }"
1765 );
1766}
1767
1768#[test]
1769fn non_module_level_function_with_not_a_name() {
1770 assert_module_error!(
1771 r#"
1772pub fn main() {
1773 fn @() { 1 } // wrong token and not a name
1774}
1775"#
1776 );
1777}
1778
1779#[test]
1780fn error_message_on_variable_starting_with_underscore2() {
1781 // https://github.com/gleam-lang/gleam/issues/3504
1782 assert_module_error!(
1783 "
1784 pub fn main() {
1785 case 1 {
1786 1 -> _with_underscore(1)
1787 }
1788 }"
1789 );
1790}
1791
1792#[test]
1793fn function_inside_a_type() {
1794 assert_module_error!(
1795 r#"
1796type Wibble {
1797 fn wobble() {}
1798}
1799"#
1800 );
1801}
1802
1803#[test]
1804fn pub_function_inside_a_type() {
1805 assert_module_error!(
1806 r#"
1807type Wibble {
1808 pub fn wobble() {}
1809}
1810"#
1811 );
1812}
1813
1814#[test]
1815fn if_like_expression() {
1816 assert_module_error!(
1817 r#"
1818pub fn main() {
1819 let a = if wibble {
1820 wobble
1821 }
1822}
1823"#
1824 );
1825}
1826
1827// https://github.com/gleam-lang/gleam/issues/3796
1828#[test]
1829fn missing_type_constructor_arguments_in_type_definition() {
1830 assert_module_error!(
1831 "
1832pub type A() {
1833 A(Int)
1834}
1835"
1836 );
1837}
1838
1839#[test]
1840fn tuple_without_hash() {
1841 assert_module_error!(
1842 r#"
1843pub fn main() {
1844 let triple = (1, 2.2, "three")
1845 io.debug(triple)
1846 let (a, *, *) = triple
1847 io.debug(a)
1848 io.debug(triple.1)
1849}
1850"#
1851 );
1852}
1853
1854#[test]
1855fn deprecation_attribute_on_type_variant() {
1856 assert_parse_module!(
1857 r#"
1858type Wibble {
1859 @deprecated("1")
1860 Wibble1
1861 Wibble2
1862}
1863"#
1864 );
1865}
1866
1867#[test]
1868
1869fn float_empty_exponent() {
1870 assert_error!("1.32e");
1871}
1872
1873#[test]
1874fn multiple_deprecation_attribute_on_type_variant() {
1875 assert_module_error!(
1876 r#"
1877type Wibble {
1878 @deprecated("1")
1879 @deprecated("2")
1880 Wibble1
1881 Wibble2
1882}
1883"#
1884 );
1885}
1886
1887#[test]
1888fn target_attribute_on_type_variant() {
1889 assert_module_error!(
1890 r#"
1891type Wibble {
1892 @target(erlang)
1893 Wibble2
1894}
1895"#
1896 );
1897}
1898
1899#[test]
1900fn internal_attribute_on_type_variant() {
1901 assert_module_error!(
1902 r#"
1903type Wibble {
1904 @internal
1905 Wibble1
1906}
1907"#
1908 );
1909}
1910
1911#[test]
1912fn external_attribute_on_type_variant() {
1913 assert_module_error!(
1914 r#"
1915type Wibble {
1916 @external(erlang, "one", "two")
1917 Wibble1
1918}
1919"#
1920 );
1921}
1922
1923#[test]
1924fn multiple_unsupported_attributes_on_type_variant() {
1925 assert_module_error!(
1926 r#"
1927type Wibble {
1928 @external(erlang, "one", "two")
1929 @target(erlang)
1930 @internal
1931 Wibble1
1932}
1933"#
1934 );
1935}
1936
1937#[test]
1938// https://github.com/gleam-lang/gleam/issues/3870
1939fn nested_tuple_access_after_function() {
1940 assert_parse!("tuple().0.1");
1941}
1942
1943#[test]
1944fn case_expression_without_body() {
1945 assert_parse!("case a");
1946}
1947
1948#[test]
1949fn assert_statement() {
1950 assert_parse!("assert 10 != 11");
1951}
1952
1953#[test]
1954fn assert_statement_with_message() {
1955 assert_parse!(r#"assert False as "Uh oh""#);
1956}
1957
1958#[test]
1959fn assert_statement_without_expression() {
1960 assert_error!("assert");
1961}
1962
1963#[test]
1964fn assert_statement_followed_by_statement() {
1965 assert_error!("assert let a = 10");
1966}
1967
1968#[test]
1969fn special_error_for_pythonic_import() {
1970 assert_module_error!("import gleam.io");
1971}
1972
1973#[test]
1974fn special_error_for_pythonic_neste_import() {
1975 assert_module_error!("import one.two.three");
1976}
1977
1978#[test]
1979fn doesnt_issue_special_error_for_pythonic_import_if_slash() {
1980 assert_module_error!("import one/two.three");
1981}
1982
1983#[test]
1984fn operator_in_pattern_size() {
1985 assert_parse!("let assert <<size, payload:size(size - 1)>> = <<>>");
1986}
1987
1988#[test]
1989fn correct_precedence_in_pattern_size() {
1990 assert_parse!("let assert <<size, payload:size(size + 2 * 8)>> = <<>>");
1991}
1992
1993#[test]
1994fn private_opaque_type_is_parsed() {
1995 assert_parse_module!("opaque type Wibble { Wobble }");
1996}
1997
1998#[test]
1999fn case_guard_with_nested_blocks() {
2000 assert_parse!(
2001 "case 1 {
2002 _ if { 1 || { 1 || 1 } } || 1 -> 1
2003}"
2004 );
2005}
2006
2007#[test]
2008fn case_guard_with_empty_block() {
2009 assert_error!(
2010 "case 1 {
2011 _ if a || {} -> 1
2012}"
2013 );
2014}
2015
2016#[test]
2017fn constant_inside_function() {
2018 assert_module_error!(
2019 "
2020pub fn main() {
2021 const x = 10
2022 x
2023}
2024"
2025 );
2026}
2027
2028#[test]
2029fn function_definition_angle_generics_error() {
2030 assert_module_error!("fn id<T>(x: T) { x }");
2031}
2032
2033#[test]
2034fn type_angle_generics_usage_without_module_error() {
2035 assert_error!("let list: List<Int, String> = []");
2036}
2037
2038#[test]
2039fn type_angle_generics_usage_with_module_error() {
2040 assert_error!("let set: set.Set<Int> = set.new()");
2041}
2042
2043#[test]
2044fn type_angle_generics_definition_error() {
2045 assert_module_error!(
2046 r#"
2047type Either<a, b> {
2048 This(a)
2049 That(b)
2050}
2051"#
2052 );
2053}
2054
2055#[test]
2056fn type_angle_generics_definition_with_upname_error() {
2057 assert_module_error!(
2058 r#"
2059type Either<A, B> {
2060 This(A)
2061 That(B)
2062}
2063"#
2064 );
2065}
2066
2067#[test]
2068fn type_angle_generics_definition_error_fallback() {
2069 // Example of a more incorrect syntax, where Gleam doesn't make a suggestion.
2070 // In this case, an example type argument is used instead.
2071 assert_module_error!(
2072 r#"
2073type Either<type A, type B> {
2074 This(A)
2075 That(B)
2076}
2077"#
2078 );
2079}
2080
2081#[test]
2082fn wrong_type_of_comments_with_hash() {
2083 assert_module_error!(
2084 r#"
2085pub fn main() {
2086 # a python-style comment
2087}
2088"#
2089 );
2090}
2091
2092#[test]
2093fn wrong_function_return_type_declaration_using_colon_instead_of_right_arrow() {
2094 assert_module_error!(
2095 r#"
2096pub fn main(): Nil {}
2097 "#
2098 );
2099}
2100
2101#[test]
2102fn const_record_update_basic() {
2103 assert_parse_module!(
2104 r#"
2105type Person {
2106 Person(name: String, age: Int)
2107}
2108
2109const alice = Person("Alice", 30)
2110const bob = Person(..alice, name: "Bob")
2111"#
2112 );
2113}
2114
2115#[test]
2116fn const_record_update_all_fields() {
2117 assert_parse_module!(
2118 r#"
2119type Person {
2120 Person(name: String, age: Int, city: String)
2121}
2122
2123const base = Person("Alice", 30, "London")
2124const updated = Person(..base, name: "Bob", age: 25, city: "Paris")
2125"#
2126 );
2127}
2128
2129#[test]
2130fn const_record_update_only() {
2131 assert_parse_module!(
2132 r#"
2133type Person {
2134 Person(name: String, age: Int)
2135}
2136
2137const alice = Person("Alice", 30)
2138const bob = Person(..alice)
2139"#
2140 );
2141}
2142
2143#[test]
2144fn const_record_update_with_module() {
2145 assert_parse_module!(
2146 r#"
2147const local_const = other.Record(..other.base, field: value)
2148"#
2149 );
2150}
2151
2152// https://github.com/gleam-lang/gleam/issues/5391
2153#[test]
2154fn byte_order_mark() {
2155 assert_parse!("\u{feff}todo");
2156}
2157
2158// https://github.com/gleam-lang/gleam/issues/5391
2159#[test]
2160fn byte_order_mark_module() {
2161 assert_parse_module!(
2162 "\u{feff}
2163const local_const = other.Record(..other.base, field: value)
2164"
2165 );
2166}
2167
2168#[test]
2169fn prepend_to_const_list_without_comma() {
2170 // While this is valid (but deprecated) syntax for expressions, prepending to
2171 // constant lists wasn't added until after the deprecation, so it was never
2172 // valid in the first place.
2173 assert_module_error!(
2174 "
2175const wibble = [2, 3]
2176const wobble = [1 ..wibble]
2177"
2178 );
2179}
2180
2181#[test]
2182fn prepend_to_const_list_with_multiple_spreads() {
2183 assert_module_error!(
2184 "
2185const wibble = [2, 3]
2186const wobble = [0, 1]
2187const wubble = [..wobble, ..wibble]
2188"
2189 );
2190}
2191
2192#[test]
2193fn prepend_to_const_list_with_no_tail() {
2194 assert_module_error!(
2195 "
2196const wibble = [1, 2, ..]
2197"
2198 );
2199}
2200
2201#[test]
2202fn prepend_no_elements_to_const_list() {
2203 assert_module_error!(
2204 "
2205const wibble = [2, 3]
2206const wobble = [..wibble]
2207"
2208 );
2209}
2210
2211#[test]
2212fn append_to_const_list() {
2213 assert_module_error!(
2214 "
2215const wibble = [2, 3]
2216const wobble = [..wibble, 4, 5]
2217"
2218 );
2219}
2220
2221#[test]
2222fn parse_error_for_greater_sign_after_invalid_qualified_type() {
2223 assert_module_error!("pub fn main() -> wibble.<a> { todo }");
2224}
2225
2226#[test]
2227fn parse_error_for_type_list_after_invalid_qualified_type_1() {
2228 assert_module_error!("pub fn main() -> wibble.() { todo }");
2229}
2230
2231#[test]
2232fn parse_error_for_type_list_after_invalid_qualified_type_2() {
2233 assert_module_error!("pub fn main() -> wibble.(a, b) { todo }");
2234}
2235
2236#[test]
2237fn unicode_left_double_quotation_mark() {
2238 assert_module_error!("pub fn main() { \u{201C}hello\u{201D} }");
2239}
2240
2241#[test]
2242fn unicode_right_double_quotation_mark() {
2243 assert_module_error!("pub fn main() { \u{201D}hello }");
2244}
2245
2246#[test]
2247fn unicode_left_single_quotation_mark() {
2248 assert_module_error!("pub fn main() { \u{2018}hello\u{2019} }");
2249}
2250
2251#[test]
2252fn unicode_right_single_quotation_mark() {
2253 assert_module_error!("pub fn main() { \u{2019}hello }");
2254}
2255
2256#[test]
2257fn unicode_en_dash() {
2258 assert_module_error!("pub fn main() { 1\u{2013}2 }");
2259}
2260
2261#[test]
2262fn unicode_em_dash() {
2263 assert_module_error!("pub fn main() { 1\u{2014}2 }");
2264}
2265
2266#[test]
2267fn unicode_asterisk_operator() {
2268 assert_module_error!("pub fn main() { 1\u{2217}2 }");
2269}
2270
2271#[test]
2272fn unicode_division_slash() {
2273 assert_module_error!("pub fn main() { 1\u{2215}2 }");
2274}
2275
2276#[test]
2277fn unicode_non_breaking_space() {
2278 assert_module_error!("pub fn main() { let\u{00A0}x = 1\n x }");
2279}
2280
2281#[test]
2282fn unicode_zero_width_space() {
2283 assert_module_error!("pub fn main() { let\u{200B}x = 1\n x }");
2284}
2285
2286#[test]
2287fn unicode_cyrillic_a() {
2288 assert_module_error!("pub fn main() { \u{0430} }");
2289}
2290
2291#[test]
2292fn unicode_cyrillic_e() {
2293 assert_module_error!("pub fn main() { \u{0435} }");
2294}
2295
2296#[test]
2297fn unicode_cyrillic_o() {
2298 assert_module_error!("pub fn main() { \u{043E} }");
2299}
2300
2301#[test]
2302fn unicode_cyrillic_p() {
2303 assert_module_error!("pub fn main() { \u{0440} }");
2304}
2305
2306#[test]
2307fn unicode_modifier_letter_capital_i() {
2308 assert_module_error!("pub fn main() { \u{1D35} }");
2309}
2310
2311#[test]
2312fn unicode_low_single_comma_quotation_mark() {
2313 assert_module_error!("pub fn main() { #(1\u{201A} 2) }");
2314}
2315
2316#[test]
2317fn unicode_fullwidth_comma() {
2318 assert_module_error!("pub fn main() { #(1\u{FF0C} 2) }");
2319}
2320
2321#[test]
2322fn unicode_ideographic_comma() {
2323 assert_module_error!("pub fn main() { #(1\u{3001} 2) }");
2324}
2325
2326#[test]
2327fn unicode_fullwidth_exclamation_mark() {
2328 assert_module_error!("pub fn main() { \u{FF01}= True }");
2329}
2330
2331#[test]
2332fn unicode_fullwidth_left_square_bracket() {
2333 assert_module_error!("pub fn main() { \u{FF3B}1, 2] }");
2334}
2335
2336#[test]
2337fn unicode_fullwidth_right_square_bracket() {
2338 assert_module_error!("pub fn main() { [1, 2\u{FF3D} }");
2339}
2340
2341#[test]
2342fn unicode_fullwidth_left_parenthesis() {
2343 assert_module_error!("pub fn main\u{FF08}) { Nil }");
2344}
2345
2346#[test]
2347fn unicode_fullwidth_right_parenthesis() {
2348 assert_module_error!("pub fn main(\u{FF09} { Nil }");
2349}
2350
2351#[test]
2352fn unicode_fullwidth_full_stop() {
2353 assert_module_error!("pub fn main() { io\u{FF0E}println(\"hi\") }");
2354}
2355
2356#[test]
2357fn unicode_ideographic_full_stop() {
2358 assert_module_error!("pub fn main() { io\u{3002}println(\"hi\") }");
2359}
2360
2361#[test]
2362fn unicode_fullwidth_less_than_sign() {
2363 assert_module_error!("pub fn main() { 1 \u{FF1C} 2 }");
2364}
2365
2366#[test]
2367fn unicode_fullwidth_greater_than_sign() {
2368 assert_module_error!("pub fn main() { 1 \u{FF1E} 2 }");
2369}
2370
2371#[test]
2372fn unicode_fullwidth_vertical_line() {
2373 assert_module_error!("pub fn main() { True \u{FF5C}\u{FF5C} False }");
2374}
2375
2376#[test]
2377fn unicode_fullwidth_at_sign() {
2378 assert_module_error!("\u{FF20}deprecated(\"old\")\npub fn main() { Nil }");
2379}
2380
2381#[test]
2382fn unicode_fullwidth_caret() {
2383 assert_module_error!("pub fn main() { 0b1 \u{FF3E} 0b1 }");
2384}
2385
2386#[test]
2387fn unicode_fullwidth_colon() {
2388 assert_module_error!("pub fn wibble(x\u{FF1A} Int) { x }");
2389}
2390
2391#[test]
2392fn missing_todo_constant_message() {
2393 assert_module_error!("pub const wibble = todo as");
2394}
2395
2396#[test]
2397fn missing_todo_constant_message_2() {
2398 assert_module_error!(
2399 "pub const wibble = todo as
2400pub fn wibble() {}"
2401 );
2402}
2403
2404// https://github.com/gleam-lang/gleam/issues/5711
2405#[test]
2406fn parsing_bit_array_constant_with_non_integer_size() {
2407 assert_module_error!("pub const wibble = <<1:size(something)>>");
2408}
2409
2410#[test]
2411fn parsing_bit_array_constant_with_non_integer_unit() {
2412 assert_module_error!("pub const wibble = <<1:unit(something)>>");
2413}
2414
2415#[test]
2416fn parsing_bit_array_expression_with_invalid_size() {
2417 assert_module_error!(
2418 "pub fn wibble() {
2419 let assert <<1:size(Upname)>> = todo
2420}"
2421 );
2422}
2423
2424#[test]
2425fn parsing_bit_array_expression_with_invalid_unit() {
2426 assert_module_error!(
2427 "pub fn wibble() {
2428 <<1:unit(Upname)>>
2429}"
2430 );
2431}
2432
2433#[test]
2434fn parsing_bit_array_pattern_with_invalid_unit() {
2435 assert_module_error!(
2436 "pub fn wibble() {
2437 let assert <<1:unit(Upname)>> = todo
2438}"
2439 );
2440}
2441
2442#[test]
2443fn parsing_bit_array_constant_with_invalid_segment_type() {
2444 assert_module_error!("pub const wibble = <<1:Upname>>");
2445}
2446
2447#[test]
2448fn parsing_invalid_external_module_name() {
2449 assert_module_error!(
2450 "
2451@external(erlang, Upname, [])
2452fn wibble() -> Nil
2453"
2454 );
2455}
2456
2457#[test]
2458fn parsing_invalid_external_function_name() {
2459 assert_module_error!(
2460 r#"
2461@external(erlang, "module", Upname)
2462fn wibble() -> Nil
2463"#
2464 );
2465}
2466
2467#[test]
2468fn parsing_invalid_deprecation_message() {
2469 assert_module_error!(
2470 r#"
2471@deprecated(Upname)
2472fn wibble() -> Nil
2473"#
2474 );
2475}
2476
2477#[test]
2478fn error_message_when_using_discard_pattern_for_function_name() {
2479 assert_module_error!("fn _wibble() -> Nil");
2480}
2481
2482#[test]
2483fn error_message_when_using_discard_pattern_for_constant_name() {
2484 assert_module_error!("const _wibble = 1");
2485}
2486
2487#[test]
2488fn error_message_when_using_discard_pattern_for_module_name() {
2489 assert_module_error!("import _wibble");
2490}
2491
2492#[test]
2493fn error_message_when_using_discard_pattern_for_as_pattern() {
2494 assert_module_error!(
2495 "pub fn main() {
2496 case todo {
2497 wibble as _wobble -> todo
2498 }
2499}"
2500 );
2501}