From 9be37b2d3fc0b3852a7f9b134197dec895030201 Mon Sep 17 00:00:00 2001
From: Michael Goulet <michael@errs.io>
Date: Sun, 22 May 2022 16:06:36 -0700
Subject: [PATCH] Parse expression after `else` as a condition if followed by
 `{`

---
 compiler/rustc_parse/src/parser/expr.rs | 62 +++++++++++++++++++++++--
 src/test/ui/parser/else-no-if.rs        | 32 +++++++++++++
 src/test/ui/parser/else-no-if.stderr    | 58 +++++++++++++++++++++++
 3 files changed, 147 insertions(+), 5 deletions(-)
 create mode 100644 src/test/ui/parser/else-no-if.rs
 create mode 100644 src/test/ui/parser/else-no-if.stderr

diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index b5467c659a205..37e3465694131 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -2010,6 +2010,12 @@ impl<'a> Parser<'a> {
         Ok(self.mk_expr(blk.span, ExprKind::Block(blk, opt_label), attrs))
     }
 
+    /// Parse a block which takes no attributes and has no label
+    fn parse_simple_block(&mut self) -> PResult<'a, P<Expr>> {
+        let blk = self.parse_block()?;
+        Ok(self.mk_expr(blk.span, ExprKind::Block(blk, None), AttrVec::new()))
+    }
+
     /// Recover on an explicitly quantified closure expression, e.g., `for<'a> |x: &'a u8| *x + 1`.
     fn recover_quantified_closure_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
         let lo = self.token.span;
