1
Fork 0

Improve parsing errors and suggestions for bad if statements

This commit is contained in:
Michael Goulet 2022-05-27 21:58:48 -07:00
parent 3bdec3c8ab
commit d1ba2d25d4
21 changed files with 325 additions and 143 deletions

View file

@ -2248,36 +2248,59 @@ impl<'a> Parser<'a> {
&mut self, &mut self,
attrs: AttrVec, attrs: AttrVec,
lo: Span, lo: Span,
cond: P<Expr>, mut cond: P<Expr>,
) -> PResult<'a, P<Expr>> { ) -> PResult<'a, P<Expr>> {
let missing_then_block_binop_span = || { let cond_span = cond.span;
match cond.kind { // Tries to interpret `cond` as either a missing expression if it's a block,
ExprKind::Binary(Spanned { span: binop_span, .. }, _, ref right) // or as an unfinished expression if it's a binop and the RHS is a block.
if let ExprKind::Block(..) = right.kind => Some(binop_span), // We could probably add more recoveries here too...
_ => None let mut recover_block_from_condition = |this: &mut Self| {
let block = match &mut cond.kind {
ExprKind::Binary(Spanned { span: binop_span, .. }, _, right)
if let ExprKind::Block(_, None) = right.kind => {
this.error_missing_if_then_block(lo, cond_span.shrink_to_lo().to(*binop_span), true).emit();
std::mem::replace(right, this.mk_expr_err(binop_span.shrink_to_hi()))
},
ExprKind::Block(_, None) => {
this.error_missing_if_cond(lo, cond_span).emit();
std::mem::replace(&mut cond, this.mk_expr_err(cond_span.shrink_to_hi()))
}
_ => {
return None;
}
};
if let ExprKind::Block(block, _) = &block.kind {
Some(block.clone())
} else {
unreachable!()
} }
}; };
// Verify that the parsed `if` condition makes sense as a condition. If it is a block, then // Parse then block
// verify that the last statement is either an implicit return (no `;`) or an explicit let thn = if self.token.is_keyword(kw::Else) {
// return. This won't catch blocks with an explicit `return`, but that would be caught by if let Some(block) = recover_block_from_condition(self) {
// the dead code lint. block
let thn = if self.token.is_keyword(kw::Else) || !cond.returns() {
if let Some(binop_span) = missing_then_block_binop_span() {
self.error_missing_if_then_block(lo, None, Some(binop_span)).emit();
self.mk_block_err(cond.span)
} else { } else {
self.error_missing_if_cond(lo, cond.span) self.error_missing_if_then_block(lo, cond_span, false).emit();
self.mk_block_err(cond_span.shrink_to_hi())
} }
} else { } else {
let attrs = self.parse_outer_attributes()?.take_for_recovery(); // For recovery. let attrs = self.parse_outer_attributes()?.take_for_recovery(); // For recovery.
let not_block = self.token != token::OpenDelim(Delimiter::Brace); let block = if self.check(&token::OpenDelim(Delimiter::Brace)) {
let block = self.parse_block().map_err(|err| { self.parse_block()?
if not_block { } else {
self.error_missing_if_then_block(lo, Some(err), missing_then_block_binop_span()) if let Some(block) = recover_block_from_condition(self) {
block
} else { } else {
err // Parse block, which will always fail, but we can add a nice note to the error
self.parse_block().map_err(|mut err| {
err.span_note(
cond_span,
"the `if` expression is missing a block after this condition",
);
err
})?
} }
})?; };
self.error_on_if_block_attrs(lo, false, block.span, &attrs); self.error_on_if_block_attrs(lo, false, block.span, &attrs);
block block
}; };
@ -2288,31 +2311,34 @@ impl<'a> Parser<'a> {
fn error_missing_if_then_block( fn error_missing_if_then_block(
&self, &self,
if_span: Span, if_span: Span,
err: Option<DiagnosticBuilder<'a, ErrorGuaranteed>>, cond_span: Span,
binop_span: Option<Span>, is_unfinished: bool,
) -> DiagnosticBuilder<'a, ErrorGuaranteed> { ) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
let msg = "this `if` expression has a condition, but no block"; let mut err = self.struct_span_err(
if_span,
let mut err = if let Some(mut err) = err { "this `if` expression is missing a block after the condition",
err.span_label(if_span, msg); );
err if is_unfinished {
err.span_help(cond_span, "this binary operation is possibly unfinished");
} else { } else {
self.struct_span_err(if_span, msg) err.span_help(cond_span.shrink_to_hi(), "add a block here");
};
if let Some(binop_span) = binop_span {
err.span_help(binop_span, "maybe you forgot the right operand of the condition?");
} }
err err
} }
fn error_missing_if_cond(&self, lo: Span, span: Span) -> P<ast::Block> { fn error_missing_if_cond(
let sp = self.sess.source_map().next_point(lo); &self,
self.struct_span_err(sp, "missing condition for `if` expression") lo: Span,
.span_label(sp, "expected if condition here") span: Span,
.emit(); ) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
self.mk_block_err(span) let next_span = self.sess.source_map().next_point(lo);
let mut err = self.struct_span_err(next_span, "missing condition for `if` expression");
err.span_label(next_span, "expected condition here");
err.span_label(
self.sess.source_map().start_point(span),
"if this block is the condition of the `if` expression, then it must be followed by another block"
);
err
} }
/// Parses the condition of a `if` or `while` expression. /// Parses the condition of a `if` or `while` expression.

