Skip to main content

rustc_parse/lexer/
unescape_error_reporting.rs

1//! Utilities for rendering escape sequence errors as diagnostics.
2
3use std::iter::once;
4use std::ops::Range;
5
6use rustc_errors::{Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed};
7use rustc_literal_escaper::{EscapeError, Mode};
8use rustc_span::{BytePos, Span};
9use tracing::debug;
10
11use crate::diagnostics::{
12    MoreThanOneCharNote, MoreThanOneCharSugg, NoBraceUnicodeSub, UnescapeError,
13};
14
15pub(crate) fn emit_unescape_error(
16    dcx: DiagCtxtHandle<'_>,
17    // interior part of the literal, between quotes
18    lit: &str,
19    // full span of the literal, including quotes and any prefix
20    full_lit_span: Span,
21    // span of the error part of the literal
22    err_span: Span,
23    mode: Mode,
24    // range of the error inside `lit`
25    range: Range<usize>,
26    error: EscapeError,
27) -> Option<ErrorGuaranteed> {
28    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_parse/src/lexer/unescape_error_reporting.rs:28",
                        "rustc_parse::lexer::unescape_error_reporting",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_parse/src/lexer/unescape_error_reporting.rs"),
                        ::tracing_core::__macro_support::Option::Some(28u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_parse::lexer::unescape_error_reporting"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("emit_unescape_error: {0:?}, {1:?}, {2:?}, {3:?}, {4:?}",
                                                    lit, full_lit_span, mode, range, error) as
                                            &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!(
29        "emit_unescape_error: {:?}, {:?}, {:?}, {:?}, {:?}",
30        lit, full_lit_span, mode, range, error
31    );
32    let last_char = || {
33        let c = lit[range.clone()].chars().next_back().unwrap();
34        let span = err_span.with_lo(err_span.hi() - BytePos(c.len_utf8() as u32));
35        (c, span)
36    };
37    Some(match error {
38        EscapeError::LoneSurrogateUnicodeEscape => {
39            dcx.emit_err(UnescapeError::InvalidUnicodeEscape { span: err_span, surrogate: true })
40        }
41        EscapeError::OutOfRangeUnicodeEscape => {
42            dcx.emit_err(UnescapeError::InvalidUnicodeEscape { span: err_span, surrogate: false })
43        }
44        EscapeError::MoreThanOneChar => {
45            use unicode_normalization::UnicodeNormalization;
46            use unicode_normalization::char::is_combining_mark;
47            let mut sugg = None;
48            let mut note = None;
49
50            let lit_chars = lit.chars().collect::<Vec<_>>();
51            let (first, rest) = lit_chars.split_first().unwrap();
52            if rest.iter().copied().all(is_combining_mark) {
53                let normalized = lit.nfc().to_string();
54                if normalized.chars().count() == 1 {
55                    let ch = normalized.chars().next().unwrap().escape_default().to_string();
56                    sugg = Some(MoreThanOneCharSugg::NormalizedForm {
57                        span: err_span,
58                        ch,
59                        normalized,
60                    });
61                }
62                let escaped_marks =
63                    rest.iter().map(|c| c.escape_default().to_string()).collect::<Vec<_>>();
64                note = Some(MoreThanOneCharNote::AllCombining {
65                    span: err_span,
66                    chr: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}", first))
    })format!("{first}"),
67                    len: escaped_marks.len(),
68                    escaped_marks: escaped_marks.join(""),
69                });
70            } else {
71                let printable: Vec<char> = lit
72                    .chars()
73                    .filter(|&x| {
74                        unicode_width::UnicodeWidthChar::width(x).unwrap_or(0) != 0
75                            && !x.is_whitespace()
76                    })
77                    .collect();
78
79                if let &[ch] = printable.as_slice() {
80                    sugg = Some(MoreThanOneCharSugg::RemoveNonPrinting {
81                        span: err_span,
82                        ch: ch.to_string(),
83                    });
84                    note = Some(MoreThanOneCharNote::NonPrinting {
85                        span: err_span,
86                        escaped: lit.escape_default().to_string(),
87                    });
88                }
89            };
90            let sugg = sugg.unwrap_or_else(|| {
91                let prefix = mode.prefix_noraw();
92                let mut escaped = String::with_capacity(lit.len());
93                let mut in_escape = false;
94                for c in lit.chars() {
95                    match c {
96                        '\\' => in_escape = !in_escape,
97                        '"' if !in_escape => escaped.push('\\'),
98                        _ => in_escape = false,
99                    }
100                    escaped.push(c);
101                }
102                if escaped.len() != lit.len() || full_lit_span.is_empty() {
103                    let sugg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}\"{1}\"", prefix, escaped))
    })format!("{prefix}\"{escaped}\"");
