about summary refs log tree commit diff
path: root/compiler/rustc_parse/src/parser
diff options
context:
space:
mode:
authorDylan DPC <99973273+Dylan-DPC@users.noreply.github.com>2022-07-12 17:06:33 +0530
committerGitHub <noreply@github.com>2022-07-12 17:06:33 +0530
commit9997c51496f3f56409f0ebad1cfe9b08b4815de0 (patch)
tree5e9ac1f524e840e3f340dabc30b7c423bdcce500 /compiler/rustc_parse/src/parser
parent76153661dc2375068f9f49ca63c76b97e284d429 (diff)
parent1c3bab2fc9f9eb30c03b5019cb9503fef62e31a5 (diff)
downloadrust-9997c51496f3f56409f0ebad1cfe9b08b4815de0.tar.gz
rust-9997c51496f3f56409f0ebad1cfe9b08b4815de0.zip
Rollup merge of #98633 - c410-f3r:yet-another-let-chain, r=estebank
Fix last `let_chains` blocker

In order to forbid things like `let x = (let y = 1);` or `if let a = 1 && { let x = let y = 1; } {}`, the parser **HAS** to know the context of `let`.

This context thing is not a surprise in the parser because you can see **a lot** of ad hoc fixes mixing parsing logic with validation logic creating code that looks more like spaghetti with tomato sauce.

To make things even greater, a new ad hoc fix was added to only allow `let`s in a valid `let_chains` context by checking the previously processed token. This was the only solution I could think of and believe me, I thought about it for a long time 👍

In the long term, it should be preferable to segregate different responsibilities or create a more robust and cleaner parser framework.

cc https://github.com/rust-lang/rust/pull/94927
cc https://github.com/rust-lang/rust/issues/53667
Diffstat (limited to 'compiler/rustc_parse/src/parser')
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs32
1 files changed, 24 insertions, 8 deletions
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 2c43563b104..f9387e29262 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -1393,7 +1393,9 @@ impl<'a> Parser<'a> {
             self.parse_yield_expr(attrs)
         } else if self.is_do_yeet() {
             self.parse_yeet_expr(attrs)
-        } else if self.eat_keyword(kw::Let) {
+        } else if self.check_keyword(kw::Let) {
+            self.manage_let_chains_context();
+            self.bump();
             self.parse_let_expr(attrs)
         } else if self.eat_keyword(kw::Underscore) {
             Ok(self.mk_expr(self.prev_token.span, ExprKind::Underscore, attrs))
@@ -2355,16 +2357,30 @@ impl<'a> Parser<'a> {
         Ok(cond)
     }
 
+    // Checks if `let` is in an invalid position like `let x = let y = 1;` or
+    // if the current `let` is in a let_chains context but nested in another
+    // expression like `if let Some(_) = _opt && [1, 2, 3][let _ = ()] = 1`.
+    //
+    // This method expects that the current token is `let`.
+    fn manage_let_chains_context(&mut self) {
+        debug_assert!(matches!(self.token.kind, TokenKind::Ident(kw::Let, _)));
+        let is_in_a_let_chains_context_but_nested_in_other_expr = self.let_expr_allowed
+            && !matches!(
+                self.prev_token.kind,
+                TokenKind::AndAnd
+                    | TokenKind::CloseDelim(Delimiter::Brace)
+                    | TokenKind::Ident(kw::If, _)
+                    | TokenKind::Ident(kw::While, _)
+            );
+        if !self.let_expr_allowed || is_in_a_let_chains_context_but_nested_in_other_expr {
+            self.struct_span_err(self.token.span, "expected expression, found `let` statement")
+                .emit();
+        }
+    }
+
     /// Parses a `let $pat = $expr` pseudo-expression.
     /// The `let` token has already been eaten.
     fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
-        if !self.let_expr_allowed {
-            self.struct_span_err(
-                self.prev_token.span,
-                "expected expression, found `let` statement",
-            )
-            .emit();
-        }
         let lo = self.prev_token.span;
         let pat = self.parse_pat_allow_top_alt(
             None,