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