1 //! Utilities for rendering escape sequence errors as diagnostics.
6 use rustc_errors::{Applicability, Handler};
7 use rustc_lexer::unescape::{EscapeError, Mode};
8 use rustc_span::{BytePos, Span};
10 use crate::errors::{MoreThanOneCharNote, MoreThanOneCharSugg, NoBraceUnicodeSub, UnescapeError};
12 pub(crate) fn emit_unescape_error(
14 // interior part of the literal, without quotes
16 // full span of the literal, including quotes
17 span_with_quotes: Span,
18 // interior span of the literal, without quotes
21 // range of the error inside `lit`
26 "emit_unescape_error: {:?}, {:?}, {:?}, {:?}, {:?}",
27 lit, span_with_quotes, mode, range, error
30 let c = lit[range.clone()].chars().rev().next().unwrap();
31 let span = span.with_lo(span.hi() - BytePos(c.len_utf8() as u32));
35 EscapeError::LoneSurrogateUnicodeEscape => {
36 handler.emit_err(UnescapeError::InvalidUnicodeEscape { span, surrogate: true });
38 EscapeError::OutOfRangeUnicodeEscape => {
39 handler.emit_err(UnescapeError::InvalidUnicodeEscape { span, surrogate: false });
41 EscapeError::MoreThanOneChar => {
42 use unicode_normalization::{char::is_combining_mark, UnicodeNormalization};
46 let lit_chars = lit.chars().collect::<Vec<_>>();
47 let (first, rest) = lit_chars.split_first().unwrap();
48 if rest.iter().copied().all(is_combining_mark) {
49 let normalized = lit.nfc().to_string();
50 if normalized.chars().count() == 1 {
51 let ch = normalized.chars().next().unwrap().escape_default().to_string();
52 sugg = Some(MoreThanOneCharSugg::NormalizedForm { span, ch, normalized });
55 rest.iter().map(|c| c.escape_default().to_string()).collect::<Vec<_>>();
56 note = Some(MoreThanOneCharNote::AllCombining {
58 chr: format!("{first}"),
59 len: escaped_marks.len(),
60 escaped_marks: escaped_marks.join(""),
63 let printable: Vec<char> = lit
66 unicode_width::UnicodeWidthChar::width(x).unwrap_or(0) != 0
71 if let &[ch] = printable.as_slice() {
73 Some(MoreThanOneCharSugg::RemoveNonPrinting { span, ch: ch.to_string() });
74 note = Some(MoreThanOneCharNote::NonPrinting {
76 escaped: lit.escape_default().to_string(),
80 let sugg = sugg.unwrap_or_else(|| {
81 let is_byte = mode.is_byte();
82 let prefix = if is_byte { "b" } else { "" };
83 let mut escaped = String::with_capacity(lit.len());
84 let mut chrs = lit.chars().peekable();
85 while let Some(first) = chrs.next() {
86 match (first, chrs.peek()) {
87 ('\\', Some('"')) => {
96 (c, _) => escaped.push(c),
99 let sugg = format!("{prefix}\"{escaped}\"");
100 MoreThanOneCharSugg::Quotes { span: span_with_quotes, is_byte, sugg }
102 handler.emit_err(UnescapeError::MoreThanOneChar {
103 span: span_with_quotes,
108 EscapeError::EscapeOnlyChar => {
109 let (c, char_span) = last_char();
110 handler.emit_err(UnescapeError::EscapeOnlyChar {
113 escaped_sugg: c.escape_default().to_string(),
114 escaped_msg: escaped_char(c),
115 byte: mode.is_byte(),
118 EscapeError::BareCarriageReturn => {
119 let double_quotes = mode.in_double_quotes();
120 handler.emit_err(UnescapeError::BareCr { span, double_quotes });
122 EscapeError::BareCarriageReturnInRawString => {
123 assert!(mode.in_double_quotes());
124 handler.emit_err(UnescapeError::BareCrRawString(span));
126 EscapeError::InvalidEscape => {
127 let (c, span) = last_char();
130 if mode.is_byte() { "unknown byte escape" } else { "unknown character escape" };
131 let ec = escaped_char(c);
132 let mut diag = handler.struct_span_err(span, &format!("{}: `{}`", label, ec));
133 diag.span_label(span, label);
134 if c == '{' || c == '}' && !mode.is_byte() {
136 "if used in a formatting string, curly braces are escaped with `{{` and `}}`",
138 } else if c == '\r' {
140 "this is an isolated carriage return; consider checking your editor and \
141 version control settings",
145 diag.span_suggestion(
147 "if you meant to write a literal backslash (perhaps escaping in a regular expression), consider a raw string literal",
148 format!("r\"{}\"", lit),
149 Applicability::MaybeIncorrect,
154 "for more information, visit \
155 <https://static.rust-lang.org/doc/master/reference.html#literals>",
160 EscapeError::TooShortHexEscape => {
161 handler.emit_err(UnescapeError::TooShortHexEscape(span));
163 EscapeError::InvalidCharInHexEscape | EscapeError::InvalidCharInUnicodeEscape => {
164 let (c, span) = last_char();
165 let is_hex = error == EscapeError::InvalidCharInHexEscape;
166 let ch = escaped_char(c);
167 handler.emit_err(UnescapeError::InvalidCharInEscape { span, is_hex, ch });
169 EscapeError::NonAsciiCharInByte => {
170 let (c, span) = last_char();
171 let desc = match mode {
172 Mode::Byte => "byte literal",
173 Mode::ByteStr => "byte string literal",
174 Mode::RawByteStr => "raw byte string literal",
175 _ => panic!("non-is_byte literal paired with NonAsciiCharInByte"),
177 let mut err = handler.struct_span_err(span, format!("non-ASCII character in {}", desc));
178 let postfix = if unicode_width::UnicodeWidthChar::width(c).unwrap_or(1) == 0 {
179 format!(" but is {:?}", c)
183 err.span_label(span, &format!("must be ASCII{}", postfix));
184 // Note: the \\xHH suggestions are not given for raw byte string
185 // literals, because they are araw and so cannot use any escapes.
186 if (c as u32) <= 0xFF && mode != Mode::RawByteStr {
190 "if you meant to use the unicode code point for {:?}, use a \\xHH escape",
193 format!("\\x{:X}", c as u32),
194 Applicability::MaybeIncorrect,
196 } else if mode == Mode::Byte {
197 err.span_label(span, "this multibyte character does not fit into a single byte");
198 } else if mode != Mode::RawByteStr {
199 let mut utf8 = String::new();
204 "if you meant to use the UTF-8 encoding of {:?}, use \\xHH escapes",
209 .map(|b: &u8| format!("\\x{:X}", *b))
210 .fold("".to_string(), |a, c| a + &c),
211 Applicability::MaybeIncorrect,
216 EscapeError::OutOfRangeHexEscape => {
217 handler.emit_err(UnescapeError::OutOfRangeHexEscape(span));
219 EscapeError::LeadingUnderscoreUnicodeEscape => {
220 let (c, span) = last_char();
221 handler.emit_err(UnescapeError::LeadingUnderscoreUnicodeEscape {
226 EscapeError::OverlongUnicodeEscape => {
227 handler.emit_err(UnescapeError::OverlongUnicodeEscape(span));
229 EscapeError::UnclosedUnicodeEscape => {
230 handler.emit_err(UnescapeError::UnclosedUnicodeEscape(span, span.shrink_to_hi()));
232 EscapeError::NoBraceInUnicodeEscape => {
233 let mut suggestion = "\\u{".to_owned();
234 let mut suggestion_len = 0;
235 let (c, char_span) = last_char();
236 let chars = once(c).chain(lit[range.end..].chars());
237 for c in chars.take(6).take_while(|c| c.is_digit(16)) {
239 suggestion_len += c.len_utf8();
242 let (label, sub) = if suggestion_len > 0 {
243 suggestion.push('}');
244 let hi = char_span.lo() + BytePos(suggestion_len as u32);
245 (None, NoBraceUnicodeSub::Suggestion { span: span.with_hi(hi), suggestion })
247 (Some(span), NoBraceUnicodeSub::Help)
249 handler.emit_err(UnescapeError::NoBraceInUnicodeEscape { span, label, sub });
251 EscapeError::UnicodeEscapeInByte => {
252 handler.emit_err(UnescapeError::UnicodeEscapeInByte(span));
254 EscapeError::EmptyUnicodeEscape => {
255 handler.emit_err(UnescapeError::EmptyUnicodeEscape(span));
257 EscapeError::ZeroChars => {
258 handler.emit_err(UnescapeError::ZeroChars(span));
260 EscapeError::LoneSlash => {
261 handler.emit_err(UnescapeError::LoneSlash(span));
263 EscapeError::UnskippedWhitespaceWarning => {
264 let (c, char_span) = last_char();
265 handler.emit_warning(UnescapeError::UnskippedWhitespace {
271 EscapeError::MultipleSkippedLinesWarning => {
272 handler.emit_warning(UnescapeError::MultipleSkippedLinesWarning(span));
277 /// Pushes a character to a message string for error reporting
278 pub(crate) fn escaped_char(c: char) -> String {
280 '\u{20}'..='\u{7e}' => {
281 // Don't escape \, ' or " for user-facing messages
284 _ => c.escape_default().to_string(),