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