104                    MoreThanOneCharSugg::QuotesFull {
105                        span: full_lit_span,
106                        is_byte: mode == Mode::Byte,
107                        sugg,
108                    }
109                } else {
110                    MoreThanOneCharSugg::Quotes {
111                        start: full_lit_span
112                            .with_hi(full_lit_span.lo() + BytePos((prefix.len() + 1) as u32)),
113                        end: full_lit_span.with_lo(full_lit_span.hi() - BytePos(1)),
114                        is_byte: mode == Mode::Byte,
115                        prefix,
116                    }
117                }
118            });
119            dcx.emit_err(UnescapeError::MoreThanOneChar {
120                span: full_lit_span,
121                note,
122                suggestion: sugg,
123            })
124        }
125        EscapeError::EscapeOnlyChar => {
126            let (c, char_span) = last_char();
127            dcx.emit_err(UnescapeError::EscapeOnlyChar {
128                span: err_span,
129                char_span,
130                escaped_sugg: c.escape_default().to_string(),
131                escaped_msg: escaped_char(c),
132                byte: mode == Mode::Byte,
133            })
134        }
135        EscapeError::BareCarriageReturn => {
136            let double_quotes = mode.in_double_quotes();
137            dcx.emit_err(UnescapeError::BareCr { span: err_span, double_quotes })
138        }
139        EscapeError::BareCarriageReturnInRawString => {
140            if !mode.in_double_quotes() {
    ::core::panicking::panic("assertion failed: mode.in_double_quotes()")
};assert!(mode.in_double_quotes());
141            dcx.emit_err(UnescapeError::BareCrRawString(err_span))
142        }
143        EscapeError::InvalidEscape => {
144            let (c, span) = last_char();
145
146            let label = if mode == Mode::Byte || mode == Mode::ByteStr {
147                "unknown byte escape"
148            } else {
149                "unknown character escape"
150            };
151            let ec = escaped_char(c);
152            let mut diag = dcx.struct_span_err(span, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}: `{1}`", label, ec))
    })format!("{label}: `{ec}`"));
153            diag.span_label(span, label);
154            if c == '{' || c == '}' && #[allow(non_exhaustive_omitted_patterns)] match mode {
    Mode::Str | Mode::RawStr => true,
    _ => false,
}matches!(mode, Mode::Str | Mode::RawStr) {
155                diag.help(
156                    "if used in a formatting string, curly braces are escaped with `{{` and `}}`",
157                );
158            } else if c == '\r' {
159                diag.help(
160                    "this is an isolated carriage return; consider checking your editor and \
161                     version control settings",
162                );
163            } else {
164                if mode == Mode::Str || mode == Mode::Char {
165                    diag.span_suggestion(
166                        full_lit_span,
167                        "if you meant to write a literal backslash (perhaps escaping in a regular expression), consider a raw string literal",
168                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("r\"{0}\"", lit))
    })format!("r\"{lit}\""),
