Skip to content

Commit

Permalink
fix code to suggest ; on parse error
Browse files Browse the repository at this point in the history
  • Loading branch information
ebobrow committed Jul 24, 2021
1 parent b543e0d commit e0995a5
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 64 deletions.
114 changes: 57 additions & 57 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Expand Up @@ -242,6 +242,63 @@ impl<'a> Parser<'a> {
expected.sort_by_cached_key(|x| x.to_string());
expected.dedup();

let sm = self.sess.source_map();
let msg = format!("expected `;`, found {}", super::token_descr(&self.token));
let appl = Applicability::MachineApplicable;
if expected.contains(&TokenType::Token(token::Semi)) {
if self.token.span == DUMMY_SP || self.prev_token.span == DUMMY_SP {
// Likely inside a macro, can't provide meaningful suggestions.
} else if !sm.is_multiline(self.prev_token.span.until(self.token.span)) {
// The current token is in the same line as the prior token, not recoverable.
} else if [token::Comma, token::Colon].contains(&self.token.kind)
&& self.prev_token.kind == token::CloseDelim(token::Paren)
{
// Likely typo: The current token is on a new line and is expected to be
// `.`, `;`, `?`, or an operator after a close delimiter token.
//
// let a = std::process::Command::new("echo")
// .arg("1")
// ,arg("2")
// ^
// https://github.com/rust-lang/rust/issues/72253
} else if self.look_ahead(1, |t| {
t == &token::CloseDelim(token::Brace)
|| t.can_begin_expr() && t.kind != token::Colon
}) && [token::Comma, token::Colon].contains(&self.token.kind)
{
// Likely typo: `,` → `;` or `:` → `;`. This is triggered if the current token is
// either `,` or `:`, and the next token could either start a new statement or is a
// block close. For example:
//
// let x = 32:
// let y = 42;
self.bump();
let sp = self.prev_token.span;
self.struct_span_err(sp, &msg)
.span_suggestion_short(sp, "change this to `;`", ";".to_string(), appl)
.emit();
return Ok(false);
} else if self.look_ahead(0, |t| {
t == &token::CloseDelim(token::Brace)
|| (
t.can_begin_expr() && t != &token::Semi && t != &token::Pound
// Avoid triggering with too many trailing `#` in raw string.
)
}) {
// Missing semicolon typo. This is triggered if the next token could either start a
// new statement or is a block close. For example:
//
// let x = 32
// let y = 42;
let sp = self.prev_token.span.shrink_to_hi();
self.struct_span_err(sp, &msg)
.span_label(self.token.span, "unexpected token")
.span_suggestion_short(sp, "add `;` here", ";".to_string(), appl)
.emit();
return Ok(false);
}
}

let expect = tokens_to_string(&expected[..]);
let actual = super::token_descr(&self.token);
let (msg_exp, (label_sp, label_exp)) = if expected.len() > 1 {
Expand Down Expand Up @@ -303,7 +360,6 @@ impl<'a> Parser<'a> {
return Err(err);
}

let sm = self.sess.source_map();
if self.prev_token.span == DUMMY_SP {
// Account for macro context where the previous span might not be
// available to avoid incorrect output (#54841).
Expand Down Expand Up @@ -1144,62 +1200,6 @@ impl<'a> Parser<'a> {
if self.eat(&token::Semi) {
return Ok(());
}
let sm = self.sess.source_map();
let msg = format!("expected `;`, found {}", super::token_descr(&self.token));
let appl = Applicability::MachineApplicable;
if self.token.span == DUMMY_SP || self.prev_token.span == DUMMY_SP {
// Likely inside a macro, can't provide meaningful suggestions.
return self.expect(&token::Semi).map(drop);
} else if !sm.is_multiline(self.prev_token.span.until(self.token.span)) {
// The current token is in the same line as the prior token, not recoverable.
} else if [token::Comma, token::Colon].contains(&self.token.kind)
&& self.prev_token.kind == token::CloseDelim(token::Paren)
{
// Likely typo: The current token is on a new line and is expected to be
// `.`, `;`, `?`, or an operator after a close delimiter token.
//
// let a = std::process::Command::new("echo")
// .arg("1")
// ,arg("2")
// ^
// https://github.com/rust-lang/rust/issues/72253
self.expect(&token::Semi)?;
return Ok(());
} else if self.look_ahead(1, |t| {
t == &token::CloseDelim(token::Brace) || t.can_begin_expr() && t.kind != token::Colon
}) && [token::Comma, token::Colon].contains(&self.token.kind)
{
// Likely typo: `,` → `;` or `:` → `;`. This is triggered if the current token is
// either `,` or `:`, and the next token could either start a new statement or is a
// block close. For example:
//
// let x = 32:
// let y = 42;
self.bump();
let sp = self.prev_token.span;
self.struct_span_err(sp, &msg)
.span_suggestion_short(sp, "change this to `;`", ";".to_string(), appl)
.emit();
return Ok(());
} else if self.look_ahead(0, |t| {
t == &token::CloseDelim(token::Brace)
|| (
t.can_begin_expr() && t != &token::Semi && t != &token::Pound
// Avoid triggering with too many trailing `#` in raw string.
)
}) {
// Missing semicolon typo. This is triggered if the next token could either start a
// new statement or is a block close. For example:
//
// let x = 32
// let y = 42;
let sp = self.prev_token.span.shrink_to_hi();
self.struct_span_err(sp, &msg)
.span_label(self.token.span, "unexpected token")
.span_suggestion_short(sp, "add `;` here", ";".to_string(), appl)
.emit();
return Ok(());
}
self.expect(&token::Semi).map(drop) // Error unconditionally
}

Expand Down
10 changes: 10 additions & 0 deletions src/test/ui/parser/issue-87197-missing-semicolon.fixed
@@ -0,0 +1,10 @@
// run-rustfix
// Parser should know when a semicolon is missing.
// https://github.com/rust-lang/rust/issues/87197

fn main() {
let x = 100; //~ ERROR: expected `;`
println!("{}", x); //~ ERROR: expected `;`
let y = 200; //~ ERROR: expected `;`
println!("{}", y);
}
10 changes: 10 additions & 0 deletions src/test/ui/parser/issue-87197-missing-semicolon.rs
@@ -0,0 +1,10 @@
// run-rustfix
// Parser should know when a semicolon is missing.
// https://github.com/rust-lang/rust/issues/87197

fn main() {
let x = 100 //~ ERROR: expected `;`
println!("{}", x) //~ ERROR: expected `;`
let y = 200 //~ ERROR: expected `;`
println!("{}", y);
}
26 changes: 26 additions & 0 deletions src/test/ui/parser/issue-87197-missing-semicolon.stderr
@@ -0,0 +1,26 @@
error: expected `;`, found `println`
--> $DIR/issue-87197-missing-semicolon.rs:6:16
|
LL | let x = 100
| ^ help: add `;` here
LL | println!("{}", x)
| ------- unexpected token

error: expected `;`, found keyword `let`
--> $DIR/issue-87197-missing-semicolon.rs:7:22
|
LL | println!("{}", x)
| ^ help: add `;` here
LL | let y = 200
| --- unexpected token

error: expected `;`, found `println`
--> $DIR/issue-87197-missing-semicolon.rs:8:16
|
LL | let y = 200
| ^ help: add `;` here
LL | println!("{}", y);
| ------- unexpected token

error: aborting due to 3 previous errors

4 changes: 2 additions & 2 deletions src/test/ui/parser/macros-no-semicolon.rs
@@ -1,5 +1,5 @@
fn main() {
assert_eq!(1, 2)
assert_eq!(3, 4) //~ ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `assert_eq`
assert_eq!(1, 2) //~ ERROR: expected `;`
assert_eq!(3, 4) //~ ERROR: expected `;`
println!("hello");
}
18 changes: 13 additions & 5 deletions src/test/ui/parser/macros-no-semicolon.stderr
@@ -1,10 +1,18 @@
error: expected one of `.`, `;`, `?`, `}`, or an operator, found `assert_eq`
--> $DIR/macros-no-semicolon.rs:3:5
error: expected `;`, found `assert_eq`
--> $DIR/macros-no-semicolon.rs:2:21
|
LL | assert_eq!(1, 2)
| - expected one of `.`, `;`, `?`, `}`, or an operator
| ^ help: add `;` here
LL | assert_eq!(3, 4)
| ^^^^^^^^^ unexpected token
| --------- unexpected token

error: aborting due to previous error
error: expected `;`, found `println`
--> $DIR/macros-no-semicolon.rs:3:21
|
LL | assert_eq!(3, 4)
| ^ help: add `;` here
LL | println!("hello");
| ------- unexpected token

error: aborting due to 2 previous errors

0 comments on commit e0995a5

Please sign in to comment.