@@ -2157,6 +2163,15 @@ impl<'a> Parser<'a> {
         let lo = self.prev_token.span;
         let cond = self.parse_cond_expr()?;
 
+        self.parse_if_after_cond(attrs, lo, cond)
+    }
+
+    fn parse_if_after_cond(
+        &mut self,
+        attrs: AttrVec,
+        lo: Span,
+        cond: P<Expr>,
+    ) -> PResult<'a, P<Expr>> {
         let missing_then_block_binop_span = || {
             match cond.kind {
                 ExprKind::Binary(Spanned { span: binop_span, .. }, _, ref right)
@@ -2164,7 +2179,6 @@ impl<'a> Parser<'a> {
                 _ => None
             }
         };
-
         // Verify that the parsed `if` condition makes sense as a condition. If it is a block, then
         // verify that the last statement is either an implicit return (no `;`) or an explicit
         // return. This won't catch blocks with an explicit `return`, but that would be caught by
@@ -2256,15 +2270,53 @@ impl<'a> Parser<'a> {
 
     /// Parses an `else { ... }` expression (`else` token already eaten).
     fn parse_else_expr(&mut self) -> PResult<'a, P<Expr>> {
-        let ctx_span = self.prev_token.span; // `else`
+        let else_span = self.prev_token.span; // `else`
         let attrs = self.parse_outer_attributes()?.take_for_recovery(); // For recovery.
         let expr = if self.eat_keyword(kw::If) {
             self.parse_if_expr(AttrVec::new())?
+        } else if self.check(&TokenKind::OpenDelim(Delimiter::Brace)) {
+            self.parse_simple_block()?
         } else {
-            let blk = self.parse_block()?;
-            self.mk_expr(blk.span, ExprKind::Block(blk, None), AttrVec::new())
+            let snapshot = self.create_snapshot_for_diagnostic();
+            let first_tok = super::token_descr(&self.token);
+            let first_tok_span = self.token.span;
+            match self.parse_expr() {
+                Ok(cond)
+                // If it's not a free-standing expression, and is followed by a block,
+                // then it's very likely the condition to an `else if`.
+                    if self.check(&TokenKind::OpenDelim(Delimiter::Brace))
+                        && classify::expr_requires_semi_to_be_stmt(&cond) =>
+                {
+                    self.struct_span_err(first_tok_span, format!("expected `{{`, found {first_tok}"))
+                        .span_label(else_span, "expected an `if` or a block after this `else`")
+                        .span_suggestion(
+                            cond.span.shrink_to_lo(),
+                            "add an `if` if this is the condition to an chained `if` statement after the `else`",
+                            "if ".to_string(),
+                            Applicability::MaybeIncorrect,
+                        ).multipart_suggestion(
+                            "... otherwise, place this expression inside of a block if it is not an `if` condition",
+                            vec![
+                                (cond.span.shrink_to_lo(), "{ ".to_string()),
+                                (cond.span.shrink_to_hi(), " }".to_string()),
+                            ],
+                            Applicability::MaybeIncorrect,
+                        )
+                        .emit();
+                    self.parse_if_after_cond(AttrVec::new(), cond.span.shrink_to_lo(), cond)?
+                }
+                Err(e) => {
+                    e.cancel();
+                    self.restore_snapshot(snapshot);
+                    self.parse_simple_block()?
+                },
+                Ok(_) => {
+                    self.restore_snapshot(snapshot);
+                    self.parse_simple_block()?
+                },
+            }
         };
-        self.error_on_if_block_attrs(ctx_span, true, expr.span, &attrs);
+        self.error_on_if_block_attrs(else_span, true, expr.span, &attrs);
         Ok(expr)
     }
 
diff --git a/src/test/ui/parser/else-no-if.rs b/src/test/ui/parser/else-no-if.rs
new file mode 100644
index 0000000000000..f0b40ecde6660
--- /dev/null
+++ b/src/test/ui/parser/else-no-if.rs
@@ -0,0 +1,32 @@
+fn foo() {
+    if true {
+    } else false {
+    //~^ ERROR expected `{`, found keyword `false`
+    }
+}
+
+fn foo2() {
+    if true {
+    } else falsy() {
+    //~^ ERROR expected `{`, found `falsy`
+    }
+}
+
+fn foo3() {
+    if true {
+    } else falsy();
+    //~^ ERROR expected `{`, found `falsy`
+}
+
+fn foo4() {
+    if true {
+    } else loop{}
+    //~^ ERROR expected `{`, found keyword `loop`
+    {}
+}
+
+fn falsy() -> bool {
+    false
+}
+
+fn main() {}
diff --git a/src/test/ui/parser/else-no-if.stderr b/src/test/ui/parser/else-no-if.stderr
new file mode 100644
index 0000000000000..27abbadd7ad24
--- /dev/null
+++ b/src/test/ui/parser/else-no-if.stderr
@@ -0,0 +1,58 @@
+error: expected `{`, found keyword `false`
+  --> $DIR/else-no-if.rs:3:12
+   |
+LL |     } else false {
+   |       ---- ^^^^^
+   |       |
+   |       expected an `if` or a block after this `else`
+   |
+help: add an `if` if this is the condition to an chained `if` statement after the `else`
+   |
+LL |     } else if false {
+   |            ++
+help: ... otherwise, place this expression inside of a block if it is not an `if` condition
+   |
+LL |     } else { false } {
+   |            +       +
+
+error: expected `{`, found `falsy`
+  --> $DIR/else-no-if.rs:10:12
+   |
+LL |     } else falsy() {
+   |       ---- ^^^^^
+   |       |
+   |       expected an `if` or a block after this `else`
+   |
+help: add an `if` if this is the condition to an chained `if` statement after the `else`
+   |
+LL |     } else if falsy() {
+   |            ++
+help: ... otherwise, place this expression inside of a block if it is not an `if` condition
+   |
+LL |     } else { falsy() } {
+   |            +         +
+
+error: expected `{`, found `falsy`
+  --> $DIR/else-no-if.rs:17:12
+   |
+LL |     } else falsy();
+   |            ^^^^^ expected `{`
+   |
+help: try placing this code inside a block
+   |
+LL |     } else { falsy() };
+   |            +         +
+
+error: expected `{`, found keyword `loop`
+  --> $DIR/else-no-if.rs:23:12
+   |
+LL |     } else loop{}
+   |            ^^^^ expected `{`
+   |
+help: try placing this code inside a block
+   |
+LL |     } else { loop{} }
+   |            +        +
+
+error: aborting due to 4 previous errors
+