169                        Applicability::MaybeIncorrect,
170                    );
171                }
172
173                diag.help(
174                    "for more information, visit \
175                     <https://doc.rust-lang.org/reference/tokens.html#literals>",
176                );
177
178                foreign_escape_suggestion(&mut diag, (&ec, span), err_span);
179            }
180            diag.emit()
181        }
182        EscapeError::TooShortHexEscape => dcx.emit_err(UnescapeError::TooShortHexEscape(err_span)),
183        EscapeError::InvalidCharInHexEscape | EscapeError::InvalidCharInUnicodeEscape => {
184            let (c, span) = last_char();
185            let is_hex = error == EscapeError::InvalidCharInHexEscape;
186            let ch = escaped_char(c);
187            dcx.emit_err(UnescapeError::InvalidCharInEscape { span, is_hex, ch })
188        }
189        EscapeError::NonAsciiCharInByte => {
190            let (c, span) = last_char();
191            let desc = match mode {
192                Mode::Byte => "byte literal",
193                Mode::ByteStr => "byte string literal",
194                Mode::RawByteStr => "raw byte string literal",
195                _ => {
    ::core::panicking::panic_fmt(format_args!("non-is_byte literal paired with NonAsciiCharInByte"));
}panic!("non-is_byte literal paired with NonAsciiCharInByte"),
196            };
197            let mut err = dcx.struct_span_err(span, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("non-ASCII character in {0}", desc))
    })format!("non-ASCII character in {desc}"));
198            let postfix = if unicode_width::UnicodeWidthChar::width(c).unwrap_or(1) == 0 {
199                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(" but is {0:?}", c))
    })format!(" but is {c:?}")
200            } else {
201                String::new()
202            };
203            err.span_label(span, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("must be ASCII{0}", postfix))
    })format!("must be ASCII{postfix}"));
204            // Note: the \\xHH suggestions are not given for raw byte string
205            // literals, because they are araw and so cannot use any escapes.
206            if (c as u32) <= 0xFF && mode != Mode::RawByteStr {
207                err.span_suggestion(
208                    span,
209                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if you meant to use the unicode code point for {0:?}, use a \\xHH escape",
                c))
    })format!(
210                        "if you meant to use the unicode code point for {c:?}, use a \\xHH escape"
211                    ),
212                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\\x{0:X}", c as u32))
    })format!("\\x{:X}", c as u32),
213                    Applicability::MaybeIncorrect,
214                );
215            } else if mode == Mode::Byte {
216                err.span_label(span, "this multibyte character does not fit into a single byte");
217            } else if mode != Mode::RawByteStr {
218                let mut utf8 = String::new();
219                utf8.push(c);
220                err.span_suggestion(
221                    span,
222                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if you meant to use the UTF-8 encoding of {0:?}, use \\xHH escapes",
                c))
    })format!("if you meant to use the UTF-8 encoding of {c:?}, use \\xHH escapes"),
223                    utf8.as_bytes()
224                        .iter()
225                        .map(|b: &u8| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\\x{0:X}", *b))
    })format!("\\x{:X}", *b))
226                        .fold("".to_string(), |a, c| a + &c),
227                    Applicability::MaybeIncorrect,
228                );
229            }
230            err.emit()
231        }
232        EscapeError::OutOfRangeHexEscape => {
233            let mut err = dcx.struct_span_err(err_span, "out of range hex escape");
234            err.span_label(err_span, "must be a character in the range [\\x00-\\x7f]");
235
236            let escape_str = &lit[range];
237            if lit.len() <= 4
238                && escape_str.len() == 4
239                && escape_str.starts_with("\\x")
240                && let Ok(value) = u8::from_str_radix(&escape_str[2..4], 16)
241                && #[allow(non_exhaustive_omitted_patterns)] match mode {
    Mode::Char | Mode::Str => true,
    _ => false,
}matches!(mode, Mode::Char | Mode::Str)
242            {
243                err.help(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if you want to write a byte literal, use `b\'{0}\'`",
                escape_str))
    })format!("if you want to write a byte literal, use `b'{}'`", escape_str));
