Skip to content

Commit

Permalink
Increase accuracy of if condition misparse suggestion
Browse files Browse the repository at this point in the history
Look at the expression that was parsed when trying to recover from a bad `if` condition to determine what was likely intended by the user beyond "maybe this was meant to be an `else` body".

```
error: expected `{`, found `map`
  --> $DIR/missing-dot-on-if-condition-expression-fixable.rs:4:30
   |
LL |     for _ in [1, 2, 3].iter()map(|x| x) {}
   |                              ^^^ expected `{`
   |
help: you might have meant to write a method call
   |
LL |     for _ in [1, 2, 3].iter().map(|x| x) {}
   |                              +
```
  • Loading branch information
estebank committed Nov 16, 2024
1 parent 917a50a commit 04fe839
Show file tree
Hide file tree
Showing 8 changed files with 385 additions and 17 deletions.
98 changes: 89 additions & 9 deletions compiler/rustc_parse/src/parser/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ impl<'a> Parser<'a> {
}

fn error_block_no_opening_brace_msg(&mut self, msg: Cow<'static, str>) -> Diag<'a> {
let prev = self.prev_token.span;
let sp = self.token.span;
let mut e = self.dcx().struct_span_err(sp, msg);
let do_not_suggest_help = self.token.is_keyword(kw::In) || self.token == token::Colon;
Expand Down Expand Up @@ -514,15 +515,94 @@ impl<'a> Parser<'a> {
} else {
stmt.span
};
e.multipart_suggestion(
"try placing this code inside a block",
vec![
(stmt_span.shrink_to_lo(), "{ ".to_string()),
(stmt_span.shrink_to_hi(), " }".to_string()),
],
// Speculative; has been misleading in the past (#46836).
Applicability::MaybeIncorrect,
);
match (&self.token.kind, &stmt.kind) {
(token::OpenDelim(Delimiter::Brace), StmtKind::Expr(expr))
if let ExprKind::Call(..) = expr.kind =>
{
// for _ in x y() {}
e.span_suggestion_verbose(
prev.between(sp),
"you might have meant to write a method call",
".".to_string(),
Applicability::MaybeIncorrect,
);
}
(token::OpenDelim(Delimiter::Brace), StmtKind::Expr(expr))
if let ExprKind::Field(..) = expr.kind =>
{
// for _ in x y.z {}
e.span_suggestion_verbose(
prev.between(sp),
"you might have meant to write a field access",
".".to_string(),
Applicability::MaybeIncorrect,
);
}
(token::CloseDelim(Delimiter::Brace), StmtKind::Expr(expr))
if let ExprKind::Struct(expr) = &expr.kind
&& let None = expr.qself
&& expr.path.segments.len() == 1 =>
{
// This is specific to "mistyped `if` condition followed by empty body"
//
// for _ in x y {}
e.span_suggestion_verbose(
prev.between(sp),
"you might have meant to write a field access",
".".to_string(),
Applicability::MaybeIncorrect,
);
}
(token::OpenDelim(Delimiter::Brace), StmtKind::Expr(expr))
if let ExprKind::Lit(lit) = expr.kind
&& let None = lit.suffix
&& let token::LitKind::Integer | token::LitKind::Float = lit.kind =>
{
// for _ in x 0 {}
// for _ in x 0.0 {}
e.span_suggestion_verbose(
prev.between(sp),
format!("you might have meant to write a field access"),
".".to_string(),
Applicability::MaybeIncorrect,
);
}
(token::OpenDelim(Delimiter::Brace), StmtKind::Expr(expr))
if let ExprKind::Loop(..)
| ExprKind::If(..)
| ExprKind::While(..)
| ExprKind::Match(..)
| ExprKind::ForLoop { .. }
| ExprKind::TryBlock(..)
| ExprKind::Ret(..)
| ExprKind::Closure(..)
| ExprKind::Struct(..)
| ExprKind::Try(..) = expr.kind =>
{
// These are more likely to have been meant as a block body.
e.multipart_suggestion(
"try placing this code inside a block",
vec![
(stmt_span.shrink_to_lo(), "{ ".to_string()),
(stmt_span.shrink_to_hi(), " }".to_string()),
],
// Speculative; has been misleading in the past (#46836).
Applicability::MaybeIncorrect,
);
}
(token::OpenDelim(Delimiter::Brace), _) => {}
(_, _) => {
e.multipart_suggestion(
"try placing this code inside a block",
vec![
(stmt_span.shrink_to_lo(), "{ ".to_string()),
(stmt_span.shrink_to_hi(), " }".to_string()),
],
// Speculative; has been misleading in the past (#46836).
Applicability::MaybeIncorrect,
);
}
}
}
Err(e) => {
self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore);
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/issues/issue-39848.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ 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)
help: try placing this code inside a block
help: you might have meant to write a method call
|
LL | if $tgt.has_{ $field() } {}
| + +
LL | if $tgt.has_.$field() {}
| +

error: aborting due to 1 previous error

5 changes: 0 additions & 5 deletions tests/ui/parser/else-no-if.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,6 @@ error: expected `{`, found `falsy`
|
LL | } else falsy! {} {
| ^^^^^ expected `{`
|
help: try placing this code inside a block
|
LL | } else { falsy! {} } {
| + +

error: expected `{`, found `falsy`
--> $DIR/else-no-if.rs:54:12
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//@ run-rustfix
#![allow(dead_code)]
fn main() {
for _ in [1, 2, 3].iter().map(|x| x) {}
//~^ ERROR expected `{`, found `map`
//~| HELP you might have meant to write a method call
}
fn foo5() {
let x = (vec![1, 2, 3],);
for _ in x.0 {}
//~^ ERROR expected `{`, found `0`
//~| HELP you might have meant to write a field access
}
fn foo6() {
let x = ((vec![1, 2, 3],),);
for _ in x.0.0 {}
//~^ ERROR expected `{`, found `0.0`
//~| HELP you might have meant to write a field access
}
fn foo7() {
let x = Some(vec![1, 2, 3]);
for _ in x.unwrap() {}
//~^ ERROR expected `{`, found `unwrap`
//~| HELP you might have meant to write a method call
}
fn foo8() {
let x = S { a: A { b: vec![1, 2, 3] } };
for _ in x.a.b {}
//~^ ERROR expected `{`, found `a`
//~| HELP you might have meant to write a field access
}

struct S {
a: A,
}

struct A {
b: Vec<i32>,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//@ run-rustfix
#![allow(dead_code)]
fn main() {
for _ in [1, 2, 3].iter()map(|x| x) {}
//~^ ERROR expected `{`, found `map`
//~| HELP you might have meant to write a method call
}
fn foo5() {
let x = (vec![1, 2, 3],);
for _ in x 0 {}
//~^ ERROR expected `{`, found `0`
//~| HELP you might have meant to write a field access
}
fn foo6() {
let x = ((vec![1, 2, 3],),);
for _ in x 0.0 {}
//~^ ERROR expected `{`, found `0.0`
//~| HELP you might have meant to write a field access
}
fn foo7() {
let x = Some(vec![1, 2, 3]);
for _ in x unwrap() {}
//~^ ERROR expected `{`, found `unwrap`
//~| HELP you might have meant to write a method call
}
fn foo8() {
let x = S { a: A { b: vec![1, 2, 3] } };
for _ in x a.b {}
//~^ ERROR expected `{`, found `a`
//~| HELP you might have meant to write a field access
}

struct S {
a: A,
}

struct A {
b: Vec<i32>,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
error: expected `{`, found `map`
--> $DIR/missing-dot-on-if-condition-expression-fixable.rs:4:30
|
LL | for _ in [1, 2, 3].iter()map(|x| x) {}
| ^^^ expected `{`
|
help: you might have meant to write a method call
|
LL | for _ in [1, 2, 3].iter().map(|x| x) {}
| +

error: expected `{`, found `0`
--> $DIR/missing-dot-on-if-condition-expression-fixable.rs:10:16
|
LL | for _ in x 0 {}
| ^ expected `{`
|
help: you might have meant to write a field access
|
LL | for _ in x.0 {}
| +

error: expected `{`, found `0.0`
--> $DIR/missing-dot-on-if-condition-expression-fixable.rs:16:16
|
LL | for _ in x 0.0 {}
| ^^^ expected `{`
|
help: you might have meant to write a field access
|
LL | for _ in x.0.0 {}
| +

error: expected `{`, found `unwrap`
--> $DIR/missing-dot-on-if-condition-expression-fixable.rs:22:16
|
LL | for _ in x unwrap() {}
| ^^^^^^ expected `{`
|
help: you might have meant to write a method call
|
LL | for _ in x.unwrap() {}
| +

error: expected `{`, found `a`
--> $DIR/missing-dot-on-if-condition-expression-fixable.rs:28:16
|
LL | for _ in x a.b {}
| ^ expected `{`
|
help: you might have meant to write a field access
|
LL | for _ in x.a.b {}
| +

error: aborting due to 5 previous errors

57 changes: 57 additions & 0 deletions tests/ui/parser/recover/missing-dot-on-if-condition-expression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
fn main() {
for _ in [1, 2, 3].iter()map(|x| x) {}
//~^ ERROR expected `{`, found `map`
//~| HELP you might have meant to write a method call
}
fn foo1() {
for _ in 1.3f64 cos() {}
//~^ ERROR expected `{`, found `cos`
//~| HELP you might have meant to write a method call
}
fn foo2() {
for _ in 1.3 cos {}
//~^ ERROR expected `{`, found `cos`
//~| HELP you might have meant to write a field access
}
fn foo3() {
for _ in 1 cos() {}
//~^ ERROR expected `{`, found `cos`
//~| HELP you might have meant to write a method call
}
fn foo4() {
for _ in 1 cos {}
//~^ ERROR expected `{`, found `cos`
//~| HELP you might have meant to write a field access
}
fn foo5() {
let x = (vec![1, 2, 3],);
for _ in x 0 {}
//~^ ERROR expected `{`, found `0`
//~| HELP you might have meant to write a field access
}
fn foo6() {
let x = ((vec![1, 2, 3],),);
for _ in x 0.0 {}
//~^ ERROR expected `{`, found `0.0`
//~| HELP you might have meant to write a field access
}
fn foo7() {
let x = Some(vec![1, 2, 3]);
for _ in x unwrap() {}
//~^ ERROR expected `{`, found `unwrap`
//~| HELP you might have meant to write a method call
}
fn foo8() {
let x = S { a: A { b: vec![1, 2, 3] } };
for _ in x a.b {}
//~^ ERROR expected `{`, found `a`
//~| HELP you might have meant to write a field access
}

struct S {
a: A,
}

struct A {
b: Vec<i32>,
}
Loading

0 comments on commit 04fe839

Please sign in to comment.