Skip to content

Commit

Permalink
Rollup merge of rust-lang#104193 - TaKO8Ki:fix-104142, r=cjgillot
Browse files Browse the repository at this point in the history
Shift no characters when using raw string literals

Fixes rust-lang#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.
  • Loading branch information
matthiaskrgr authored Nov 16, 2022
2 parents 3fd3668 + 9857de2 commit d93d8ee
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 76 deletions.
150 changes: 74 additions & 76 deletions compiler/rustc_parse_format/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
if str_style.is_some() {
return (vec![], true);
}

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;
}
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((_, 'u'))) if !is_raw => {
}
('\\', 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;
}
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)
}
}
}
('\\', 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;
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
}

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)
(skips, true)
}

#[cfg(test)]
Expand Down
3 changes: 3 additions & 0 deletions src/test/ui/fmt/format-raw-string-error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!(r#"\'\'\'\'\'\'\'\'\'\'\'\'\'\'}"#); //~ ERROR invalid format string: unmatched `}` found
}
10 changes: 10 additions & 0 deletions src/test/ui/fmt/format-raw-string-error.stderr
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions src/test/ui/fmt/issue-104142.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
println!(
r#"
\"\'}、"# //~ ERROR invalid format string: unmatched `}` found
);
}
10 changes: 10 additions & 0 deletions src/test/ui/fmt/issue-104142.stderr
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit d93d8ee

Please sign in to comment.