Fork of daniellemaywood.uk/gleam — Wasm codegen work
37 kB
901 lines
1use crate::ast::{SrcSpan, TypeAst};
2use crate::diagnostic::{ExtraLabel, Label};
3use crate::error::{wrap, wrap_format};
4use crate::parse::Token;
5use ecow::EcoString;
6use itertools::Itertools;
7
8#[derive(Debug, PartialEq, Eq, Clone, Copy)]
9pub struct LexicalError {
10 pub error: LexicalErrorType,
11 pub location: SrcSpan,
12}
13
14#[derive(Debug, PartialEq, Eq, Clone, Copy)]
15pub enum InvalidUnicodeEscapeError {
16 /// Expected '{'
17 MissingOpeningBrace,
18 /// Expected hex digit or '}'
19 ExpectedHexDigitOrCloseBrace,
20 /// Expected between 1 and 6 hex digits
21 InvalidNumberOfHexDigits,
22 /// Invalid Unicode codepoint
23 InvalidCodepoint,
24}
25
26#[derive(Debug, PartialEq, Eq, Clone, Copy)]
27pub enum LexicalErrorType {
28 // String contains an unescaped slash
29 BadStringEscape,
30 // \u{...} escape sequence is invalid
31 InvalidUnicodeEscape(InvalidUnicodeEscapeError),
32 // 0x012 , 2 is out of radix
33 DigitOutOfRadix,
34 // 1_000_ is not allowed
35 NumTrailingUnderscore,
36 // 0x, 0b, 0o without a value
37 RadixIntNoValue,
38 // // 1.0e, for example, where there is no exponent
39 MissingExponent,
40 // Unterminated string literal
41 UnexpectedStringEnd,
42 UnrecognizedToken {
43 tok: char,
44 },
45 InvalidTripleEqual,
46 /// A character was encountered that visually looks like a correct
47 /// character, but in reality it's some other unicode characters.
48 /// For example, a non-breaking-space instead of a regular space.
49 VisuallySimilarInvalidCharacter {
50 name: &'static str,
51 correct: &'static str,
52 },
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct ParseError {
57 pub error: ParseErrorType,
58 pub location: SrcSpan,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum ParseErrorType {
63 ExpectedEqual, // expect "="
64 ExpectedExpr, // after "->" in a case clause
65 ExpectedName, // any token used when a Name was expected
66 ExpectedPattern, // after ':' where a pattern is expected
67 ExpectedType, // after ':' or '->' where a type annotation is expected
68 ExpectedUpName, // any token used when a UpName was expected
69 ExpectedValue, // no value after "="
70 ExpectedDefinition, // after attributes
71 ExpectedDeprecationMessage, // after "deprecated"
72 ExpectedFunctionDefinition, // after function-only attributes
73 ExpectedTargetName, // after "@target("
74 ExprLparStart, // it seems "(" was used to start an expression
75 ExtraSeparator, // #(1,,) <- the 2nd comma is an extra separator
76 IncorrectName, // UpName or DiscardName used when Name was expected
77 IncorrectUpName, // Name or DiscardName used when UpName was expected
78 InvalidBitArraySegment, // <<7:hello>> `hello` is an invalid BitArray segment
79 InvalidBitArrayUnit, // in <<1:unit(x)>> x must be 1 <= x <= 256
80 InvalidTailPattern, // only name and _name are allowed after ".." in list pattern
81 InvalidTupleAccess, // only positive int literals for tuple access
82 LexError {
83 error: LexicalError,
84 },
85 NestedBitArrayPattern, // <<<<1>>, 2>>, <<1>> is not allowed in there
86 NoLetBinding, // Bindings and rebinds always require let and must always bind to a value.
87 NoValueAfterEqual, // = <something other than a value>
88 NotConstType, // :fn(), name, _ are not valid const types
89 OpNakedRight, // Operator with no value to the right
90 OpaqueTypeAlias, // Type aliases cannot be opaque
91 TooManyArgHoles, // a function call can have at most 1 arg hole
92 DuplicateAttribute, // an attribute was used more than once
93 UnknownAttribute, // an attribute was used that is not known
94 UnknownTarget, // an unknown target was used
95 ListSpreadWithoutElements, // Pointless spread: `[..xs]`
96 ListSpreadFollowedByElements, // trying to append something after the spread: `[..xs, x]`
97 ListSpreadWithAnotherSpread {
98 first_spread_location: SrcSpan,
99 }, // trying to use multiple spreads: `[..xs, ..ys]`
100 UnexpectedLabel, // argument labels were provided, but are not supported in this context
101 UnexpectedEof,
102 UnexpectedReservedWord, // reserved word used when a name was expected
103 UnexpectedToken {
104 token: Token,
105 expected: Vec<EcoString>,
106 hint: Option<EcoString>,
107 },
108 UnexpectedFunction, // a function was used called outside of another function
109 /// A variable was assigned or discarded on the left hand side of a <> pattern
110 ConcatPatternVariableLeftHandSide,
111 /// A variable was assigned as infix between prefix and suffix string
112 /// patterns using <>
113 ConcatPatternVariableWithSuffix {
114 name: EcoString,
115 },
116 ListSpreadWithoutTail, // let x = [1, ..]
117 ExpectedFunctionBody, // let x = fn()
118 RedundantInternalAttribute, // for a private definition marked as internal
119 InvalidModuleTypePattern, // for patterns that have a dot like: `name.thing`
120 ListPatternSpreadFollowedByElements, // When there is a pattern after a spread [..rest, pattern]
121 ExpectedRecordConstructor {
122 name: EcoString,
123 public: bool,
124 opaque: bool,
125 field: EcoString,
126 field_type: Option<Box<TypeAst>>,
127 },
128 CallInClauseGuard, // case x { _ if f() -> 1 }
129 IfExpression,
130 TypeDefinitionNoArguments, // pub type Wibble() { ... }
131 UnknownAttributeRecordVariant, // an attribute was used that is not know for a custom type variant
132 // a Python-like import was written, such as `import gleam.io`, instead of `import gleam/io`
133 IncorrectImportModuleSeparator {
134 module: EcoString,
135 item: EcoString,
136 },
137 /// This can happen when there's an empty block in a case clause guard.
138 /// For example: `_ if a == {}`
139 EmptyGuardBlock,
140 // When the use tries to define a constant inside a function
141 ConstantInsideFunction,
142 FunctionDefinitionAngleGenerics, // fn something<T>() { ... }
143 // let a: List<String> = []
144 TypeUsageAngleGenerics {
145 module: Option<EcoString>,
146 name: EcoString,
147 arguments: Vec<TypeAst>,
148 },
149 // type Something<T> {
150 TypeDefinitionAngleGenerics {
151 name: EcoString,
152 arguments: Vec<EcoString>,
153 },
154 // `const x = todo as` with no message after the `as`.
155 MissingConstantAsMessage,
156}
157
158pub(crate) struct ParseErrorDetails {
159 pub text: String,
160 pub label_text: EcoString,
161 pub extra_labels: Vec<ExtraLabel>,
162 pub hint: Option<String>,
163}
164
165impl ParseErrorType {
166 pub(crate) fn details(&self) -> ParseErrorDetails {
167 match self {
168 ParseErrorType::ExpectedEqual => ParseErrorDetails {
169 text: "".into(),
170 hint: None,
171 label_text: "I was expecting a '=' after this".into(),
172 extra_labels: vec![],
173 },
174
175 ParseErrorType::ExpectedExpr => ParseErrorDetails {
176 text: "".into(),
177 hint: None,
178 label_text: "I was expecting an expression after this".into(),
179 extra_labels: vec![],
180 },
181
182 ParseErrorType::ExpectedName => ParseErrorDetails {
183 text: "".into(),
184 hint: None,
185 label_text: "I was expecting a name here".into(),
186 extra_labels: vec![],
187 },
188
189 ParseErrorType::ExpectedPattern => ParseErrorDetails {
190 text: "".into(),
191 hint: None,
192 label_text: "I was expecting a pattern after this".into(),
193 extra_labels: vec![],
194 },
195
196 ParseErrorType::ExpectedType => ParseErrorDetails {
197 text: "See: https://tour.gleam.run/basics/assignments/".into(),
198 hint: None,
199 label_text: "I was expecting a type after this".into(),
200 extra_labels: vec![],
201 },
202
203 ParseErrorType::ExpectedUpName => ParseErrorDetails {
204 text: "".into(),
205 hint: None,
206 label_text: "I was expecting a type name here".into(),
207 extra_labels: vec![],
208 },
209
210 ParseErrorType::ExpectedValue => ParseErrorDetails {
211 text: "".into(),
212 hint: None,
213 label_text: "I was expecting a value after this".into(),
214 extra_labels: vec![],
215 },
216
217 ParseErrorType::ExpectedDefinition => ParseErrorDetails {
218 text: "".into(),
219 hint: None,
220 label_text: "I was expecting a definition after this".into(),
221 extra_labels: vec![],
222 },
223
224 ParseErrorType::ExpectedDeprecationMessage => ParseErrorDetails {
225 text: "See: https://tour.gleam.run/functions/deprecations/".into(),
226 hint: None,
227 label_text: "A deprecation attribute must have a string message.".into(),
228 extra_labels: vec![],
229 },
230
231 ParseErrorType::ExpectedFunctionDefinition => ParseErrorDetails {
232 text: "".into(),
233 hint: None,
234 label_text: "I was expecting a function definition after this".into(),
235 extra_labels: vec![],
236 },
237
238 ParseErrorType::ExpectedTargetName => ParseErrorDetails {
239 text: "Try `erlang`, `javascript`.".into(),
240 hint: None,
241 label_text: "I was expecting a target name after this".into(),
242 extra_labels: vec![],
243 },
244
245 ParseErrorType::ExtraSeparator => ParseErrorDetails {
246 text: "".into(),
247 hint: Some("Try removing it?".into()),
248 label_text: "This is an extra delimiter".into(),
249 extra_labels: vec![],
250 },
251
252 ParseErrorType::ExprLparStart => ParseErrorDetails {
253 text: "".into(),
254 hint: Some(
255 "To group expressions in Gleam, use \"{\" and \"}\"; \
256tuples are created with `#(` and `)`."
257 .into(),
258 ),
259 label_text: "This parenthesis cannot be understood here".into(),
260 extra_labels: vec![],
261 },
262
263 ParseErrorType::IncorrectName => ParseErrorDetails {
264 text: "".into(),
265 hint: Some(wrap(
266 "Variable and module names start with a lowercase letter, \
267and can contain a-z, 0-9, or _.",
268 )),
269 label_text: "I'm expecting a lowercase name here".into(),
270 extra_labels: vec![],
271 },
272
273 ParseErrorType::IncorrectUpName => ParseErrorDetails {
274 text: "".into(),
275 hint: Some(wrap(
276 "Type names start with a uppercase letter, and can \
277contain a-z, A-Z, or 0-9.",
278 )),
279 label_text: "I'm expecting a type name here".into(),
280 extra_labels: vec![],
281 },
282
283 ParseErrorType::InvalidBitArraySegment => ParseErrorDetails {
284 text: "See: https://tour.gleam.run/data-types/bit-arrays/".into(),
285 hint: Some(format!(
286 "Valid BitArray segment options are:\n{}",
287 wrap(
288 "bits, bytes, int, float, utf8, utf16, utf32, utf8_codepoint, \
289utf16_codepoint, utf32_codepoint, signed, unsigned, big, little, native, size, unit.",
290 )
291 )),
292 label_text: "This is not a valid BitArray segment option".into(),
293 extra_labels: vec![],
294 },
295
296 ParseErrorType::InvalidBitArrayUnit => ParseErrorDetails {
297 text: "See: https://tour.gleam.run/data-types/bit-arrays/".into(),
298 hint: Some("Unit must be an integer literal >= 1 and <= 256.".into()),
299 label_text: "This is not a valid BitArray unit value".into(),
300 extra_labels: vec![],
301 },
302
303 ParseErrorType::InvalidTailPattern => ParseErrorDetails {
304 text: "".into(),
305 hint: None,
306 label_text: "This part of a list pattern can only be a name or a discard".into(),
307 extra_labels: vec![],
308 },
309
310 ParseErrorType::InvalidTupleAccess => ParseErrorDetails {
311 text: "".into(),
312 hint: Some(
313 "Only non negative integer literals like 0, or 1_000 can be used.".into(),
314 ),
315 label_text: "This integer is not valid for tuple access".into(),
316 extra_labels: vec![],
317 },
318
319 ParseErrorType::LexError { error: lex_err } => {
320 let (label_text, text_lines) = lex_err.to_parse_error_info();
321 let text = text_lines.join("\n");
322 ParseErrorDetails {
323 text,
324 hint: None,
325 label_text: label_text.into(),
326 extra_labels: vec![],
327 }
328 }
329
330 ParseErrorType::NestedBitArrayPattern => ParseErrorDetails {
331 text: "".into(),
332 hint: None,
333 label_text: "BitArray patterns cannot be nested".into(),
334 extra_labels: vec![],
335 },
336
337 ParseErrorType::NotConstType => ParseErrorDetails {
338 text: "See: https://tour.gleam.run/basics/constants/".into(),
339 hint: None,
340 label_text: "This type is not allowed in module constants".into(),
341 extra_labels: vec![],
342 },
343
344 ParseErrorType::NoLetBinding => ParseErrorDetails {
345 text: "See: https://tour.gleam.run/basics/assignments/".into(),
346 hint: Some("Use let for binding.".into()),
347 label_text: "There must be a 'let' to bind variable to value".into(),
348 extra_labels: vec![],
349 },
350
351 ParseErrorType::NoValueAfterEqual => ParseErrorDetails {
352 text: "".into(),
353 hint: None,
354 label_text: "I was expecting to see a value after this equals sign".into(),
355 extra_labels: vec![],
356 },
357
358 ParseErrorType::OpaqueTypeAlias => ParseErrorDetails {
359 text: "See: https://tour.gleam.run/basics/type-aliases/".into(),
360 hint: None,
361 label_text: "Type Aliases cannot be opaque".into(),
362 extra_labels: vec![],
363 },
364
365 ParseErrorType::OpNakedRight => ParseErrorDetails {
366 text: "".into(),
367 hint: Some("Remove it or put a value after it.".into()),
368 label_text: "This operator has no value on its right side".into(),
369 extra_labels: vec![],
370 },
371
372 ParseErrorType::TooManyArgHoles => ParseErrorDetails {
373 text: "See: https://tour.gleam.run/functions/functions/".into(),
374 hint: Some("Function calls can have at most one argument hole.".into()),
375 label_text: "There is more than 1 argument hole in this function call".into(),
376 extra_labels: vec![],
377 },
378
379 ParseErrorType::UnexpectedEof => ParseErrorDetails {
380 text: "".into(),
381 hint: None,
382 label_text: "The module ended unexpectedly".into(),
383 extra_labels: vec![],
384 },
385
386 ParseErrorType::ListSpreadWithoutElements => ParseErrorDetails {
387 text: "See: https://tour.gleam.run/basics/lists/".into(),
388 hint: Some("Try prepending some elements [1, 2, ..list].".into()),
389 label_text: "This spread does nothing".into(),
390 extra_labels: vec![],
391 },
392
393 ParseErrorType::ListSpreadWithAnotherSpread {
394 first_spread_location,
395 } => ParseErrorDetails {
396 text: [
397 "Lists are immutable and singly-linked, so to join two or more lists",
398 "all the elements of the lists would need to be copied into a new list.",
399 "This would be slow, so there is no built-in syntax for it.",
400 ]
401 .join("\n"),
402 hint: None,
403 label_text: "I wasn't expecting a second list here".into(),
404 extra_labels: vec![ExtraLabel {
405 src_info: None,
406 label: Label {
407 text: Some("You're using a list here".into()),
408 span: *first_spread_location,
409 },
410 }],
411 },
412
413 ParseErrorType::ListSpreadFollowedByElements => ParseErrorDetails {
414 text: [
415 "Lists are immutable and singly-linked, so to append items to them",
416 "all the elements of a list would need to be copied into a new list.",
417 "This would be slow, so there is no built-in syntax for it.",
418 ]
419 .join("\n"),
420 hint: Some(
421 "Prepend items to the list and then reverse it once you are done.".into(),
422 ),
423 label_text: "I wasn't expecting elements after this".into(),
424 extra_labels: vec![],
425 },
426
427 ParseErrorType::ListPatternSpreadFollowedByElements => ParseErrorDetails {
428 text: [
429 "Lists are immutable and singly-linked, so to match on the end",
430 "of a list would require the whole list to be traversed. This",
431 "would be slow, so there is no built-in syntax for it. Pattern",
432 "match on the start of the list instead.",
433 ]
434 .join("\n"),
435 hint: None,
436 label_text: "I wasn't expecting elements after this".into(),
437 extra_labels: vec![],
438 },
439
440 ParseErrorType::UnexpectedReservedWord => ParseErrorDetails {
441 text: "".into(),
442 hint: Some("I was expecting to see a name here.".into()),
443 label_text: "This is a reserved word".into(),
444 extra_labels: vec![],
445 },
446
447 ParseErrorType::UnexpectedLabel => ParseErrorDetails {
448 text: "Please remove the argument label.".into(),
449 hint: None,
450 label_text: "Argument labels are not allowed for anonymous functions".into(),
451 extra_labels: vec![],
452 },
453
454 ParseErrorType::UnexpectedToken {
455 token,
456 expected,
457 hint,
458 } => {
459 let found = match token {
460 Token::Int { .. } => "an Int".to_string(),
461 Token::Float { .. } => "a Float".to_string(),
462 Token::String { .. } => "a String".to_string(),
463 Token::CommentDoc { .. } => "a comment".to_string(),
464 Token::DiscardName { .. } => "a discard name".to_string(),
465 Token::Name { .. } | Token::UpName { .. } => "a name".to_string(),
466 _ if token.is_reserved_word() => format!("the keyword {token}"),
467 Token::LeftParen
468 | Token::RightParen
469 | Token::LeftSquare
470 | Token::RightSquare
471 | Token::LeftBrace
472 | Token::RightBrace
473 | Token::Plus
474 | Token::Minus
475 | Token::Star
476 | Token::Slash
477 | Token::Less
478 | Token::Greater
479 | Token::LessEqual
480 | Token::GreaterEqual
481 | Token::Percent
482 | Token::PlusDot
483 | Token::MinusDot
484 | Token::StarDot
485 | Token::SlashDot
486 | Token::LessDot
487 | Token::GreaterDot
488 | Token::LessEqualDot
489 | Token::GreaterEqualDot
490 | Token::Concatenate
491 | Token::Colon
492 | Token::Comma
493 | Token::Hash
494 | Token::Bang
495 | Token::Equal
496 | Token::EqualEqual
497 | Token::NotEqual
498 | Token::Vbar
499 | Token::VbarVbar
500 | Token::AmperAmper
501 | Token::LtLt
502 | Token::GtGt
503 | Token::Pipe
504 | Token::Dot
505 | Token::RArrow
506 | Token::LArrow
507 | Token::DotDot
508 | Token::At
509 | Token::EndOfFile
510 | Token::CommentNormal
511 | Token::CommentModule
512 | Token::NewLine
513 | Token::As
514 | Token::Assert
515 | Token::Auto
516 | Token::Case
517 | Token::Const
518 | Token::Delegate
519 | Token::Derive
520 | Token::Echo
521 | Token::Else
522 | Token::Fn
523 | Token::If
524 | Token::Implement
525 | Token::Import
526 | Token::Let
527 | Token::Macro
528 | Token::Opaque
529 | Token::Panic
530 | Token::Pub
531 | Token::Test
532 | Token::Todo
533 | Token::Type
534 | Token::Use => token.to_string(),
535 };
536
537 let mut messages = std::iter::once(format!("Found {found}, expected one of: "))
538 .chain(expected.iter().map(|s| format!("- {s}")));
539
540 ParseErrorDetails {
541 text: messages.join("\n"),
542 hint: hint.as_ref().map(|hint| hint.to_string()),
543 label_text: "I was not expecting this".into(),
544 extra_labels: vec![],
545 }
546 }
547
548 ParseErrorType::ConcatPatternVariableLeftHandSide => ParseErrorDetails {
549 text: [
550 "We can't tell what size this prefix should be so we don't know",
551 "how to handle this pattern.",
552 "",
553 "If you want to match one character consider using `pop_grapheme`",
554 "from the stdlib's `gleam/string` module.",
555 ]
556 .join("\n"),
557 hint: None,
558 label_text: "This must be a string literal".into(),
559 extra_labels: vec![],
560 },
561
562 ParseErrorType::ConcatPatternVariableWithSuffix { name } => ParseErrorDetails {
563 text: [
564 "A string pattern can only match on a literal string prefix.",
565 "",
566 &wrap_format!(
567 "Matching on a literal suffix is not possible, because `{name}` \
568would have an unknown size."
569 ),
570 ]
571 .join("\n"),
572 hint: None,
573 label_text: "This pattern is not allowed".into(),
574 extra_labels: vec![],
575 },
576
577 ParseErrorType::UnexpectedFunction => ParseErrorDetails {
578 text: "".into(),
579 hint: None,
580 label_text: "Functions can only be called within other functions".into(),
581 extra_labels: vec![],
582 },
583
584 ParseErrorType::ListSpreadWithoutTail => ParseErrorDetails {
585 text: "If a list expression has a spread then a tail must also be given.".into(),
586 hint: None,
587 label_text: "I was expecting a value after this spread".into(),
588 extra_labels: vec![],
589 },
590
591 ParseErrorType::UnknownAttribute => ParseErrorDetails {
592 text: "".into(),
593 hint: Some("Try `deprecated`, `external` or `internal` instead.".into()),
594 label_text: "I don't recognise this attribute".into(),
595 extra_labels: vec![],
596 },
597
598 ParseErrorType::DuplicateAttribute => ParseErrorDetails {
599 text: "This attribute has already been given.".into(),
600 hint: None,
601 label_text: "Duplicate attribute".into(),
602 extra_labels: vec![],
603 },
604
605 ParseErrorType::UnknownTarget => ParseErrorDetails {
606 text: "Try `erlang`, `javascript`.".into(),
607 hint: None,
608 label_text: "I don't recognise this target".into(),
609 extra_labels: vec![],
610 },
611
612 ParseErrorType::ExpectedFunctionBody => ParseErrorDetails {
613 text: "".into(),
614 hint: None,
615 label_text: "This function does not have a body".into(),
616 extra_labels: vec![],
617 },
618
619 ParseErrorType::RedundantInternalAttribute => ParseErrorDetails {
620 text: "Only a public definition can be annotated as internal.".into(),
621 hint: Some("Remove the `@internal` annotation.".into()),
622 label_text: "Redundant internal attribute".into(),
623 extra_labels: vec![],
624 },
625
626 ParseErrorType::InvalidModuleTypePattern => ParseErrorDetails {
627 text: [
628 "I'm expecting a pattern here,",
629 "or a variable to bind a value to, etc.",
630 ]
631 .join("\n"),
632 hint: Some(
633 "A pattern can be a constructor name, a literal value
634See: https://tour.gleam.run/flow-control/case-expressions/"
635 .into(),
636 ),
637 label_text: "Invalid pattern".into(),
638 extra_labels: vec![],
639 },
640
641 ParseErrorType::ExpectedRecordConstructor {
642 name,
643 public,
644 opaque,
645 field,
646 field_type,
647 } => {
648 let (accessor, opaque) = match *public {
649 true if *opaque => ("pub ", "opaque "),
650 true => ("pub ", ""),
651 false => ("", ""),
652 };
653
654 let mut annotation = EcoString::new();
655 match field_type {
656 Some(t) => t.print(&mut annotation),
657 None => annotation.push_str("Type"),
658 };
659
660 ParseErrorDetails {
661 text: [
662 "Each custom type variant must have a constructor:\n".into(),
663 format!("{accessor}{opaque}type {name} {{"),
664 format!(" {name}("),
665 format!(" {field}: {annotation},"),
666 " )".into(),
667 "}".into(),
668 ]
669 .join("\n"),
670 hint: None,
671 label_text: "I was not expecting this".into(),
672 extra_labels: vec![],
673 }
674 }
675
676 ParseErrorType::CallInClauseGuard => ParseErrorDetails {
677 text: "Functions cannot be called in clause guards.".into(),
678 hint: None,
679 label_text: "Unsupported expression".into(),
680 extra_labels: vec![],
681 },
682
683 ParseErrorType::IfExpression => ParseErrorDetails {
684 text: [
685 "If you want to write a conditional expression you can use a `case`:",
686 "",
687 " case condition {",
688 " True -> todo",
689 " False -> todo",
690 " }",
691 "",
692 "See: https://tour.gleam.run/flow-control/case-expressions/",
693 ]
694 .join("\n"),
695 hint: None,
696 label_text: "Gleam doesn't have if expressions".into(),
697 extra_labels: vec![],
698 },
699
700 ParseErrorType::TypeDefinitionNoArguments => ParseErrorDetails {
701 text: "A generic type must have at least a generic parameter.".into(),
702 hint: Some("If a type is not generic you should omit the `()`.".into()),
703 label_text: "I was expecting generic parameters here".into(),
704 extra_labels: vec![],
705 },
706
707 ParseErrorType::UnknownAttributeRecordVariant => ParseErrorDetails {
708 text: "".into(),
709 hint: Some("Did you mean `@deprecated`?".into()),
710 label_text: "This attribute cannot be used on a variant.".into(),
711 extra_labels: vec![],
712 },
713
714 ParseErrorType::IncorrectImportModuleSeparator { module, item } => ParseErrorDetails {
715 text: [
716 "Perhaps you meant one of:".into(),
717 "".into(),
718 format!(" import {module}/{item}"),
719 format!(" import {module}.{{item}}"),
720 ]
721 .join("\n"),
722 hint: None,
723 label_text: "I was expecting either `/` or `.{` here.".into(),
724 extra_labels: vec![],
725 },
726
727 ParseErrorType::EmptyGuardBlock => ParseErrorDetails {
728 text: "".into(),
729 hint: None,
730 label_text: "A clause guard block cannot be empty".into(),
731 extra_labels: vec![],
732 },
733
734 ParseErrorType::MissingConstantAsMessage => ParseErrorDetails {
735 text: "".into(),
736 hint: None,
737 label_text: "I was expecting to see a constant expression after this `as`".into(),
738 extra_labels: vec![],
739 },
740
741 ParseErrorType::ConstantInsideFunction => ParseErrorDetails {
742 text: wrap(
743 "All variables are immutable in Gleam, so constants inside \
744functions are not necessary.",
745 ),
746 hint: Some(
747 "Either move this into the global scope or use `let` binding instead.".into(),
748 ),
749 label_text: "Constants are not allowed inside functions".into(),
750 extra_labels: vec![],
751 },
752
753 ParseErrorType::FunctionDefinitionAngleGenerics => ParseErrorDetails {
754 text: "\
755Generic function type variables do not need to be predeclared like they
756would be in some other languages, instead they are written with lowercase
757names.
758
759 fn example(argument: generic) -> generic
760
761See: https://tour.gleam.run/functions/generic-functions/"
762 .into(),
763 hint: None,
764 label_text: "I was expecting `(` here.".into(),
765 extra_labels: vec![],
766 },
767
768 ParseErrorType::TypeUsageAngleGenerics {
769 module,
770 name,
771 arguments,
772 } => {
773 let type_arguments = arguments
774 .iter()
775 .map(|argument| {
776 let mut argument_string = EcoString::new();
777 argument.print(&mut argument_string);
778 argument_string
779 })
780 .join(", ");
781 let replacement_type = match module {
782 Some(module) => format!("{module}.{name}({type_arguments})"),
783 None => format!("{name}({type_arguments})"),
784 };
785
786 ParseErrorDetails {
787 text: format!(
788 "\
789Type parameters use lowercase names and are surrounded by parentheses.
790
791 {replacement_type}
792
793See: https://tour.gleam.run/data-types/generic-custom-types/"
794 ),
795 hint: None,
796 label_text: "I was expecting `(` here.".into(),
797 extra_labels: vec![],
798 }
799 }
800
801 ParseErrorType::TypeDefinitionAngleGenerics { name, arguments } => {
802 let comma_separated_arguments = arguments.join(", ");
803
804 ParseErrorDetails {
805 text: format!(
806 "\
807Type parameters use lowercase names and are surrounded by parentheses.
808
809 type {name}({comma_separated_arguments}) {{
810
811See: https://tour.gleam.run/data-types/generic-custom-types/"
812 ),
813 hint: None,
814 label_text: "I was expecting `(` here.".into(),
815 extra_labels: vec![],
816 }
817 }
818 }
819 }
820}
821
822impl LexicalError {
823 pub fn to_parse_error_info(&self) -> (&'static str, Vec<String>) {
824 match &self.error {
825 LexicalErrorType::BadStringEscape => (
826 "I don't understand this escape code",
827 vec![
828 "Hint: Add another backslash before it.".into(),
829 "See: https://tour.gleam.run/basics/strings".into(),
830 ],
831 ),
832 LexicalErrorType::DigitOutOfRadix => {
833 ("This digit is too big for the specified radix", vec![])
834 }
835 LexicalErrorType::NumTrailingUnderscore => (
836 "Numbers cannot have a trailing underscore",
837 vec!["Hint: remove it.".into()],
838 ),
839 LexicalErrorType::RadixIntNoValue => ("This integer has no value", vec![]),
840 LexicalErrorType::MissingExponent => (
841 "This float is missing an exponent",
842 vec!["Hint: Add an exponent or remove the trailing `e`".into()],
843 ),
844 LexicalErrorType::UnexpectedStringEnd => {
845 ("The string starting here was left open", vec![])
846 }
847 LexicalErrorType::UnrecognizedToken { tok } if *tok == ';' => (
848 "Remove this semicolon",
849 vec![
850 "Hint: Semicolons used to be whitespace and did nothing.".into(),
851 "You can safely remove them without your program changing.".into(),
852 ],
853 ),
854 LexicalErrorType::UnrecognizedToken { tok } if *tok == '\'' => (
855 "Unexpected single quote",
856 vec!["Hint: Strings are written with double quotes.".into()],
857 ),
858 LexicalErrorType::UnrecognizedToken { .. } => (
859 "I can't figure out what to do with this character",
860 vec!["Hint: Is it a typo?".into()],
861 ),
862 LexicalErrorType::InvalidUnicodeEscape(
863 InvalidUnicodeEscapeError::MissingOpeningBrace,
864 ) => (
865 "Expected '{' in Unicode escape sequence",
866 vec!["Hint: Add it.".into()],
867 ),
868 LexicalErrorType::InvalidUnicodeEscape(
869 InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace,
870 ) => (
871 "Expected hex digit or '}' in Unicode escape sequence",
872 vec![
873 "Hint: Hex digits are digits from 0 to 9 and letters from a to f or A to F."
874 .into(),
875 ],
876 ),
877 LexicalErrorType::InvalidUnicodeEscape(
878 InvalidUnicodeEscapeError::InvalidNumberOfHexDigits,
879 ) => (
880 "Expected between 1 and 6 hex digits in Unicode escape sequence",
881 vec![],
882 ),
883 LexicalErrorType::InvalidUnicodeEscape(InvalidUnicodeEscapeError::InvalidCodepoint) => {
884 ("Invalid Unicode codepoint", vec![])
885 }
886 LexicalErrorType::InvalidTripleEqual => (
887 "Did you mean `==`?",
888 vec![
889 "Gleam uses `==` to check for equality between two values.".into(),
890 "See: https://tour.gleam.run/basics/equality".into(),
891 ],
892 ),
893 LexicalErrorType::VisuallySimilarInvalidCharacter { name, correct } => (
894 "Unexpected character",
895 vec![wrap(&format!(
896 "This looks like ascii {correct}, but it is actually the unicode {name}."
897 ))],
898 ),
899 }
900 }
901}