From ac037c1359afa273bd5573b5be1b21d074c22219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 10 Apr 2019 18:07:52 -0700 Subject: [PATCH 1/2] Recover from missing semicolon based on the found token When encountering one of a few keywords when a semicolon was expected, suggest the semicolon and recover: ``` error: expected one of `.`, `;`, `?`, or an operator, found `let` --> $DIR/recover-missing-semi.rs:4:5 | LL | let _: usize = () | - help: missing semicolon here LL | LL | let _ = 3; | ^^^ error[E0308]: mismatched types --> $DIR/recover-missing-semi.rs:2:20 | LL | let _: usize = () | ^^ expected usize, found () | = note: expected type `usize` found type `()` ``` --- src/libsyntax/parse/parser.rs | 27 +++++++++++++ src/test/ui/parser/recover-missing-semi.rs | 13 +++++++ .../ui/parser/recover-missing-semi.stderr | 39 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 src/test/ui/parser/recover-missing-semi.rs create mode 100644 src/test/ui/parser/recover-missing-semi.stderr diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index 37360a56395..d2875a5f275 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -796,6 +796,10 @@ impl<'a> Parser<'a> { .chain(inedible.iter().map(|x| TokenType::Token(x.clone()))) .chain(self.expected_tokens.iter().cloned()) .collect::>(); + let expects_semi = expected.iter().any(|t| match t { + TokenType::Token(token::Semi) => true, + _ => false, + }); expected.sort_by_cached_key(|x| x.to_string()); expected.dedup(); let expect = tokens_to_string(&expected[..]); @@ -835,6 +839,17 @@ impl<'a> Parser<'a> { Applicability::MaybeIncorrect, ); } + let is_semi_suggestable = expects_semi && ( + self.token.is_keyword(keywords::Break) || + self.token.is_keyword(keywords::Continue) || + self.token.is_keyword(keywords::For) || + self.token.is_keyword(keywords::If) || + self.token.is_keyword(keywords::Let) || + self.token.is_keyword(keywords::Loop) || + self.token.is_keyword(keywords::Match) || + self.token.is_keyword(keywords::Return) || + self.token.is_keyword(keywords::While) + ); let sp = if self.token == token::Token::Eof { // This is EOF, don't want to point at the following char, but rather the last token self.prev_span @@ -853,6 +868,18 @@ impl<'a> Parser<'a> { let cm = self.sess.source_map(); match (cm.lookup_line(self.span.lo()), cm.lookup_line(sp.lo())) { + (Ok(ref a), Ok(ref b)) if a.line != b.line && is_semi_suggestable => { + // The spans are in different lines, expected `;` and found `let` or `return`. + // High likelihood that it is only a missing `;`. + err.span_suggestion_short( + label_sp, + "missing semicolon here", + ";".to_string(), + Applicability::MaybeIncorrect, + ); + err.emit(); + return Ok(true); + } (Ok(ref a), Ok(ref b)) if a.line == b.line => { // When the spans are in the same line, it means that the only content between // them is whitespace, point at the found token in that case: diff --git a/src/test/ui/parser/recover-missing-semi.rs b/src/test/ui/parser/recover-missing-semi.rs new file mode 100644 index 00000000000..1893dc716be --- /dev/null +++ b/src/test/ui/parser/recover-missing-semi.rs @@ -0,0 +1,13 @@ +fn main() { + let _: usize = () + //~^ ERROR mismatched types + let _ = 3; + //~^ ERROR expected one of +} + +fn foo() -> usize { + let _: usize = () + //~^ ERROR mismatched types + return 3; + //~^ ERROR expected one of +} diff --git a/src/test/ui/parser/recover-missing-semi.stderr b/src/test/ui/parser/recover-missing-semi.stderr new file mode 100644 index 00000000000..25ce408d8b2 --- /dev/null +++ b/src/test/ui/parser/recover-missing-semi.stderr @@ -0,0 +1,39 @@ +error: expected one of `.`, `;`, `?`, or an operator, found `let` + --> $DIR/recover-missing-semi.rs:4:5 + | +LL | let _: usize = () + | - help: missing semicolon here +LL | +LL | let _ = 3; + | ^^^ + +error: expected one of `.`, `;`, `?`, or an operator, found `return` + --> $DIR/recover-missing-semi.rs:11:5 + | +LL | let _: usize = () + | - help: missing semicolon here +LL | +LL | return 3; + | ^^^^^^ + +error[E0308]: mismatched types + --> $DIR/recover-missing-semi.rs:2:20 + | +LL | let _: usize = () + | ^^ expected usize, found () + | + = note: expected type `usize` + found type `()` + +error[E0308]: mismatched types + --> $DIR/recover-missing-semi.rs:9:20 + | +LL | let _: usize = () + | ^^ expected usize, found () + | + = note: expected type `usize` + found type `()` + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0308`. From 9b6b3d618c16976a273cfd4f95408eef37e6c82e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 11 Apr 2019 14:24:31 -0700 Subject: [PATCH 2/2] review comments --- src/libsyntax/parse/parser.rs | 31 +++++++++---------- .../ui/parser/recover-missing-semi.stderr | 4 +-- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index d2875a5f275..7992867692e 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -796,10 +796,6 @@ impl<'a> Parser<'a> { .chain(inedible.iter().map(|x| TokenType::Token(x.clone()))) .chain(self.expected_tokens.iter().cloned()) .collect::>(); - let expects_semi = expected.iter().any(|t| match t { - TokenType::Token(token::Semi) => true, - _ => false, - }); expected.sort_by_cached_key(|x| x.to_string()); expected.dedup(); let expect = tokens_to_string(&expected[..]); @@ -839,17 +835,6 @@ impl<'a> Parser<'a> { Applicability::MaybeIncorrect, ); } - let is_semi_suggestable = expects_semi && ( - self.token.is_keyword(keywords::Break) || - self.token.is_keyword(keywords::Continue) || - self.token.is_keyword(keywords::For) || - self.token.is_keyword(keywords::If) || - self.token.is_keyword(keywords::Let) || - self.token.is_keyword(keywords::Loop) || - self.token.is_keyword(keywords::Match) || - self.token.is_keyword(keywords::Return) || - self.token.is_keyword(keywords::While) - ); let sp = if self.token == token::Token::Eof { // This is EOF, don't want to point at the following char, but rather the last token self.prev_span @@ -866,6 +851,20 @@ impl<'a> Parser<'a> { } } + let is_semi_suggestable = expected.iter().any(|t| match t { + TokenType::Token(token::Semi) => true, // we expect a `;` here + _ => false, + }) && ( // a `;` would be expected before the current keyword + self.token.is_keyword(keywords::Break) || + self.token.is_keyword(keywords::Continue) || + self.token.is_keyword(keywords::For) || + self.token.is_keyword(keywords::If) || + self.token.is_keyword(keywords::Let) || + self.token.is_keyword(keywords::Loop) || + self.token.is_keyword(keywords::Match) || + self.token.is_keyword(keywords::Return) || + self.token.is_keyword(keywords::While) + ); let cm = self.sess.source_map(); match (cm.lookup_line(self.span.lo()), cm.lookup_line(sp.lo())) { (Ok(ref a), Ok(ref b)) if a.line != b.line && is_semi_suggestable => { @@ -873,7 +872,7 @@ impl<'a> Parser<'a> { // High likelihood that it is only a missing `;`. err.span_suggestion_short( label_sp, - "missing semicolon here", + "a semicolon may be missing here", ";".to_string(), Applicability::MaybeIncorrect, ); diff --git a/src/test/ui/parser/recover-missing-semi.stderr b/src/test/ui/parser/recover-missing-semi.stderr index 25ce408d8b2..99339e4dd50 100644 --- a/src/test/ui/parser/recover-missing-semi.stderr +++ b/src/test/ui/parser/recover-missing-semi.stderr @@ -2,7 +2,7 @@ error: expected one of `.`, `;`, `?`, or an operator, found `let` --> $DIR/recover-missing-semi.rs:4:5 | LL | let _: usize = () - | - help: missing semicolon here + | - help: a semicolon may be missing here LL | LL | let _ = 3; | ^^^ @@ -11,7 +11,7 @@ error: expected one of `.`, `;`, `?`, or an operator, found `return` --> $DIR/recover-missing-semi.rs:11:5 | LL | let _: usize = () - | - help: missing semicolon here + | - help: a semicolon may be missing here LL | LL | return 3; | ^^^^^^