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