244                err.help(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if you want to write a Unicode character, use `\'\\u{{{0:X}}}\'`",
                value))
    })format!(
245                    "if you want to write a Unicode character, use `'\\u{{{:X}}}'`",
246                    value
247                ));
248            }
249
250            err.emit()
251        }
252        EscapeError::LeadingUnderscoreUnicodeEscape => {
253            let (c, span) = last_char();
254            dcx.emit_err(UnescapeError::LeadingUnderscoreUnicodeEscape {
255                span,
256                ch: escaped_char(c),
257            })
258        }
259        EscapeError::OverlongUnicodeEscape => {
260            dcx.emit_err(UnescapeError::OverlongUnicodeEscape(err_span))
261        }
262        EscapeError::UnclosedUnicodeEscape => {
263            dcx.emit_err(UnescapeError::UnclosedUnicodeEscape(err_span, err_span.shrink_to_hi()))
264        }
265        EscapeError::NoBraceInUnicodeEscape => {
266            let mut suggestion = "\\u{".to_owned();
267            let mut suggestion_len = 0;
268            let (c, char_span) = last_char();
269            let chars = once(c).chain(lit[range.end..].chars());
270            for c in chars.take(6).take_while(|c| c.is_digit(16)) {
271                suggestion.push(c);
272                suggestion_len += c.len_utf8();
273            }
274
275            let (label, sub) = if suggestion_len > 0 {
276                suggestion.push('}');
277                let hi = char_span.lo() + BytePos(suggestion_len as u32);
278                (None, NoBraceUnicodeSub::Suggestion { span: err_span.with_hi(hi), suggestion })
279            } else {
280                (Some(err_span), NoBraceUnicodeSub::Help)
281            };
282            dcx.emit_err(UnescapeError::NoBraceInUnicodeEscape { span: err_span, label, sub })
283        }
284        EscapeError::UnicodeEscapeInByte => {
285            dcx.emit_err(UnescapeError::UnicodeEscapeInByte(err_span))
286        }
287        EscapeError::EmptyUnicodeEscape => {
288            dcx.emit_err(UnescapeError::EmptyUnicodeEscape(err_span))
289        }
290        EscapeError::ZeroChars => dcx.emit_err(UnescapeError::ZeroChars(err_span)),
291        EscapeError::LoneSlash => dcx.emit_err(UnescapeError::LoneSlash(err_span)),
292        EscapeError::NulInCStr => dcx.emit_err(UnescapeError::NulInCStr { span: err_span }),
293        EscapeError::UnskippedWhitespaceWarning => {
294            let (c, char_span) = last_char();
295            dcx.emit_warn(UnescapeError::UnskippedWhitespace {
296                span: err_span,
297                ch: escaped_char(c),
298                char_span,
299            });
300            return None;
301        }
302        EscapeError::MultipleSkippedLinesWarning => {
303            dcx.emit_warn(UnescapeError::MultipleSkippedLinesWarning(err_span));
304            return None;
305        }
306    })
307}
308
309/// Add additional suggestions for escapes that are supported by C.
310fn foreign_escape_suggestion(
311    diag: &mut Diag<'_>,
312    (escaped_char, escape_span): (&str, Span),
313    err_span: Span,
314) {
315    if escaped_char == "?" {
316        diag.span_suggestion(
317            err_span,
318            "if you meant to write a literal question mark, don't escape the character",
319            "?",
320            Applicability::MaybeIncorrect,
321        );
322        return;
323    }
324
325    if u8::from_str_radix(escaped_char, 8).is_ok() {
326        diag.help(r"if you meant to write an ASCII control code, use a `\xNN` hex escape");
327        return;
328    }
329
330    let (name, hex) = match escaped_char {
331        "a" => ("an audible bell", "07"),
332        "b" => ("a backspace", "08"),
333        "f" => ("a form feed", "0C"),
334        "v" => ("a vertical tab", "0B"),
335        "e" => ("an ANSI escape sequence", "1B"),
336        _ => return,
337    };
338
339    diag.span_suggestion(
340        escape_span,
341        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if you meant to write {0}, use a hex escape",
                name))
    })format!("if you meant to write {name}, use a hex escape"),
342        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("x{0}", hex))
    })format!("x{hex}"),
343        Applicability::MaybeIncorrect,
344    );
345}
346
347/// Pushes a character to a message string for error reporting
348pub(crate) fn escaped_char(c: char) -> String {
349    match c {
350        '\u{20}'..='\u{7e}' => {
351            // Don't escape \, ' or " for user-facing messages
352            c.to_string()
353        }
354        _ => c.escape_default().to_string(),
355    }
356}