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