Fork of daniellemaywood.uk/gleam — Wasm codegen work
46 kB
2550 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 external_with_no_arguments() {
752 assert_module_error!(
753 r#"
754@external
755pub fn one(x: Int) -> Int {
756 todo
757}"#
758 );
759}
760
761#[test]
762fn unknown_target() {
763 assert_module_error!(
764 r#"
765@target(abc)
766pub fn one() {}"#
767 );
768}
769
770#[test]
771fn missing_target() {
772 assert_module_error!(
773 r#"
774@target()
775pub fn one() {}"#
776 );
777}
778
779#[test]
780fn missing_target_and_bracket() {
781 assert_module_error!(
782 r#"
783@target(
784pub fn one() {}"#
785 );
786}
787
788#[test]
789fn unknown_attribute() {
790 assert_module_error!(
791 r#"@go_faster()
792pub fn main() { 1 }"#
793 );
794}
795
796#[test]
797fn incomplete_function() {
798 assert_error!("fn()");
799}
800
801#[test]
802fn multiple_deprecation_attributes() {
803 assert_module_error!(
804 r#"
805@deprecated("1")
806@deprecated("2")
807pub fn main() -> Nil {
808 Nil
809}
810"#
811 );
812}
813
814#[test]
815fn deprecation_with_no_message() {
816 assert_module_error!(
817 r#"
818@deprecated
819pub fn main() -> Nil {
820 Nil
821}
822"#
823 );
824}
825
826#[test]
827fn deprecation_with_no_message_on_constructor() {
828 assert_module_error!(
829 r#"
830pub type HashAlgorithm {
831 @deprecated
832 Md5
833 Sha224
834 Sha512
835}
836"#
837 );
838}
839
840#[test]
841fn deprecation_without_message() {
842 assert_module_error!(
843 r#"
844@deprecated()
845pub fn main() -> Nil {
846 Nil
847}
848"#
849 );
850}
851
852#[test]
853fn multiple_internal_attributes() {
854 assert_module_error!(
855 r#"
856@internal
857@internal
858pub fn main() -> Nil {
859 Nil
860}
861"#
862 );
863}
864
865#[test]
866fn attributes_with_no_definition() {
867 assert_module_error!(
868 r#"
869@deprecated("1")
870@target(erlang)
871"#
872 );
873}
874
875#[test]
876fn external_attribute_with_custom_type() {
877 assert_parse_module!(
878 r#"
879@external(erlang, "gleam_stdlib", "dict")
880@external(javascript, "./gleam_stdlib.d.ts", "Dict")
881pub type Dict(key, value)
882"#
883 );
884}
885
886#[test]
887fn external_attribute_with_non_fn_definition() {
888 assert_module_error!(
889 r#"
890@external(erlang, "module", "fun")
891pub type Fun = Fun
892"#
893 );
894}
895
896#[test]
897fn attributes_with_improper_definition() {
898 assert_module_error!(
899 r#"
900@deprecated("1")
901@external(erlang, "module", "fun")
902"#
903 );
904}
905
906#[test]
907fn const_with_function_call() {
908 assert_module_error!(
909 r#"
910pub fn wibble() { 123 }
911const wib: Int = wibble()
912"#
913 );
914}
915
916#[test]
917fn const_with_function_call_with_args() {
918 assert_module_error!(
919 r#"
920pub fn wibble() { 123 }
921const wib: Int = wibble(1, "wobble")
922"#
923 );
924}
925
926#[test]
927fn import_type() {
928 assert_parse_module!(r#"import wibble.{type Wobble, Wobble, type Wabble}"#);
929}
930
931#[test]
932fn reserved_auto() {
933 assert_module_error!(r#"const auto = 1"#);
934}
935
936#[test]
937fn reserved_delegate() {
938 assert_module_error!(r#"const delegate = 1"#);
939}
940
941#[test]
942fn reserved_derive() {
943 assert_module_error!(r#"const derive = 1"#);
944}
945
946#[test]
947fn reserved_else() {
948 assert_module_error!(r#"const else = 1"#);
949}
950
951#[test]
952fn reserved_implement() {
953 assert_module_error!(r#"const implement = 1"#);
954}
955
956#[test]
957fn reserved_macro() {
958 assert_module_error!(r#"const macro = 1"#);
959}
960
961#[test]
962fn reserved_test() {
963 assert_module_error!(r#"const test = 1"#);
964}
965
966#[test]
967fn reserved_echo() {
968 assert_module_error!(r#"const echo = 1"#);
969}
970
971#[test]
972fn capture_with_name() {
973 assert_module_error!(
974 r#"
975pub fn main() {
976 add(_name, 1)
977}
978
979fn add(x, y) {
980 x + y
981}
982"#
983 );
984}
985
986#[test]
987fn list_spread_with_no_tail_in_the_middle_of_a_list() {
988 assert_module_error!(
989 r#"
990pub fn main() -> Nil {
991 let xs = [1, 2, 3]
992 [1, 2, .., 3 + 3, 4]
993}
994"#
995 );
996}
997
998#[test]
999fn list_spread_followed_by_extra_items() {
1000 assert_module_error!(
1001 r#"
1002pub fn main() -> Nil {
1003 let xs = [1, 2, 3]
1004 [1, 2, ..xs, 3 + 3, 4]
1005}
1006"#
1007 );
1008}
1009
1010#[test]
1011fn list_spread_followed_by_extra_item_and_another_spread() {
1012 assert_module_error!(
1013 r#"
1014pub fn main() -> Nil {
1015 let xs = [1, 2, 3]
1016 let ys = [5, 6, 7]
1017 [..xs, 4, ..ys]
1018}
1019"#
1020 );
1021}
1022
1023#[test]
1024fn list_spread_followed_by_other_spread() {
1025 assert_module_error!(
1026 r#"
1027pub fn main() -> Nil {
1028 let xs = [1, 2, 3]
1029 let ys = [5, 6, 7]
1030 [1, ..xs, ..ys]
1031}
1032"#
1033 );
1034}
1035
1036#[test]
1037fn list_spread_as_first_item_followed_by_other_items() {
1038 assert_module_error!(
1039 r#"
1040pub fn main() -> Nil {
1041 let xs = [1, 2, 3]
1042 [..xs, 3 + 3, 4]
1043}
1044"#
1045 );
1046}
1047
1048// Tests for nested tuples and structs in tuples
1049// https://github.com/gleam-lang/gleam/issues/1980
1050
1051#[test]
1052fn nested_tuples() {
1053 assert_parse!(
1054 r#"
1055let tup = #(#(5, 6))
1056{tup.0}.1
1057"#
1058 );
1059}
1060
1061#[test]
1062fn nested_tuples_no_block() {
1063 assert_parse!(
1064 r#"
1065let tup = #(#(5, 6))
1066tup.0.1
1067"#
1068 );
1069}
1070
1071#[test]
1072fn deeply_nested_tuples() {
1073 assert_parse!(
1074 r#"
1075let tup = #(#(#(#(4))))
1076{{{tup.0}.0}.0}.0
1077"#
1078 );
1079}
1080
1081#[test]
1082fn deeply_nested_tuples_no_block() {
1083 assert_parse!(
1084 r#"
1085let tup = #(#(#(#(4))))
1086tup.0.0.0.0
1087"#
1088 );
1089}
1090
1091#[test]
1092fn inner_single_quote_parses() {
1093 assert_parse!(
1094 r#"
1095let a = "inner 'quotes'"
1096"#
1097 );
1098}
1099
1100#[test]
1101fn string_single_char_suggestion() {
1102 assert_module_error!(
1103 "
1104 pub fn main() {
1105 let a = 'example'
1106 }
1107 "
1108 );
1109}
1110
1111#[test]
1112fn private_internal_const() {
1113 assert_module_error!(
1114 "
1115@internal
1116const wibble = 1
1117"
1118 );
1119}
1120
1121#[test]
1122fn private_internal_type_alias() {
1123 assert_module_error!(
1124 "
1125@internal
1126type Alias = Int
1127"
1128 );
1129}
1130
1131#[test]
1132fn private_internal_function() {
1133 assert_module_error!(
1134 "
1135@internal
1136fn wibble() { todo }
1137"
1138 );
1139}
1140
1141#[test]
1142fn private_internal_type() {
1143 assert_module_error!(
1144 "
1145@internal
1146type Wibble {
1147 Wibble
1148}
1149"
1150 );
1151}
1152
1153#[test]
1154fn wrong_record_access_pattern() {
1155 assert_module_error!(
1156 "
1157pub fn main() {
1158 case wibble {
1159 wibble.thing -> 1
1160 }
1161}
1162"
1163 );
1164}
1165
1166#[test]
1167fn tuple_invalid_expr() {
1168 assert_module_error!(
1169 "
1170fn main() {
1171 #(1, 2, const)
1172}
1173"
1174 );
1175}
1176
1177#[test]
1178fn bit_array_invalid_segment() {
1179 assert_module_error!(
1180 "
1181fn main() {
1182 <<72, 101, 108, 108, 111, 44, 32, 74, 111, 101, const>>
1183}
1184"
1185 );
1186}
1187
1188#[test]
1189fn case_invalid_expression() {
1190 assert_module_error!(
1191 "
1192fn main() {
1193 case 1, type {
1194 _, _ -> 0
1195 }
1196}
1197"
1198 );
1199}
1200
1201#[test]
1202fn case_invalid_case_pattern() {
1203 assert_module_error!(
1204 "
1205fn main() {
1206 case 1 {
1207 -> -> 0
1208 }
1209}
1210"
1211 );
1212}
1213
1214#[test]
1215fn case_clause_no_subject() {
1216 assert_module_error!(
1217 "
1218fn main() {
1219 case 1 {
1220 -> 1
1221 _ -> 2
1222 }
1223}
1224"
1225 );
1226}
1227
1228#[test]
1229fn case_alternative_clause_no_subject() {
1230 assert_module_error!(
1231 "
1232fn main() {
1233 case 1 {
1234 1 | -> 1
1235 _ -> 1
1236 }
1237}
1238"
1239 );
1240}
1241
1242#[test]
1243fn use_invalid_assignments() {
1244 assert_module_error!(
1245 "
1246fn main() {
1247 use fn <- result.try(get_username())
1248}
1249"
1250 );
1251}
1252
1253#[test]
1254fn assignment_pattern_invalid_tuple() {
1255 assert_module_error!(
1256 "
1257fn main() {
1258 let #(a, case, c) = #(1, 2, 3)
1259}
1260"
1261 );
1262}
1263
1264#[test]
1265fn assignment_pattern_invalid_bit_segment() {
1266 assert_module_error!(
1267 "
1268fn main() {
1269 let <<b1, pub>> = <<24, 3>>
1270}
1271"
1272 );
1273}
1274
1275#[test]
1276fn case_list_pattern_after_spread() {
1277 assert_module_error!(
1278 "
1279fn main() {
1280 case somelist {
1281 [..rest, last] -> 1
1282 _ -> 2
1283 }
1284}
1285"
1286 );
1287}
1288
1289#[test]
1290fn type_invalid_constructor() {
1291 assert_module_error!(
1292 "
1293type A {
1294 A(String)
1295 type
1296}
1297"
1298 );
1299}
1300
1301// Tests whether diagnostic presents an example of how to formulate a proper
1302// record constructor based off a common user error pattern.
1303// https://github.com/gleam-lang/gleam/issues/3324
1304
1305#[test]
1306fn type_invalid_record_constructor() {
1307 assert_module_error!(
1308 "
1309pub type User {
1310 name: String,
1311}
1312"
1313 );
1314}
1315
1316#[test]
1317fn type_invalid_record_constructor_without_field_type() {
1318 assert_module_error!(
1319 "
1320pub opaque type User {
1321 name
1322}
1323"
1324 );
1325}
1326
1327#[test]
1328fn type_invalid_record_constructor_invalid_field_type() {
1329 assert_module_error!(
1330 r#"
1331type User {
1332 name: "Test User",
1333}
1334"#
1335 );
1336}
1337
1338#[test]
1339fn type_invalid_type_name() {
1340 assert_module_error!(
1341 "
1342type A(a, type) {
1343 A
1344}
1345"
1346 );
1347}
1348
1349#[test]
1350fn type_invalid_constructor_arg() {
1351 assert_module_error!(
1352 "
1353type A {
1354 A(type: String)
1355}
1356"
1357 );
1358}
1359
1360#[test]
1361fn type_invalid_record() {
1362 assert_module_error!(
1363 "
1364type A {
1365 One
1366 Two
1367 3
1368}
1369"
1370 );
1371}
1372
1373#[test]
1374fn function_type_invalid_param_type() {
1375 assert_module_error!(
1376 "
1377fn f(g: fn(Int, 1) -> Int) -> Int {
1378 g(0, 1)
1379}
1380"
1381 );
1382}
1383
1384#[test]
1385fn function_invalid_signature() {
1386 assert_module_error!(
1387 r#"
1388fn f(a, "b") -> String {
1389 a <> b
1390}
1391"#
1392 );
1393}
1394
1395#[test]
1396fn const_invalid_tuple() {
1397 assert_module_error!(
1398 "
1399const a = #(1, 2, <-)
1400"
1401 );
1402}
1403
1404#[test]
1405fn const_invalid_list() {
1406 assert_module_error!(
1407 "
1408const a = [1, 2, <-]
1409"
1410 );
1411}
1412
1413#[test]
1414fn const_invalid_bit_array_segment() {
1415 assert_module_error!(
1416 "
1417const a = <<1, 2, <->>
1418"
1419 );
1420}
1421
1422#[test]
1423fn const_invalid_record_constructor() {
1424 assert_module_error!(
1425 "
1426type A {
1427 A(String, Int)
1428}
1429const a = A(\"a\", let)
1430"
1431 );
1432}
1433
1434// record access should parse even if there is no label written
1435#[test]
1436fn record_access_no_label() {
1437 assert_parse_module!(
1438 "
1439type Wibble {
1440 Wibble(wibble: String)
1441}
1442
1443fn wobble() {
1444 Wibble(\"a\").
1445}
1446"
1447 );
1448}
1449
1450#[test]
1451fn newline_tokens() {
1452 assert_eq!(
1453 make_tokenizer("1\n\n2\n").collect_vec(),
1454 [
1455 Ok((
1456 0,
1457 Token::Int {
1458 value: "1".into(),
1459 int_value: 1.into()
1460 },
1461 1
1462 )),
1463 Ok((1, Token::NewLine, 2)),
1464 Ok((2, Token::NewLine, 3)),
1465 Ok((
1466 3,
1467 Token::Int {
1468 value: "2".into(),
1469 int_value: 2.into()
1470 },
1471 4
1472 )),
1473 Ok((4, Token::NewLine, 5))
1474 ]
1475 );
1476}
1477
1478// https://github.com/gleam-lang/gleam/issues/1756
1479#[test]
1480fn arithmetic_in_guards() {
1481 assert_parse!(
1482 "
1483case 2, 3 {
1484 x, y if x + y == 1 -> True
1485}"
1486 );
1487}
1488
1489#[test]
1490fn const_string_concat() {
1491 assert_parse_module!(
1492 "
1493const cute = \"cute\"
1494const cute_bee = cute <> \"bee\"
1495"
1496 );
1497}
1498
1499#[test]
1500fn const_string_concat_naked_right() {
1501 assert_module_error!(
1502 "
1503const no_cute_bee = \"cute\" <>
1504"
1505 );
1506}
1507
1508#[test]
1509fn function_call_in_case_clause_guard() {
1510 assert_error!(
1511 r#"
1512let my_string = "hello"
1513case my_string {
1514 _ if length(my_string) > 2 -> io.debug("doesn't work')
1515}"#
1516 );
1517}
1518
1519#[test]
1520fn dot_access_function_call_in_case_clause_guard() {
1521 assert_error!(
1522 r#"
1523let my_string = "hello"
1524case my_string {
1525 _ if string.length(my_string) > 2 -> io.debug("doesn't work')
1526}"#
1527 );
1528}
1529
1530#[test]
1531fn invalid_left_paren_in_case_clause_guard() {
1532 assert_error!(
1533 r#"
1534let my_string = "hello"
1535case my_string {
1536 _ if string.length( > 2 -> io.debug("doesn't work')
1537}"#
1538 );
1539}
1540
1541#[test]
1542fn string_concatenation_in_case_clause_guard() {
1543 assert_parse!(
1544 r#"
1545let my_string = "hello "
1546case my_string {
1547 _ if my_string <> "world" == "hello world" -> io.debug("ok")
1548}"#
1549 );
1550}
1551
1552#[test]
1553fn constant_record_with_empty_arguments_is_record() {
1554 assert_parse_module!("pub const wibble = Wibble()");
1555}
1556
1557#[test]
1558fn calling_module_constant_as_constructor() {
1559 assert_module_error!(
1560 "
1561pub type Wibble { Wibble(Int) }
1562pub const wibble = Wibble
1563pub const a = wibble(1)
1564"
1565 );
1566}
1567
1568#[test]
1569fn invalid_label_shorthand() {
1570 assert_module_error!(
1571 "
1572pub fn main() {
1573 wibble(:)
1574}
1575"
1576 );
1577}
1578
1579#[test]
1580fn invalid_label_shorthand_2() {
1581 assert_module_error!(
1582 "
1583pub fn main() {
1584 wibble(:,)
1585}
1586"
1587 );
1588}
1589
1590#[test]
1591fn invalid_label_shorthand_3() {
1592 assert_module_error!(
1593 "
1594pub fn main() {
1595 wibble(:arg)
1596}
1597"
1598 );
1599}
1600
1601#[test]
1602fn invalid_label_shorthand_4() {
1603 assert_module_error!(
1604 "
1605pub fn main() {
1606 wibble(arg::)
1607}
1608"
1609 );
1610}
1611
1612#[test]
1613fn invalid_label_shorthand_5() {
1614 assert_module_error!(
1615 "
1616pub fn main() {
1617 wibble(arg::arg)
1618}
1619"
1620 );
1621}
1622
1623#[test]
1624fn invalid_pattern_label_shorthand() {
1625 assert_module_error!(
1626 "
1627pub fn main() {
1628 let Wibble(:) = todo
1629}
1630"
1631 );
1632}
1633
1634#[test]
1635fn invalid_pattern_label_shorthand_2() {
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_3() {
1647 assert_module_error!(
1648 "
1649pub fn main() {
1650 let Wibble(arg::) = todo
1651}
1652"
1653 );
1654}
1655
1656#[test]
1657fn invalid_pattern_label_shorthand_4() {
1658 assert_module_error!(
1659 "
1660pub fn main() {
1661 let Wibble(arg: arg:) = todo
1662}
1663"
1664 );
1665}
1666
1667#[test]
1668fn invalid_pattern_label_shorthand_5() {
1669 assert_module_error!(
1670 "
1671pub fn main() {
1672 let Wibble(arg1: arg2:) = todo
1673}
1674"
1675 );
1676}
1677
1678fn first_parsed_docstring(src: &str) -> EcoString {
1679 let parsed =
1680 crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null())
1681 .expect("should parse");
1682
1683 parsed
1684 .module
1685 .definitions
1686 .first()
1687 .expect("parsed a definition")
1688 .definition
1689 .get_doc()
1690 .expect("definition without doc")
1691}
1692
1693#[test]
1694fn doc_comment_before_comment_is_not_attached_to_following_function() {
1695 assert_eq!(
1696 first_parsed_docstring(
1697 r#"
1698 /// Not included!
1699 // pub fn call()
1700
1701 /// Doc!
1702 pub fn wibble() {}
1703"#
1704 ),
1705 " Doc!\n"
1706 )
1707}
1708
1709#[test]
1710fn doc_comment_before_comment_is_not_attached_to_following_type() {
1711 assert_eq!(
1712 first_parsed_docstring(
1713 r#"
1714 /// Not included!
1715 // pub fn call()
1716
1717 /// Doc!
1718 pub type Wibble
1719"#
1720 ),
1721 " Doc!\n"
1722 )
1723}
1724
1725#[test]
1726fn doc_comment_before_comment_is_not_attached_to_following_type_alias() {
1727 assert_eq!(
1728 first_parsed_docstring(
1729 r#"
1730 /// Not included!
1731 // pub fn call()
1732
1733 /// Doc!
1734 pub type Wibble = Int
1735"#
1736 ),
1737 " Doc!\n"
1738 )
1739}
1740
1741#[test]
1742fn doc_comment_before_comment_is_not_attached_to_following_constant() {
1743 assert_eq!(
1744 first_parsed_docstring(
1745 r#"
1746 /// Not included!
1747 // pub fn call()
1748
1749 /// Doc!
1750 pub const wibble = 1
1751"#
1752 ),
1753 " Doc!\n"
1754 );
1755}
1756
1757#[test]
1758fn non_module_level_function_with_a_name() {
1759 assert_module_error!(
1760 r#"
1761pub fn main() {
1762 fn my() { 1 }
1763}
1764"#
1765 );
1766}
1767
1768#[test]
1769fn error_message_on_variable_starting_with_underscore() {
1770 // https://github.com/gleam-lang/gleam/issues/3504
1771 assert_module_error!(
1772 "
1773 pub fn main() {
1774 let val = _func_starting_with_underscore(1)
1775 }"
1776 );
1777}
1778
1779#[test]
1780fn non_module_level_function_with_not_a_name() {
1781 assert_module_error!(
1782 r#"
1783pub fn main() {
1784 fn @() { 1 } // wrong token and not a name
1785}
1786"#
1787 );
1788}
1789
1790#[test]
1791fn error_message_on_variable_starting_with_underscore2() {
1792 // https://github.com/gleam-lang/gleam/issues/3504
1793 assert_module_error!(
1794 "
1795 pub fn main() {
1796 case 1 {
1797 1 -> _with_underscore(1)
1798 }
1799 }"
1800 );
1801}
1802
1803#[test]
1804fn function_inside_a_type() {
1805 assert_module_error!(
1806 r#"
1807type Wibble {
1808 fn wobble() {}
1809}
1810"#
1811 );
1812}
1813
1814#[test]
1815fn pub_function_inside_a_type() {
1816 assert_module_error!(
1817 r#"
1818type Wibble {
1819 pub fn wobble() {}
1820}
1821"#
1822 );
1823}
1824
1825#[test]
1826fn if_like_expression() {
1827 assert_module_error!(
1828 r#"
1829pub fn main() {
1830 let a = if wibble {
1831 wobble
1832 }
1833}
1834"#
1835 );
1836}
1837
1838// https://github.com/gleam-lang/gleam/issues/3796
1839#[test]
1840fn missing_type_constructor_arguments_in_type_definition() {
1841 assert_module_error!(
1842 "
1843pub type A() {
1844 A(Int)
1845}
1846"
1847 );
1848}
1849
1850#[test]
1851fn tuple_without_hash() {
1852 assert_module_error!(
1853 r#"
1854pub fn main() {
1855 let triple = (1, 2.2, "three")
1856 io.debug(triple)
1857 let (a, *, *) = triple
1858 io.debug(a)
1859 io.debug(triple.1)
1860}
1861"#
1862 );
1863}
1864
1865#[test]
1866fn deprecation_attribute_on_type_variant() {
1867 assert_parse_module!(
1868 r#"
1869type Wibble {
1870 @deprecated("1")
1871 Wibble1
1872 Wibble2
1873}
1874"#
1875 );
1876}
1877
1878#[test]
1879
1880fn float_empty_exponent() {
1881 assert_error!("1.32e");
1882}
1883
1884#[test]
1885fn multiple_deprecation_attribute_on_type_variant() {
1886 assert_module_error!(
1887 r#"
1888type Wibble {
1889 @deprecated("1")
1890 @deprecated("2")
1891 Wibble1
1892 Wibble2
1893}
1894"#
1895 );
1896}
1897
1898#[test]
1899fn target_attribute_on_type_variant() {
1900 assert_module_error!(
1901 r#"
1902type Wibble {
1903 @target(erlang)
1904 Wibble2
1905}
1906"#
1907 );
1908}
1909
1910#[test]
1911fn internal_attribute_on_type_variant() {
1912 assert_module_error!(
1913 r#"
1914type Wibble {
1915 @internal
1916 Wibble1
1917}
1918"#
1919 );
1920}
1921
1922#[test]
1923fn external_attribute_on_type_variant() {
1924 assert_module_error!(
1925 r#"
1926type Wibble {
1927 @external(erlang, "one", "two")
1928 Wibble1
1929}
1930"#
1931 );
1932}
1933
1934#[test]
1935fn multiple_unsupported_attributes_on_type_variant() {
1936 assert_module_error!(
1937 r#"
1938type Wibble {
1939 @external(erlang, "one", "two")
1940 @target(erlang)
1941 @internal
1942 Wibble1
1943}
1944"#
1945 );
1946}
1947
1948#[test]
1949// https://github.com/gleam-lang/gleam/issues/3870
1950fn nested_tuple_access_after_function() {
1951 assert_parse!("tuple().0.1");
1952}
1953
1954#[test]
1955fn case_expression_without_body() {
1956 assert_parse!("case a");
1957}
1958
1959#[test]
1960fn assert_statement() {
1961 assert_parse!("assert 10 != 11");
1962}
1963
1964#[test]
1965fn assert_statement_with_message() {
1966 assert_parse!(r#"assert False as "Uh oh""#);
1967}
1968
1969#[test]
1970fn assert_statement_without_expression() {
1971 assert_error!("assert");
1972}
1973
1974#[test]
1975fn assert_statement_followed_by_statement() {
1976 assert_error!("assert let a = 10");
1977}
1978
1979#[test]
1980fn special_error_for_pythonic_import() {
1981 assert_module_error!("import gleam.io");
1982}
1983
1984#[test]
1985fn special_error_for_pythonic_neste_import() {
1986 assert_module_error!("import one.two.three");
1987}
1988
1989#[test]
1990fn doesnt_issue_special_error_for_pythonic_import_if_slash() {
1991 assert_module_error!("import one/two.three");
1992}
1993
1994#[test]
1995fn operator_in_pattern_size() {
1996 assert_parse!("let assert <<size, payload:size(size - 1)>> = <<>>");
1997}
1998
1999#[test]
2000fn correct_precedence_in_pattern_size() {
2001 assert_parse!("let assert <<size, payload:size(size + 2 * 8)>> = <<>>");
2002}
2003
2004#[test]
2005fn private_opaque_type_is_parsed() {
2006 assert_parse_module!("opaque type Wibble { Wobble }");
2007}
2008
2009#[test]
2010fn case_guard_with_nested_blocks() {
2011 assert_parse!(
2012 "case 1 {
2013 _ if { 1 || { 1 || 1 } } || 1 -> 1
2014}"
2015 );
2016}
2017
2018#[test]
2019fn case_guard_with_empty_block() {
2020 assert_error!(
2021 "case 1 {
2022 _ if a || {} -> 1
2023}"
2024 );
2025}
2026
2027#[test]
2028fn constant_inside_function() {
2029 assert_module_error!(
2030 "
2031pub fn main() {
2032 const x = 10
2033 x
2034}
2035"
2036 );
2037}
2038
2039#[test]
2040fn function_definition_angle_generics_error() {
2041 assert_module_error!("fn id<T>(x: T) { x }");
2042}
2043
2044#[test]
2045fn type_angle_generics_usage_without_module_error() {
2046 assert_error!("let list: List<Int, String> = []");
2047}
2048
2049#[test]
2050fn type_angle_generics_usage_with_module_error() {
2051 assert_error!("let set: set.Set<Int> = set.new()");
2052}
2053
2054#[test]
2055fn type_angle_generics_definition_error() {
2056 assert_module_error!(
2057 r#"
2058type Either<a, b> {
2059 This(a)
2060 That(b)
2061}
2062"#
2063 );
2064}
2065
2066#[test]
2067fn type_angle_generics_definition_with_upname_error() {
2068 assert_module_error!(
2069 r#"
2070type Either<A, B> {
2071 This(A)
2072 That(B)
2073}
2074"#
2075 );
2076}
2077
2078#[test]
2079fn type_angle_generics_definition_error_fallback() {
2080 // Example of a more incorrect syntax, where Gleam doesn't make a suggestion.
2081 // In this case, an example type argument is used instead.
2082 assert_module_error!(
2083 r#"
2084type Either<type A, type B> {
2085 This(A)
2086 That(B)
2087}
2088"#
2089 );
2090}
2091
2092#[test]
2093fn wrong_type_of_comments_with_hash() {
2094 assert_module_error!(
2095 r#"
2096pub fn main() {
2097 # a python-style comment
2098}
2099"#
2100 );
2101}
2102
2103#[test]
2104fn wrong_function_return_type_declaration_using_colon_instead_of_right_arrow() {
2105 assert_module_error!(
2106 r#"
2107pub fn main(): Nil {}
2108 "#
2109 );
2110}
2111
2112#[test]
2113fn const_record_update_basic() {
2114 assert_parse_module!(
2115 r#"
2116type Person {
2117 Person(name: String, age: Int)
2118}
2119
2120const alice = Person("Alice", 30)
2121const bob = Person(..alice, name: "Bob")
2122"#
2123 );
2124}
2125
2126#[test]
2127fn const_record_update_all_fields() {
2128 assert_parse_module!(
2129 r#"
2130type Person {
2131 Person(name: String, age: Int, city: String)
2132}
2133
2134const base = Person("Alice", 30, "London")
2135const updated = Person(..base, name: "Bob", age: 25, city: "Paris")
2136"#
2137 );
2138}
2139
2140#[test]
2141fn const_record_update_only() {
2142 assert_parse_module!(
2143 r#"
2144type Person {
2145 Person(name: String, age: Int)
2146}
2147
2148const alice = Person("Alice", 30)
2149const bob = Person(..alice)
2150"#
2151 );
2152}
2153
2154#[test]
2155fn const_record_update_with_module() {
2156 assert_parse_module!(
2157 r#"
2158const local_const = other.Record(..other.base, field: value)
2159"#
2160 );
2161}
2162
2163// https://github.com/gleam-lang/gleam/issues/5391
2164#[test]
2165fn byte_order_mark() {
2166 assert_parse!("\u{feff}todo");
2167}
2168
2169// https://github.com/gleam-lang/gleam/issues/5391
2170#[test]
2171fn byte_order_mark_module() {
2172 assert_parse_module!(
2173 "\u{feff}
2174const local_const = other.Record(..other.base, field: value)
2175"
2176 );
2177}
2178
2179#[test]
2180fn prepend_to_const_list_without_comma() {
2181 // While this is valid (but deprecated) syntax for expressions, prepending to
2182 // constant lists wasn't added until after the deprecation, so it was never
2183 // valid in the first place.
2184 assert_module_error!(
2185 "
2186const wibble = [2, 3]
2187const wobble = [1 ..wibble]
2188"
2189 );
2190}
2191
2192#[test]
2193fn prepend_to_const_list_with_multiple_spreads() {
2194 assert_module_error!(
2195 "
2196const wibble = [2, 3]
2197const wobble = [0, 1]
2198const wubble = [..wobble, ..wibble]
2199"
2200 );
2201}
2202
2203#[test]
2204fn prepend_to_const_list_with_no_tail() {
2205 assert_module_error!(
2206 "
2207const wibble = [1, 2, ..]
2208"
2209 );
2210}
2211
2212#[test]
2213fn prepend_no_elements_to_const_list() {
2214 assert_module_error!(
2215 "
2216const wibble = [2, 3]
2217const wobble = [..wibble]
2218"
2219 );
2220}
2221
2222#[test]
2223fn parse_negative_binary_number() {
2224 assert_parse_module!("pub fn main() { -0b101 }");
2225}
2226
2227#[test]
2228fn parse_negative_octal_number() {
2229 assert_parse_module!("pub fn main() { -0o157 }");
2230}
2231
2232#[test]
2233fn parse_negative_hexadecimal_number() {
2234 assert_parse_module!("pub fn main() { -0x6f }");
2235}
2236
2237#[test]
2238fn append_to_const_list() {
2239 assert_module_error!(
2240 "
2241const wibble = [2, 3]
2242const wobble = [..wibble, 4, 5]
2243"
2244 );
2245}
2246
2247#[test]
2248fn parse_error_for_greater_sign_after_invalid_qualified_type() {
2249 assert_module_error!("pub fn main() -> wibble.<a> { todo }");
2250}
2251
2252#[test]
2253fn parse_error_for_type_list_after_invalid_qualified_type_1() {
2254 assert_module_error!("pub fn main() -> wibble.() { todo }");
2255}
2256
2257#[test]
2258fn parse_error_for_type_list_after_invalid_qualified_type_2() {
2259 assert_module_error!("pub fn main() -> wibble.(a, b) { todo }");
2260}
2261
2262#[test]
2263fn unicode_left_double_quotation_mark() {
2264 assert_module_error!("pub fn main() { \u{201C}hello\u{201D} }");
2265}
2266
2267#[test]
2268fn unicode_right_double_quotation_mark() {
2269 assert_module_error!("pub fn main() { \u{201D}hello }");
2270}
2271
2272#[test]
2273fn unicode_left_single_quotation_mark() {
2274 assert_module_error!("pub fn main() { \u{2018}hello\u{2019} }");
2275}
2276
2277#[test]
2278fn unicode_right_single_quotation_mark() {
2279 assert_module_error!("pub fn main() { \u{2019}hello }");
2280}
2281
2282#[test]
2283fn unicode_en_dash() {
2284 assert_module_error!("pub fn main() { 1\u{2013}2 }");
2285}
2286
2287#[test]
2288fn unicode_em_dash() {
2289 assert_module_error!("pub fn main() { 1\u{2014}2 }");
2290}
2291
2292#[test]
2293fn unicode_asterisk_operator() {
2294 assert_module_error!("pub fn main() { 1\u{2217}2 }");
2295}
2296
2297#[test]
2298fn unicode_division_slash() {
2299 assert_module_error!("pub fn main() { 1\u{2215}2 }");
2300}
2301
2302#[test]
2303fn unicode_non_breaking_space() {
2304 assert_module_error!("pub fn main() { let\u{00A0}x = 1\n x }");
2305}
2306
2307#[test]
2308fn unicode_zero_width_space() {
2309 assert_module_error!("pub fn main() { let\u{200B}x = 1\n x }");
2310}
2311
2312#[test]
2313fn unicode_cyrillic_a() {
2314 assert_module_error!("pub fn main() { \u{0430} }");
2315}
2316
2317#[test]
2318fn unicode_cyrillic_e() {
2319 assert_module_error!("pub fn main() { \u{0435} }");
2320}
2321
2322#[test]
2323fn unicode_cyrillic_o() {
2324 assert_module_error!("pub fn main() { \u{043E} }");
2325}
2326
2327#[test]
2328fn unicode_cyrillic_p() {
2329 assert_module_error!("pub fn main() { \u{0440} }");
2330}
2331
2332#[test]
2333fn unicode_modifier_letter_capital_i() {
2334 assert_module_error!("pub fn main() { \u{1D35} }");
2335}
2336
2337#[test]
2338fn unicode_low_single_comma_quotation_mark() {
2339 assert_module_error!("pub fn main() { #(1\u{201A} 2) }");
2340}
2341
2342#[test]
2343fn unicode_fullwidth_comma() {
2344 assert_module_error!("pub fn main() { #(1\u{FF0C} 2) }");
2345}
2346
2347#[test]
2348fn unicode_ideographic_comma() {
2349 assert_module_error!("pub fn main() { #(1\u{3001} 2) }");
2350}
2351
2352#[test]
2353fn unicode_fullwidth_exclamation_mark() {
2354 assert_module_error!("pub fn main() { \u{FF01}= True }");
2355}
2356
2357#[test]
2358fn unicode_fullwidth_left_square_bracket() {
2359 assert_module_error!("pub fn main() { \u{FF3B}1, 2] }");
2360}
2361
2362#[test]
2363fn unicode_fullwidth_right_square_bracket() {
2364 assert_module_error!("pub fn main() { [1, 2\u{FF3D} }");
2365}
2366
2367#[test]
2368fn unicode_fullwidth_left_parenthesis() {
2369 assert_module_error!("pub fn main\u{FF08}) { Nil }");
2370}
2371
2372#[test]
2373fn unicode_fullwidth_right_parenthesis() {
2374 assert_module_error!("pub fn main(\u{FF09} { Nil }");
2375}
2376
2377#[test]
2378fn unicode_fullwidth_full_stop() {
2379 assert_module_error!("pub fn main() { io\u{FF0E}println(\"hi\") }");
2380}
2381
2382#[test]
2383fn unicode_ideographic_full_stop() {
2384 assert_module_error!("pub fn main() { io\u{3002}println(\"hi\") }");
2385}
2386
2387#[test]
2388fn unicode_fullwidth_less_than_sign() {
2389 assert_module_error!("pub fn main() { 1 \u{FF1C} 2 }");
2390}
2391
2392#[test]
2393fn unicode_fullwidth_greater_than_sign() {
2394 assert_module_error!("pub fn main() { 1 \u{FF1E} 2 }");
2395}
2396
2397#[test]
2398fn unicode_fullwidth_vertical_line() {
2399 assert_module_error!("pub fn main() { True \u{FF5C}\u{FF5C} False }");
2400}
2401
2402#[test]
2403fn unicode_fullwidth_at_sign() {
2404 assert_module_error!("\u{FF20}deprecated(\"old\")\npub fn main() { Nil }");
2405}
2406
2407#[test]
2408fn unicode_fullwidth_caret() {
2409 assert_module_error!("pub fn main() { 0b1 \u{FF3E} 0b1 }");
2410}
2411
2412#[test]
2413fn unicode_fullwidth_colon() {
2414 assert_module_error!("pub fn wibble(x\u{FF1A} Int) { x }");
2415}
2416
2417#[test]
2418fn missing_todo_constant_message() {
2419 assert_module_error!("pub const wibble = todo as");
2420}
2421
2422#[test]
2423fn missing_todo_constant_message_2() {
2424 assert_module_error!(
2425 "pub const wibble = todo as
2426pub fn wibble() {}"
2427 );
2428}
2429
2430// https://github.com/gleam-lang/gleam/issues/5711
2431#[test]
2432fn parsing_bit_array_constant_with_non_integer_size() {
2433 assert_module_error!("pub const wibble = <<1:size(something)>>");
2434}
2435
2436#[test]
2437fn parsing_bit_array_constant_with_non_integer_unit() {
2438 assert_module_error!("pub const wibble = <<1:unit(something)>>");
2439}
2440
2441#[test]
2442fn parsing_bit_array_expression_with_invalid_size() {
2443 assert_module_error!(
2444 "pub fn wibble() {
2445 let assert <<1:size(Upname)>> = todo
2446}"
2447 );
2448}
2449
2450#[test]
2451fn parsing_bit_array_expression_with_invalid_unit() {
2452 assert_module_error!(
2453 "pub fn wibble() {
2454 <<1:unit(Upname)>>
2455}"
2456 );
2457}
2458
2459#[test]
2460fn parsing_bit_array_pattern_with_invalid_unit() {
2461 assert_module_error!(
2462 "pub fn wibble() {
2463 let assert <<1:unit(Upname)>> = todo
2464}"
2465 );
2466}
2467
2468#[test]
2469fn parsing_bit_array_constant_with_invalid_segment_type() {
2470 assert_module_error!("pub const wibble = <<1:Upname>>");
2471}
2472
2473#[test]
2474fn parsing_invalid_external_module_name() {
2475 assert_module_error!(
2476 "
2477@external(erlang, Upname, [])
2478fn wibble() -> Nil
2479"
2480 );
2481}
2482
2483#[test]
2484fn parsing_invalid_external_function_name() {
2485 assert_module_error!(
2486 r#"
2487@external(erlang, "module", Upname)
2488fn wibble() -> Nil
2489"#
2490 );
2491}
2492
2493#[test]
2494fn parsing_invalid_deprecation_message() {
2495 assert_module_error!(
2496 r#"
2497@deprecated(Upname)
2498fn wibble() -> Nil
2499"#
2500 );
2501}
2502
2503#[test]
2504fn error_message_when_using_discard_pattern_for_function_name() {
2505 assert_module_error!("fn _wibble() -> Nil");
2506}
2507
2508#[test]
2509fn error_message_when_using_discard_pattern_for_constant_name() {
2510 assert_module_error!("const _wibble = 1");
2511}
2512
2513#[test]
2514fn error_message_when_using_discard_pattern_for_module_name() {
2515 assert_module_error!("import _wibble");
2516}
2517
2518#[test]
2519fn error_message_when_using_discard_pattern_for_as_pattern() {
2520 assert_module_error!(
2521 "pub fn main() {
2522 case todo {
2523 wibble as _wobble -> todo
2524 }
2525}"
2526 );
2527}
2528
2529#[test]
2530fn missing_constructor_name_with_multiple_fields() {
2531 assert_module_error!(
2532 r#"
2533pub type Wibble(a) {
2534 field: Int,
2535 other: a
2536}
2537"#
2538 );
2539}
2540
2541#[test]
2542fn lowercase_constructor_name_in_custom_type() {
2543 assert_module_error!(
2544 r#"
2545pub type Wibble {
2546 wibble(Int, String)
2547}
2548"#
2549 );
2550}