litrs/
err.rs

1use std::{fmt, ops::Range};
2
3
4/// An error signaling that a different kind of token was expected. Returned by
5/// the various `TryFrom` impls.
6#[derive(Debug, Clone, Copy)]
7pub struct InvalidToken {
8    pub(crate) expected: TokenKind,
9    pub(crate) actual: TokenKind,
10    pub(crate) span: Span,
11}
12
13impl InvalidToken {
14    /// Returns a token stream representing `compile_error!("msg");` where
15    /// `"msg"` is the output of `self.to_string()`. **Panics if called outside
16    /// of a proc-macro context!**
17    pub fn to_compile_error(&self) -> proc_macro::TokenStream {
18        use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, TokenTree};
19
20        let span = match self.span {
21            Span::One(s) => s,
22            #[cfg(feature = "proc-macro2")]
23            Span::Two(s) => s.unwrap(),
24        };
25        let msg = self.to_string();
26        let tokens = vec![
27            TokenTree::from(Ident::new("compile_error", span)),
28            TokenTree::from(Punct::new('!', Spacing::Alone)),
29            TokenTree::from(Group::new(
30                Delimiter::Parenthesis,
31                TokenTree::from(proc_macro::Literal::string(&msg)).into(),
32            )),
33        ];
34
35
36        tokens.into_iter().map(|mut t| { t.set_span(span); t }).collect()
37    }
38
39    /// Like [`to_compile_error`][Self::to_compile_error], but returns a token
40    /// stream from `proc_macro2` and does not panic outside of a proc-macro
41    /// context.
42    #[cfg(feature = "proc-macro2")]
43    pub fn to_compile_error2(&self) -> proc_macro2::TokenStream {
44        use proc_macro2::{Delimiter, Group, Ident, Punct, Spacing, TokenTree};
45
46        let span = match self.span {
47            Span::One(s) => proc_macro2::Span::from(s),
48            Span::Two(s) => s,
49        };
50        let msg = self.to_string();
51        let tokens = vec![
52            TokenTree::from(Ident::new("compile_error", span)),
53            TokenTree::from(Punct::new('!', Spacing::Alone)),
54            TokenTree::from(Group::new(
55                Delimiter::Parenthesis,
56                TokenTree::from(proc_macro2::Literal::string(&msg)).into(),
57            )),
58        ];
59
60
61        tokens.into_iter().map(|mut t| { t.set_span(span); t }).collect()
62    }
63}
64
65impl std::error::Error for InvalidToken {}
66
67impl fmt::Display for InvalidToken {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        fn kind_desc(kind: TokenKind) -> &'static str {
70            match kind {
71                TokenKind::Punct => "a punctuation character",
72                TokenKind::Ident => "an identifier",
73                TokenKind::Group => "a group",
74                TokenKind::Literal => "a literal",
75                TokenKind::BoolLit => "a bool literal (`true` or `false`)",
76                TokenKind::ByteLit => "a byte literal (e.g. `b'r')",
77                TokenKind::ByteStringLit => r#"a byte string literal (e.g. `b"fox"`)"#,
78                TokenKind::CharLit => "a character literal (e.g. `'P'`)",
79                TokenKind::FloatLit => "a float literal (e.g. `3.14`)",
80                TokenKind::IntegerLit => "an integer literal (e.g. `27`)",
81                TokenKind::StringLit => r#"a string literal (e.g. "Ferris")"#,
82                TokenKind::CStringLit => r#"a C string literal (e.g. c"Ferris")"#,
83            }
84        }
85
86        write!(f, "expected {}, but found {}", kind_desc(self.expected), kind_desc(self.actual))
87    }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub(crate) enum TokenKind {
92    Punct,
93    Ident,
94    Group,
95    Literal,
96    BoolLit,
97    ByteLit,
98    ByteStringLit,
99    CharLit,
100    FloatLit,
101    IntegerLit,
102    StringLit,
103    CStringLit,
104}
105
106/// Unfortunately, we have to deal with both cases.
107#[derive(Debug, Clone, Copy)]
108pub(crate) enum Span {
109    One(proc_macro::Span),
110    #[cfg(feature = "proc-macro2")]
111    Two(proc_macro2::Span),
112}
113
114impl From<proc_macro::Span> for Span {
115    fn from(src: proc_macro::Span) -> Self {
116        Self::One(src)
117    }
118}
119
120#[cfg(feature = "proc-macro2")]
121impl From<proc_macro2::Span> for Span {
122    fn from(src: proc_macro2::Span) -> Self {
123        Self::Two(src)
124    }
125}
126
127/// Errors during parsing.
128///
129/// This type should be seen primarily for error reporting and not for catching
130/// specific cases. The span and error kind are not guaranteed to be stable
131/// over different versions of this library, meaning that a returned error can
132/// change from one version to the next. There are simply too many fringe cases
133/// that are not easy to classify as a specific error kind. It depends entirely
134/// on the specific parser code how an invalid input is categorized.
135///
136/// Consider these examples:
137/// - `'\` can be seen as
138///     - invalid escape in character literal, or
139///     - unterminated character literal.
140/// - `'''` can be seen as
141///     - empty character literal, or
142///     - unescaped quote character in character literal.
143/// - `0b64` can be seen as
144///     - binary integer literal with invalid digit 6, or
145///     - binary integer literal with invalid digit 4, or
146///     - decimal integer literal with invalid digit b, or
147///     - decimal integer literal 0 with unknown type suffix `b64`.
148///
149/// If you want to see more if these examples, feel free to check out the unit
150/// tests of this library.
151///
152/// While this library does its best to emit sensible and precise errors, and to
153/// keep the returned errors as stable as possible, full stability cannot be
154/// guaranteed.
155#[derive(Debug, Clone)]
156pub struct ParseError {
157    pub(crate) span: Option<Range<usize>>,
158    pub(crate) kind: ParseErrorKind,
159}
160
161impl ParseError {
162    /// Returns a span of this error, if available. **Note**: the returned span
163    /// might change in future versions of this library. See [the documentation
164    /// of this type][ParseError] for more information.
165    pub fn span(&self) -> Option<Range<usize>> {
166        self.span.clone()
167    }
168
169    /// Adds `offset` to the start and endpoint of the inner span.
170    pub(crate) fn offset_span(self, offset: usize) -> Self {
171        Self {
172            span: self.span.map(|span| span.start + offset..span.end + offset),
173            ..self
174        }
175    }
176}
177
178/// This is a free standing function instead of an associated one to reduce
179/// noise around parsing code. There are lots of places that create errors, we
180/// I wanna keep them as short as possible.
181pub(crate) fn perr(span: impl SpanLike, kind: ParseErrorKind) -> ParseError {
182    ParseError {
183        span: span.into_span(),
184        kind,
185    }
186}
187
188pub(crate) trait SpanLike {
189    fn into_span(self) -> Option<Range<usize>>;
190}
191
192impl SpanLike for Option<Range<usize>> {
193    #[inline(always)]
194    fn into_span(self) -> Option<Range<usize>> {
195        self
196    }
197}
198impl SpanLike for Range<usize> {
199    #[inline(always)]
200    fn into_span(self) -> Option<Range<usize>> {
201        Some(self)
202    }
203}
204impl SpanLike for usize {
205    #[inline(always)]
206    fn into_span(self) -> Option<Range<usize>> {
207        Some(self..self + 1)
208    }
209}
210
211
212/// Kinds of errors.
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214#[non_exhaustive]
215pub(crate) enum ParseErrorKind {
216    /// The input was an empty string
217    Empty,
218
219    /// An unexpected char was encountered.
220    UnexpectedChar,
221
222    /// Literal was not recognized.
223    InvalidLiteral,
224
225    /// Input does not start with decimal digit when trying to parse an integer.
226    DoesNotStartWithDigit,
227
228    /// A digit invalid for the specified integer base was found.
229    InvalidDigit,
230
231    /// Integer literal does not contain any valid digits.
232    NoDigits,
233
234    /// Exponent of a float literal does not contain any digits.
235    NoExponentDigits,
236
237    /// An unknown escape code, e.g. `\b`.
238    UnknownEscape,
239
240    /// A started escape sequence where the input ended before the escape was
241    /// finished.
242    UnterminatedEscape,
243
244    /// An `\x` escape where the two digits are not valid hex digits.
245    InvalidXEscape,
246
247    /// A string or character literal using the `\xNN` escape where `NN > 0x7F`.
248    NonAsciiXEscape,
249
250    /// A `\u{...}` escape in a byte or byte string literal.
251    UnicodeEscapeInByteLiteral,
252
253    /// A Unicode escape that does not start with a hex digit.
254    InvalidStartOfUnicodeEscape,
255
256    /// A `\u{...}` escape that lacks the opening brace.
257    UnicodeEscapeWithoutBrace,
258
259    /// In a `\u{...}` escape, a non-hex digit and non-underscore character was
260    /// found.
261    NonHexDigitInUnicodeEscape,
262
263    /// More than 6 digits found in unicode escape.
264    TooManyDigitInUnicodeEscape,
265
266    /// The value from a unicode escape does not represent a valid character.
267    InvalidUnicodeEscapeChar,
268
269    /// A `\u{..` escape that is not terminated (lacks the closing brace).
270    UnterminatedUnicodeEscape,
271
272    /// A character literal that's not terminated.
273    UnterminatedCharLiteral,
274
275    /// A character literal that contains more than one character.
276    OverlongCharLiteral,
277
278    /// An empty character literal, i.e. `''`.
279    EmptyCharLiteral,
280
281    UnterminatedByteLiteral,
282    OverlongByteLiteral,
283    EmptyByteLiteral,
284    NonAsciiInByteLiteral,
285
286    /// A `'` character was not escaped in a character or byte literal, or a `"`
287    /// character was not escaped in a string or byte string literal.
288    UnescapedSingleQuote,
289
290    /// A \n, \t or \r raw character in a char or byte literal.
291    UnescapedSpecialWhitespace,
292
293    /// When parsing a character, byte, string or byte string literal directly
294    /// and the input does not start with the corresponding quote character
295    /// (plus optional raw string prefix).
296    DoesNotStartWithQuote,
297
298    /// Unterminated raw string literal.
299    UnterminatedRawString,
300
301    /// String literal without a `"` at the end.
302    UnterminatedString,
303
304    /// Invalid start for a string literal.
305    InvalidStringLiteralStart,
306
307    /// Invalid start for a byte literal.
308    InvalidByteLiteralStart,
309
310    InvalidByteStringLiteralStart,
311
312    /// Not starting with `c"` or `cr`.
313    InvalidCStringLiteralStart,
314
315    /// A `\0` escape inside a C string.
316    DisallowedNulEscape,
317
318    /// A nul byte inside a C String.
319    NulByte,
320
321    /// `\r` in a (raw) string or (raw) byte string literal.
322    CarriageReturn,
323
324    /// Rust only allows 256 hashes in raw * string literals.
325    TooManyHashes,
326
327    /// Literal suffix is not a valid identifier.
328    InvalidSuffix,
329
330    /// Returned by `Float::parse` if an integer literal (no fractional nor
331    /// exponent part) is passed.
332    UnexpectedIntegerLit,
333
334    /// Integer suffixes cannot start with `e` or `E` as this conflicts with the
335    /// grammar for float literals.
336    IntegerSuffixStartingWithE,
337}
338
339impl std::error::Error for ParseError {}
340
341impl fmt::Display for ParseError {
342    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
343        use ParseErrorKind::*;
344
345        let description = match self.kind {
346            Empty => "input is empty",
347            UnexpectedChar => "unexpected character",
348            InvalidLiteral => "invalid literal",
349            DoesNotStartWithDigit => "number literal does not start with decimal digit",
350            InvalidDigit => "integer literal contains a digit invalid for its base",
351            NoDigits => "integer literal does not contain any digits",
352            NoExponentDigits => "exponent of floating point literal does not contain any digits",
353            UnknownEscape => "unknown escape",
354            UnterminatedEscape => "unterminated escape: input ended too soon",
355            InvalidXEscape => r"invalid `\x` escape: not followed by two hex digits",
356            NonAsciiXEscape => r"`\x` escape in char/string literal exceed ASCII range",
357            UnicodeEscapeInByteLiteral => r"`\u{...}` escape in byte (string) literal not allowed",
358            InvalidStartOfUnicodeEscape => r"invalid start of `\u{...}` escape",
359            UnicodeEscapeWithoutBrace => r"`Unicode \u{...}` escape without opening brace",
360            NonHexDigitInUnicodeEscape => r"non-hex digit found in `\u{...}` escape",
361            TooManyDigitInUnicodeEscape => r"more than six digits in `\u{...}` escape",
362            InvalidUnicodeEscapeChar => r"value specified in `\u{...}` escape is not a valid char",
363            UnterminatedUnicodeEscape => r"unterminated `\u{...}` escape",
364            UnterminatedCharLiteral => "character literal is not terminated",
365            OverlongCharLiteral => "character literal contains more than one character",
366            EmptyCharLiteral => "empty character literal",
367            UnterminatedByteLiteral => "byte literal is not terminated",
368            OverlongByteLiteral => "byte literal contains more than one byte",
369            EmptyByteLiteral => "empty byte literal",
370            NonAsciiInByteLiteral => "non ASCII character in byte (string) literal",
371            UnescapedSingleQuote => "character literal contains unescaped ' character",
372            UnescapedSpecialWhitespace => r"unescaped newline (\n), tab (\t) or cr (\r) character",
373            DoesNotStartWithQuote => "invalid start for char/byte/string literal",
374            UnterminatedRawString => "unterminated raw (byte) string literal",
375            UnterminatedString => "unterminated (byte) string literal",
376            InvalidStringLiteralStart => "invalid start for string literal",
377            InvalidByteLiteralStart => "invalid start for byte literal",
378            InvalidByteStringLiteralStart => "invalid start for byte string literal",
379            InvalidCStringLiteralStart => "invalid start for C string literal",
380            DisallowedNulEscape => r"`\0` escape not allowed inside C string literal",
381            NulByte => r"nul byte not allowed inside C string literal",
382            CarriageReturn => r"`\r` not allowed in string literals",
383            TooManyHashes => "raw string literal has too many # symbols (max 256)",
384            InvalidSuffix => "literal suffix is not a valid identifier",
385            UnexpectedIntegerLit => "expected float literal, but found integer",
386            IntegerSuffixStartingWithE => "integer literal suffix must not start with 'e' or 'E'",
387        };
388
389        description.fmt(f)?;
390        if let Some(span) = &self.span {
391            write!(f, " (at {}..{})", span.start, span.end)?;
392        }
393
394        Ok(())
395    }
396}