solar_parse/lexer/unescape/
errors.rs

1use solar_interface::{diagnostics::DiagCtxt, BytePos, Span};
2use std::ops::Range;
3
4/// Errors and warnings that can occur during string unescaping.
5#[derive(Debug, PartialEq, Eq)]
6pub enum EscapeError {
7    /// Escaped '\' character without continuation.
8    LoneSlash,
9    /// Invalid escape character (e.g. '\z').
10    InvalidEscape,
11    /// Raw '\r' encountered.
12    BareCarriageReturn,
13    /// Can only skip one line of whitespace.
14    ///
15    /// ```text
16    /// "this is \
17    ///  ok" == "this is ok";
18    ///
19    /// "this is \
20    ///  \
21    ///  also ok" == "this is also ok";
22    ///
23    /// "this is \
24    ///  
25    ///  not ok"; // error: cannot skip multiple lines
26    /// ```
27    CannotSkipMultipleLines,
28
29    /// Numeric character escape is too short (e.g. '\x1').
30    HexEscapeTooShort,
31    /// Invalid character in numeric escape (e.g. '\xz1').
32    InvalidHexEscape,
33
34    /// Unicode character escape is too short (e.g. '\u1').
35    UnicodeEscapeTooShort,
36    /// Invalid character in unicode character escape (e.g. '\uz111').
37    InvalidUnicodeEscape,
38
39    /// Newline in string literal. These must be escaped.
40    StrNewline,
41    /// Non-ASCII character in non-unicode literal.
42    StrNonAsciiChar,
43
44    /// Non hex-digit character in hex literal.
45    HexNotHexDigit,
46    /// Underscore in hex literal.
47    HexBadUnderscore,
48    /// Odd number of hex digits in hex literal.
49    HexOddDigits,
50    /// Hex literal with the `0x` prefix.
51    HexPrefix,
52}
53
54impl EscapeError {
55    fn msg(&self) -> &'static str {
56        match self {
57            Self::LoneSlash => "invalid trailing slash in literal",
58            Self::InvalidEscape => "unknown character escape",
59            Self::BareCarriageReturn => "bare CR not allowed in string, use `\\r` instead",
60            Self::CannotSkipMultipleLines => "cannot skip multiple lines with `\\`",
61            Self::HexEscapeTooShort => "hex escape must be followed by 2 hex digits",
62            Self::InvalidHexEscape => "invalid character in hex escape",
63            Self::UnicodeEscapeTooShort => "unicode escape must be followed by 4 hex digits",
64            Self::InvalidUnicodeEscape => "invalid character in unicode escape",
65            Self::StrNewline => "unescaped newline",
66            Self::StrNonAsciiChar => "unicode characters are not allowed in string literals; use a `unicode\"...\"` literal instead",
67            Self::HexNotHexDigit => "invalid hex digit",
68            Self::HexBadUnderscore => "invalid underscore in hex literal",
69            Self::HexOddDigits => "odd number of hex nibbles",
70            Self::HexPrefix => "hex prefix is not allowed",
71        }
72    }
73}
74
75pub(crate) fn emit_unescape_error(
76    dcx: &DiagCtxt,
77    // interior part of the literal, between quotes
78    lit: &str,
79    // span of the error part of the literal
80    err_span: Span,
81    // range of the error inside `lit`
82    range: Range<usize>,
83    error: EscapeError,
84) {
85    let last_char = || {
86        let c = lit[range.clone()].chars().next_back().unwrap();
87        let span = err_span.with_lo(err_span.hi() - BytePos(c.len_utf8() as u32));
88        (c, span)
89    };
90    let mut diag = dcx.err(error.msg()).span(err_span);
91    if matches!(
92        error,
93        EscapeError::InvalidEscape
94            | EscapeError::InvalidHexEscape
95            | EscapeError::InvalidUnicodeEscape
96    ) {
97        diag = diag.span(last_char().1);
98    }
99    diag.emit();
100}