Add warning when whitespace is not skipped after an escaped newline.
This commit is contained in:
parent
1195bea5a7
commit
5d59b4412e
compiler
rustc_ast/src/util
rustc_lexer/src
rustc_parse/src/lexer
@ -63,7 +63,11 @@ impl LitKind {
|
|||||||
unescape_literal(&s, Mode::Str, &mut |_, unescaped_char| {
|
unescape_literal(&s, Mode::Str, &mut |_, unescaped_char| {
|
||||||
match unescaped_char {
|
match unescaped_char {
|
||||||
Ok(c) => buf.push(c),
|
Ok(c) => buf.push(c),
|
||||||
Err(_) => error = Err(LitError::LexerError),
|
Err(err) => {
|
||||||
|
if err.is_fatal() {
|
||||||
|
error = Err(LitError::LexerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
error?;
|
error?;
|
||||||
@ -83,7 +87,11 @@ impl LitKind {
|
|||||||
unescape_literal(&s, Mode::RawStr, &mut |_, unescaped_char| {
|
unescape_literal(&s, Mode::RawStr, &mut |_, unescaped_char| {
|
||||||
match unescaped_char {
|
match unescaped_char {
|
||||||
Ok(c) => buf.push(c),
|
Ok(c) => buf.push(c),
|
||||||
Err(_) => error = Err(LitError::LexerError),
|
Err(err) => {
|
||||||
|
if err.is_fatal() {
|
||||||
|
error = Err(LitError::LexerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
error?;
|
error?;
|
||||||
@ -100,7 +108,11 @@ impl LitKind {
|
|||||||
unescape_byte_literal(&s, Mode::ByteStr, &mut |_, unescaped_byte| {
|
unescape_byte_literal(&s, Mode::ByteStr, &mut |_, unescaped_byte| {
|
||||||
match unescaped_byte {
|
match unescaped_byte {
|
||||||
Ok(c) => buf.push(c),
|
Ok(c) => buf.push(c),
|
||||||
Err(_) => error = Err(LitError::LexerError),
|
Err(err) => {
|
||||||
|
if err.is_fatal() {
|
||||||
|
error = Err(LitError::LexerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
error?;
|
error?;
|
||||||
@ -114,7 +126,11 @@ impl LitKind {
|
|||||||
unescape_byte_literal(&s, Mode::RawByteStr, &mut |_, unescaped_byte| {
|
unescape_byte_literal(&s, Mode::RawByteStr, &mut |_, unescaped_byte| {
|
||||||
match unescaped_byte {
|
match unescaped_byte {
|
||||||
Ok(c) => buf.push(c),
|
Ok(c) => buf.push(c),
|
||||||
Err(_) => error = Err(LitError::LexerError),
|
Err(err) => {
|
||||||
|
if err.is_fatal() {
|
||||||
|
error = Err(LitError::LexerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
error?;
|
error?;
|
||||||
|
@ -7,7 +7,7 @@ use std::str::Chars;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
/// Errors that can occur during string unescaping.
|
/// Errors and warnings that can occur during string unescaping.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum EscapeError {
|
pub enum EscapeError {
|
||||||
/// Expected 1 char, but 0 were found.
|
/// Expected 1 char, but 0 were found.
|
||||||
@ -56,6 +56,20 @@ pub enum EscapeError {
|
|||||||
NonAsciiCharInByte,
|
NonAsciiCharInByte,
|
||||||
/// Non-ascii character in byte string literal.
|
/// Non-ascii character in byte string literal.
|
||||||
NonAsciiCharInByteString,
|
NonAsciiCharInByteString,
|
||||||
|
|
||||||
|
/// After a line ending with '\', the next line contains whitespace
|
||||||
|
/// characters that are not skipped.
|
||||||
|
UnskippedWhitespaceWarning,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EscapeError {
|
||||||
|
/// Returns true for actual errors, as opposed to warnings.
|
||||||
|
pub fn is_fatal(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
EscapeError::UnskippedWhitespaceWarning => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes a contents of a literal (without quotes) and produces a
|
/// Takes a contents of a literal (without quotes) and produces a
|
||||||
@ -283,7 +297,7 @@ where
|
|||||||
// if unescaped '\' character is followed by '\n'.
|
// if unescaped '\' character is followed by '\n'.
|
||||||
// For details see [Rust language reference]
|
// For details see [Rust language reference]
|
||||||
// (https://doc.rust-lang.org/reference/tokens.html#string-literals).
|
// (https://doc.rust-lang.org/reference/tokens.html#string-literals).
|
||||||
skip_ascii_whitespace(&mut chars);
|
skip_ascii_whitespace(&mut chars, start, callback);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ => scan_escape(first_char, &mut chars, mode),
|
_ => scan_escape(first_char, &mut chars, mode),
|
||||||
@ -297,13 +311,25 @@ where
|
|||||||
callback(start..end, unescaped_char);
|
callback(start..end, unescaped_char);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip_ascii_whitespace(chars: &mut Chars<'_>) {
|
fn skip_ascii_whitespace<F>(chars: &mut Chars<'_>, start: usize, callback: &mut F)
|
||||||
|
where
|
||||||
|
F: FnMut(Range<usize>, Result<char, EscapeError>),
|
||||||
|
{
|
||||||
let str = chars.as_str();
|
let str = chars.as_str();
|
||||||
let first_non_space = str
|
let first_non_space = str
|
||||||
.bytes()
|
.bytes()
|
||||||
.position(|b| b != b' ' && b != b'\t' && b != b'\n' && b != b'\r')
|
.position(|b| b != b' ' && b != b'\t' && b != b'\n' && b != b'\r')
|
||||||
.unwrap_or(str.len());
|
.unwrap_or(str.len());
|
||||||
*chars = str[first_non_space..].chars()
|
let tail = &str[first_non_space..];
|
||||||
|
if let Some(c) = tail.chars().nth(0) {
|
||||||
|
// For error reporting, we would like the span to contain the character that was not
|
||||||
|
// skipped. The +1 is necessary to account for the leading \ that started the escape.
|
||||||
|
let end = start + first_non_space + c.len_utf8() + 1;
|
||||||
|
if c.is_whitespace() {
|
||||||
|
callback(start..end, Err(EscapeError::UnskippedWhitespaceWarning));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*chars = tail.chars();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +98,25 @@ fn test_unescape_char_good() {
|
|||||||
check(r"\u{1F63b}", '😻');
|
check(r"\u{1F63b}", '😻');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unescape_str_warn() {
|
||||||
|
fn check(literal: &str, expected: &[(Range<usize>, Result<char, EscapeError>)]) {
|
||||||
|
let mut unescaped = Vec::with_capacity(literal.len());
|
||||||
|
unescape_literal(literal, Mode::Str, &mut |range, res| unescaped.push((range, res)));
|
||||||
|
assert_eq!(unescaped, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
check(
|
||||||
|
"\\\n \u{a0} x",
|
||||||
|
&[
|
||||||
|
(0..5, Err(EscapeError::UnskippedWhitespaceWarning)),
|
||||||
|
(3..5, Ok('\u{a0}')),
|
||||||
|
(5..6, Ok(' ')),
|
||||||
|
(6..7, Ok('x')),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unescape_str_good() {
|
fn test_unescape_str_good() {
|
||||||
fn check(literal_text: &str, expected: &str) {
|
fn check(literal_text: &str, expected: &str) {
|
||||||
|
@ -253,6 +253,12 @@ pub(crate) fn emit_unescape_error(
|
|||||||
let msg = "invalid trailing slash in literal";
|
let msg = "invalid trailing slash in literal";
|
||||||
handler.struct_span_err(span, msg).span_label(span, msg).emit();
|
handler.struct_span_err(span, msg).span_label(span, msg).emit();
|
||||||
}
|
}
|
||||||
|
EscapeError::UnskippedWhitespaceWarning => {
|
||||||
|
let (c, char_span) = last_char();
|
||||||
|
let msg =
|
||||||
|
format!("non-ASCII whitespace symbol '{}' is not skipped", c.escape_unicode());
|
||||||
|
handler.struct_span_warn(span, &msg).span_label(char_span, &msg).emit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user