Rollup merge of #104193 - TaKO8Ki:fix-104142, r=cjgillot
Shift no characters when using raw string literals Fixes #104142 Given the following code: ```rust fn main() { println!(r#"\'\'\'\'\'\'\'\'\'\'\'\'\'\'}"#); } ``` The current output is: ``` error: invalid format string: unmatched `}` found --> src/main.rs:2:59 | 2 | println!(r#"\'\'\'\'\'\'\'\'\'\'\'\'\'\'}"#); //~ ERROR invalid format string: unmatched `}` found | ^ unmatched `}` in format string | = note: if you intended to print `}`, you can escape it using `}}` error: could not compile `debug_playground` due to previous error ``` The output should look like: ``` error: invalid format string: unmatched `}` found --> src/main.rs:2:45 | 2 | println!(r#"\'\'\'\'\'\'\'\'\'\'\'\'\'\'}"#); //~ ERROR invalid format string: unmatched `}` found | ^ unmatched `}` in format string | = note: if you intended to print `}`, you can escape it using `}}` error: could not compile `debug_playground` due to previous error ``` This pull request fixes the wrong span for `invalid format string` error and also solves the ICE.
This commit is contained in:
commit
88a19197b9
@ -818,96 +818,94 @@ fn find_skips_from_snippet(
|
||||
_ => return (vec![], false),
|
||||
};
|
||||
|
||||
fn find_skips(snippet: &str, is_raw: bool) -> Vec<usize> {
|
||||
let mut s = snippet.char_indices();
|
||||
let mut skips = vec![];
|
||||
while let Some((pos, c)) = s.next() {
|
||||
match (c, s.clone().next()) {
|
||||
// skip whitespace and empty lines ending in '\\'
|
||||
('\\', Some((next_pos, '\n'))) if !is_raw => {
|
||||
skips.push(pos);
|
||||
skips.push(next_pos);
|
||||
let _ = s.next();
|
||||
|
||||
while let Some((pos, c)) = s.clone().next() {
|
||||
if matches!(c, ' ' | '\n' | '\t') {
|
||||
skips.push(pos);
|
||||
let _ = s.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
('\\', Some((next_pos, 'n' | 't' | 'r' | '0' | '\\' | '\'' | '\"'))) => {
|
||||
skips.push(next_pos);
|
||||
let _ = s.next();
|
||||
}
|
||||
('\\', Some((_, 'x'))) if !is_raw => {
|
||||
for _ in 0..3 {
|
||||
// consume `\xAB` literal
|
||||
if let Some((pos, _)) = s.next() {
|
||||
skips.push(pos);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
('\\', Some((_, 'u'))) if !is_raw => {
|
||||
if let Some((pos, _)) = s.next() {
|
||||
skips.push(pos);
|
||||
}
|
||||
if let Some((next_pos, next_c)) = s.next() {
|
||||
if next_c == '{' {
|
||||
// consume up to 6 hexanumeric chars
|
||||
let digits_len =
|
||||
s.clone().take(6).take_while(|(_, c)| c.is_digit(16)).count();
|
||||
|
||||
let len_utf8 = s
|
||||
.as_str()
|
||||
.get(..digits_len)
|
||||
.and_then(|digits| u32::from_str_radix(digits, 16).ok())
|
||||
.and_then(char::from_u32)
|
||||
.map_or(1, char::len_utf8);
|
||||
|
||||
// Skip the digits, for chars that encode to more than 1 utf-8 byte
|
||||
// exclude as many digits as it is greater than 1 byte
|
||||
//
|
||||
// So for a 3 byte character, exclude 2 digits
|
||||
let required_skips =
|
||||
digits_len.saturating_sub(len_utf8.saturating_sub(1));
|
||||
|
||||
// skip '{' and '}' also
|
||||
for pos in (next_pos..).take(required_skips + 2) {
|
||||
skips.push(pos)
|
||||
}
|
||||
|
||||
s.nth(digits_len);
|
||||
} else if next_c.is_digit(16) {
|
||||
skips.push(next_pos);
|
||||
// We suggest adding `{` and `}` when appropriate, accept it here as if
|
||||
// it were correct
|
||||
let mut i = 0; // consume up to 6 hexanumeric chars
|
||||
while let (Some((next_pos, c)), _) = (s.next(), i < 6) {
|
||||
if c.is_digit(16) {
|
||||
skips.push(next_pos);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
skips
|
||||
if str_style.is_some() {
|
||||
return (vec![], true);
|
||||
}
|
||||
|
||||
let r_start = str_style.map_or(0, |r| r + 1);
|
||||
let r_end = str_style.unwrap_or(0);
|
||||
let s = &snippet[r_start + 1..snippet.len() - r_end - 1];
|
||||
(find_skips(s, str_style.is_some()), true)
|
||||
let snippet = &snippet[1..snippet.len() - 1];
|
||||
|
||||
let mut s = snippet.char_indices();
|
||||
let mut skips = vec![];
|
||||
while let Some((pos, c)) = s.next() {
|
||||
match (c, s.clone().next()) {
|
||||
// skip whitespace and empty lines ending in '\\'
|
||||
('\\', Some((next_pos, '\n'))) => {
|
||||
skips.push(pos);
|
||||
skips.push(next_pos);
|
||||
let _ = s.next();
|
||||
|
||||
while let Some((pos, c)) = s.clone().next() {
|
||||
if matches!(c, ' ' | '\n' | '\t') {
|
||||
skips.push(pos);
|
||||
let _ = s.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
('\\', Some((next_pos, 'n' | 't' | 'r' | '0' | '\\' | '\'' | '\"'))) => {
|
||||
skips.push(next_pos);
|
||||
let _ = s.next();
|
||||
}
|
||||
('\\', Some((_, 'x'))) => {
|
||||
for _ in 0..3 {
|
||||
// consume `\xAB` literal
|
||||
if let Some((pos, _)) = s.next() {
|
||||
skips.push(pos);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
('\\', Some((_, 'u'))) => {
|
||||
if let Some((pos, _)) = s.next() {
|
||||
skips.push(pos);
|
||||
}
|
||||
if let Some((next_pos, next_c)) = s.next() {
|
||||
if next_c == '{' {
|
||||
// consume up to 6 hexanumeric chars
|
||||
let digits_len =
|
||||
s.clone().take(6).take_while(|(_, c)| c.is_digit(16)).count();
|
||||
|
||||
let len_utf8 = s
|
||||
.as_str()
|
||||
.get(..digits_len)
|
||||
.and_then(|digits| u32::from_str_radix(digits, 16).ok())
|
||||
.and_then(char::from_u32)
|
||||
.map_or(1, char::len_utf8);
|
||||
|
||||
// Skip the digits, for chars that encode to more than 1 utf-8 byte
|
||||
// exclude as many digits as it is greater than 1 byte
|
||||
//
|
||||
// So for a 3 byte character, exclude 2 digits
|
||||
let required_skips = digits_len.saturating_sub(len_utf8.saturating_sub(1));
|
||||
|
||||
// skip '{' and '}' also
|
||||
for pos in (next_pos..).take(required_skips + 2) {
|
||||
skips.push(pos)
|
||||
}
|
||||
|
||||
s.nth(digits_len);
|
||||
} else if next_c.is_digit(16) {
|
||||
skips.push(next_pos);
|
||||
// We suggest adding `{` and `}` when appropriate, accept it here as if
|
||||
// it were correct
|
||||
let mut i = 0; // consume up to 6 hexanumeric chars
|
||||
while let (Some((next_pos, c)), _) = (s.next(), i < 6) {
|
||||
if c.is_digit(16) {
|
||||
skips.push(next_pos);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
(skips, true)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
3
src/test/ui/fmt/format-raw-string-error.rs
Normal file
3
src/test/ui/fmt/format-raw-string-error.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!(r#"\'\'\'\'\'\'\'\'\'\'\'\'\'\'}"#); //~ ERROR invalid format string: unmatched `}` found
|
||||
}
|
10
src/test/ui/fmt/format-raw-string-error.stderr
Normal file
10
src/test/ui/fmt/format-raw-string-error.stderr
Normal file
@ -0,0 +1,10 @@
|
||||
error: invalid format string: unmatched `}` found
|
||||
--> $DIR/format-raw-string-error.rs:2:45
|
||||
|
|
||||
LL | println!(r#"\'\'\'\'\'\'\'\'\'\'\'\'\'\'}"#);
|
||||
| ^ unmatched `}` in format string
|
||||
|
|
||||
= note: if you intended to print `}`, you can escape it using `}}`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
6
src/test/ui/fmt/issue-104142.rs
Normal file
6
src/test/ui/fmt/issue-104142.rs
Normal file
@ -0,0 +1,6 @@
|
||||
fn main() {
|
||||
println!(
|
||||
r#"
|
||||
\"\'}、"# //~ ERROR invalid format string: unmatched `}` found
|
||||
);
|
||||
}
|
10
src/test/ui/fmt/issue-104142.stderr
Normal file
10
src/test/ui/fmt/issue-104142.stderr
Normal file
@ -0,0 +1,10 @@
|
||||
error: invalid format string: unmatched `}` found
|
||||
--> $DIR/issue-104142.rs:4:9
|
||||
|
|
||||
LL | \"\'}、"#
|
||||
| ^ unmatched `}` in format string
|
||||
|
|
||||
= note: if you intended to print `}`, you can escape it using `}}`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
Loading…
x
Reference in New Issue
Block a user