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