1use 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 lit: &str,
19 full_lit_span: Span,
21 err_span: Span,
23 mode: Mode,
24 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 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
309fn 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
347pub(crate) fn escaped_char(c: char) -> String {
349 match c {
350 '\u{20}'..='\u{7e}' => {
351 c.to_string()
353 }
354 _ => c.escape_default().to_string(),
355 }
356}