about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2024-11-14 20:29:24 +0000
committerEsteban Küber <esteban@kuber.com.ar>2024-11-16 20:03:31 +0000
commit04fe8391775683e58d861f28678bf80940c91f44 (patch)
treefcb880c349d5a63a1ddf9c1d08699ec83a79dab9
parent917a50a03931a9861c19a46f3e2a02a28f1da936 (diff)
downloadrust-04fe8391775683e58d861f28678bf80940c91f44.tar.gz
rust-04fe8391775683e58d861f28678bf80940c91f44.zip
Increase accuracy of `if` condition misparse suggestion
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) {}
   |                              +
```
-rw-r--r--compiler/rustc_parse/src/parser/stmt.rs98
-rw-r--r--tests/ui/issues/issue-39848.stderr6
-rw-r--r--tests/ui/parser/else-no-if.stderr5
-rw-r--r--tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.fixed39
-rw-r--r--tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.rs39
-rw-r--r--tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.stderr57
-rw-r--r--tests/ui/parser/recover/missing-dot-on-if-condition-expression.rs57
-rw-r--r--tests/ui/parser/recover/missing-dot-on-if-condition-expression.stderr101
8 files changed, 385 insertions, 17 deletions
diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs
index b7cdae3e3e1..e8877b8c541 100644
--- a/compiler/rustc_parse/src/parser/stmt.rs
+++ b/compiler/rustc_parse/src/parser/stmt.rs
@@ -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;
@@ -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);
diff --git a/tests/ui/issues/issue-39848.stderr b/tests/ui/issues/issue-39848.stderr
index a6c6c61f170..1ffed2d4a1d 100644
--- a/tests/ui/issues/issue-39848.stderr
+++ b/tests/ui/issues/issue-39848.stderr
@@ -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
 
diff --git a/tests/ui/parser/else-no-if.stderr b/tests/ui/parser/else-no-if.stderr
index 2e3e8f6b50e..c86791ec896 100644
--- a/tests/ui/parser/else-no-if.stderr
+++ b/tests/ui/parser/else-no-if.stderr
@@ -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
diff --git a/tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.fixed b/tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.fixed
new file mode 100644
index 00000000000..ea9dc4cf6cc
--- /dev/null
+++ b/tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.fixed
@@ -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>,
+}
diff --git a/tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.rs b/tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.rs
new file mode 100644
index 00000000000..1833f458a8a
--- /dev/null
+++ b/tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.rs
@@ -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>,
+}
diff --git a/tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.stderr b/tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.stderr
new file mode 100644
index 00000000000..87f76efffa6
--- /dev/null
+++ b/tests/ui/parser/recover/missing-dot-on-if-condition-expression-fixable.stderr
@@ -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
+
diff --git a/tests/ui/parser/recover/missing-dot-on-if-condition-expression.rs b/tests/ui/parser/recover/missing-dot-on-if-condition-expression.rs
new file mode 100644
index 00000000000..c4a9ed66a83
--- /dev/null
+++ b/tests/ui/parser/recover/missing-dot-on-if-condition-expression.rs
@@ -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>,
+}
diff --git a/tests/ui/parser/recover/missing-dot-on-if-condition-expression.stderr b/tests/ui/parser/recover/missing-dot-on-if-condition-expression.stderr
new file mode 100644
index 00000000000..bfb72b95682
--- /dev/null
+++ b/tests/ui/parser/recover/missing-dot-on-if-condition-expression.stderr
@@ -0,0 +1,101 @@
+error: expected `{`, found `map`
+  --> $DIR/missing-dot-on-if-condition-expression.rs:2: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 `cos`
+  --> $DIR/missing-dot-on-if-condition-expression.rs:7:21
+   |
+LL |     for _ in 1.3f64 cos() {}
+   |                     ^^^ expected `{`
+   |
+help: you might have meant to write a method call
+   |
+LL |     for _ in 1.3f64.cos() {}
+   |                    +
+
+error: expected `{`, found `cos`
+  --> $DIR/missing-dot-on-if-condition-expression.rs:12:18
+   |
+LL |     for _ in 1.3 cos {}
+   |                  ^^^ expected `{`
+   |
+help: you might have meant to write a field access
+   |
+LL |     for _ in 1.3.cos {}
+   |                 +
+
+error: expected `{`, found `cos`
+  --> $DIR/missing-dot-on-if-condition-expression.rs:17:16
+   |
+LL |     for _ in 1 cos() {}
+   |                ^^^ expected `{`
+   |
+help: you might have meant to write a method call
+   |
+LL |     for _ in 1.cos() {}
+   |               +
+
+error: expected `{`, found `cos`
+  --> $DIR/missing-dot-on-if-condition-expression.rs:22:16
+   |
+LL |     for _ in 1 cos {}
+   |                ^^^ expected `{`
+   |
+help: you might have meant to write a field access
+   |
+LL |     for _ in 1.cos {}
+   |               +
+
+error: expected `{`, found `0`
+  --> $DIR/missing-dot-on-if-condition-expression.rs:28: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.rs:34: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.rs:40: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.rs:46: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 9 previous errors
+