Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / parse / error.rs
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}