From 3524c3ef4371d0bf4dd03568a004039f18c154f7 Mon Sep 17 00:00:00 2001 From: Sasha Date: Mon, 31 Aug 2020 11:45:50 +0200 Subject: [PATCH] Improve recovery on malformed format call If a comma in a format call is replaced with a similar token, then we emit an error and continue parsing, instead of stopping at this point. --- compiler/rustc_builtin_macros/src/format.rs | 28 ++++++++---- compiler/rustc_parse/src/parser/mod.rs | 4 ++ src/test/ui/codemap_tests/bad-format-args.rs | 2 +- .../ui/codemap_tests/bad-format-args.stderr | 2 +- .../ui/fmt/incorrect-first-separator.stderr | 32 -------------- ...st-separator.rs => incorrect-separator.rs} | 15 +++++-- src/test/ui/fmt/incorrect-separator.stderr | 44 +++++++++++++++++++ src/test/ui/macros/missing-comma.rs | 2 +- src/test/ui/macros/missing-comma.stderr | 2 +- src/test/ui/parser/unicode-quote-chars.rs | 2 +- src/test/ui/parser/unicode-quote-chars.stderr | 2 +- src/tools/clippy/tests/ui/issue-3145.rs | 2 +- src/tools/clippy/tests/ui/issue-3145.stderr | 4 +- 13 files changed, 88 insertions(+), 53 deletions(-) delete mode 100644 src/test/ui/fmt/incorrect-first-separator.stderr rename src/test/ui/fmt/{incorrect-first-separator.rs => incorrect-separator.rs} (55%) create mode 100644 src/test/ui/fmt/incorrect-separator.stderr diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 48506148ed9a7..5d6f791f13719 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -161,14 +161,26 @@ fn parse_args<'a>( while p.token != token::Eof { if !p.eat(&token::Comma) { if first { - // After `format!(""` we always expect *only* a comma... - let mut err = ecx.struct_span_err(p.token.span, "expected token: `,`"); - err.span_label(p.token.span, "expected `,`"); - p.maybe_annotate_with_ascription(&mut err, false); - return Err(err); - } else { - // ...after that delegate to `expect` to also include the other expected tokens. - let _ = p.expect(&token::Comma)?; + p.clear_expected_tokens(); + } + + // `Parser::expect` tries to recover using the + // `Parser::unexpected_try_recover` function. This function is able + // to recover if the expected token is a closing delimiter. + // + // As `,` is not a closing delimiter, it will always return an `Err` + // variant. + let mut err = p.expect(&token::Comma).unwrap_err(); + + match token::TokenKind::Comma.similar_tokens() { + Some(tks) if tks.contains(&p.token.kind) => { + // If a similar token is found, then it may be a typo. We + // consider it as a comma, and continue parsing. + err.emit(); + p.bump(); + } + // Otherwise stop the parsing and return the error. + _ => return Err(err), } } first = false; diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index d67ed74bc9976..999853c4b8cb8 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -1233,6 +1233,10 @@ impl<'a> Parser<'a> { *t == token::OpenDelim(token::Brace) || *t == token::BinOp(token::Star) }) } + + pub fn clear_expected_tokens(&mut self) { + self.expected_tokens.clear(); + } } crate fn make_unclosed_delims_error( diff --git a/src/test/ui/codemap_tests/bad-format-args.rs b/src/test/ui/codemap_tests/bad-format-args.rs index dff248344a53d..e89a45a84f5ce 100644 --- a/src/test/ui/codemap_tests/bad-format-args.rs +++ b/src/test/ui/codemap_tests/bad-format-args.rs @@ -1,5 +1,5 @@ fn main() { format!(); //~ ERROR requires at least a format string argument - format!("" 1); //~ ERROR expected token: `,` + format!("" 1); //~ ERROR expected `,`, found `1` format!("", 1 1); //~ ERROR expected one of } diff --git a/src/test/ui/codemap_tests/bad-format-args.stderr b/src/test/ui/codemap_tests/bad-format-args.stderr index 96d7b07b0e253..5ed023e1f2134 100644 --- a/src/test/ui/codemap_tests/bad-format-args.stderr +++ b/src/test/ui/codemap_tests/bad-format-args.stderr @@ -6,7 +6,7 @@ LL | format!(); | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: expected token: `,` +error: expected `,`, found `1` --> $DIR/bad-format-args.rs:3:16 | LL | format!("" 1); diff --git a/src/test/ui/fmt/incorrect-first-separator.stderr b/src/test/ui/fmt/incorrect-first-separator.stderr deleted file mode 100644 index 60d2a82855e89..0000000000000 --- a/src/test/ui/fmt/incorrect-first-separator.stderr +++ /dev/null @@ -1,32 +0,0 @@ -error: unknown start of token: \u{326} - --> $DIR/incorrect-first-separator.rs:19:28 - | -LL | format!("A number: {}" ̦ iter::once(42).next().unwrap()); - | ^ - -error: expected token: `,` - --> $DIR/incorrect-first-separator.rs:7:27 - | -LL | format!("A number: {}". iter::once(42).next().unwrap()); - | ^ expected `,` - -error: expected token: `,` - --> $DIR/incorrect-first-separator.rs:12:28 - | -LL | format!("A number: {}" / iter::once(42).next().unwrap()); - | ^ expected `,` - -error: expected token: `,` - --> $DIR/incorrect-first-separator.rs:15:27 - | -LL | format!("A number: {}"; iter::once(42).next().unwrap()); - | ^ expected `,` - -error: expected token: `,` - --> $DIR/incorrect-first-separator.rs:19:30 - | -LL | format!("A number: {}" ̦ iter::once(42).next().unwrap()); - | ^^^^ expected `,` - -error: aborting due to 5 previous errors - diff --git a/src/test/ui/fmt/incorrect-first-separator.rs b/src/test/ui/fmt/incorrect-separator.rs similarity index 55% rename from src/test/ui/fmt/incorrect-first-separator.rs rename to src/test/ui/fmt/incorrect-separator.rs index 0b097fdfab81f..b8d2e4a3473b6 100644 --- a/src/test/ui/fmt/incorrect-first-separator.rs +++ b/src/test/ui/fmt/incorrect-separator.rs @@ -5,18 +5,25 @@ use std::iter; fn main() { format!("A number: {}". iter::once(42).next().unwrap()); - //~^ ERROR expected token: `,` + //~^ ERROR expected `,`, found `.` // Other kind of types are also checked: format!("A number: {}" / iter::once(42).next().unwrap()); - //~^ ERROR expected token: `,` + //~^ ERROR expected `,`, found `/` format!("A number: {}"; iter::once(42).next().unwrap()); - //~^ ERROR expected token: `,` + //~^ ERROR expected `,`, found `;` // Note: this character is an COMBINING COMMA BELOW unicode char format!("A number: {}" ̦ iter::once(42).next().unwrap()); - //~^ ERROR expected token: `,` + //~^ ERROR expected `,`, found `iter` //~^^ ERROR unknown start of token: \u{326} + + // Here recovery is tested. + // If the `compile_error!` is emitted, then the parser is able to recover + // from the incorrect first separator. + format!("{}". compile_error!("fail")); + //~^ ERROR expected `,`, found `.` + //~^^ ERROR fail } diff --git a/src/test/ui/fmt/incorrect-separator.stderr b/src/test/ui/fmt/incorrect-separator.stderr new file mode 100644 index 0000000000000..5a3e5515bb939 --- /dev/null +++ b/src/test/ui/fmt/incorrect-separator.stderr @@ -0,0 +1,44 @@ +error: unknown start of token: \u{326} + --> $DIR/incorrect-separator.rs:19:28 + | +LL | format!("A number: {}" ̦ iter::once(42).next().unwrap()); + | ^ + +error: expected `,`, found `.` + --> $DIR/incorrect-separator.rs:7:27 + | +LL | format!("A number: {}". iter::once(42).next().unwrap()); + | ^ expected `,` + +error: expected `,`, found `/` + --> $DIR/incorrect-separator.rs:12:28 + | +LL | format!("A number: {}" / iter::once(42).next().unwrap()); + | ^ expected `,` + +error: expected `,`, found `;` + --> $DIR/incorrect-separator.rs:15:27 + | +LL | format!("A number: {}"; iter::once(42).next().unwrap()); + | ^ expected `,` + +error: expected `,`, found `iter` + --> $DIR/incorrect-separator.rs:19:30 + | +LL | format!("A number: {}" ̦ iter::once(42).next().unwrap()); + | ^^^^ expected `,` + +error: expected `,`, found `.` + --> $DIR/incorrect-separator.rs:26:17 + | +LL | format!("{}". compile_error!("fail")); + | ^ expected `,` + +error: fail + --> $DIR/incorrect-separator.rs:26:19 + | +LL | format!("{}". compile_error!("fail")); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/test/ui/macros/missing-comma.rs b/src/test/ui/macros/missing-comma.rs index 2002fed6c93aa..92f8a77950583 100644 --- a/src/test/ui/macros/missing-comma.rs +++ b/src/test/ui/macros/missing-comma.rs @@ -17,7 +17,7 @@ macro_rules! check { fn main() { println!("{}" a); - //~^ ERROR expected token: `,` + //~^ ERROR expected `,`, found `a` foo!(a b); //~^ ERROR no rules expected the token `b` foo!(a, b, c, d e); diff --git a/src/test/ui/macros/missing-comma.stderr b/src/test/ui/macros/missing-comma.stderr index f96848f8239f7..6da92bdea19e0 100644 --- a/src/test/ui/macros/missing-comma.stderr +++ b/src/test/ui/macros/missing-comma.stderr @@ -1,4 +1,4 @@ -error: expected token: `,` +error: expected `,`, found `a` --> $DIR/missing-comma.rs:19:19 | LL | println!("{}" a); diff --git a/src/test/ui/parser/unicode-quote-chars.rs b/src/test/ui/parser/unicode-quote-chars.rs index 1812dad81afc3..eeaea3628bbe5 100644 --- a/src/test/ui/parser/unicode-quote-chars.rs +++ b/src/test/ui/parser/unicode-quote-chars.rs @@ -6,5 +6,5 @@ fn main() { //~^^ HELP Unicode characters '“' (Left Double Quotation Mark) and '”' (Right Double Quotation Mark) look like '"' (Quotation Mark), but are not //~^^^ ERROR unknown start of token: \u{201d} //~^^^^ HELP Unicode character '”' (Right Double Quotation Mark) looks like '"' (Quotation Mark), but it is not - //~^^^^^ ERROR expected token: `,` + //~^^^^^ ERROR expected `,`, found `world` } diff --git a/src/test/ui/parser/unicode-quote-chars.stderr b/src/test/ui/parser/unicode-quote-chars.stderr index 4b0cb96ed211f..d9ec92b3f8a83 100644 --- a/src/test/ui/parser/unicode-quote-chars.stderr +++ b/src/test/ui/parser/unicode-quote-chars.stderr @@ -20,7 +20,7 @@ help: Unicode character '”' (Right Double Quotation Mark) looks like '"' (Quot LL | println!(“hello world"); | ^ -error: expected token: `,` +error: expected `,`, found `world` --> $DIR/unicode-quote-chars.rs:4:21 | LL | println!(“hello world”); diff --git a/src/tools/clippy/tests/ui/issue-3145.rs b/src/tools/clippy/tests/ui/issue-3145.rs index f497d5550af5f..586d13647d15e 100644 --- a/src/tools/clippy/tests/ui/issue-3145.rs +++ b/src/tools/clippy/tests/ui/issue-3145.rs @@ -1,3 +1,3 @@ fn main() { - println!("{}" a); //~ERROR expected token: `,` + println!("{}" a); //~ERROR expected `,`, found `a` } diff --git a/src/tools/clippy/tests/ui/issue-3145.stderr b/src/tools/clippy/tests/ui/issue-3145.stderr index cb0d95f5e2643..a35032aa150dc 100644 --- a/src/tools/clippy/tests/ui/issue-3145.stderr +++ b/src/tools/clippy/tests/ui/issue-3145.stderr @@ -1,7 +1,7 @@ -error: expected token: `,` +error: expected `,`, found `a` --> $DIR/issue-3145.rs:2:19 | -LL | println!("{}" a); //~ERROR expected token: `,` +LL | println!("{}" a); //~ERROR expected `,`, found `a` | ^ expected `,` error: aborting due to previous error