Fork of daniellemaywood.uk/gleam — Wasm codegen work
Something went wrong. Try again.
39 kB · 963 lines
Rust
at wasm
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964// SPDX-License-Identifier: Apache-2.0// SPDX-FileCopyrightText: 2020 The Gleam contributors
use crate::ast::{RecordConstructorArg, SrcSpan, TypeAst};use crate::diagnostic::{ExtraLabel, Label};use crate::error::{wrap, wrap_format};use crate::parse::Token;use ecow::EcoString;use itertools::Itertools;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]pub struct LexicalError { pub error: LexicalErrorType, pub location: SrcSpan,}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]pub enum InvalidUnicodeEscapeError { /// Expected '{' MissingOpeningBrace, /// Expected hex digit or '}' ExpectedHexDigitOrCloseBrace, /// Expected between 1 and 6 hex digits InvalidNumberOfHexDigits, /// Invalid Unicode codepoint InvalidCodepoint,}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]pub enum LexicalErrorType { // String contains an unescaped slash BadStringEscape, // \u{...} escape sequence is invalid InvalidUnicodeEscape(InvalidUnicodeEscapeError), // 0x012 , 2 is out of radix DigitOutOfRadix, // 1_000_ is not allowed NumTrailingUnderscore, // 0x, 0b, 0o without a value RadixIntNoValue, // // 1.0e, for example, where there is no exponent MissingExponent, // Unterminated string literal UnexpectedStringEnd, UnrecognizedToken { tok: char, }, InvalidTripleEqual, /// A character was encountered that visually looks like a correct /// character, but in reality it's some other unicode characters. /// For example, a non-breaking-space instead of a regular space. VisuallySimilarInvalidCharacter { name: &'static str, correct: &'static str, },}
#[derive(Debug, Clone, PartialEq, Eq)]pub struct ParseError { pub error: ParseErrorType, pub location: SrcSpan,}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]/// Where we found an incorrect name.pub enum IncorrectNamePosition { /// After `as`. For example: `as _wibble`. AsPattern, /// As a module in an import: `import _wibble` Module, /// As a function name: `fn _wibble()` Function, /// As an attribute name: `@_wibble` Attribute, /// As a constant name: `const _wibble = ...` Constant, /// As a target name: `@external(_wibble, ...)` Target, /// Used as a variable expression: `let a = _wibble`, `let a = _wibble(10)` Variable,}
#[derive(Debug, Clone, PartialEq, Eq)]pub enum ParseErrorType { ExpectedEqual, // expect "=" ExpectedExpr, // after "->" in a case clause ExpectedName, // any token used when a Name was expected ExpectedPattern, // after ':' where a pattern is expected ExpectedType, // after ':' or '->' where a type annotation is expected ExpectedUpName, // any token used when a UpName was expected ExpectedValue, // no value after "=" ExpectedDefinition, // after attributes ExpectedDeprecationMessage, // after "deprecated" ExpectedExternalArguments, // after "@external" ExpectedFunctionDefinition, // after function-only attributes ExpectedTargetName, // after "@target(" ExprLparStart, // it seems "(" was used to start an expression ExtraSeparator, // #(1,,) <- the 2nd comma is an extra separator // UpName or DiscardName used when Name was expected IncorrectName { // Where we found the incorrect name. kind: IncorrectNamePosition, }, IncorrectUpName, // Name or DiscardName used when UpName was expected InvalidBitArraySegment, // <<7:hello>> `hello` is an invalid BitArray segment InvalidBitArrayUnit, // in <<1:unit(x)>> x must be 1 <= x <= 256 InvalidTailPattern, // only name and _name are allowed after ".." in list pattern InvalidTupleAccess, // only positive int literals for tuple access LexError { error: LexicalError, }, NestedBitArrayPattern, // <<<<1>>, 2>>, <<1>> is not allowed in there NoLetBinding, // Bindings and rebinds always require let and must always bind to a value. NoValueAfterEqual, // = <something other than a value> NotConstType, // :fn(), name, _ are not valid const types OpNakedRight, // Operator with no value to the right OpaqueTypeAlias, // Type aliases cannot be opaque TooManyArgHoles, // a function call can have at most 1 arg hole DuplicateAttribute, // an attribute was used more than once UnknownAttribute, // an attribute was used that is not known UnknownTarget, // an unknown target was used ListSpreadWithoutElements, // Pointless spread: `[..xs]` ListSpreadFollowedByElements, // trying to append something after the spread: `[..xs, x]` ListSpreadWithAnotherSpread { first_spread_location: SrcSpan, }, // trying to use multiple spreads: `[..xs, ..ys]` UnexpectedLabel, // argument labels were provided, but are not supported in this context UnexpectedEof, UnexpectedReservedWord, // reserved word used when a name was expected UnexpectedToken { token: Token, expected: Vec<EcoString>, hint: Option<EcoString>, }, UnexpectedFunction, // a function was used called outside of another function /// A variable was assigned or discarded on the left hand side of a <> pattern ConcatPatternVariableLeftHandSide, /// A variable was assigned as infix between prefix and suffix string /// patterns using <> ConcatPatternVariableWithSuffix { name: EcoString, }, ListSpreadWithoutTail, // let x = [1, ..] ExpectedFunctionBody, // let x = fn() RedundantInternalAttribute, // for a private definition marked as internal InvalidModuleTypePattern, // for patterns that have a dot like: `name.thing` ListPatternSpreadFollowedByElements, // When there is a pattern after a spread [..rest, pattern]
/// This happens when someone forgets to write the type constructor around /// its fields in a custom type definition. For example: /// /// ```gleam /// pub type Wibble { /// String, /// field: Int, /// field_2: a, /// } /// ``` ExpectedRecordConstructor { type_name: EcoString, public: bool, opaque: bool, /// Those are the fields that have been written. fields: Vec<RecordConstructorArg<()>>, }, CallInClauseGuard, // case x { _ if f() -> 1 } IfExpression, TypeDefinitionNoArguments, // pub type Wibble() { ... } UnknownAttributeRecordVariant, // an attribute was used that is not know for a custom type variant // a Python-like import was written, such as `import gleam.io`, instead of `import gleam/io` IncorrectImportModuleSeparator { module: EcoString, item: EcoString, }, /// This can happen when there's an empty block in a case clause guard. /// For example: `_ if a == {}` EmptyGuardBlock, // When the use tries to define a constant inside a function ConstantInsideFunction, FunctionDefinitionAngleGenerics, // fn something<T>() { ... } // let a: List<String> = [] TypeUsageAngleGenerics { module: Option<EcoString>, name: EcoString, arguments: Vec<TypeAst>, }, // type Something<T> { TypeDefinitionAngleGenerics { name: EcoString, arguments: Vec<EcoString>, }, // `const x = todo as` with no message after the `as`. MissingConstantAsMessage,}
pub(crate) struct ParseErrorDetails { pub text: String, pub label_text: EcoString, pub extra_labels: Vec<ExtraLabel>, pub hint: Option<String>,}
impl ParseErrorType { pub(crate) fn details(&self) -> ParseErrorDetails { match self { ParseErrorType::ExpectedEqual => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a '=' after this".into(), extra_labels: vec![], },
ParseErrorType::ExpectedExpr => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting an expression after this".into(), extra_labels: vec![], },
ParseErrorType::ExpectedName => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a name here".into(), extra_labels: vec![], },
ParseErrorType::ExpectedPattern => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a pattern after this".into(), extra_labels: vec![], },
ParseErrorType::ExpectedType => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/assignments/".into(), hint: None, label_text: "I was expecting a type after this".into(), extra_labels: vec![], },
ParseErrorType::ExpectedUpName => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a type name here".into(), extra_labels: vec![], },
ParseErrorType::ExpectedValue => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a value after this".into(), extra_labels: vec![], },
ParseErrorType::ExpectedDefinition => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a definition after this".into(), extra_labels: vec![], },
ParseErrorType::ExpectedDeprecationMessage => ParseErrorDetails { text: "See: https://tour.gleam.run/functions/deprecations/".into(), hint: None, label_text: "A deprecation attribute must have a string message.".into(), extra_labels: vec![], },
ParseErrorType::ExpectedExternalArguments => ParseErrorDetails { text: "".into(), hint: Some("See https://tour.gleam.run/advanced-features/externals/".into()), label_text: "This attribute is incomplete".into(), extra_labels: vec![], },
ParseErrorType::ExpectedFunctionDefinition => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a function definition after this".into(), extra_labels: vec![], },
ParseErrorType::ExpectedTargetName => ParseErrorDetails { text: "Try `erlang`, `javascript`, `wasm`.".into(), hint: None, label_text: "I was expecting a target name after this".into(), extra_labels: vec![], },
ParseErrorType::ExtraSeparator => ParseErrorDetails { text: "".into(), hint: Some("Try removing it?".into()), label_text: "This is an extra delimiter".into(), extra_labels: vec![], },
ParseErrorType::ExprLparStart => ParseErrorDetails { text: "".into(), hint: Some( "To group expressions in Gleam, use \"{\" and \"}\"; \tuples are created with `#(` and `)`." .into(), ), label_text: "This parenthesis cannot be understood here".into(), extra_labels: vec![], },
ParseErrorType::IncorrectName { kind } => { let subject = match kind { IncorrectNamePosition::AsPattern => "A name after `as`", IncorrectNamePosition::Module => "A module name", IncorrectNamePosition::Function => "A function name", IncorrectNamePosition::Attribute => "An attribute name", IncorrectNamePosition::Constant => "A constant name", IncorrectNamePosition::Target => "A target name", IncorrectNamePosition::Variable => "A variable name", }; ParseErrorDetails { text: wrap_format!( "{subject} must start with a lowercase letter, and can \contain a-z, 0-9, or _.", ), hint: None, label_text: "I'm expecting a lowercase name here".into(), extra_labels: vec![], } }
ParseErrorType::IncorrectUpName => ParseErrorDetails { text: "".into(), hint: Some(wrap( "Type names start with a uppercase letter, and can \contain a-z, A-Z, or 0-9.", )), label_text: "I'm expecting a type name here".into(), extra_labels: vec![], },
ParseErrorType::InvalidBitArraySegment => ParseErrorDetails { text: "See: https://tour.gleam.run/data-types/bit-arrays/".into(), hint: Some(format!( "Valid BitArray segment options are:\n{}", wrap( "bits, bytes, int, float, utf8, utf16, utf32, utf8_codepoint, \utf16_codepoint, utf32_codepoint, signed, unsigned, big, little, native, size, unit.", ) )), label_text: "This is not a valid BitArray segment option".into(), extra_labels: vec![], },
ParseErrorType::InvalidBitArrayUnit => ParseErrorDetails { text: "See: https://tour.gleam.run/data-types/bit-arrays/".into(), hint: Some("Unit must be an integer literal >= 1 and <= 256.".into()), label_text: "This is not a valid BitArray unit value".into(), extra_labels: vec![], },
ParseErrorType::InvalidTailPattern => ParseErrorDetails { text: "".into(), hint: None, label_text: "This part of a list pattern can only be a name or a discard".into(), extra_labels: vec![], },
ParseErrorType::InvalidTupleAccess => ParseErrorDetails { text: "".into(), hint: Some( "Only non negative integer literals like 0, or 1_000 can be used.".into(), ), label_text: "This integer is not valid for tuple access".into(), extra_labels: vec![], },
ParseErrorType::LexError { error: lex_err } => { let (label_text, text_lines) = lex_err.to_parse_error_info(); let text = text_lines.join("\n"); ParseErrorDetails { text, hint: None, label_text: label_text.into(), extra_labels: vec![], } }
ParseErrorType::NestedBitArrayPattern => ParseErrorDetails { text: "".into(), hint: None, label_text: "BitArray patterns cannot be nested".into(), extra_labels: vec![], },
ParseErrorType::NotConstType => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/constants/".into(), hint: None, label_text: "This type is not allowed in module constants".into(), extra_labels: vec![], },
ParseErrorType::NoLetBinding => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/assignments/".into(), hint: Some("Use let for binding.".into()), label_text: "There must be a 'let' to bind variable to value".into(), extra_labels: vec![], },
ParseErrorType::NoValueAfterEqual => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting to see a value after this equals sign".into(), extra_labels: vec![], },
ParseErrorType::OpaqueTypeAlias => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/type-aliases/".into(), hint: None, label_text: "Type Aliases cannot be opaque".into(), extra_labels: vec![], },
ParseErrorType::OpNakedRight => ParseErrorDetails { text: "".into(), hint: Some("Remove it or put a value after it.".into()), label_text: "This operator has no value on its right side".into(), extra_labels: vec![], },
ParseErrorType::TooManyArgHoles => ParseErrorDetails { text: "See: https://tour.gleam.run/functions/functions/".into(), hint: Some("Function calls can have at most one argument hole.".into()), label_text: "There is more than 1 argument hole in this function call".into(), extra_labels: vec![], },
ParseErrorType::UnexpectedEof => ParseErrorDetails { text: "".into(), hint: None, label_text: "The module ended unexpectedly".into(), extra_labels: vec![], },
ParseErrorType::ListSpreadWithoutElements => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/lists/".into(), hint: Some("Try prepending some elements [1, 2, ..list].".into()), label_text: "This spread does nothing".into(), extra_labels: vec![], },
ParseErrorType::ListSpreadWithAnotherSpread { first_spread_location, } => ParseErrorDetails { text: [ "Lists are immutable and singly-linked, so to join two or more lists", "all the elements of the lists would need to be copied into a new list.", "This would be slow, so there is no built-in syntax for it.", ] .join("\n"), hint: None, label_text: "I wasn't expecting a second list here".into(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some("You're using a list here".into()), span: *first_spread_location, }, }], },
ParseErrorType::ListSpreadFollowedByElements => ParseErrorDetails { text: [ "Lists are immutable and singly-linked, so to append items to them", "all the elements of a list would need to be copied into a new list.", "This would be slow, so there is no built-in syntax for it.", ] .join("\n"), hint: Some( "Prepend items to the list and then reverse it once you are done.".into(), ), label_text: "I wasn't expecting elements after this".into(), extra_labels: vec![], },
ParseErrorType::ListPatternSpreadFollowedByElements => ParseErrorDetails { text: [ "Lists are immutable and singly-linked, so to match on the end", "of a list would require the whole list to be traversed. This", "would be slow, so there is no built-in syntax for it. Pattern", "match on the start of the list instead.", ] .join("\n"), hint: None, label_text: "I wasn't expecting elements after this".into(), extra_labels: vec![], },
ParseErrorType::UnexpectedReservedWord => ParseErrorDetails { text: "".into(), hint: Some("I was expecting to see a name here.".into()), label_text: "This is a reserved word".into(), extra_labels: vec![], },
ParseErrorType::UnexpectedLabel => ParseErrorDetails { text: "Please remove the argument label.".into(), hint: None, label_text: "Argument labels are not allowed for anonymous functions".into(), extra_labels: vec![], },
ParseErrorType::UnexpectedToken { token, expected, hint, } => { let found = match token { Token::Int { .. } => "an Int".to_string(), Token::Float { .. } => "a Float".to_string(), Token::String { .. } => "a String".to_string(), Token::CommentDoc { .. } => "a comment".to_string(), Token::DiscardName { .. } => "a discard name".to_string(), Token::Name { .. } | Token::UpName { .. } => "a name".to_string(), _ if token.is_reserved_word() => format!("the keyword {token}"), Token::LeftParen | Token::RightParen | Token::LeftSquare | Token::RightSquare | Token::LeftBrace | Token::RightBrace | Token::Plus | Token::Minus | Token::Star | Token::Slash | Token::Less | Token::Greater | Token::LessEqual | Token::GreaterEqual | Token::Percent | Token::PlusDot | Token::MinusDot | Token::StarDot | Token::SlashDot | Token::LessDot | Token::GreaterDot | Token::LessEqualDot | Token::GreaterEqualDot | Token::Concatenate | Token::Colon | Token::Comma | Token::Hash | Token::Bang | Token::Equal | Token::EqualEqual | Token::NotEqual | Token::Vbar | Token::VbarVbar | Token::AmperAmper | Token::LtLt | Token::GtGt | Token::Pipe | Token::Dot | Token::RArrow | Token::LArrow | Token::DotDot | Token::At | Token::EndOfFile | Token::CommentNormal | Token::CommentModule | Token::NewLine | Token::As | Token::Assert | Token::Auto | Token::Case | Token::Const | Token::Delegate | Token::Derive | Token::Echo | Token::Else | Token::Fn | Token::If | Token::Implement | Token::Import | Token::Let | Token::Macro | Token::Opaque | Token::Panic | Token::Pub | Token::Test | Token::Todo | Token::Type | Token::Use => token.to_string(), };
let mut messages = std::iter::once(format!("Found {found}, expected one of: ")) .chain(expected.iter().map(|s| format!("- {s}")));
ParseErrorDetails { text: messages.join("\n"), hint: hint.as_ref().map(|hint| hint.to_string()), label_text: "I was not expecting this".into(), extra_labels: vec![], } }
ParseErrorType::ConcatPatternVariableLeftHandSide => ParseErrorDetails { text: [ "We can't tell what size this prefix should be so we don't know", "how to handle this pattern.", "", "If you want to match one character consider using `pop_grapheme`", "from the stdlib's `gleam/string` module.", ] .join("\n"), hint: None, label_text: "This must be a string literal".into(), extra_labels: vec![], },
ParseErrorType::ConcatPatternVariableWithSuffix { name } => ParseErrorDetails { text: [ "A string pattern can only match on a literal string prefix.", "", &wrap_format!( "Matching on a literal suffix is not possible, because `{name}` \would have an unknown size." ), ] .join("\n"), hint: None, label_text: "This pattern is not allowed".into(), extra_labels: vec![], },
ParseErrorType::UnexpectedFunction => ParseErrorDetails { text: "".into(), hint: None, label_text: "Functions can only be called within other functions".into(), extra_labels: vec![], },
ParseErrorType::ListSpreadWithoutTail => ParseErrorDetails { text: "If a list expression has a spread then a tail must also be given.".into(), hint: None, label_text: "I was expecting a value after this spread".into(), extra_labels: vec![], },
ParseErrorType::UnknownAttribute => ParseErrorDetails { text: "".into(), hint: Some("Try `deprecated`, `external` or `internal` instead.".into()), label_text: "I don't recognise this attribute".into(), extra_labels: vec![], },
ParseErrorType::DuplicateAttribute => ParseErrorDetails { text: "This attribute has already been given.".into(), hint: None, label_text: "Duplicate attribute".into(), extra_labels: vec![], },
ParseErrorType::UnknownTarget => ParseErrorDetails { text: "Try `erlang`, `javascript`, `wasm`.".into(), hint: None, label_text: "I don't recognise this target".into(), extra_labels: vec![], },
ParseErrorType::ExpectedFunctionBody => ParseErrorDetails { text: "".into(), hint: None, label_text: "This function does not have a body".into(), extra_labels: vec![], },
ParseErrorType::RedundantInternalAttribute => ParseErrorDetails { text: "Only a public definition can be annotated as internal.".into(), hint: Some("Remove the `@internal` annotation.".into()), label_text: "Redundant internal attribute".into(), extra_labels: vec![], },
ParseErrorType::InvalidModuleTypePattern => ParseErrorDetails { text: [ "I'm expecting a pattern here,", "or a variable to bind a value to, etc.", ] .join("\n"), hint: Some( "A pattern can be a constructor name, a literal valueSee: https://tour.gleam.run/flow-control/case-expressions/" .into(), ), label_text: "Invalid pattern".into(), extra_labels: vec![], },
ParseErrorType::ExpectedRecordConstructor { type_name, public, opaque, fields, } => { let (accessor, opaque) = match *public { true if *opaque => ("pub ", "opaque "), true => ("pub ", ""), false => ("", ""), };
let fields = fields .iter() .map(|field| { let mut type_ = EcoString::new(); field.ast.print(&mut type_);
match field.label.as_ref() { Some((_, label)) => format!(" {label}: {type_},"), None => format!(" {type_},"), } }) .join("\n");
ParseErrorDetails { text: format!( "Each custom type variant must have a constructor:
{accessor}{opaque}type {type_name} {{ {type_name}({fields} )}}" ), hint: None, label_text: "I was not expecting this".into(), extra_labels: vec![], } }
ParseErrorType::CallInClauseGuard => ParseErrorDetails { text: "Functions cannot be called in clause guards.".into(), hint: None, label_text: "Unsupported expression".into(), extra_labels: vec![], },
ParseErrorType::IfExpression => ParseErrorDetails { text: [ "If you want to write a conditional expression you can use a `case`:", "", " case condition {", " True -> todo", " False -> todo", " }", "", "See: https://tour.gleam.run/flow-control/case-expressions/", ] .join("\n"), hint: None, label_text: "Gleam doesn't have if expressions".into(), extra_labels: vec![], },
ParseErrorType::TypeDefinitionNoArguments => ParseErrorDetails { text: "A generic type must have at least a generic parameter.".into(), hint: Some("If a type is not generic you should omit the `()`.".into()), label_text: "I was expecting generic parameters here".into(), extra_labels: vec![], },
ParseErrorType::UnknownAttributeRecordVariant => ParseErrorDetails { text: "".into(), hint: Some("Did you mean `@deprecated`?".into()), label_text: "This attribute cannot be used on a variant.".into(), extra_labels: vec![], },
ParseErrorType::IncorrectImportModuleSeparator { module, item } => ParseErrorDetails { text: [ "Perhaps you meant one of:".into(), "".into(), format!(" import {module}/{item}"), format!(" import {module}.{{item}}"), ] .join("\n"), hint: None, label_text: "I was expecting either `/` or `.{` here.".into(), extra_labels: vec![], },
ParseErrorType::EmptyGuardBlock => ParseErrorDetails { text: "".into(), hint: None, label_text: "A clause guard block cannot be empty".into(), extra_labels: vec![], },
ParseErrorType::MissingConstantAsMessage => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting to see a constant expression after this `as`".into(), extra_labels: vec![], },
ParseErrorType::ConstantInsideFunction => ParseErrorDetails { text: wrap( "All variables are immutable in Gleam, so constants inside \functions are not necessary.", ), hint: Some( "Either move this into the global scope or use `let` binding instead.".into(), ), label_text: "Constants are not allowed inside functions".into(), extra_labels: vec![], },
ParseErrorType::FunctionDefinitionAngleGenerics => ParseErrorDetails { text: "\Generic function type variables do not need to be predeclared like theywould be in some other languages, instead they are written with lowercasenames.
fn example(argument: generic) -> generic
See: https://tour.gleam.run/functions/generic-functions/" .into(), hint: None, label_text: "I was expecting `(` here.".into(), extra_labels: vec![], },
ParseErrorType::TypeUsageAngleGenerics { module, name, arguments, } => { let type_arguments = arguments .iter() .map(|argument| { let mut argument_string = EcoString::new(); argument.print(&mut argument_string); argument_string }) .join(", "); let replacement_type = match module { Some(module) => format!("{module}.{name}({type_arguments})"), None => format!("{name}({type_arguments})"), };
ParseErrorDetails { text: format!( "\Type parameters use lowercase names and are surrounded by parentheses.
{replacement_type}
See: https://tour.gleam.run/data-types/generic-custom-types/" ), hint: None, label_text: "I was expecting `(` here.".into(), extra_labels: vec![], } }
ParseErrorType::TypeDefinitionAngleGenerics { name, arguments } => { let comma_separated_arguments = arguments.join(", ");
ParseErrorDetails { text: format!( "\Type parameters use lowercase names and are surrounded by parentheses.
type {name}({comma_separated_arguments}) {{
See: https://tour.gleam.run/data-types/generic-custom-types/" ), hint: None, label_text: "I was expecting `(` here.".into(), extra_labels: vec![], } } } }}
impl LexicalError { pub fn to_parse_error_info(&self) -> (&'static str, Vec<String>) { match &self.error { LexicalErrorType::BadStringEscape => ( "I don't understand this escape code", vec![ "Hint: Add another backslash before it.".into(), "See: https://tour.gleam.run/basics/strings".into(), ], ), LexicalErrorType::DigitOutOfRadix => { ("This digit is too big for the specified radix", vec![]) } LexicalErrorType::NumTrailingUnderscore => ( "Numbers cannot have a trailing underscore", vec!["Hint: remove it.".into()], ), LexicalErrorType::RadixIntNoValue => ("This integer has no value", vec![]), LexicalErrorType::MissingExponent => ( "This float is missing an exponent", vec!["Hint: Add an exponent or remove the trailing `e`".into()], ), LexicalErrorType::UnexpectedStringEnd => { ("The string starting here was left open", vec![]) } LexicalErrorType::UnrecognizedToken { tok } if *tok == ';' => ( "Remove this semicolon", vec![ "Hint: Semicolons used to be whitespace and did nothing.".into(), "You can safely remove them without your program changing.".into(), ], ), LexicalErrorType::UnrecognizedToken { tok } if *tok == '\'' => ( "Unexpected single quote", vec!["Hint: Strings are written with double quotes.".into()], ), LexicalErrorType::UnrecognizedToken { .. } => ( "I can't figure out what to do with this character", vec!["Hint: Is it a typo?".into()], ), LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::MissingOpeningBrace, ) => ( "Expected '{' in Unicode escape sequence", vec!["Hint: Add it.".into()], ), LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace, ) => ( "Expected hex digit or '}' in Unicode escape sequence", vec![ "Hint: Hex digits are digits from 0 to 9 and letters from a to f or A to F." .into(), ], ), LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::InvalidNumberOfHexDigits, ) => ( "Expected between 1 and 6 hex digits in Unicode escape sequence", vec![], ), LexicalErrorType::InvalidUnicodeEscape(InvalidUnicodeEscapeError::InvalidCodepoint) => { ("Invalid Unicode codepoint", vec![]) } LexicalErrorType::InvalidTripleEqual => ( "Did you mean `==`?", vec![ "Gleam uses `==` to check for equality between two values.".into(), "See: https://tour.gleam.run/basics/equality".into(), ], ), LexicalErrorType::VisuallySimilarInvalidCharacter { name, correct } => ( "Unexpected character", vec![wrap(&format!( "This looks like ascii {correct}, but it is actually the unicode {name}." ))], ), } }}