View file

@ -432,10 +432,23 @@ impl<'a> Parser<'a> {
// //
// which is valid in other languages, but not Rust. // which is valid in other languages, but not Rust.
match self.parse_stmt_without_recovery(false, ForceCollect::No) { match self.parse_stmt_without_recovery(false, ForceCollect::No) {
// If the next token is an open brace (e.g., `if a b {`), the place- // If the next token is an open brace, e.g., we have:
// inside-a-block suggestion would be more likely wrong than right. //
// if expr other_expr {
// ^ ^ ^- lookahead(1) is a brace
// | |- current token is not "else"
// |- (statement we just parsed)
//
// the place-inside-a-block suggestion would be more likely wrong than right.
//
// FIXME(compiler-errors): this should probably parse an arbitrary expr and not
// just lookahead one token, so we can see if there's a brace after _that_,
// since we want to protect against:
// `if 1 1 + 1 {` being suggested as `if { 1 } 1 + 1 {`
// + +
Ok(Some(_)) Ok(Some(_))
if self.look_ahead(1, |t| t == &token::OpenDelim(Delimiter::Brace)) if (!self.token.is_keyword(kw::Else)
&& self.look_ahead(1, |t| t == &token::OpenDelim(Delimiter::Brace)))
|| do_not_suggest_help => {} || do_not_suggest_help => {}
// Do not suggest `if foo println!("") {;}` (as would be seen in test for #46836). // Do not suggest `if foo println!("") {;}` (as would be seen in test for #46836).
Ok(Some(Stmt { kind: StmtKind::Empty, .. })) => {} Ok(Some(Stmt { kind: StmtKind::Empty, .. })) => {}

View file

@ -25,10 +25,16 @@ LL | println!("Then when?");
error: expected `{`, found `;` error: expected `{`, found `;`
--> $DIR/issue-46836-identifier-not-instead-of-negation.rs:20:31 --> $DIR/issue-46836-identifier-not-instead-of-negation.rs:20:31
| |
LL | if not // lack of braces is [sic]
| -- this `if` expression has a condition, but no block
LL | println!("Then when?"); LL | println!("Then when?");
| ^ expected `{` | ^ expected `{`
|
note: the `if` expression is missing a block after this condition
--> $DIR/issue-46836-identifier-not-instead-of-negation.rs:19:8
|
LL | if not // lack of braces is [sic]
| ________^
LL | | println!("Then when?");
| |______________________________^
error: unexpected `2` after identifier error: unexpected `2` after identifier
--> $DIR/issue-46836-identifier-not-instead-of-negation.rs:26:24 --> $DIR/issue-46836-identifier-not-instead-of-negation.rs:26:24

View file

@ -1,9 +1,7 @@
fn main() { fn main() {
let n = 1; let n = 1;
if 5 == { if 5 == {
//~^ NOTE this `if` expression has a condition, but no block //~^ ERROR this `if` expression is missing a block after the condition
println!("five"); println!("five");
} }
} }
//~^ ERROR expected `{`, found `}`
//~| NOTE expected `{`

View file

@ -1,17 +1,14 @@
error: expected `{`, found `}` error: this `if` expression is missing a block after the condition
--> $DIR/if-without-block.rs:7:1 --> $DIR/if-without-block.rs:3:5
| |
LL | if 5 == { LL | if 5 == {
| -- this `if` expression has a condition, but no block | ^^
...
LL | }
| ^ expected `{`
| |
help: maybe you forgot the right operand of the condition? help: this binary operation is possibly unfinished
--> $DIR/if-without-block.rs:3:10 --> $DIR/if-without-block.rs:3:8
| |
LL | if 5 == { LL | if 5 == {
| ^^ | ^^^^
error: aborting due to previous error error: aborting due to previous error

View file

@ -2,13 +2,19 @@ error: expected `{`, found `foo`
--> $DIR/issue-39848.rs:3:21 --> $DIR/issue-39848.rs:3:21
| |
LL | if $tgt.has_$field() {} LL | if $tgt.has_$field() {}
| -- ^^^^^^ expected `{` | ^^^^^^ expected `{`
| |
| this `if` expression has a condition, but no block
... ...
LL | get_opt!(bar, foo); LL | get_opt!(bar, foo);
| ------------------ in this macro invocation | ------------------ in this macro invocation
| |
note: the `if` expression is missing a block after this condition
--> $DIR/issue-39848.rs:3:12
|
LL | if $tgt.has_$field() {}
| ^^^^^^^^^
...
LL | get_opt!(bar, foo);
| ------------------ in this macro invocation
= note: this error originates in the macro `get_opt` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the macro `get_opt` (in Nightly builds, run with -Z macro-backtrace for more info)
help: try placing this code inside a block help: try placing this code inside a block
| |

View file

@ -2,18 +2,25 @@ error: expected `{`, found `=>`
--> $DIR/missing-block-hint.rs:3:18 --> $DIR/missing-block-hint.rs:3:18
| |
LL | if (foo) => {} LL | if (foo) => {}
| -- ^^ expected `{` | ^^ expected `{`
| | |
| this `if` expression has a condition, but no block note: the `if` expression is missing a block after this condition
--> $DIR/missing-block-hint.rs:3:12
|
LL | if (foo) => {}
| ^^^^^
error: expected `{`, found `bar` error: expected `{`, found `bar`
--> $DIR/missing-block-hint.rs:7:13 --> $DIR/missing-block-hint.rs:7:13
| |
LL | if (foo)
| -- this `if` expression has a condition, but no block
LL | bar; LL | bar;
| ^^^ expected `{` | ^^^ expected `{`
| |
note: the `if` expression is missing a block after this condition
--> $DIR/missing-block-hint.rs:6:12
|
LL | if (foo)
| ^^^^^
help: try placing this code inside a block help: try placing this code inside a block
| |
LL | { bar; } LL | { bar; }

View file

@ -0,0 +1,38 @@
fn a() {
if {}
//~^ ERROR missing condition for `if` expression
}
fn b() {
if true && {}
//~^ ERROR this `if` expression is missing a block after the condition
}
fn c() {
let x = {};
if true x
//~^ ERROR expected `{`, found `x`
}
fn a2() {
if {} else {}
//~^ ERROR missing condition for `if` expression
}
fn b2() {
if true && {} else {}
//~^ ERROR this `if` expression is missing a block after the condition
}
fn c2() {
let x = {};
if true x else {}
//~^ ERROR expected `{`, found `x`
}
fn d() {
if true else {}
//~^ ERROR this `if` expression is missing a block after the condition
}
fn main() {}

View file

@ -0,0 +1,86 @@
error: missing condition for `if` expression
--> $DIR/bad-if-statements.rs:2:7
|
LL | if {}
| ^- if this block is the condition of the `if` expression, then it must be followed by another block
| |
| expected condition here
error: this `if` expression is missing a block after the condition
--> $DIR/bad-if-statements.rs:7:5
|
LL | if true && {}
| ^^
|
help: this binary operation is possibly unfinished
--> $DIR/bad-if-statements.rs:7:8
|
LL | if true && {}
| ^^^^^^^
error: expected `{`, found `x`
--> $DIR/bad-if-statements.rs:13:13
|
LL | if true x
| ^ expected `{`
|
note: the `if` expression is missing a block after this condition
--> $DIR/bad-if-statements.rs:13:8
|
LL | if true x
| ^^^^
help: try placing this code inside a block
|
LL | if true { x }
| + +
error: missing condition for `if` expression
--> $DIR/bad-if-statements.rs:18:7
|
LL | if {} else {}
| ^- if this block is the condition of the `if` expression, then it must be followed by another block
| |
| expected condition here
error: this `if` expression is missing a block after the condition
--> $DIR/bad-if-statements.rs:23:5
|
LL | if true && {} else {}
| ^^
|
help: this binary operation is possibly unfinished
--> $DIR/bad-if-statements.rs:23:8
|
LL | if true && {} else {}
| ^^^^^^^
error: expected `{`, found `x`
--> $DIR/bad-if-statements.rs:29:13
|
LL | if true x else {}
| ^ expected `{`
|
note: the `if` expression is missing a block after this condition
--> $DIR/bad-if-statements.rs:29:8
|
LL | if true x else {}
| ^^^^
help: try placing this code inside a block
|
LL | if true { x } else {}
| + +
error: this `if` expression is missing a block after the condition
--> $DIR/bad-if-statements.rs:34:5
|
LL | if true else {}
| ^^
|
help: add a block here
--> $DIR/bad-if-statements.rs:34:12
|
LL | if true else {}
| ^
error: aborting due to 7 previous errors

View file

@ -0,0 +1,8 @@
// check-pass
// This regressed from 1.20 -> 1.21 -- the condition is unreachable,
// but it's still an expression, and should parse fine.
fn main() {
if { if true { return; } else { return; }; } {}
}

View file

@ -2,9 +2,13 @@ error: expected `{`, found `)`
--> $DIR/issue-61858.rs:2:15 --> $DIR/issue-61858.rs:2:15
| |
LL | (if foobar) LL | (if foobar)
| -- ^ expected `{` | ^ expected `{`
| | |
| this `if` expression has a condition, but no block note: the `if` expression is missing a block after this condition
--> $DIR/issue-61858.rs:2:9
|
LL | (if foobar)
| ^^^^^^
error: aborting due to previous error error: aborting due to previous error

View file

@ -1,7 +1,8 @@
macro_rules! x { macro_rules! x {
($($c:tt)*) => { ($($c:tt)*) => {
$($c)ö* {} //~ ERROR missing condition for `if` expression $($c)ö* {}
}; //~| ERROR mismatched types //~^ ERROR missing condition for `if` expression
};
} }
fn main() { fn main() {

View file

@ -2,19 +2,9 @@ error: missing condition for `if` expression
--> $DIR/issue-68091-unicode-ident-after-if.rs:3:14 --> $DIR/issue-68091-unicode-ident-after-if.rs:3:14
| |
LL | $($c)ö* {} LL | $($c)ö* {}
| ^ expected if condition here | ^ - if this block is the condition of the `if` expression, then it must be followed by another block
| |
| expected condition here
error[E0308]: mismatched types error: aborting due to previous error
--> $DIR/issue-68091-unicode-ident-after-if.rs:3:17
|
LL | $($c)ö* {}
| ^^ expected `bool`, found `()`
...
LL | x!(if);
| ------ in this macro invocation
|
= note: this error originates in the macro `x` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0308`.

View file

@ -2,9 +2,8 @@
fn main() { fn main() {
let value = if true && { let value = if true && {
//~^ ERROR: this `if` expression has a condition, but no block //~^ ERROR: this `if` expression is missing a block after the condition
//~| HELP: maybe you forgot the right operand of the condition? //~| HELP: this binary operation is possibly unfinished
3 3
//~^ ERROR: mismatched types [E0308]
} else { 4 }; } else { 4 };
} }

View file

@ -1,21 +1,14 @@
error: this `if` expression has a condition, but no block error: this `if` expression is missing a block after the condition
--> $DIR/issue-91421.rs:4:17 --> $DIR/issue-91421.rs:4:17
| |
LL | let value = if true && { LL | let value = if true && {
| ^^ | ^^
| |
help: maybe you forgot the right operand of the condition? help: this binary operation is possibly unfinished
--> $DIR/issue-91421.rs:4:25 --> $DIR/issue-91421.rs:4:20
| |
LL | let value = if true && { LL | let value = if true && {
| ^^ | ^^^^^^^
error[E0308]: mismatched types error: aborting due to previous error
--> $DIR/issue-91421.rs:7:9
|
LL | 3
| ^ expected `bool`, found integer
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0308`.

View file

@ -1,15 +1,15 @@
fn main() { fn main() {
if true { if true {
} else if { //~ ERROR missing condition } else if {
//~^ ERROR mismatched types //~^ ERROR missing condition for `if` expression
} else { } else {
} }
} }
fn foo() { fn foo() {
if true { if true {
} else if { //~ ERROR missing condition } else if {
//~^ ERROR mismatched types //~^ ERROR missing condition for `if` expression
} }
bar(); bar();
} }

View file

@ -2,32 +2,17 @@ error: missing condition for `if` expression
--> $DIR/issue-13483.rs:3:14 --> $DIR/issue-13483.rs:3:14
| |
LL | } else if { LL | } else if {
| ^ expected if condition here | ^- if this block is the condition of the `if` expression, then it must be followed by another block
| |
| expected condition here
error: missing condition for `if` expression error: missing condition for `if` expression
--> $DIR/issue-13483.rs:11:14 --> $DIR/issue-13483.rs:11:14
| |
LL | } else if { LL | } else if {
| ^ expected if condition here | ^- if this block is the condition of the `if` expression, then it must be followed by another block
| |
| expected condition here
error[E0308]: mismatched types error: aborting due to 2 previous errors
--> $DIR/issue-13483.rs:3:15
|
LL | } else if {
| _______________^
LL | |
LL | | } else {
| |_____^ expected `bool`, found `()`
error[E0308]: mismatched types
--> $DIR/issue-13483.rs:11:15
|
LL | } else if {
| _______________^
LL | |
LL | | }
| |_____^ expected `bool`, found `()`
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0308`.

View file

@ -2,9 +2,13 @@ error: expected `{`, found keyword `in`
--> $DIR/issue-51602.rs:2:10 --> $DIR/issue-51602.rs:2:10
| |
LL | if i in 1..10 { LL | if i in 1..10 {
| -- ^^ expected `{` | ^^ expected `{`
| | |
| this `if` expression has a condition, but no block note: the `if` expression is missing a block after this condition
--> $DIR/issue-51602.rs:2:8
|
LL | if i in 1..10 {
| ^
error: aborting due to previous error error: aborting due to previous error

View file

@ -57,10 +57,13 @@ error: expected `{`, found `macro_rules`
--> $DIR/issue-62554.rs:6:23 --> $DIR/issue-62554.rs:6:23
| |
LL | fn foo(u: u8) { if u8 macro_rules! u8 { (u6) => { fn uuuuuuuuuuu() { use s loo mod u8 { LL | fn foo(u: u8) { if u8 macro_rules! u8 { (u6) => { fn uuuuuuuuuuu() { use s loo mod u8 {
| -- ^^^^^^^^^^^ expected `{` | ^^^^^^^^^^^ expected `{`
| |
| this `if` expression has a condition, but no block
| |
note: the `if` expression is missing a block after this condition
--> $DIR/issue-62554.rs:6:20
|
LL | fn foo(u: u8) { if u8 macro_rules! u8 { (u6) => { fn uuuuuuuuuuu() { use s loo mod u8 {
| ^^
help: try placing this code inside a block help: try placing this code inside a block
| |
LL | fn foo(u: u8) { if u8 { macro_rules! u8 { (u6) => { fn uuuuuuuuuuu() { use s loo mod u8 { } LL | fn foo(u: u8) { if u8 { macro_rules! u8 { (u6) => { fn uuuuuuuuuuu() { use s loo mod u8 { }

View file

@ -21,15 +21,15 @@ fn main() {
}; };
if let Some(n) = opt else { if let Some(n) = opt else {
//~^ ERROR missing condition for `if` expression //~^ ERROR this `if` expression is missing a block after the condition
return; return;
}; };
if let Some(n) = opt && n == 1 else { if let Some(n) = opt && n == 1 else {
//~^ ERROR missing condition for `if` expression //~^ ERROR this `if` expression is missing a block after the condition
return; return;
}; };
if let Some(n) = opt && let another = n else { if let Some(n) = opt && let another = n else {
//~^ ERROR missing condition for `if` expression //~^ ERROR this `if` expression is missing a block after the condition
return; return;
}; };

View file

@ -20,23 +20,41 @@ help: wrap the expression in parentheses
LL | let Some(n) = (opt && let another = n) else { LL | let Some(n) = (opt && let another = n) else {
| + + | + +
error: missing condition for `if` expression error: this `if` expression is missing a block after the condition
--> $DIR/ensure-that-let-else-does-not-interact-with-let-chains.rs:23:7 --> $DIR/ensure-that-let-else-does-not-interact-with-let-chains.rs:23:5
| |
LL | if let Some(n) = opt else { LL | if let Some(n) = opt else {
| ^ expected if condition here | ^^
|
help: add a block here
--> $DIR/ensure-that-let-else-does-not-interact-with-let-chains.rs:23:25
|
LL | if let Some(n) = opt else {
| ^
error: missing condition for `if` expression error: this `if` expression is missing a block after the condition
--> $DIR/ensure-that-let-else-does-not-interact-with-let-chains.rs:27:7 --> $DIR/ensure-that-let-else-does-not-interact-with-let-chains.rs:27:5
| |
LL | if let Some(n) = opt && n == 1 else { LL | if let Some(n) = opt && n == 1 else {
| ^ expected if condition here | ^^
|
help: add a block here
--> $DIR/ensure-that-let-else-does-not-interact-with-let-chains.rs:27:35
|
LL | if let Some(n) = opt && n == 1 else {
| ^
error: missing condition for `if` expression error: this `if` expression is missing a block after the condition
--> $DIR/ensure-that-let-else-does-not-interact-with-let-chains.rs:31:7 --> $DIR/ensure-that-let-else-does-not-interact-with-let-chains.rs:31:5
| |
LL | if let Some(n) = opt && let another = n else { LL | if let Some(n) = opt && let another = n else {
| ^ expected if condition here | ^^
|
help: add a block here
--> $DIR/ensure-that-let-else-does-not-interact-with-let-chains.rs:31:44
|
LL | if let Some(n) = opt && let another = n else {
| ^
error: expected `{`, found keyword `else` error: expected `{`, found keyword `else`
--> $DIR/ensure-that-let-else-does-not-interact-with-let-chains.rs:37:33 --> $DIR/ensure-that-let-else-does-not-interact-with-let-chains.rs:37:33