solar_parse/lexer/unescape/
errors.rs

1use solar_interface::{BytePos, Span, diagnostics::DiagCtxt};
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 => {
67                "unicode characters are not allowed in string literals; use a `unicode\"...\"` literal instead"
68            }
69            Self::HexNotHexDigit => "invalid hex digit",
70            Self::HexBadUnderscore => "invalid underscore in hex literal",
71            Self::HexOddDigits => "odd number of hex nibbles",
72            Self::HexPrefix => "hex prefix is not allowed",
73        }
74    }
75}
76
77pub(crate) fn emit_unescape_error(
78    dcx: &DiagCtxt,
79    // interior part of the literal, between quotes
80    lit: &str,
81    // span of the error part of the literal
82    err_span: Span,
83    // range of the error inside `lit`
84    range: Range<usize>,
85    error: EscapeError,
86) {
87    let last_char = || {
88        let c = lit[range.clone()].chars().next_back().unwrap();
89        let span = err_span.with_lo(err_span.hi() - BytePos(c.len_utf8() as u32));
90        (c, span)
91    };
92    let mut diag = dcx.err(error.msg()).span(err_span);
93    if matches!(
94        error,
95        EscapeError::InvalidEscape
96            | EscapeError::InvalidHexEscape
97            | EscapeError::InvalidUnicodeEscape
98    ) {
99        diag = diag.span(last_char().1);
100    }
101    diag.emit();
102}