about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock4
-rw-r--r--crates/hir_def/src/body/lower.rs73
-rw-r--r--crates/hir_def/src/body/scope.rs124
-rw-r--r--crates/hir_def/src/expr.rs16
-rw-r--r--crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs6
-rw-r--r--crates/hir_expand/src/lib.rs8
-rw-r--r--crates/hir_ty/src/infer/expr.rs29
-rw-r--r--crates/hir_ty/src/tests/macros.rs4
-rw-r--r--crates/hir_ty/src/tests/patterns.rs10
-rw-r--r--crates/hir_ty/src/tests/simple.rs1
-rw-r--r--crates/ide/src/hover/render.rs4
-rw-r--r--crates/ide/src/inlay_hints.rs15
-rw-r--r--crates/ide_assists/src/handlers/convert_bool_then.rs7
-rw-r--r--crates/ide_assists/src/handlers/convert_to_guarded_return.rs35
-rw-r--r--crates/ide_assists/src/handlers/convert_while_to_loop.rs24
-rw-r--r--crates/ide_assists/src/handlers/extract_function.rs20
-rw-r--r--crates/ide_assists/src/handlers/invert_if.rs5
-rw-r--r--crates/ide_assists/src/handlers/move_guard.rs87
-rw-r--r--crates/ide_assists/src/handlers/replace_if_let_with_match.rs42
-rw-r--r--crates/ide_assists/src/handlers/replace_let_with_if_let.rs2
-rw-r--r--crates/ide_completion/src/context.rs12
-rw-r--r--crates/ide_db/src/helpers.rs1
-rw-r--r--crates/ide_db/src/helpers/node_ext.rs26
-rw-r--r--crates/parser/src/grammar/expressions.rs10
-rw-r--r--crates/parser/src/grammar/expressions/atom.rs33
-rw-r--r--crates/parser/src/syntax_kind/generated.rs2
-rw-r--r--crates/parser/src/tests/top_entries.rs24
-rw-r--r--crates/parser/test_data/parser/err/0008_item_block_recovery.txt5
-rw-r--r--crates/parser/test_data/parser/err/0019_let_recover.rs2
-rw-r--r--crates/parser/test_data/parser/err/0019_let_recover.txt32
-rw-r--r--crates/parser/test_data/parser/err/0024_many_type_parens.txt214
-rw-r--r--crates/parser/test_data/parser/inline/ok/0030_cond.rast209
-rw-r--r--crates/parser/test_data/parser/inline/ok/0030_cond.rs7
-rw-r--r--crates/parser/test_data/parser/inline/ok/0030_cond.txt209
-rw-r--r--crates/parser/test_data/parser/inline/ok/0030_let_expr.rast90
-rw-r--r--crates/parser/test_data/parser/inline/ok/0030_let_expr.rs4
-rw-r--r--crates/parser/test_data/parser/inline/ok/0030_let_expr.txt90
-rw-r--r--crates/parser/test_data/parser/inline/ok/0031_while_expr.txt24
-rw-r--r--crates/parser/test_data/parser/inline/ok/0064_if_expr.txt48
-rw-r--r--crates/parser/test_data/parser/inline/ok/0088_break_ambiguity.txt10
-rw-r--r--crates/parser/test_data/parser/inline/ok/0096_no_semi_after_block.txt10
-rw-r--r--crates/parser/test_data/parser/inline/ok/0109_label.txt5
-rw-r--r--crates/parser/test_data/parser/inline/ok/0118_match_guard.txt27
-rw-r--r--crates/parser/test_data/parser/ok/0033_label_break.txt76
-rw-r--r--crates/parser/test_data/parser/ok/0035_weird_exprs.txt135
-rw-r--r--crates/parser/test_data/parser/ok/0047_minus_in_inner_pattern.txt2
-rw-r--r--crates/parser/test_data/parser/ok/0056_neq_in_type.txt77
-rw-r--r--crates/parser/test_data/parser/ok/0059_loops_in_parens.txt5
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--crates/syntax/src/ast/generated/nodes.rs73
-rw-r--r--crates/syntax/src/ast/make.rs12
-rw-r--r--crates/syntax/src/ast/node_ext.rs6
-rw-r--r--crates/syntax/src/tests/ast_src.rs2
-rw-r--r--crates/syntax/src/validation.rs31
-rw-r--r--crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast10
-rw-r--r--crates/syntax/test_data/parser/validation/invalid_let_expr.rast216
-rw-r--r--crates/syntax/test_data/parser/validation/invalid_let_expr.rs14
57 files changed, 1177 insertions, 1094 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ef0bd5e3bce..64654c9961d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1832,9 +1832,9 @@ checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
 
 [[package]]
 name = "ungrammar"
-version = "1.14.9"
+version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "66be59c2fd880e3d76d1a6cf6d34114008f1d8af2748d4ad9d39ea712f14fda9"
+checksum = "ed01567101450f7d600508e7680df6005ae4fe97119d79b0364cc5910ff39732"
 
 [[package]]
 name = "unicase"
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 7cbeef1488a..06ad7ce4cd0 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -28,7 +28,7 @@ use crate::{
     db::DefDatabase,
     expr::{
         dummy_expr_id, Array, BindingAnnotation, Expr, ExprId, Label, LabelId, Literal, MatchArm,
-        MatchGuard, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
+        Pat, PatId, RecordFieldPat, RecordLitField, Statement,
     },
     intern::Interned,
     item_scope::BuiltinShadowMode,
@@ -155,9 +155,6 @@ impl ExprCollector<'_> {
     fn alloc_expr_desugared(&mut self, expr: Expr) -> ExprId {
         self.make_expr(expr, Err(SyntheticSyntax))
     }
-    fn unit(&mut self) -> ExprId {
-        self.alloc_expr_desugared(Expr::Tuple { exprs: Box::default() })
-    }
     fn missing_expr(&mut self) -> ExprId {
         self.alloc_expr_desugared(Expr::Missing)
     }
@@ -215,33 +212,15 @@ impl ExprCollector<'_> {
                     }
                 });
 
-                let condition = match e.condition() {
-                    None => self.missing_expr(),
-                    Some(condition) => match condition.pat() {
-                        None => self.collect_expr_opt(condition.expr()),
-                        // if let -- desugar to match
-                        Some(pat) => {
-                            let pat = self.collect_pat(pat);
-                            let match_expr = self.collect_expr_opt(condition.expr());
-                            let placeholder_pat = self.missing_pat();
-                            let arms = vec![
-                                MatchArm { pat, expr: then_branch, guard: None },
-                                MatchArm {
-                                    pat: placeholder_pat,
-                                    expr: else_branch.unwrap_or_else(|| self.unit()),
-                                    guard: None,
-                                },
-                            ]
-                            .into();
-                            return Some(
-                                self.alloc_expr(Expr::Match { expr: match_expr, arms }, syntax_ptr),
-                            );
-                        }
-                    },
-                };
+                let condition = self.collect_expr_opt(e.condition());
 
                 self.alloc_expr(Expr::If { condition, then_branch, else_branch }, syntax_ptr)
             }
+            ast::Expr::LetExpr(e) => {
+                let pat = self.collect_pat_opt(e.pat());
+                let expr = self.collect_expr_opt(e.expr());
+                self.alloc_expr(Expr::Let { pat, expr }, syntax_ptr)
+            }
             ast::Expr::BlockExpr(e) => match e.modifier() {
                 Some(ast::BlockModifier::Try(_)) => {
                     let body = self.collect_block(e);
@@ -282,31 +261,7 @@ impl ExprCollector<'_> {
                 let label = e.label().map(|label| self.collect_label(label));
                 let body = self.collect_block_opt(e.loop_body());
 
-                let condition = match e.condition() {
-                    None => self.missing_expr(),
-                    Some(condition) => match condition.pat() {
-                        None => self.collect_expr_opt(condition.expr()),
-                        // if let -- desugar to match
-                        Some(pat) => {
-                            cov_mark::hit!(infer_resolve_while_let);
-                            let pat = self.collect_pat(pat);
-                            let match_expr = self.collect_expr_opt(condition.expr());
-                            let placeholder_pat = self.missing_pat();
-                            let break_ =
-                                self.alloc_expr_desugared(Expr::Break { expr: None, label: None });
-                            let arms = vec![
-                                MatchArm { pat, expr: body, guard: None },
-                                MatchArm { pat: placeholder_pat, expr: break_, guard: None },
-                            ]
-                            .into();
-                            let match_expr =
-                                self.alloc_expr_desugared(Expr::Match { expr: match_expr, arms });
-                            return Some(
-                                self.alloc_expr(Expr::Loop { body: match_expr, label }, syntax_ptr),
-                            );
-                        }
-                    },
-                };
+                let condition = self.collect_expr_opt(e.condition());
 
                 self.alloc_expr(Expr::While { condition, body, label }, syntax_ptr)
             }
@@ -352,15 +307,9 @@ impl ExprCollector<'_> {
                             self.check_cfg(&arm).map(|()| MatchArm {
                                 pat: self.collect_pat_opt(arm.pat()),
                                 expr: self.collect_expr_opt(arm.expr()),
-                                guard: arm.guard().map(|guard| match guard.pat() {
-                                    Some(pat) => MatchGuard::IfLet {
-                                        pat: self.collect_pat(pat),
-                                        expr: self.collect_expr_opt(guard.expr()),
-                                    },
-                                    None => {
-                                        MatchGuard::If { expr: self.collect_expr_opt(guard.expr()) }
-                                    }
-                                }),
+                                guard: arm
+                                    .guard()
+                                    .map(|guard| self.collect_expr_opt(guard.condition())),
                             })
                         })
                         .collect()
diff --git a/crates/hir_def/src/body/scope.rs b/crates/hir_def/src/body/scope.rs
index 2658eece8e8..fc36f1ae526 100644
--- a/crates/hir_def/src/body/scope.rs
+++ b/crates/hir_def/src/body/scope.rs
@@ -8,7 +8,7 @@ use rustc_hash::FxHashMap;
 use crate::{
     body::Body,
     db::DefDatabase,
-    expr::{Expr, ExprId, LabelId, MatchGuard, Pat, PatId, Statement},
+    expr::{Expr, ExprId, LabelId, Pat, PatId, Statement},
     BlockId, DefWithBodyId,
 };
 
@@ -53,9 +53,9 @@ impl ExprScopes {
     fn new(body: &Body) -> ExprScopes {
         let mut scopes =
             ExprScopes { scopes: Arena::default(), scope_by_expr: FxHashMap::default() };
-        let root = scopes.root_scope();
+        let mut root = scopes.root_scope();
         scopes.add_params_bindings(body, root, &body.params);
-        compute_expr_scopes(body.body_expr, body, &mut scopes, root);
+        compute_expr_scopes(body.body_expr, body, &mut scopes, &mut root);
         scopes
     }
 
@@ -151,32 +151,32 @@ fn compute_block_scopes(
         match stmt {
             Statement::Let { pat, initializer, else_branch, .. } => {
                 if let Some(expr) = initializer {
-                    compute_expr_scopes(*expr, body, scopes, scope);
+                    compute_expr_scopes(*expr, body, scopes, &mut scope);
                 }
                 if let Some(expr) = else_branch {
-                    compute_expr_scopes(*expr, body, scopes, scope);
+                    compute_expr_scopes(*expr, body, scopes, &mut scope);
                 }
                 scope = scopes.new_scope(scope);
                 scopes.add_bindings(body, scope, *pat);
             }
             Statement::Expr { expr, .. } => {
-                compute_expr_scopes(*expr, body, scopes, scope);
+                compute_expr_scopes(*expr, body, scopes, &mut scope);
             }
         }
     }
     if let Some(expr) = tail {
-        compute_expr_scopes(expr, body, scopes, scope);
+        compute_expr_scopes(expr, body, scopes, &mut scope);
     }
 }
 
-fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) {
+fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: &mut ScopeId) {
     let make_label =
         |label: &Option<LabelId>| label.map(|label| (label, body.labels[label].name.clone()));
 
-    scopes.set_scope(expr, scope);
+    scopes.set_scope(expr, *scope);
     match &body[expr] {
         Expr::Block { statements, tail, id, label } => {
-            let scope = scopes.new_block_scope(scope, *id, make_label(label));
+            let scope = scopes.new_block_scope(*scope, *id, make_label(label));
             // Overwrite the old scope for the block expr, so that every block scope can be found
             // via the block itself (important for blocks that only contain items, no expressions).
             scopes.set_scope(expr, scope);
@@ -184,46 +184,49 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
         }
         Expr::For { iterable, pat, body: body_expr, label } => {
             compute_expr_scopes(*iterable, body, scopes, scope);
-            let scope = scopes.new_labeled_scope(scope, make_label(label));
+            let mut scope = scopes.new_labeled_scope(*scope, make_label(label));
             scopes.add_bindings(body, scope, *pat);
-            compute_expr_scopes(*body_expr, body, scopes, scope);
+            compute_expr_scopes(*body_expr, body, scopes, &mut scope);
         }
         Expr::While { condition, body: body_expr, label } => {
-            let scope = scopes.new_labeled_scope(scope, make_label(label));
-            compute_expr_scopes(*condition, body, scopes, scope);
-            compute_expr_scopes(*body_expr, body, scopes, scope);
+            let mut scope = scopes.new_labeled_scope(*scope, make_label(label));
+            compute_expr_scopes(*condition, body, scopes, &mut scope);
+            compute_expr_scopes(*body_expr, body, scopes, &mut scope);
         }
         Expr::Loop { body: body_expr, label } => {
-            let scope = scopes.new_labeled_scope(scope, make_label(label));
-            compute_expr_scopes(*body_expr, body, scopes, scope);
+            let mut scope = scopes.new_labeled_scope(*scope, make_label(label));
+            compute_expr_scopes(*body_expr, body, scopes, &mut scope);
         }
         Expr::Lambda { args, body: body_expr, .. } => {
-            let scope = scopes.new_scope(scope);
+            let mut scope = scopes.new_scope(*scope);
             scopes.add_params_bindings(body, scope, args);
-            compute_expr_scopes(*body_expr, body, scopes, scope);
+            compute_expr_scopes(*body_expr, body, scopes, &mut scope);
         }
         Expr::Match { expr, arms } => {
             compute_expr_scopes(*expr, body, scopes, scope);
             for arm in arms.iter() {
-                let mut scope = scopes.new_scope(scope);
+                let mut scope = scopes.new_scope(*scope);
                 scopes.add_bindings(body, scope, arm.pat);
-                match arm.guard {
-                    Some(MatchGuard::If { expr: guard }) => {
-                        scopes.set_scope(guard, scope);
-                        compute_expr_scopes(guard, body, scopes, scope);
-                    }
-                    Some(MatchGuard::IfLet { pat, expr: guard }) => {
-                        scopes.set_scope(guard, scope);
-                        compute_expr_scopes(guard, body, scopes, scope);
-                        scope = scopes.new_scope(scope);
-                        scopes.add_bindings(body, scope, pat);
-                    }
-                    _ => {}
-                };
-                scopes.set_scope(arm.expr, scope);
-                compute_expr_scopes(arm.expr, body, scopes, scope);
+                if let Some(guard) = arm.guard {
+                    scope = scopes.new_scope(scope);
+                    compute_expr_scopes(guard, body, scopes, &mut scope);
+                }
+                compute_expr_scopes(arm.expr, body, scopes, &mut scope);
+            }
+        }
+        &Expr::If { condition, then_branch, else_branch } => {
+            let mut then_branch_scope = scopes.new_scope(*scope);
+            compute_expr_scopes(condition, body, scopes, &mut then_branch_scope);
+            compute_expr_scopes(then_branch, body, scopes, &mut then_branch_scope);
+            if let Some(else_branch) = else_branch {
+                compute_expr_scopes(else_branch, body, scopes, scope);
             }
         }
+        &Expr::Let { pat, expr } => {
+            compute_expr_scopes(expr, body, scopes, scope);
+            *scope = scopes.new_scope(*scope);
+            scopes.add_bindings(body, *scope, pat);
+        }
         e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)),
     };
 }
@@ -500,8 +503,7 @@ fn foo() {
     }
 
     #[test]
-    fn while_let_desugaring() {
-        cov_mark::check!(infer_resolve_while_let);
+    fn while_let_adds_binding() {
         do_check_local_name(
             r#"
 fn test() {
@@ -513,5 +515,53 @@ fn test() {
 "#,
             75,
         );
+        do_check_local_name(
+            r#"
+fn test() {
+    let foo: Option<f32> = None;
+    while (((let Option::Some(_) = foo))) && let Option::Some(spam) = foo {
+        spam$0
+    }
+}
+"#,
+            107,
+        );
+    }
+
+    #[test]
+    fn match_guard_if_let() {
+        do_check_local_name(
+            r#"
+fn test() {
+    let foo: Option<f32> = None;
+    match foo {
+        _ if let Option::Some(spam) = foo => spam$0,
+    }
+}
+"#,
+            93,
+        );
+    }
+
+    #[test]
+    fn let_chains_can_reference_previous_lets() {
+        do_check_local_name(
+            r#"
+fn test() {
+    let foo: Option<i32> = None;
+    if let Some(spam) = foo && spa$0m > 1 && let Some(spam) = foo && spam > 1 {}
+}
+"#,
+            61,
+        );
+        do_check_local_name(
+            r#"
+fn test() {
+    let foo: Option<i32> = None;
+    if let Some(spam) = foo && spam > 1 && let Some(spam) = foo && sp$0am > 1 {}
+}
+"#,
+            100,
+        );
     }
 }
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index 6534f970ee6..4dca8238880 100644
--- a/crates/hir_def/src/expr.rs
+++ b/crates/hir_def/src/expr.rs
@@ -59,6 +59,10 @@ pub enum Expr {
         then_branch: ExprId,
         else_branch: Option<ExprId>,
     },
+    Let {
+        pat: PatId,
+        expr: ExprId,
+    },
     Block {
         id: BlockId,
         statements: Box<[Statement]>,
@@ -189,18 +193,11 @@ pub enum Array {
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct MatchArm {
     pub pat: PatId,
-    pub guard: Option<MatchGuard>,
+    pub guard: Option<ExprId>,
     pub expr: ExprId,
 }
 
 #[derive(Debug, Clone, Eq, PartialEq)]
-pub enum MatchGuard {
-    If { expr: ExprId },
-
-    IfLet { pat: PatId, expr: ExprId },
-}
-
-#[derive(Debug, Clone, Eq, PartialEq)]
 pub struct RecordLitField {
     pub name: Name,
     pub expr: ExprId,
@@ -232,6 +229,9 @@ impl Expr {
                     f(else_branch);
                 }
             }
+            Expr::Let { expr, .. } => {
+                f(*expr);
+            }
             Expr::Block { statements, tail, .. } => {
                 for stmt in statements.iter() {
                     match stmt {
diff --git a/crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs b/crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs
index 5f4b7d6d0bc..84cc3f3872f 100644
--- a/crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs
+++ b/crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs
@@ -108,18 +108,18 @@ fn expansion_does_not_parse_as_expression() {
     check(
         r#"
 macro_rules! stmts {
-    () => { let _ = 0; }
+    () => { fn foo() {} }
 }
 
 fn f() { let _ = stmts!/*+errors*/(); }
 "#,
         expect![[r#"
 macro_rules! stmts {
-    () => { let _ = 0; }
+    () => { fn foo() {} }
 }
 
 fn f() { let _ = /* parse error: expected expression */
-let _ = 0;; }
+fn foo() {}; }
 "#]],
     )
 }
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 56cf7aed8ee..e33c2565c31 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -811,10 +811,10 @@ impl ExpandTo {
             MACRO_TYPE => ExpandTo::Type,
 
             ARG_LIST | TRY_EXPR | TUPLE_EXPR | PAREN_EXPR | ARRAY_EXPR | FOR_EXPR | PATH_EXPR
-            | CLOSURE_EXPR | CONDITION | BREAK_EXPR | RETURN_EXPR | MATCH_EXPR | MATCH_ARM
-            | MATCH_GUARD | RECORD_EXPR_FIELD | CALL_EXPR | INDEX_EXPR | METHOD_CALL_EXPR
-            | FIELD_EXPR | AWAIT_EXPR | CAST_EXPR | REF_EXPR | PREFIX_EXPR | RANGE_EXPR
-            | BIN_EXPR => ExpandTo::Expr,
+            | CLOSURE_EXPR | BREAK_EXPR | RETURN_EXPR | MATCH_EXPR | MATCH_ARM | MATCH_GUARD
+            | RECORD_EXPR_FIELD | CALL_EXPR | INDEX_EXPR | METHOD_CALL_EXPR | FIELD_EXPR
+            | AWAIT_EXPR | CAST_EXPR | REF_EXPR | PREFIX_EXPR | RANGE_EXPR | BIN_EXPR
+            | LET_EXPR => ExpandTo::Expr,
             LET_STMT => {
                 // FIXME: Handle LHS Pattern
                 ExpandTo::Expr
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index 4f1bdee705d..13f64d68252 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -8,10 +8,7 @@ use std::{
 
 use chalk_ir::{cast::Cast, fold::Shift, Mutability, TyVariableKind};
 use hir_def::{
-    expr::{
-        ArithOp, Array, BinaryOp, CmpOp, Expr, ExprId, Literal, MatchGuard, Ordering, Statement,
-        UnaryOp,
-    },
+    expr::{ArithOp, Array, BinaryOp, CmpOp, Expr, ExprId, Literal, Ordering, Statement, UnaryOp},
     path::{GenericArg, GenericArgs},
     resolver::resolver_for_expr,
     FieldId, FunctionId, ItemContainerId, Lookup,
@@ -158,6 +155,11 @@ impl<'a> InferenceContext<'a> {
 
                 coerce.complete()
             }
+            &Expr::Let { pat, expr } => {
+                let input_ty = self.infer_expr(expr, &Expectation::none());
+                self.infer_pat(pat, &input_ty, BindingMode::default());
+                TyKind::Scalar(Scalar::Bool).intern(Interner)
+            }
             Expr::Block { statements, tail, label, id: _ } => {
                 let old_resolver = mem::replace(
                     &mut self.resolver,
@@ -378,20 +380,11 @@ impl<'a> InferenceContext<'a> {
                 for arm in arms.iter() {
                     self.diverges = Diverges::Maybe;
                     let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default());
-                    match arm.guard {
-                        Some(MatchGuard::If { expr: guard_expr }) => {
-                            self.infer_expr(
-                                guard_expr,
-                                &Expectation::has_type(
-                                    TyKind::Scalar(Scalar::Bool).intern(Interner),
-                                ),
-                            );
-                        }
-                        Some(MatchGuard::IfLet { expr, pat }) => {
-                            let input_ty = self.infer_expr(expr, &Expectation::none());
-                            let _pat_ty = self.infer_pat(pat, &input_ty, BindingMode::default());
-                        }
-                        _ => {}
+                    if let Some(guard_expr) = arm.guard {
+                        self.infer_expr(
+                            guard_expr,
+                            &Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)),
+                        );
                     }
 
                     let arm_ty = self.infer_expr_inner(arm.expr, &expected);
diff --git a/crates/hir_ty/src/tests/macros.rs b/crates/hir_ty/src/tests/macros.rs
index a61175f2733..344e7293c59 100644
--- a/crates/hir_ty/src/tests/macros.rs
+++ b/crates/hir_ty/src/tests/macros.rs
@@ -190,7 +190,6 @@ fn expr_macro_def_expanded_in_various_places() {
             !0..6 '1isize': isize
             !0..6 '1isize': isize
             !0..6 '1isize': isize
-            !0..6 '1isize': isize
             39..442 '{     ...!(); }': ()
             73..94 'spam!(...am!())': {unknown}
             100..119 'for _ ...!() {}': ()
@@ -198,6 +197,7 @@ fn expr_macro_def_expanded_in_various_places() {
             117..119 '{}': ()
             124..134 '|| spam!()': || -> isize
             140..156 'while ...!() {}': ()
+            146..153 'spam!()': bool
             154..156 '{}': ()
             161..174 'break spam!()': !
             180..194 'return spam!()': !
@@ -271,7 +271,6 @@ fn expr_macro_rules_expanded_in_various_places() {
             !0..6 '1isize': isize
             !0..6 '1isize': isize
             !0..6 '1isize': isize
-            !0..6 '1isize': isize
             53..456 '{     ...!(); }': ()
             87..108 'spam!(...am!())': {unknown}
             114..133 'for _ ...!() {}': ()
@@ -279,6 +278,7 @@ fn expr_macro_rules_expanded_in_various_places() {
             131..133 '{}': ()
             138..148 '|| spam!()': || -> isize
             154..170 'while ...!() {}': ()
+            160..167 'spam!()': bool
             168..170 '{}': ()
             175..188 'break spam!()': !
             194..208 'return spam!()': !
diff --git a/crates/hir_ty/src/tests/patterns.rs b/crates/hir_ty/src/tests/patterns.rs
index 5b08d65c467..acdd8f50efb 100644
--- a/crates/hir_ty/src/tests/patterns.rs
+++ b/crates/hir_ty/src/tests/patterns.rs
@@ -55,6 +55,7 @@ fn infer_pattern() {
             139..140 'g': {unknown}
             143..144 'e': {unknown}
             157..204 'if let...     }': ()
+            160..175 'let [val] = opt': bool
             164..169 '[val]': [{unknown}]
             165..168 'val': {unknown}
             172..175 'opt': [{unknown}]
@@ -62,6 +63,7 @@ fn infer_pattern() {
             190..191 'h': {unknown}
             194..197 'val': {unknown}
             210..236 'if let...rue {}': ()
+            213..233 'let x ... &true': bool
             217..225 'x @ true': &bool
             221..225 'true': bool
             221..225 'true': bool
@@ -111,36 +113,42 @@ fn infer_literal_pattern() {
             37..38 'x': &i32
             46..208 '{     ...) {} }': ()
             52..75 'if let...y() {}': ()
+            55..72 'let "f... any()': bool
             59..64 '"foo"': &str
             59..64 '"foo"': &str
             67..70 'any': fn any<&str>() -> &str
             67..72 'any()': &str
             73..75 '{}': ()
             80..99 'if let...y() {}': ()
+            83..96 'let 1 = any()': bool
             87..88 '1': i32
             87..88 '1': i32
             91..94 'any': fn any<i32>() -> i32
             91..96 'any()': i32
             97..99 '{}': ()
             104..126 'if let...y() {}': ()
+            107..123 'let 1u... any()': bool
             111..115 '1u32': u32
             111..115 '1u32': u32
             118..121 'any': fn any<u32>() -> u32
             118..123 'any()': u32
             124..126 '{}': ()
             131..153 'if let...y() {}': ()
+            134..150 'let 1f... any()': bool
             138..142 '1f32': f32
             138..142 '1f32': f32
             145..148 'any': fn any<f32>() -> f32
             145..150 'any()': f32
             151..153 '{}': ()
             158..179 'if let...y() {}': ()
+            161..176 'let 1.0 = any()': bool
             165..168 '1.0': f64
             165..168 '1.0': f64
             171..174 'any': fn any<f64>() -> f64
             171..176 'any()': f64
             177..179 '{}': ()
             184..206 'if let...y() {}': ()
+            187..203 'let tr... any()': bool
             191..195 'true': bool
             191..195 'true': bool
             198..201 'any': fn any<bool>() -> bool
@@ -163,10 +171,12 @@ fn infer_range_pattern() {
             8..9 'x': &i32
             17..75 '{     ...2 {} }': ()
             23..45 'if let...u32 {}': ()
+            26..42 'let 1....= 2u32': bool
             30..35 '1..76': u32
             38..42 '2u32': u32
             43..45 '{}': ()
             50..73 'if let...u32 {}': ()
+            53..70 'let 1....= 2u32': bool
             57..63 '1..=76': u32
             66..70 '2u32': u32
             71..73 '{}': ()
diff --git a/crates/hir_ty/src/tests/simple.rs b/crates/hir_ty/src/tests/simple.rs
index f4d082ea8c6..c11a70fa663 100644
--- a/crates/hir_ty/src/tests/simple.rs
+++ b/crates/hir_ty/src/tests/simple.rs
@@ -2248,6 +2248,7 @@ fn generic_default_in_struct_literal() {
             176..193 'Thing ...1i32 }': Thing<i32>
             187..191 '1i32': i32
             199..240 'if let...     }': ()
+            202..221 'let Th... } = z': bool
             206..217 'Thing { t }': Thing<i32>
             214..215 't': i32
             220..221 'z': Thing<i32>
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index f94348ec581..ce9055c0909 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -18,7 +18,7 @@ use syntax::{
     algo, ast,
     display::{fn_as_proc_macro_label, macro_label},
     match_ast, AstNode, Direction,
-    SyntaxKind::{CONDITION, LET_STMT},
+    SyntaxKind::{LET_EXPR, LET_STMT},
     SyntaxToken, T,
 };
 
@@ -484,7 +484,7 @@ fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
             let let_kw = if ident
                 .syntax()
                 .parent()
-                .map_or(false, |p| p.kind() == LET_STMT || p.kind() == CONDITION)
+                .map_or(false, |p| p.kind() == LET_STMT || p.kind() == LET_EXPR)
             {
                 "let "
             } else {
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index cc304cb10a6..2ca756cbe04 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -243,7 +243,7 @@ fn is_named_constructor(
     let expr = match_ast! {
         match let_node {
             ast::LetStmt(it) => it.initializer(),
-            ast::Condition(it) => it.expr(),
+            ast::LetExpr(it) => it.expr(),
             _ => None,
         }
     }?;
@@ -372,15 +372,10 @@ fn should_not_display_type_hint(
             match node {
                 ast::LetStmt(it) => return it.ty().is_some(),
                 ast::Param(it) => return it.ty().is_some(),
-                ast::MatchArm(_it) => return pat_is_enum_variant(db, bind_pat, pat_ty),
-                ast::IfExpr(it) => {
-                    return it.condition().and_then(|condition| condition.pat()).is_some()
-                        && pat_is_enum_variant(db, bind_pat, pat_ty);
-                },
-                ast::WhileExpr(it) => {
-                    return it.condition().and_then(|condition| condition.pat()).is_some()
-                        && pat_is_enum_variant(db, bind_pat, pat_ty);
-                },
+                ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
+                ast::LetExpr(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
+                ast::IfExpr(_) => return false,
+                ast::WhileExpr(_) => return false,
                 ast::ForExpr(it) => {
                     // We *should* display hint only if user provided "in {expr}" and we know the type of expr (and it's not unit).
                     // Type of expr should be iterable.
diff --git a/crates/ide_assists/src/handlers/convert_bool_then.rs b/crates/ide_assists/src/handlers/convert_bool_then.rs
index b8c55eb852f..274718e6ea9 100644
--- a/crates/ide_assists/src/handlers/convert_bool_then.rs
+++ b/crates/ide_assists/src/handlers/convert_bool_then.rs
@@ -2,7 +2,7 @@ use hir::{known, AsAssocItem, Semantics};
 use ide_db::{
     helpers::{
         for_each_tail_expr,
-        node_ext::{block_as_lone_tail, preorder_expr},
+        node_ext::{block_as_lone_tail, is_pattern_cond, preorder_expr},
         FamousDefs,
     },
     RootDatabase,
@@ -45,8 +45,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext) ->
         return None;
     }
 
-    let cond = expr.condition().filter(|cond| !cond.is_pattern_cond())?;
-    let cond = cond.expr()?;
+    let cond = expr.condition().filter(|cond| !is_pattern_cond(cond.clone()))?;
     let then = expr.then_branch()?;
     let else_ = match expr.else_branch()? {
         ast::ElseBranch::Block(b) => b,
@@ -209,7 +208,7 @@ pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext) ->
                 _ => receiver,
             };
             let if_expr = make::expr_if(
-                make::condition(cond, None),
+                cond,
                 closure_body.reset_indent(),
                 Some(ast::ElseBranch::Block(make::block_expr(None, Some(none_path)))),
             )
diff --git a/crates/ide_assists/src/handlers/convert_to_guarded_return.rs b/crates/ide_assists/src/handlers/convert_to_guarded_return.rs
index 884905a9188..193d1cdfb24 100644
--- a/crates/ide_assists/src/handlers/convert_to_guarded_return.rs
+++ b/crates/ide_assists/src/handlers/convert_to_guarded_return.rs
@@ -1,5 +1,6 @@
 use std::iter::once;
 
+use ide_db::helpers::node_ext::{is_pattern_cond, single_let};
 use syntax::{
     ast::{
         self,
@@ -48,25 +49,28 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
     let cond = if_expr.condition()?;
 
     // Check if there is an IfLet that we can handle.
-    let if_let_pat = match cond.pat() {
-        None => None, // No IfLet, supported.
-        Some(ast::Pat::TupleStructPat(pat)) if pat.fields().count() == 1 => {
-            let path = pat.path()?;
-            if path.qualifier().is_some() {
-                return None;
-            }
+    let (if_let_pat, cond_expr) = if is_pattern_cond(cond.clone()) {
+        let let_ = single_let(cond)?;
+        match let_.pat() {
+            Some(ast::Pat::TupleStructPat(pat)) if pat.fields().count() == 1 => {
+                let path = pat.path()?;
+                if path.qualifier().is_some() {
+                    return None;
+                }
 
-            let bound_ident = pat.fields().next().unwrap();
-            if !ast::IdentPat::can_cast(bound_ident.syntax().kind()) {
-                return None;
-            }
+                let bound_ident = pat.fields().next().unwrap();
+                if !ast::IdentPat::can_cast(bound_ident.syntax().kind()) {
+                    return None;
+                }
 
-            Some((path, bound_ident))
+                (Some((path, bound_ident)), let_.expr()?)
+            }
+            _ => return None, // Unsupported IfLet.
         }
-        Some(_) => return None, // Unsupported IfLet.
+    } else {
+        (None, cond)
     };
 
-    let cond_expr = cond.expr()?;
     let then_block = if_expr.then_branch()?;
     let then_block = then_block.stmt_list()?;
 
@@ -119,8 +123,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
                         let then_branch =
                             make::block_expr(once(make::expr_stmt(early_expression).into()), None);
                         let cond = invert_boolean_expression(cond_expr);
-                        make::expr_if(make::condition(cond, None), then_branch, None)
-                            .indent(if_indent_level)
+                        make::expr_if(cond, then_branch, None).indent(if_indent_level)
                     };
                     new_expr.syntax().clone_for_update()
                 }
diff --git a/crates/ide_assists/src/handlers/convert_while_to_loop.rs b/crates/ide_assists/src/handlers/convert_while_to_loop.rs
index 2bc64e77a38..0fa2dcfbde1 100644
--- a/crates/ide_assists/src/handlers/convert_while_to_loop.rs
+++ b/crates/ide_assists/src/handlers/convert_while_to_loop.rs
@@ -1,5 +1,6 @@
 use std::iter::once;
 
+use ide_db::helpers::node_ext::is_pattern_cond;
 use syntax::{
     ast::{
         self,
@@ -42,7 +43,6 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext) -> O
     let while_expr = while_kw.parent().and_then(ast::WhileExpr::cast)?;
     let while_body = while_expr.loop_body()?;
     let while_cond = while_expr.condition()?;
-    let while_cond_expr = while_cond.expr()?;
 
     let target = while_expr.syntax().text_range();
     acc.add(
@@ -55,19 +55,15 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext) -> O
             let break_block =
                 make::block_expr(once(make::expr_stmt(make::expr_break(None)).into()), None)
                     .indent(while_indent_level);
-            let block_expr = match while_cond.pat() {
-                Some(_) => {
-                    let if_expr = make::expr_if(while_cond, while_body, Some(break_block.into()));
-                    let stmts = once(make::expr_stmt(if_expr).into());
-                    make::block_expr(stmts, None)
-                }
-                None => {
-                    let if_cond = make::condition(invert_boolean_expression(while_cond_expr), None);
-                    let if_expr = make::expr_if(if_cond, break_block, None);
-                    let stmts =
-                        once(make::expr_stmt(if_expr).into()).chain(while_body.statements());
-                    make::block_expr(stmts, while_body.tail_expr())
-                }
+            let block_expr = if is_pattern_cond(while_cond.clone()) {
+                let if_expr = make::expr_if(while_cond, while_body, Some(break_block.into()));
+                let stmts = once(make::expr_stmt(if_expr).into());
+                make::block_expr(stmts, None)
+            } else {
+                let if_cond = invert_boolean_expression(while_cond);
+                let if_expr = make::expr_if(if_cond, break_block, None);
+                let stmts = once(make::expr_stmt(if_expr).into()).chain(while_body.statements());
+                make::block_expr(stmts, while_body.tail_expr())
             };
 
             let replacement = make::expr_loop(block_expr.indent(while_indent_level));
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs
index 877c5b0ceff..21cfc76ac9b 100644
--- a/crates/ide_assists/src/handlers/extract_function.rs
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -1219,28 +1219,26 @@ impl FlowHandler {
                 let stmt = make::expr_stmt(action);
                 let block = make::block_expr(iter::once(stmt.into()), None);
                 let controlflow_break_path = make::path_from_text("ControlFlow::Break");
-                let condition = make::condition(
+                let condition = make::expr_let(
+                    make::tuple_struct_pat(
+                        controlflow_break_path,
+                        iter::once(make::wildcard_pat().into()),
+                    )
+                    .into(),
                     call_expr,
-                    Some(
-                        make::tuple_struct_pat(
-                            controlflow_break_path,
-                            iter::once(make::wildcard_pat().into()),
-                        )
-                        .into(),
-                    ),
                 );
-                make::expr_if(condition, block, None)
+                make::expr_if(condition.into(), block, None)
             }
             FlowHandler::IfOption { action } => {
                 let path = make::ext::ident_path("Some");
                 let value_pat = make::ext::simple_ident_pat(make::name("value"));
                 let pattern = make::tuple_struct_pat(path, iter::once(value_pat.into()));
-                let cond = make::condition(call_expr, Some(pattern.into()));
+                let cond = make::expr_let(pattern.into(), call_expr);
                 let value = make::expr_path(make::ext::ident_path("value"));
                 let action_expr = action.make_result_handler(Some(value));
                 let action_stmt = make::expr_stmt(action_expr);
                 let then = make::block_expr(iter::once(action_stmt.into()), None);
-                make::expr_if(cond, then, None)
+                make::expr_if(cond.into(), then, None)
             }
             FlowHandler::MatchOption { none } => {
                 let some_name = "value";
diff --git a/crates/ide_assists/src/handlers/invert_if.rs b/crates/ide_assists/src/handlers/invert_if.rs
index 20f6b0c54c9..46f11f4af32 100644
--- a/crates/ide_assists/src/handlers/invert_if.rs
+++ b/crates/ide_assists/src/handlers/invert_if.rs
@@ -1,3 +1,4 @@
+use ide_db::helpers::node_ext::is_pattern_cond;
 use syntax::{
     ast::{self, AstNode},
     T,
@@ -34,12 +35,12 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
         return None;
     }
 
+    let cond = expr.condition()?;
     // This assist should not apply for if-let.
-    if expr.condition()?.is_pattern_cond() {
+    if is_pattern_cond(cond.clone()) {
         return None;
     }
 
-    let cond = expr.condition()?.expr()?;
     let then_node = expr.then_branch()?.syntax().clone();
     let else_block = match expr.else_branch()? {
         ast::ElseBranch::Block(it) => it,
diff --git a/crates/ide_assists/src/handlers/move_guard.rs b/crates/ide_assists/src/handlers/move_guard.rs
index 366f308f6ef..5c05cb921d9 100644
--- a/crates/ide_assists/src/handlers/move_guard.rs
+++ b/crates/ide_assists/src/handlers/move_guard.rs
@@ -1,8 +1,5 @@
 use syntax::{
-    ast::{
-        edit::AstNodeEdit, make, AstNode, BlockExpr, Condition, ElseBranch, Expr, IfExpr, MatchArm,
-        Pat,
-    },
+    ast::{edit::AstNodeEdit, make, AstNode, BlockExpr, ElseBranch, Expr, IfExpr, MatchArm, Pat},
     SyntaxKind::WHITESPACE,
 };
 
@@ -44,18 +41,11 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
     }
     let space_before_guard = guard.syntax().prev_sibling_or_token();
 
-    // FIXME: support `if let` guards too
-    if guard.let_token().is_some() {
-        return None;
-    }
-    let guard_condition = guard.expr()?;
+    let guard_condition = guard.condition()?;
     let arm_expr = match_arm.expr()?;
-    let if_expr = make::expr_if(
-        make::condition(guard_condition, None),
-        make::block_expr(None, Some(arm_expr.clone())),
-        None,
-    )
-    .indent(arm_expr.indent_level());
+    let if_expr =
+        make::expr_if(guard_condition, make::block_expr(None, Some(arm_expr.clone())), None)
+            .indent(arm_expr.indent_level());
 
     let target = guard.syntax().text_range();
     acc.add(
@@ -193,17 +183,13 @@ pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContex
     )
 }
 
-// Parses an if-else-if chain to get the conditons and the then branches until we encounter an else
+// Parses an if-else-if chain to get the conditions and the then branches until we encounter an else
 // branch or the end.
-fn parse_if_chain(if_expr: IfExpr) -> Option<(Vec<(Condition, BlockExpr)>, Option<BlockExpr>)> {
+fn parse_if_chain(if_expr: IfExpr) -> Option<(Vec<(Expr, BlockExpr)>, Option<BlockExpr>)> {
     let mut conds_blocks = Vec::new();
     let mut curr_if = if_expr;
     let tail = loop {
         let cond = curr_if.condition()?;
-        // Not support moving if let to arm guard
-        if cond.is_pattern_cond() {
-            return None;
-        }
         conds_blocks.push((cond, curr_if.then_branch()?));
         match curr_if.else_branch() {
             Some(ElseBranch::IfExpr(e)) => {
@@ -281,6 +267,31 @@ fn main() {
     }
 
     #[test]
+    fn move_let_guard_to_arm_body_works() {
+        check_assist(
+            move_guard_to_arm_body,
+            r#"
+fn main() {
+    match 92 {
+        x $0if (let 1 = x) => false,
+        _ => true
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x => if (let 1 = x) {
+            false
+        },
+        _ => true
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
     fn move_guard_to_arm_body_works_complex_match() {
         check_assist(
             move_guard_to_arm_body,
@@ -440,13 +451,21 @@ fn main() {
     }
 
     #[test]
-    fn move_arm_cond_to_match_guard_if_let_not_works() {
-        check_assist_not_applicable(
+    fn move_arm_cond_to_match_guard_if_let_works() {
+        check_assist(
             move_arm_cond_to_match_guard,
             r#"
 fn main() {
     match 92 {
-        x => if let 62 = x { $0false },
+        x => if let 62 = x && true { $0false },
+        _ => true
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x if let 62 = x && true => false,
         _ => true
     }
 }
@@ -898,7 +917,7 @@ fn main() {
 
     #[test]
     fn move_arm_cond_to_match_guard_elseif_iflet() {
-        check_assist_not_applicable(
+        check_assist(
             move_arm_cond_to_match_guard,
             r#"
 fn main() {
@@ -915,9 +934,21 @@ fn main() {
             4
         },
     }
-}
-"#,
-        )
+}"#,
+            r#"
+fn main() {
+    match 92 {
+        3 => 0,
+        x if x > 10 => 1,
+        x if x > 5 => 2,
+        x if let 4 = 4 => {
+            42;
+            3
+        }
+        x => 4,
+    }
+}"#,
+        );
     }
 
     #[test]
diff --git a/crates/ide_assists/src/handlers/replace_if_let_with_match.rs b/crates/ide_assists/src/handlers/replace_if_let_with_match.rs
index 77909347927..b594c64c412 100644
--- a/crates/ide_assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ide_assists/src/handlers/replace_if_let_with_match.rs
@@ -1,7 +1,12 @@
 use std::iter::{self, successors};
 
 use either::Either;
-use ide_db::{defs::NameClass, ty_filter::TryEnum, RootDatabase};
+use ide_db::{
+    defs::NameClass,
+    helpers::node_ext::{is_pattern_cond, single_let},
+    ty_filter::TryEnum,
+    RootDatabase,
+};
 use syntax::{
     ast::{
         self,
@@ -60,15 +65,22 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
             None
         }
     });
-    let scrutinee_to_be_expr = if_expr.condition()?.expr()?;
+    let scrutinee_to_be_expr = if_expr.condition()?;
+    let scrutinee_to_be_expr = match single_let(scrutinee_to_be_expr.clone()) {
+        Some(cond) => cond.expr()?,
+        None => scrutinee_to_be_expr,
+    };
 
     let mut pat_seen = false;
     let mut cond_bodies = Vec::new();
     for if_expr in if_exprs {
         let cond = if_expr.condition()?;
-        let expr = cond.expr()?;
-        let cond = match cond.pat() {
-            Some(pat) => {
+        let cond = match single_let(cond.clone()) {
+            Some(let_) => {
+                let pat = let_.pat()?;
+                let expr = let_.expr()?;
+                // FIXME: If one `let` is wrapped in parentheses and the second is not,
+                // we'll exit here.
                 if scrutinee_to_be_expr.syntax().text() != expr.syntax().text() {
                     // Only if all condition expressions are equal we can merge them into a match
                     return None;
@@ -76,7 +88,9 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
                 pat_seen = true;
                 Either::Left(pat)
             }
-            None => Either::Right(expr),
+            // Multiple `let`, unsupported.
+            None if is_pattern_cond(cond.clone()) => return None,
+            None => Either::Right(cond),
         };
         let body = if_expr.then_branch()?;
         cond_bodies.push((cond, body));
@@ -217,11 +231,11 @@ pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext)
                 }
             }
 
-            let condition = make::condition(scrutinee, Some(if_let_pat));
+            let condition = make::expr_let(if_let_pat, scrutinee);
             let then_block = make_block_expr(then_expr.reset_indent());
             let else_expr = if is_empty_expr(&else_expr) { None } else { Some(else_expr) };
             let if_let_expr = make::expr_if(
-                condition,
+                condition.into(),
                 then_block,
                 else_expr.map(make_block_expr).map(ast::ElseBranch::Block),
             )
@@ -373,6 +387,18 @@ impl VariantData {
     }
 
     #[test]
+    fn test_if_let_with_match_let_chain() {
+        check_assist_not_applicable(
+            replace_if_let_with_match,
+            r#"
+fn main() {
+    if $0let true = true && let Some(1) = None {}
+}
+"#,
+        )
+    }
+
+    #[test]
     fn test_if_let_with_match_basic() {
         check_assist(
             replace_if_let_with_match,
diff --git a/crates/ide_assists/src/handlers/replace_let_with_if_let.rs b/crates/ide_assists/src/handlers/replace_let_with_if_let.rs
index 1062cc39537..a5fa8a110d3 100644
--- a/crates/ide_assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/ide_assists/src/handlers/replace_let_with_if_let.rs
@@ -62,7 +62,7 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
 
             let block =
                 make::ext::empty_block_expr().indent(IndentLevel::from_node(let_stmt.syntax()));
-            let if_ = make::expr_if(make::condition(init, Some(pat)), block, None);
+            let if_ = make::expr_if(make::expr_let(pat, init).into(), block, None);
             let stmt = make::expr_stmt(if_);
 
             edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index d711215491c..e986c28b146 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -575,6 +575,14 @@ impl<'a> CompletionContext<'a> {
 
                         (ty, name)
                     },
+                    ast::LetExpr(it) => {
+                        cov_mark::hit!(expected_type_if_let_without_leading_char);
+                        let ty = it.pat()
+                            .and_then(|pat| self.sema.type_of_pat(&pat))
+                            .or_else(|| it.expr().and_then(|it| self.sema.type_of_expr(&it)))
+                            .map(TypeInfo::original);
+                        (ty, None)
+                    },
                     ast::ArgList(_) => {
                         cov_mark::hit!(expected_type_fn_param);
                         ActiveParameter::at_token(
@@ -641,9 +649,7 @@ impl<'a> CompletionContext<'a> {
                         (ty, None)
                     },
                     ast::IfExpr(it) => {
-                        cov_mark::hit!(expected_type_if_let_without_leading_char);
                         let ty = it.condition()
-                            .and_then(|cond| cond.expr())
                             .and_then(|e| self.sema.type_of_expr(&e))
                             .map(TypeInfo::original);
                         (ty, None)
@@ -939,7 +945,7 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont
                         return (PatternRefutability::Irrefutable, has_type_ascription)
                     },
                     ast::MatchArm(_) => PatternRefutability::Refutable,
-                    ast::Condition(_) => PatternRefutability::Refutable,
+                    ast::LetExpr(_) => PatternRefutability::Refutable,
                     ast::ForExpr(_) => PatternRefutability::Irrefutable,
                     _ => PatternRefutability::Irrefutable,
                 }
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index 2d3d6409338..9c6d3775c7f 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -226,6 +226,7 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) {
         | ast::Expr::TryExpr(_)
         | ast::Expr::TupleExpr(_)
         | ast::Expr::WhileExpr(_)
+        | ast::Expr::LetExpr(_)
         | ast::Expr::YieldExpr(_) => cb(expr),
     }
 }
diff --git a/crates/ide_db/src/helpers/node_ext.rs b/crates/ide_db/src/helpers/node_ext.rs
index 82178ed7496..5df3ed1366f 100644
--- a/crates/ide_db/src/helpers/node_ext.rs
+++ b/crates/ide_db/src/helpers/node_ext.rs
@@ -216,3 +216,29 @@ pub fn vis_eq(this: &ast::Visibility, other: &ast::Visibility) -> bool {
         _ => false,
     }
 }
+
+/// Returns the `let` only if there is exactly one (that is, `let pat = expr`
+/// or `((let pat = expr))`, but not `let pat = expr && expr` or `non_let_expr`).
+pub fn single_let(expr: ast::Expr) -> Option<ast::LetExpr> {
+    match expr {
+        ast::Expr::ParenExpr(expr) => expr.expr().and_then(single_let),
+        ast::Expr::LetExpr(expr) => Some(expr),
+        _ => None,
+    }
+}
+
+pub fn is_pattern_cond(expr: ast::Expr) -> bool {
+    match expr {
+        ast::Expr::BinExpr(expr)
+            if expr.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And)) =>
+        {
+            expr.lhs()
+                .map(is_pattern_cond)
+                .or_else(|| expr.rhs().map(is_pattern_cond))
+                .unwrap_or(false)
+        }
+        ast::Expr::ParenExpr(expr) => expr.expr().map_or(false, is_pattern_cond),
+        ast::Expr::LetExpr(_) => true,
+        _ => false,
+    }
+}
diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs
index 9dbba89c568..a40db15049d 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -29,6 +29,15 @@ fn expr_no_struct(p: &mut Parser) {
     expr_bp(p, None, r, 1);
 }
 
+/// Parses the expression in `let pattern = expression`.
+/// It needs to be parsed with lower precedence than `&&`, so that
+/// `if let true = true && false` is parsed as `if (let true = true) && (true)`
+/// and not `if let true = (true && true)`.
+fn expr_let(p: &mut Parser) {
+    let r = Restrictions { forbid_structs: true, prefer_stmt: false };
+    expr_bp(p, None, r, 5);
+}
+
 pub(super) fn stmt(p: &mut Parser, semicolon: Semicolon) {
     if p.eat(T![;]) {
         return;
@@ -185,6 +194,7 @@ fn current_op(p: &Parser) -> (u8, SyntaxKind) {
         T![%] if p.at(T![%=])  => (1,  T![%=]),
         T![%]                  => (11, T![%]),
         T![&] if p.at(T![&=])  => (1,  T![&=]),
+        // If you update this, remember to update `expr_let()` too.
         T![&] if p.at(T![&&])  => (4,  T![&&]),
         T![&]                  => (8,  T![&]),
         T![/] if p.at(T![/=])  => (1,  T![/=]),
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index 4b7a1b31fbd..e2c1b1fec57 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -79,6 +79,7 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMar
             closure_expr(p)
         }
         T![if] => if_expr(p),
+        T![let] => let_expr(p),
 
         T![loop] => loop_expr(p, None),
         T![box] => box_expr(p, None),
@@ -286,7 +287,7 @@ fn if_expr(p: &mut Parser) -> CompletedMarker {
     assert!(p.at(T![if]));
     let m = p.start();
     p.bump(T![if]);
-    condition(p);
+    expr_no_struct(p);
     block_expr(p);
     if p.at(T![else]) {
         p.bump(T![else]);
@@ -335,7 +336,7 @@ fn while_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
     assert!(p.at(T![while]));
     let m = m.unwrap_or_else(|| p.start());
     p.bump(T![while]);
-    condition(p);
+    expr_no_struct(p);
     block_expr(p);
     m.complete(p, WHILE_EXPR)
 }
@@ -355,22 +356,18 @@ fn for_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
     m.complete(p, FOR_EXPR)
 }
 
-// test cond
-// fn foo() { if let Some(_) = None {} }
-// fn bar() {
-//     if let Some(_) | Some(_) = None {}
-//     if let | Some(_) = None {}
-//     while let Some(_) | Some(_) = None {}
-//     while let | Some(_) = None {}
+// test let_expr
+// fn foo() {
+//     if let Some(_) = None && true {}
+//     while 1 == 5 && (let None = None) {}
 // }
-fn condition(p: &mut Parser) {
+fn let_expr(p: &mut Parser) -> CompletedMarker {
     let m = p.start();
-    if p.eat(T![let]) {
-        patterns::pattern_top(p);
-        p.expect(T![=]);
-    }
-    expr_no_struct(p);
-    m.complete(p, CONDITION);
+    p.bump(T![let]);
+    patterns::pattern_top(p);
+    p.expect(T![=]);
+    expr_let(p);
+    m.complete(p, LET_EXPR)
 }
 
 // test match_expr
@@ -482,10 +479,6 @@ fn match_guard(p: &mut Parser) -> CompletedMarker {
     assert!(p.at(T![if]));
     let m = p.start();
     p.bump(T![if]);
-    if p.eat(T![let]) {
-        patterns::pattern_top(p);
-        p.expect(T![=]);
-    }
     expr(p);
     m.complete(p, MATCH_GUARD)
 }
diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs
index 601a5792afd..d04b5dbf008 100644
--- a/crates/parser/src/syntax_kind/generated.rs
+++ b/crates/parser/src/syntax_kind/generated.rs
@@ -178,7 +178,6 @@ pub enum SyntaxKind {
     CLOSURE_EXPR,
     IF_EXPR,
     WHILE_EXPR,
-    CONDITION,
     LOOP_EXPR,
     FOR_EXPR,
     CONTINUE_EXPR,
@@ -188,6 +187,7 @@ pub enum SyntaxKind {
     STMT_LIST,
     RETURN_EXPR,
     YIELD_EXPR,
+    LET_EXPR,
     MATCH_EXPR,
     MATCH_ARM_LIST,
     MATCH_ARM,
diff --git a/crates/parser/src/tests/top_entries.rs b/crates/parser/src/tests/top_entries.rs
index 24e41b46f8e..eb640dc7fc7 100644
--- a/crates/parser/src/tests/top_entries.rs
+++ b/crates/parser/src/tests/top_entries.rs
@@ -289,17 +289,19 @@ fn expr() {
         TopEntryPoint::Expr,
         "let _ = 0;",
         expect![[r#"
-        ERROR
-          LET_KW "let"
-          WHITESPACE " "
-          UNDERSCORE "_"
-          WHITESPACE " "
-          EQ "="
-          WHITESPACE " "
-          INT_NUMBER "0"
-          SEMICOLON ";"
-        error 0: expected expression
-    "#]],
+            ERROR
+              LET_EXPR
+                LET_KW "let"
+                WHITESPACE " "
+                WILDCARD_PAT
+                  UNDERSCORE "_"
+                WHITESPACE " "
+                EQ "="
+                WHITESPACE " "
+                LITERAL
+                  INT_NUMBER "0"
+              SEMICOLON ";"
+        "#]],
     );
 }
 
diff --git a/crates/parser/test_data/parser/err/0008_item_block_recovery.txt b/crates/parser/test_data/parser/err/0008_item_block_recovery.txt
index 6dd70e7cd9b..60b2fe98755 100644
--- a/crates/parser/test_data/parser/err/0008_item_block_recovery.txt
+++ b/crates/parser/test_data/parser/err/0008_item_block_recovery.txt
@@ -29,9 +29,8 @@ SOURCE_FILE
     IF_EXPR
       IF_KW "if"
       WHITESPACE " "
-      CONDITION
-        LITERAL
-          TRUE_KW "true"
+      LITERAL
+        TRUE_KW "true"
       WHITESPACE " "
       BLOCK_EXPR
         STMT_LIST
diff --git a/crates/parser/test_data/parser/err/0019_let_recover.rs b/crates/parser/test_data/parser/err/0019_let_recover.rs
index 48bf3d68bd3..5108d5a49be 100644
--- a/crates/parser/test_data/parser/err/0019_let_recover.rs
+++ b/crates/parser/test_data/parser/err/0019_let_recover.rs
@@ -1,5 +1,5 @@
 fn foo() {
-    let foo =
+    let foo = 11
     let bar = 1;
     let
     let baz = 92;
diff --git a/crates/parser/test_data/parser/err/0019_let_recover.txt b/crates/parser/test_data/parser/err/0019_let_recover.txt
index 25722b13558..7d62e0cc14f 100644
--- a/crates/parser/test_data/parser/err/0019_let_recover.txt
+++ b/crates/parser/test_data/parser/err/0019_let_recover.txt
@@ -20,6 +20,9 @@ SOURCE_FILE
               IDENT "foo"
           WHITESPACE " "
           EQ "="
+          WHITESPACE " "
+          LITERAL
+            INT_NUMBER "11"
         WHITESPACE "\n    "
         LET_STMT
           LET_KW "let"
@@ -57,9 +60,8 @@ SOURCE_FILE
           IF_EXPR
             IF_KW "if"
             WHITESPACE " "
-            CONDITION
-              LITERAL
-                TRUE_KW "true"
+            LITERAL
+              TRUE_KW "true"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
@@ -73,9 +75,8 @@ SOURCE_FILE
           WHILE_EXPR
             WHILE_KW "while"
             WHITESPACE " "
-            CONDITION
-              LITERAL
-                TRUE_KW "true"
+            LITERAL
+              TRUE_KW "true"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
@@ -95,13 +96,12 @@ SOURCE_FILE
         WHITESPACE "\n"
         R_CURLY "}"
   WHITESPACE "\n"
-error 24: expected expression
-error 24: expected SEMICOLON
-error 49: expected pattern
-error 49: expected SEMICOLON
-error 75: expected pattern
-error 75: expected SEMICOLON
-error 98: expected pattern
-error 98: expected SEMICOLON
-error 124: expected pattern
-error 124: expected SEMICOLON
+error 27: expected SEMICOLON
+error 52: expected pattern
+error 52: expected SEMICOLON
+error 78: expected pattern
+error 78: expected SEMICOLON
+error 101: expected pattern
+error 101: expected SEMICOLON
+error 127: expected pattern
+error 127: expected SEMICOLON
diff --git a/crates/parser/test_data/parser/err/0024_many_type_parens.txt b/crates/parser/test_data/parser/err/0024_many_type_parens.txt
index 446e1a82338..82e6a11249b 100644
--- a/crates/parser/test_data/parser/err/0024_many_type_parens.txt
+++ b/crates/parser/test_data/parser/err/0024_many_type_parens.txt
@@ -180,116 +180,118 @@ SOURCE_FILE
         ERROR
           PLUS "+"
         WHITESPACE " "
-        EXPR_STMT
-          TUPLE_EXPR
-            L_PAREN "("
-            FOR_EXPR
-              FOR_KW "for"
-              PATH_PAT
-                PATH
-                  PATH_SEGMENT
-                    L_ANGLE "<"
-                    ERROR
-                      LIFETIME_IDENT "'a"
-                    R_ANGLE ">"
-              WHITESPACE " "
+        TUPLE_EXPR
+          L_PAREN "("
+          FOR_EXPR
+            FOR_KW "for"
+            PATH_PAT
+              PATH
+                PATH_SEGMENT
+                  L_ANGLE "<"
+                  ERROR
+                    LIFETIME_IDENT "'a"
+                  R_ANGLE ">"
+            WHITESPACE " "
+            BIN_EXPR
               BIN_EXPR
                 BIN_EXPR
                   BIN_EXPR
-                    BIN_EXPR
-                      PATH_EXPR
-                        PATH
-                          PATH_SEGMENT
-                            NAME_REF
-                              IDENT "Trait"
-                      L_ANGLE "<"
-                      ERROR
-                        LIFETIME_IDENT "'a"
-                    R_ANGLE ">"
-                    ERROR
-                      R_PAREN ")"
-                  WHITESPACE " "
-                  PLUS "+"
-                  WHITESPACE " "
-                  PAREN_EXPR
-                    L_PAREN "("
                     PATH_EXPR
                       PATH
                         PATH_SEGMENT
                           NAME_REF
-                            IDENT "Copy"
+                            IDENT "Trait"
+                    L_ANGLE "<"
+                    ERROR
+                      LIFETIME_IDENT "'a"
+                  R_ANGLE ">"
+                  ERROR
                     R_PAREN ")"
-                R_ANGLE ">"
-                ERROR
-                  SEMICOLON ";"
-        WHITESPACE "\n    "
-        LET_STMT
-          LET_KW "let"
-          WHITESPACE " "
-          WILDCARD_PAT
-            UNDERSCORE "_"
-          COLON ":"
+                WHITESPACE " "
+                PLUS "+"
+                WHITESPACE " "
+                PAREN_EXPR
+                  L_PAREN "("
+                  PATH_EXPR
+                    PATH
+                      PATH_SEGMENT
+                        NAME_REF
+                          IDENT "Copy"
+                  R_PAREN ")"
+              R_ANGLE ">"
+              ERROR
+                SEMICOLON ";"
+          WHITESPACE "\n    "
+          LET_EXPR
+            LET_KW "let"
+            WHITESPACE " "
+            WILDCARD_PAT
+              UNDERSCORE "_"
+            ERROR
+              COLON ":"
           WHITESPACE " "
-          DYN_TRAIT_TYPE
-            TYPE_BOUND_LIST
-              TYPE_BOUND
-                PATH_TYPE
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "Box"
-                      GENERIC_ARG_LIST
-                        L_ANGLE "<"
-                        TYPE_ARG
-                          PAREN_TYPE
-                            L_PAREN "("
-                            FOR_TYPE
-                              FOR_KW "for"
-                              GENERIC_PARAM_LIST
-                                L_ANGLE "<"
-                                LIFETIME_PARAM
-                                  LIFETIME
-                                    LIFETIME_IDENT "'a"
-                                R_ANGLE ">"
-                              WHITESPACE " "
-                              PATH_TYPE
-                                PATH
-                                  PATH_SEGMENT
-                                    NAME_REF
-                                      IDENT "Trait"
-                                    GENERIC_ARG_LIST
-                                      L_ANGLE "<"
-                                      LIFETIME_ARG
-                                        LIFETIME
-                                          LIFETIME_IDENT "'a"
-                                      R_ANGLE ">"
-                            R_PAREN ")"
-              WHITESPACE " "
-              PLUS "+"
-              WHITESPACE " "
-              TYPE_BOUND
-                L_PAREN "("
-                PATH_TYPE
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "Copy"
-                R_PAREN ")"
-              WHITESPACE " "
-              PLUS "+"
-              WHITESPACE " "
-              TYPE_BOUND
+          BIN_EXPR
+            BIN_EXPR
+              PATH_EXPR
+                PATH
+                  PATH_SEGMENT
+                    NAME_REF
+                      IDENT "Box"
+              L_ANGLE "<"
+              TUPLE_EXPR
                 L_PAREN "("
-                QUESTION "?"
-                PATH_TYPE
+                FOR_EXPR
+                  FOR_KW "for"
+                  PATH_PAT
+                    PATH
+                      PATH_SEGMENT
+                        L_ANGLE "<"
+                        ERROR
+                          LIFETIME_IDENT "'a"
+                        R_ANGLE ">"
+                  WHITESPACE " "
+                  BIN_EXPR
+                    BIN_EXPR
+                      BIN_EXPR
+                        BIN_EXPR
+                          PATH_EXPR
+                            PATH
+                              PATH_SEGMENT
+                                NAME_REF
+                                  IDENT "Trait"
+                          L_ANGLE "<"
+                          ERROR
+                            LIFETIME_IDENT "'a"
+                        R_ANGLE ">"
+                        ERROR
+                          R_PAREN ")"
+                      WHITESPACE " "
+                      PLUS "+"
+                      WHITESPACE " "
+                      PAREN_EXPR
+                        L_PAREN "("
+                        PATH_EXPR
+                          PATH
+                            PATH_SEGMENT
+                              NAME_REF
+                                IDENT "Copy"
+                        R_PAREN ")"
+                    WHITESPACE " "
+                    PLUS "+"
+                    WHITESPACE " "
+                    PAREN_EXPR
+                      L_PAREN "("
+                      ERROR
+                        QUESTION "?"
+                PATH_EXPR
                   PATH
                     PATH_SEGMENT
                       NAME_REF
                         IDENT "Sized"
                 R_PAREN ")"
-        ERROR
-          R_ANGLE ">"
-        SEMICOLON ";"
+            R_ANGLE ">"
+            ERROR
+              SEMICOLON ";"
         WHITESPACE "\n"
         R_CURLY "}"
   WHITESPACE "\n"
@@ -312,10 +314,18 @@ error 168: expected expression
 error 179: expected expression
 error 180: expected a block
 error 180: expected COMMA
-error 180: expected expression
-error 180: expected R_PAREN
-error 180: expected SEMICOLON
-error 215: expected COMMA
-error 215: expected R_ANGLE
-error 235: expected SEMICOLON
-error 235: expected expression
+error 190: expected EQ
+error 190: expected expression
+error 191: expected COMMA
+error 201: expected type
+error 204: expected IN_KW
+error 211: expected expression
+error 214: expected expression
+error 228: expected expression
+error 229: expected R_PAREN
+error 229: expected a block
+error 229: expected COMMA
+error 236: expected expression
+error 237: expected COMMA
+error 237: expected expression
+error 237: expected R_PAREN
diff --git a/crates/parser/test_data/parser/inline/ok/0030_cond.rast b/crates/parser/test_data/parser/inline/ok/0030_cond.rast
deleted file mode 100644
index 3aa330f55d1..00000000000
--- a/crates/parser/test_data/parser/inline/ok/0030_cond.rast
+++ /dev/null
@@ -1,209 +0,0 @@
-SOURCE_FILE@0..197
-  FN@0..37
-    FN_KW@0..2 "fn"
-    WHITESPACE@2..3 " "
-    NAME@3..6
-      IDENT@3..6 "foo"
-    PARAM_LIST@6..8
-      L_PAREN@6..7 "("
-      R_PAREN@7..8 ")"
-    WHITESPACE@8..9 " "
-    BLOCK_EXPR@9..37
-      STMT_LIST@9..37
-        L_CURLY@9..10 "{"
-        WHITESPACE@10..11 " "
-        IF_EXPR@11..35
-          IF_KW@11..13 "if"
-          WHITESPACE@13..14 " "
-          CONDITION@14..32
-            LET_KW@14..17 "let"
-            WHITESPACE@17..18 " "
-            TUPLE_STRUCT_PAT@18..25
-              PATH@18..22
-                PATH_SEGMENT@18..22
-                  NAME_REF@18..22
-                    IDENT@18..22 "Some"
-              L_PAREN@22..23 "("
-              WILDCARD_PAT@23..24
-                UNDERSCORE@23..24 "_"
-              R_PAREN@24..25 ")"
-            WHITESPACE@25..26 " "
-            EQ@26..27 "="
-            WHITESPACE@27..28 " "
-            PATH_EXPR@28..32
-              PATH@28..32
-                PATH_SEGMENT@28..32
-                  NAME_REF@28..32
-                    IDENT@28..32 "None"
-          WHITESPACE@32..33 " "
-          BLOCK_EXPR@33..35
-            STMT_LIST@33..35
-              L_CURLY@33..34 "{"
-              R_CURLY@34..35 "}"
-        WHITESPACE@35..36 " "
-        R_CURLY@36..37 "}"
-  WHITESPACE@37..38 "\n"
-  FN@38..196
-    FN_KW@38..40 "fn"
-    WHITESPACE@40..41 " "
-    NAME@41..44
-      IDENT@41..44 "bar"
-    PARAM_LIST@44..46
-      L_PAREN@44..45 "("
-      R_PAREN@45..46 ")"
-    WHITESPACE@46..47 " "
-    BLOCK_EXPR@47..196
-      STMT_LIST@47..196
-        L_CURLY@47..48 "{"
-        WHITESPACE@48..53 "\n    "
-        EXPR_STMT@53..87
-          IF_EXPR@53..87
-            IF_KW@53..55 "if"
-            WHITESPACE@55..56 " "
-            CONDITION@56..84
-              LET_KW@56..59 "let"
-              WHITESPACE@59..60 " "
-              OR_PAT@60..77
-                TUPLE_STRUCT_PAT@60..67
-                  PATH@60..64
-                    PATH_SEGMENT@60..64
-                      NAME_REF@60..64
-                        IDENT@60..64 "Some"
-                  L_PAREN@64..65 "("
-                  WILDCARD_PAT@65..66
-                    UNDERSCORE@65..66 "_"
-                  R_PAREN@66..67 ")"
-                WHITESPACE@67..68 " "
-                PIPE@68..69 "|"
-                WHITESPACE@69..70 " "
-                TUPLE_STRUCT_PAT@70..77
-                  PATH@70..74
-                    PATH_SEGMENT@70..74
-                      NAME_REF@70..74
-                        IDENT@70..74 "Some"
-                  L_PAREN@74..75 "("
-                  WILDCARD_PAT@75..76
-                    UNDERSCORE@75..76 "_"
-                  R_PAREN@76..77 ")"
-              WHITESPACE@77..78 " "
-              EQ@78..79 "="
-              WHITESPACE@79..80 " "
-              PATH_EXPR@80..84
-                PATH@80..84
-                  PATH_SEGMENT@80..84
-                    NAME_REF@80..84
-                      IDENT@80..84 "None"
-            WHITESPACE@84..85 " "
-            BLOCK_EXPR@85..87
-              STMT_LIST@85..87
-                L_CURLY@85..86 "{"
-                R_CURLY@86..87 "}"
-        WHITESPACE@87..92 "\n    "
-        EXPR_STMT@92..118
-          IF_EXPR@92..118
-            IF_KW@92..94 "if"
-            WHITESPACE@94..95 " "
-            CONDITION@95..115
-              LET_KW@95..98 "let"
-              WHITESPACE@98..99 " "
-              PIPE@99..100 "|"
-              WHITESPACE@100..101 " "
-              TUPLE_STRUCT_PAT@101..108
-                PATH@101..105
-                  PATH_SEGMENT@101..105
-                    NAME_REF@101..105
-                      IDENT@101..105 "Some"
-                L_PAREN@105..106 "("
-                WILDCARD_PAT@106..107
-                  UNDERSCORE@106..107 "_"
-                R_PAREN@107..108 ")"
-              WHITESPACE@108..109 " "
-              EQ@109..110 "="
-              WHITESPACE@110..111 " "
-              PATH_EXPR@111..115
-                PATH@111..115
-                  PATH_SEGMENT@111..115
-                    NAME_REF@111..115
-                      IDENT@111..115 "None"
-            WHITESPACE@115..116 " "
-            BLOCK_EXPR@116..118
-              STMT_LIST@116..118
-                L_CURLY@116..117 "{"
-                R_CURLY@117..118 "}"
-        WHITESPACE@118..123 "\n    "
-        EXPR_STMT@123..160
-          WHILE_EXPR@123..160
-            WHILE_KW@123..128 "while"
-            WHITESPACE@128..129 " "
-            CONDITION@129..157
-              LET_KW@129..132 "let"
-              WHITESPACE@132..133 " "
-              OR_PAT@133..150
-                TUPLE_STRUCT_PAT@133..140
-                  PATH@133..137
-                    PATH_SEGMENT@133..137
-                      NAME_REF@133..137
-                        IDENT@133..137 "Some"
-                  L_PAREN@137..138 "("
-                  WILDCARD_PAT@138..139
-                    UNDERSCORE@138..139 "_"
-                  R_PAREN@139..140 ")"
-                WHITESPACE@140..141 " "
-                PIPE@141..142 "|"
-                WHITESPACE@142..143 " "
-                TUPLE_STRUCT_PAT@143..150
-                  PATH@143..147
-                    PATH_SEGMENT@143..147
-                      NAME_REF@143..147
-                        IDENT@143..147 "Some"
-                  L_PAREN@147..148 "("
-                  WILDCARD_PAT@148..149
-                    UNDERSCORE@148..149 "_"
-                  R_PAREN@149..150 ")"
-              WHITESPACE@150..151 " "
-              EQ@151..152 "="
-              WHITESPACE@152..153 " "
-              PATH_EXPR@153..157
-                PATH@153..157
-                  PATH_SEGMENT@153..157
-                    NAME_REF@153..157
-                      IDENT@153..157 "None"
-            WHITESPACE@157..158 " "
-            BLOCK_EXPR@158..160
-              STMT_LIST@158..160
-                L_CURLY@158..159 "{"
-                R_CURLY@159..160 "}"
-        WHITESPACE@160..165 "\n    "
-        WHILE_EXPR@165..194
-          WHILE_KW@165..170 "while"
-          WHITESPACE@170..171 " "
-          CONDITION@171..191
-            LET_KW@171..174 "let"
-            WHITESPACE@174..175 " "
-            PIPE@175..176 "|"
-            WHITESPACE@176..177 " "
-            TUPLE_STRUCT_PAT@177..184
-              PATH@177..181
-                PATH_SEGMENT@177..181
-                  NAME_REF@177..181
-                    IDENT@177..181 "Some"
-              L_PAREN@181..182 "("
-              WILDCARD_PAT@182..183
-                UNDERSCORE@182..183 "_"
-              R_PAREN@183..184 ")"
-            WHITESPACE@184..185 " "
-            EQ@185..186 "="
-            WHITESPACE@186..187 " "
-            PATH_EXPR@187..191
-              PATH@187..191
-                PATH_SEGMENT@187..191
-                  NAME_REF@187..191
-                    IDENT@187..191 "None"
-          WHITESPACE@191..192 " "
-          BLOCK_EXPR@192..194
-            STMT_LIST@192..194
-              L_CURLY@192..193 "{"
-              R_CURLY@193..194 "}"
-        WHITESPACE@194..195 "\n"
-        R_CURLY@195..196 "}"
-  WHITESPACE@196..197 "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0030_cond.rs b/crates/parser/test_data/parser/inline/ok/0030_cond.rs
deleted file mode 100644
index 2552a2621f9..00000000000
--- a/crates/parser/test_data/parser/inline/ok/0030_cond.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-fn foo() { if let Some(_) = None {} }
-fn bar() {
-    if let Some(_) | Some(_) = None {}
-    if let | Some(_) = None {}
-    while let Some(_) | Some(_) = None {}
-    while let | Some(_) = None {}
-}
diff --git a/crates/parser/test_data/parser/inline/ok/0030_cond.txt b/crates/parser/test_data/parser/inline/ok/0030_cond.txt
deleted file mode 100644
index 5b899370139..00000000000
--- a/crates/parser/test_data/parser/inline/ok/0030_cond.txt
+++ /dev/null
@@ -1,209 +0,0 @@
-SOURCE_FILE
-  FN
-    FN_KW "fn"
-    WHITESPACE " "
-    NAME
-      IDENT "foo"
-    PARAM_LIST
-      L_PAREN "("
-      R_PAREN ")"
-    WHITESPACE " "
-    BLOCK_EXPR
-      STMT_LIST
-        L_CURLY "{"
-        WHITESPACE " "
-        IF_EXPR
-          IF_KW "if"
-          WHITESPACE " "
-          CONDITION
-            LET_KW "let"
-            WHITESPACE " "
-            TUPLE_STRUCT_PAT
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "Some"
-              L_PAREN "("
-              WILDCARD_PAT
-                UNDERSCORE "_"
-              R_PAREN ")"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            PATH_EXPR
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "None"
-          WHITESPACE " "
-          BLOCK_EXPR
-            STMT_LIST
-              L_CURLY "{"
-              R_CURLY "}"
-        WHITESPACE " "
-        R_CURLY "}"
-  WHITESPACE "\n"
-  FN
-    FN_KW "fn"
-    WHITESPACE " "
-    NAME
-      IDENT "bar"
-    PARAM_LIST
-      L_PAREN "("
-      R_PAREN ")"
-    WHITESPACE " "
-    BLOCK_EXPR
-      STMT_LIST
-        L_CURLY "{"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          IF_EXPR
-            IF_KW "if"
-            WHITESPACE " "
-            CONDITION
-              LET_KW "let"
-              WHITESPACE " "
-              OR_PAT
-                TUPLE_STRUCT_PAT
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "Some"
-                  L_PAREN "("
-                  WILDCARD_PAT
-                    UNDERSCORE "_"
-                  R_PAREN ")"
-                WHITESPACE " "
-                PIPE "|"
-                WHITESPACE " "
-                TUPLE_STRUCT_PAT
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "Some"
-                  L_PAREN "("
-                  WILDCARD_PAT
-                    UNDERSCORE "_"
-                  R_PAREN ")"
-              WHITESPACE " "
-              EQ "="
-              WHITESPACE " "
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "None"
-            WHITESPACE " "
-            BLOCK_EXPR
-              STMT_LIST
-                L_CURLY "{"
-                R_CURLY "}"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          IF_EXPR
-            IF_KW "if"
-            WHITESPACE " "
-            CONDITION
-              LET_KW "let"
-              WHITESPACE " "
-              PIPE "|"
-              WHITESPACE " "
-              TUPLE_STRUCT_PAT
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "Some"
-                L_PAREN "("
-                WILDCARD_PAT
-                  UNDERSCORE "_"
-                R_PAREN ")"
-              WHITESPACE " "
-              EQ "="
-              WHITESPACE " "
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "None"
-            WHITESPACE " "
-            BLOCK_EXPR
-              STMT_LIST
-                L_CURLY "{"
-                R_CURLY "}"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          WHILE_EXPR
-            WHILE_KW "while"
-            WHITESPACE " "
-            CONDITION
-              LET_KW "let"
-              WHITESPACE " "
-              OR_PAT
-                TUPLE_STRUCT_PAT
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "Some"
-                  L_PAREN "("
-                  WILDCARD_PAT
-                    UNDERSCORE "_"
-                  R_PAREN ")"
-                WHITESPACE " "
-                PIPE "|"
-                WHITESPACE " "
-                TUPLE_STRUCT_PAT
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "Some"
-                  L_PAREN "("
-                  WILDCARD_PAT
-                    UNDERSCORE "_"
-                  R_PAREN ")"
-              WHITESPACE " "
-              EQ "="
-              WHITESPACE " "
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "None"
-            WHITESPACE " "
-            BLOCK_EXPR
-              STMT_LIST
-                L_CURLY "{"
-                R_CURLY "}"
-        WHITESPACE "\n    "
-        WHILE_EXPR
-          WHILE_KW "while"
-          WHITESPACE " "
-          CONDITION
-            LET_KW "let"
-            WHITESPACE " "
-            PIPE "|"
-            WHITESPACE " "
-            TUPLE_STRUCT_PAT
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "Some"
-              L_PAREN "("
-              WILDCARD_PAT
-                UNDERSCORE "_"
-              R_PAREN ")"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            PATH_EXPR
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "None"
-          WHITESPACE " "
-          BLOCK_EXPR
-            STMT_LIST
-              L_CURLY "{"
-              R_CURLY "}"
-        WHITESPACE "\n"
-        R_CURLY "}"
-  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0030_let_expr.rast b/crates/parser/test_data/parser/inline/ok/0030_let_expr.rast
new file mode 100644
index 00000000000..dcffcb1ce2f
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/0030_let_expr.rast
@@ -0,0 +1,90 @@
+SOURCE_FILE
+  FN
+    FN_KW "fn"
+    WHITESPACE " "
+    NAME
+      IDENT "foo"
+    PARAM_LIST
+      L_PAREN "("
+      R_PAREN ")"
+    WHITESPACE " "
+    BLOCK_EXPR
+      STMT_LIST
+        L_CURLY "{"
+        WHITESPACE "\n    "
+        EXPR_STMT
+          IF_EXPR
+            IF_KW "if"
+            WHITESPACE " "
+            BIN_EXPR
+              LET_EXPR
+                LET_KW "let"
+                WHITESPACE " "
+                TUPLE_STRUCT_PAT
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "Some"
+                  L_PAREN "("
+                  WILDCARD_PAT
+                    UNDERSCORE "_"
+                  R_PAREN ")"
+                WHITESPACE " "
+                EQ "="
+                WHITESPACE " "
+                PATH_EXPR
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "None"
+              WHITESPACE " "
+              AMP2 "&&"
+              WHITESPACE " "
+              LITERAL
+                TRUE_KW "true"
+            WHITESPACE " "
+            BLOCK_EXPR
+              STMT_LIST
+                L_CURLY "{"
+                R_CURLY "}"
+        WHITESPACE "\n    "
+        WHILE_EXPR
+          WHILE_KW "while"
+          WHITESPACE " "
+          BIN_EXPR
+            BIN_EXPR
+              LITERAL
+                INT_NUMBER "1"
+              WHITESPACE " "
+              EQ2 "=="
+              WHITESPACE " "
+              LITERAL
+                INT_NUMBER "5"
+            WHITESPACE " "
+            AMP2 "&&"
+            WHITESPACE " "
+            PAREN_EXPR
+              L_PAREN "("
+              LET_EXPR
+                LET_KW "let"
+                WHITESPACE " "
+                IDENT_PAT
+                  NAME
+                    IDENT "None"
+                WHITESPACE " "
+                EQ "="
+                WHITESPACE " "
+                PATH_EXPR
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "None"
+              R_PAREN ")"
+          WHITESPACE " "
+          BLOCK_EXPR
+            STMT_LIST
+              L_CURLY "{"
+              R_CURLY "}"
+        WHITESPACE "\n"
+        R_CURLY "}"
+  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0030_let_expr.rs b/crates/parser/test_data/parser/inline/ok/0030_let_expr.rs
new file mode 100644
index 00000000000..0131d5e3382
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/0030_let_expr.rs
@@ -0,0 +1,4 @@
+fn foo() {
+    if let Some(_) = None && true {}
+    while 1 == 5 && (let None = None) {}
+}
diff --git a/crates/parser/test_data/parser/inline/ok/0030_let_expr.txt b/crates/parser/test_data/parser/inline/ok/0030_let_expr.txt
new file mode 100644
index 00000000000..dcffcb1ce2f
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/0030_let_expr.txt
@@ -0,0 +1,90 @@
+SOURCE_FILE
+  FN
+    FN_KW "fn"
+    WHITESPACE " "
+    NAME
+      IDENT "foo"
+    PARAM_LIST
+      L_PAREN "("
+      R_PAREN ")"
+    WHITESPACE " "
+    BLOCK_EXPR
+      STMT_LIST
+        L_CURLY "{"
+        WHITESPACE "\n    "
+        EXPR_STMT
+          IF_EXPR
+            IF_KW "if"
+            WHITESPACE " "
+            BIN_EXPR
+              LET_EXPR
+                LET_KW "let"
+                WHITESPACE " "
+                TUPLE_STRUCT_PAT
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "Some"
+                  L_PAREN "("
+                  WILDCARD_PAT
+                    UNDERSCORE "_"
+                  R_PAREN ")"
+                WHITESPACE " "
+                EQ "="
+                WHITESPACE " "
+                PATH_EXPR
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "None"
+              WHITESPACE " "
+              AMP2 "&&"
+              WHITESPACE " "
+              LITERAL
+                TRUE_KW "true"
+            WHITESPACE " "
+            BLOCK_EXPR
+              STMT_LIST
+                L_CURLY "{"
+                R_CURLY "}"
+        WHITESPACE "\n    "
+        WHILE_EXPR
+          WHILE_KW "while"
+          WHITESPACE " "
+          BIN_EXPR
+            BIN_EXPR
+              LITERAL
+                INT_NUMBER "1"
+              WHITESPACE " "
+              EQ2 "=="
+              WHITESPACE " "
+              LITERAL
+                INT_NUMBER "5"
+            WHITESPACE " "
+            AMP2 "&&"
+            WHITESPACE " "
+            PAREN_EXPR
+              L_PAREN "("
+              LET_EXPR
+                LET_KW "let"
+                WHITESPACE " "
+                IDENT_PAT
+                  NAME
+                    IDENT "None"
+                WHITESPACE " "
+                EQ "="
+                WHITESPACE " "
+                PATH_EXPR
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "None"
+              R_PAREN ")"
+          WHITESPACE " "
+          BLOCK_EXPR
+            STMT_LIST
+              L_CURLY "{"
+              R_CURLY "}"
+        WHITESPACE "\n"
+        R_CURLY "}"
+  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0031_while_expr.txt b/crates/parser/test_data/parser/inline/ok/0031_while_expr.txt
index fc1ca4934c5..16c522414af 100644
--- a/crates/parser/test_data/parser/inline/ok/0031_while_expr.txt
+++ b/crates/parser/test_data/parser/inline/ok/0031_while_expr.txt
@@ -16,9 +16,8 @@ SOURCE_FILE
           WHILE_EXPR
             WHILE_KW "while"
             WHITESPACE " "
-            CONDITION
-              LITERAL
-                TRUE_KW "true"
+            LITERAL
+              TRUE_KW "true"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
@@ -30,7 +29,7 @@ SOURCE_FILE
           WHILE_EXPR
             WHILE_KW "while"
             WHITESPACE " "
-            CONDITION
+            LET_EXPR
               LET_KW "let"
               WHITESPACE " "
               TUPLE_STRUCT_PAT
@@ -69,15 +68,14 @@ SOURCE_FILE
           WHILE_EXPR
             WHILE_KW "while"
             WHITESPACE " "
-            CONDITION
-              BLOCK_EXPR
-                STMT_LIST
-                  L_CURLY "{"
-                  WHITESPACE " "
-                  LITERAL
-                    TRUE_KW "true"
-                  WHITESPACE " "
-                  R_CURLY "}"
+            BLOCK_EXPR
+              STMT_LIST
+                L_CURLY "{"
+                WHITESPACE " "
+                LITERAL
+                  TRUE_KW "true"
+                WHITESPACE " "
+                R_CURLY "}"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
diff --git a/crates/parser/test_data/parser/inline/ok/0064_if_expr.txt b/crates/parser/test_data/parser/inline/ok/0064_if_expr.txt
index c1f8381271f..e2e964e44d1 100644
--- a/crates/parser/test_data/parser/inline/ok/0064_if_expr.txt
+++ b/crates/parser/test_data/parser/inline/ok/0064_if_expr.txt
@@ -16,9 +16,8 @@ SOURCE_FILE
           IF_EXPR
             IF_KW "if"
             WHITESPACE " "
-            CONDITION
-              LITERAL
-                TRUE_KW "true"
+            LITERAL
+              TRUE_KW "true"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
@@ -30,9 +29,8 @@ SOURCE_FILE
           IF_EXPR
             IF_KW "if"
             WHITESPACE " "
-            CONDITION
-              LITERAL
-                TRUE_KW "true"
+            LITERAL
+              TRUE_KW "true"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
@@ -51,9 +49,8 @@ SOURCE_FILE
           IF_EXPR
             IF_KW "if"
             WHITESPACE " "
-            CONDITION
-              LITERAL
-                TRUE_KW "true"
+            LITERAL
+              TRUE_KW "true"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
@@ -65,9 +62,8 @@ SOURCE_FILE
             IF_EXPR
               IF_KW "if"
               WHITESPACE " "
-              CONDITION
-                LITERAL
-                  FALSE_KW "false"
+              LITERAL
+                FALSE_KW "false"
               WHITESPACE " "
               BLOCK_EXPR
                 STMT_LIST
@@ -86,12 +82,11 @@ SOURCE_FILE
           IF_EXPR
             IF_KW "if"
             WHITESPACE " "
-            CONDITION
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "S"
+            PATH_EXPR
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "S"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
@@ -103,15 +98,14 @@ SOURCE_FILE
           IF_EXPR
             IF_KW "if"
             WHITESPACE " "
-            CONDITION
-              BLOCK_EXPR
-                STMT_LIST
-                  L_CURLY "{"
-                  WHITESPACE " "
-                  LITERAL
-                    TRUE_KW "true"
-                  WHITESPACE " "
-                  R_CURLY "}"
+            BLOCK_EXPR
+              STMT_LIST
+                L_CURLY "{"
+                WHITESPACE " "
+                LITERAL
+                  TRUE_KW "true"
+                WHITESPACE " "
+                R_CURLY "}"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
diff --git a/crates/parser/test_data/parser/inline/ok/0088_break_ambiguity.txt b/crates/parser/test_data/parser/inline/ok/0088_break_ambiguity.txt
index 50ce9933b99..cbf5e84e8cd 100644
--- a/crates/parser/test_data/parser/inline/ok/0088_break_ambiguity.txt
+++ b/crates/parser/test_data/parser/inline/ok/0088_break_ambiguity.txt
@@ -15,9 +15,8 @@ SOURCE_FILE
           IF_EXPR
             IF_KW "if"
             WHITESPACE " "
-            CONDITION
-              BREAK_EXPR
-                BREAK_KW "break"
+            BREAK_EXPR
+              BREAK_KW "break"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
@@ -28,9 +27,8 @@ SOURCE_FILE
           WHILE_EXPR
             WHILE_KW "while"
             WHITESPACE " "
-            CONDITION
-              BREAK_EXPR
-                BREAK_KW "break"
+            BREAK_EXPR
+              BREAK_KW "break"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
diff --git a/crates/parser/test_data/parser/inline/ok/0096_no_semi_after_block.txt b/crates/parser/test_data/parser/inline/ok/0096_no_semi_after_block.txt
index 2a853713934..e4e215593ee 100644
--- a/crates/parser/test_data/parser/inline/ok/0096_no_semi_after_block.txt
+++ b/crates/parser/test_data/parser/inline/ok/0096_no_semi_after_block.txt
@@ -16,9 +16,8 @@ SOURCE_FILE
           IF_EXPR
             IF_KW "if"
             WHITESPACE " "
-            CONDITION
-              LITERAL
-                TRUE_KW "true"
+            LITERAL
+              TRUE_KW "true"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
@@ -50,9 +49,8 @@ SOURCE_FILE
           WHILE_EXPR
             WHILE_KW "while"
             WHITESPACE " "
-            CONDITION
-              LITERAL
-                TRUE_KW "true"
+            LITERAL
+              TRUE_KW "true"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
diff --git a/crates/parser/test_data/parser/inline/ok/0109_label.txt b/crates/parser/test_data/parser/inline/ok/0109_label.txt
index bd57fa9d4a1..48d0bde845a 100644
--- a/crates/parser/test_data/parser/inline/ok/0109_label.txt
+++ b/crates/parser/test_data/parser/inline/ok/0109_label.txt
@@ -35,9 +35,8 @@ SOURCE_FILE
             WHITESPACE " "
             WHILE_KW "while"
             WHITESPACE " "
-            CONDITION
-              LITERAL
-                TRUE_KW "true"
+            LITERAL
+              TRUE_KW "true"
             WHITESPACE " "
             BLOCK_EXPR
               STMT_LIST
diff --git a/crates/parser/test_data/parser/inline/ok/0118_match_guard.txt b/crates/parser/test_data/parser/inline/ok/0118_match_guard.txt
index a28b6ea5d1f..96318b52195 100644
--- a/crates/parser/test_data/parser/inline/ok/0118_match_guard.txt
+++ b/crates/parser/test_data/parser/inline/ok/0118_match_guard.txt
@@ -49,19 +49,20 @@ SOURCE_FILE
               MATCH_GUARD
                 IF_KW "if"
                 WHITESPACE " "
-                LET_KW "let"
-                WHITESPACE " "
-                IDENT_PAT
-                  NAME
-                    IDENT "foo"
-                WHITESPACE " "
-                EQ "="
-                WHITESPACE " "
-                PATH_EXPR
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "bar"
+                LET_EXPR
+                  LET_KW "let"
+                  WHITESPACE " "
+                  IDENT_PAT
+                    NAME
+                      IDENT "foo"
+                  WHITESPACE " "
+                  EQ "="
+                  WHITESPACE " "
+                  PATH_EXPR
+                    PATH
+                      PATH_SEGMENT
+                        NAME_REF
+                          IDENT "bar"
               WHITESPACE " "
               FAT_ARROW "=>"
               WHITESPACE " "
diff --git a/crates/parser/test_data/parser/ok/0033_label_break.txt b/crates/parser/test_data/parser/ok/0033_label_break.txt
index 9807bf0d9a6..df1acd6b83b 100644
--- a/crates/parser/test_data/parser/ok/0033_label_break.txt
+++ b/crates/parser/test_data/parser/ok/0033_label_break.txt
@@ -51,16 +51,15 @@ SOURCE_FILE
                 IF_EXPR
                   IF_KW "if"
                   WHITESPACE " "
-                  CONDITION
-                    CALL_EXPR
-                      PATH_EXPR
-                        PATH
-                          PATH_SEGMENT
-                            NAME_REF
-                              IDENT "condition_not_met"
-                      ARG_LIST
-                        L_PAREN "("
-                        R_PAREN ")"
+                  CALL_EXPR
+                    PATH_EXPR
+                      PATH
+                        PATH_SEGMENT
+                          NAME_REF
+                            IDENT "condition_not_met"
+                    ARG_LIST
+                      L_PAREN "("
+                      R_PAREN ")"
                   WHITESPACE " "
                   BLOCK_EXPR
                     STMT_LIST
@@ -92,16 +91,15 @@ SOURCE_FILE
                 IF_EXPR
                   IF_KW "if"
                   WHITESPACE " "
-                  CONDITION
-                    CALL_EXPR
-                      PATH_EXPR
-                        PATH
-                          PATH_SEGMENT
-                            NAME_REF
-                              IDENT "condition_not_met"
-                      ARG_LIST
-                        L_PAREN "("
-                        R_PAREN ")"
+                  CALL_EXPR
+                    PATH_EXPR
+                      PATH
+                        PATH_SEGMENT
+                          NAME_REF
+                            IDENT "condition_not_met"
+                    ARG_LIST
+                      L_PAREN "("
+                      R_PAREN ")"
                   WHITESPACE " "
                   BLOCK_EXPR
                     STMT_LIST
@@ -153,16 +151,15 @@ SOURCE_FILE
                 IF_EXPR
                   IF_KW "if"
                   WHITESPACE " "
-                  CONDITION
-                    CALL_EXPR
-                      PATH_EXPR
-                        PATH
-                          PATH_SEGMENT
-                            NAME_REF
-                              IDENT "foo"
-                      ARG_LIST
-                        L_PAREN "("
-                        R_PAREN ")"
+                  CALL_EXPR
+                    PATH_EXPR
+                      PATH
+                        PATH_SEGMENT
+                          NAME_REF
+                            IDENT "foo"
+                    ARG_LIST
+                      L_PAREN "("
+                      R_PAREN ")"
                   WHITESPACE " "
                   BLOCK_EXPR
                     STMT_LIST
@@ -187,16 +184,15 @@ SOURCE_FILE
                 IF_EXPR
                   IF_KW "if"
                   WHITESPACE " "
-                  CONDITION
-                    CALL_EXPR
-                      PATH_EXPR
-                        PATH
-                          PATH_SEGMENT
-                            NAME_REF
-                              IDENT "bar"
-                      ARG_LIST
-                        L_PAREN "("
-                        R_PAREN ")"
+                  CALL_EXPR
+                    PATH_EXPR
+                      PATH
+                        PATH_SEGMENT
+                          NAME_REF
+                            IDENT "bar"
+                    ARG_LIST
+                      L_PAREN "("
+                      R_PAREN ")"
                   WHITESPACE " "
                   BLOCK_EXPR
                     STMT_LIST
diff --git a/crates/parser/test_data/parser/ok/0035_weird_exprs.txt b/crates/parser/test_data/parser/ok/0035_weird_exprs.txt
index 5f62748c479..4ec703e517c 100644
--- a/crates/parser/test_data/parser/ok/0035_weird_exprs.txt
+++ b/crates/parser/test_data/parser/ok/0035_weird_exprs.txt
@@ -280,21 +280,20 @@ SOURCE_FILE
                   WHILE_EXPR
                     WHILE_KW "while"
                     WHITESPACE " "
-                    CONDITION
-                      PREFIX_EXPR
-                        BANG "!"
-                        METHOD_CALL_EXPR
-                          PATH_EXPR
-                            PATH
-                              PATH_SEGMENT
-                                NAME_REF
-                                  IDENT "x"
-                          DOT "."
-                          NAME_REF
-                            IDENT "get"
-                          ARG_LIST
-                            L_PAREN "("
-                            R_PAREN ")"
+                    PREFIX_EXPR
+                      BANG "!"
+                      METHOD_CALL_EXPR
+                        PATH_EXPR
+                          PATH
+                            PATH_SEGMENT
+                              NAME_REF
+                                IDENT "x"
+                        DOT "."
+                        NAME_REF
+                          IDENT "get"
+                        ARG_LIST
+                          L_PAREN "("
+                          R_PAREN ")"
                     WHITESPACE " "
                     BLOCK_EXPR
                       STMT_LIST
@@ -443,12 +442,11 @@ SOURCE_FILE
                 WHILE_EXPR
                   WHILE_KW "while"
                   WHITESPACE " "
-                  CONDITION
-                    PAREN_EXPR
-                      L_PAREN "("
-                      RETURN_EXPR
-                        RETURN_KW "return"
-                      R_PAREN ")"
+                  PAREN_EXPR
+                    L_PAREN "("
+                    RETURN_EXPR
+                      RETURN_KW "return"
+                    R_PAREN ")"
                   WHITESPACE " "
                   BLOCK_EXPR
                     STMT_LIST
@@ -457,12 +455,11 @@ SOURCE_FILE
                       IF_EXPR
                         IF_KW "if"
                         WHITESPACE " "
-                        CONDITION
-                          PAREN_EXPR
-                            L_PAREN "("
-                            RETURN_EXPR
-                              RETURN_KW "return"
-                            R_PAREN ")"
+                        PAREN_EXPR
+                          L_PAREN "("
+                          RETURN_EXPR
+                            RETURN_KW "return"
+                          R_PAREN ")"
                         WHITESPACE " "
                         BLOCK_EXPR
                           STMT_LIST
@@ -495,12 +492,11 @@ SOURCE_FILE
                                         IF_EXPR
                                           IF_KW "if"
                                           WHITESPACE " "
-                                          CONDITION
-                                            PAREN_EXPR
-                                              L_PAREN "("
-                                              RETURN_EXPR
-                                                RETURN_KW "return"
-                                              R_PAREN ")"
+                                          PAREN_EXPR
+                                            L_PAREN "("
+                                            RETURN_EXPR
+                                              RETURN_KW "return"
+                                            R_PAREN ")"
                                           WHITESPACE " "
                                           BLOCK_EXPR
                                             STMT_LIST
@@ -549,12 +545,11 @@ SOURCE_FILE
                         IF_EXPR
                           IF_KW "if"
                           WHITESPACE " "
-                          CONDITION
-                            PAREN_EXPR
-                              L_PAREN "("
-                              RETURN_EXPR
-                                RETURN_KW "return"
-                              R_PAREN ")"
+                          PAREN_EXPR
+                            L_PAREN "("
+                            RETURN_EXPR
+                              RETURN_KW "return"
+                            R_PAREN ")"
                           WHITESPACE " "
                           BLOCK_EXPR
                             STMT_LIST
@@ -572,12 +567,11 @@ SOURCE_FILE
               IF_EXPR
                 IF_KW "if"
                 WHITESPACE " "
-                CONDITION
-                  PAREN_EXPR
-                    L_PAREN "("
-                    RETURN_EXPR
-                      RETURN_KW "return"
-                    R_PAREN ")"
+                PAREN_EXPR
+                  L_PAREN "("
+                  RETURN_EXPR
+                    RETURN_KW "return"
+                  R_PAREN ")"
                 WHITESPACE " "
                 BLOCK_EXPR
                   STMT_LIST
@@ -1037,9 +1031,8 @@ SOURCE_FILE
                 IF_EXPR
                   IF_KW "if"
                   WHITESPACE " "
-                  CONDITION
-                    BREAK_EXPR
-                      BREAK_KW "break"
+                  BREAK_EXPR
+                    BREAK_KW "break"
                   WHITESPACE " "
                   BLOCK_EXPR
                     STMT_LIST
@@ -1089,18 +1082,17 @@ SOURCE_FILE
                 IF_EXPR
                   IF_KW "if"
                   WHITESPACE " "
-                  CONDITION
-                    BIN_EXPR
-                      PATH_EXPR
-                        PATH
-                          PATH_SEGMENT
-                            NAME_REF
-                              IDENT "i"
-                      WHITESPACE " "
-                      EQ2 "=="
-                      WHITESPACE " "
-                      LITERAL
-                        INT_NUMBER "1"
+                  BIN_EXPR
+                    PATH_EXPR
+                      PATH
+                        PATH_SEGMENT
+                          NAME_REF
+                            IDENT "i"
+                    WHITESPACE " "
+                    EQ2 "=="
+                    WHITESPACE " "
+                    LITERAL
+                      INT_NUMBER "1"
                   WHITESPACE " "
                   BLOCK_EXPR
                     STMT_LIST
@@ -1344,18 +1336,17 @@ SOURCE_FILE
         IF_EXPR
           IF_KW "if"
           WHITESPACE " "
-          CONDITION
-            BIN_EXPR
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "u8"
-              WHITESPACE " "
-              NEQ "!="
-              WHITESPACE " "
-              LITERAL
-                INT_NUMBER "0u8"
+          BIN_EXPR
+            PATH_EXPR
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "u8"
+            WHITESPACE " "
+            NEQ "!="
+            WHITESPACE " "
+            LITERAL
+              INT_NUMBER "0u8"
           WHITESPACE " "
           BLOCK_EXPR
             STMT_LIST
diff --git a/crates/parser/test_data/parser/ok/0047_minus_in_inner_pattern.txt b/crates/parser/test_data/parser/ok/0047_minus_in_inner_pattern.txt
index ac23e7d1d96..aecc71d4829 100644
--- a/crates/parser/test_data/parser/ok/0047_minus_in_inner_pattern.txt
+++ b/crates/parser/test_data/parser/ok/0047_minus_in_inner_pattern.txt
@@ -219,7 +219,7 @@ SOURCE_FILE
         IF_EXPR
           IF_KW "if"
           WHITESPACE " "
-          CONDITION
+          LET_EXPR
             LET_KW "let"
             WHITESPACE " "
             TUPLE_STRUCT_PAT
diff --git a/crates/parser/test_data/parser/ok/0056_neq_in_type.txt b/crates/parser/test_data/parser/ok/0056_neq_in_type.txt
index 2d78eaffc52..55ce31275fb 100644
--- a/crates/parser/test_data/parser/ok/0056_neq_in_type.txt
+++ b/crates/parser/test_data/parser/ok/0056_neq_in_type.txt
@@ -15,47 +15,46 @@ SOURCE_FILE
         IF_EXPR
           IF_KW "if"
           WHITESPACE " "
-          CONDITION
-            BIN_EXPR
-              CAST_EXPR
-                METHOD_CALL_EXPR
-                  LITERAL
-                    FLOAT_NUMBER "1.0f32"
-                  DOT "."
-                  NAME_REF
-                    IDENT "floor"
-                  ARG_LIST
-                    L_PAREN "("
-                    R_PAREN ")"
-                WHITESPACE " "
-                AS_KW "as"
-                WHITESPACE " "
-                PATH_TYPE
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "i64"
+          BIN_EXPR
+            CAST_EXPR
+              METHOD_CALL_EXPR
+                LITERAL
+                  FLOAT_NUMBER "1.0f32"
+                DOT "."
+                NAME_REF
+                  IDENT "floor"
+                ARG_LIST
+                  L_PAREN "("
+                  R_PAREN ")"
               WHITESPACE " "
-              NEQ "!="
+              AS_KW "as"
               WHITESPACE " "
-              CAST_EXPR
-                METHOD_CALL_EXPR
-                  LITERAL
-                    FLOAT_NUMBER "1.0f32"
-                  DOT "."
-                  NAME_REF
-                    IDENT "floor"
-                  ARG_LIST
-                    L_PAREN "("
-                    R_PAREN ")"
-                WHITESPACE " "
-                AS_KW "as"
-                WHITESPACE " "
-                PATH_TYPE
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "i64"
+              PATH_TYPE
+                PATH
+                  PATH_SEGMENT
+                    NAME_REF
+                      IDENT "i64"
+            WHITESPACE " "
+            NEQ "!="
+            WHITESPACE " "
+            CAST_EXPR
+              METHOD_CALL_EXPR
+                LITERAL
+                  FLOAT_NUMBER "1.0f32"
+                DOT "."
+                NAME_REF
+                  IDENT "floor"
+                ARG_LIST
+                  L_PAREN "("
+                  R_PAREN ")"
+              WHITESPACE " "
+              AS_KW "as"
+              WHITESPACE " "
+              PATH_TYPE
+                PATH
+                  PATH_SEGMENT
+                    NAME_REF
+                      IDENT "i64"
           WHITESPACE " "
           BLOCK_EXPR
             STMT_LIST
diff --git a/crates/parser/test_data/parser/ok/0059_loops_in_parens.txt b/crates/parser/test_data/parser/ok/0059_loops_in_parens.txt
index 1eeb6c957f7..79bc7f971d1 100644
--- a/crates/parser/test_data/parser/ok/0059_loops_in_parens.txt
+++ b/crates/parser/test_data/parser/ok/0059_loops_in_parens.txt
@@ -86,9 +86,8 @@ SOURCE_FILE
               WHILE_EXPR
                 WHILE_KW "while"
                 WHITESPACE " "
-                CONDITION
-                  LITERAL
-                    TRUE_KW "true"
+                LITERAL
+                  TRUE_KW "true"
                 WHITESPACE " "
                 BLOCK_EXPR
                   STMT_LIST
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index 43c1f2fa80b..f59cd4f257c 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -30,7 +30,7 @@ rayon = "1"
 expect-test = "1.2.0-pre.1"
 proc-macro2 = "1.0.8"
 quote = "1.0.2"
-ungrammar = "=1.14.9"
+ungrammar = "=1.15.0"
 
 test_utils = { path = "../test_utils" }
 sourcegen = { path = "../sourcegen" }
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index 09c5af210f5..6c4729ef365 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -884,7 +884,7 @@ pub struct IfExpr {
 impl ast::HasAttrs for IfExpr {}
 impl IfExpr {
     pub fn if_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![if]) }
-    pub fn condition(&self) -> Option<Condition> { support::child(&self.syntax) }
+    pub fn condition(&self) -> Option<Expr> { support::child(&self.syntax) }
     pub fn else_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![else]) }
 }
 
@@ -1038,7 +1038,7 @@ impl ast::HasAttrs for WhileExpr {}
 impl ast::HasLoopBody for WhileExpr {}
 impl WhileExpr {
     pub fn while_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![while]) }
-    pub fn condition(&self) -> Option<Condition> { support::child(&self.syntax) }
+    pub fn condition(&self) -> Option<Expr> { support::child(&self.syntax) }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -1052,6 +1052,18 @@ impl YieldExpr {
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct LetExpr {
+    pub(crate) syntax: SyntaxNode,
+}
+impl ast::HasAttrs for LetExpr {}
+impl LetExpr {
+    pub fn let_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![let]) }
+    pub fn pat(&self) -> Option<Pat> { support::child(&self.syntax) }
+    pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
+    pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct StmtList {
     pub(crate) syntax: SyntaxNode,
 }
@@ -1107,17 +1119,6 @@ impl ArgList {
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct Condition {
-    pub(crate) syntax: SyntaxNode,
-}
-impl Condition {
-    pub fn let_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![let]) }
-    pub fn pat(&self) -> Option<Pat> { support::child(&self.syntax) }
-    pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
-    pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct MatchArmList {
     pub(crate) syntax: SyntaxNode,
 }
@@ -1147,10 +1148,7 @@ pub struct MatchGuard {
 }
 impl MatchGuard {
     pub fn if_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![if]) }
-    pub fn let_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![let]) }
-    pub fn pat(&self) -> Option<Pat> { support::child(&self.syntax) }
-    pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
-    pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+    pub fn condition(&self) -> Option<Expr> { support::child(&self.syntax) }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -1524,6 +1522,7 @@ pub enum Expr {
     TupleExpr(TupleExpr),
     WhileExpr(WhileExpr),
     YieldExpr(YieldExpr),
+    LetExpr(LetExpr),
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -2664,6 +2663,17 @@ impl AstNode for YieldExpr {
     }
     fn syntax(&self) -> &SyntaxNode { &self.syntax }
 }
+impl AstNode for LetExpr {
+    fn can_cast(kind: SyntaxKind) -> bool { kind == LET_EXPR }
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
 impl AstNode for StmtList {
     fn can_cast(kind: SyntaxKind) -> bool { kind == STMT_LIST }
     fn cast(syntax: SyntaxNode) -> Option<Self> {
@@ -2719,17 +2729,6 @@ impl AstNode for ArgList {
     }
     fn syntax(&self) -> &SyntaxNode { &self.syntax }
 }
-impl AstNode for Condition {
-    fn can_cast(kind: SyntaxKind) -> bool { kind == CONDITION }
-    fn cast(syntax: SyntaxNode) -> Option<Self> {
-        if Self::can_cast(syntax.kind()) {
-            Some(Self { syntax })
-        } else {
-            None
-        }
-    }
-    fn syntax(&self) -> &SyntaxNode { &self.syntax }
-}
 impl AstNode for MatchArmList {
     fn can_cast(kind: SyntaxKind) -> bool { kind == MATCH_ARM_LIST }
     fn cast(syntax: SyntaxNode) -> Option<Self> {
@@ -3336,6 +3335,9 @@ impl From<WhileExpr> for Expr {
 impl From<YieldExpr> for Expr {
     fn from(node: YieldExpr) -> Expr { Expr::YieldExpr(node) }
 }
+impl From<LetExpr> for Expr {
+    fn from(node: LetExpr) -> Expr { Expr::LetExpr(node) }
+}
 impl AstNode for Expr {
     fn can_cast(kind: SyntaxKind) -> bool {
         match kind {
@@ -3344,7 +3346,7 @@ impl AstNode for Expr {
             | INDEX_EXPR | LITERAL | LOOP_EXPR | MACRO_CALL | MACRO_STMTS | MATCH_EXPR
             | METHOD_CALL_EXPR | PAREN_EXPR | PATH_EXPR | PREFIX_EXPR | RANGE_EXPR
             | RECORD_EXPR | REF_EXPR | RETURN_EXPR | TRY_EXPR | TUPLE_EXPR | WHILE_EXPR
-            | YIELD_EXPR => true,
+            | YIELD_EXPR | LET_EXPR => true,
             _ => false,
         }
     }
@@ -3381,6 +3383,7 @@ impl AstNode for Expr {
             TUPLE_EXPR => Expr::TupleExpr(TupleExpr { syntax }),
             WHILE_EXPR => Expr::WhileExpr(WhileExpr { syntax }),
             YIELD_EXPR => Expr::YieldExpr(YieldExpr { syntax }),
+            LET_EXPR => Expr::LetExpr(LetExpr { syntax }),
             _ => return None,
         };
         Some(res)
@@ -3418,6 +3421,7 @@ impl AstNode for Expr {
             Expr::TupleExpr(it) => &it.syntax,
             Expr::WhileExpr(it) => &it.syntax,
             Expr::YieldExpr(it) => &it.syntax,
+            Expr::LetExpr(it) => &it.syntax,
         }
     }
 }
@@ -3883,6 +3887,7 @@ impl AstNode for AnyHasAttrs {
             | TUPLE_EXPR
             | WHILE_EXPR
             | YIELD_EXPR
+            | LET_EXPR
             | STMT_LIST
             | RECORD_EXPR_FIELD_LIST
             | RECORD_EXPR_FIELD
@@ -4537,6 +4542,11 @@ impl std::fmt::Display for YieldExpr {
         std::fmt::Display::fmt(self.syntax(), f)
     }
 }
+impl std::fmt::Display for LetExpr {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
 impl std::fmt::Display for StmtList {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(self.syntax(), f)
@@ -4562,11 +4572,6 @@ impl std::fmt::Display for ArgList {
         std::fmt::Display::fmt(self.syntax(), f)
     }
 }
-impl std::fmt::Display for Condition {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        std::fmt::Display::fmt(self.syntax(), f)
-    }
-}
 impl std::fmt::Display for MatchArmList {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(self.syntax(), f)
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 8a1bcebbf67..1a754ef4609 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -397,7 +397,7 @@ pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Ex
     expr_from_text(&format!("match {} {}", expr, match_arm_list))
 }
 pub fn expr_if(
-    condition: ast::Condition,
+    condition: ast::Expr,
     then_branch: ast::BlockExpr,
     else_branch: Option<ast::ElseBranch>,
 ) -> ast::Expr {
@@ -456,14 +456,8 @@ pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr {
 fn expr_from_text(text: &str) -> ast::Expr {
     ast_from_text(&format!("const C: () = {};", text))
 }
-
-pub fn condition(expr: ast::Expr, pattern: Option<ast::Pat>) -> ast::Condition {
-    match pattern {
-        None => ast_from_text(&format!("const _: () = while {} {{}};", expr)),
-        Some(pattern) => {
-            ast_from_text(&format!("const _: () = while let {} = {} {{}};", pattern, expr))
-        }
-    }
+pub fn expr_let(pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr {
+    ast_from_text(&format!("const _: () = while let {} = {} {{}};", pattern, expr))
 }
 
 pub fn arg_list(args: impl IntoIterator<Item = ast::Expr>) -> ast::ArgList {
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 067e13ee14d..5ff6519c9cc 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -528,12 +528,6 @@ impl ast::Item {
     }
 }
 
-impl ast::Condition {
-    pub fn is_pattern_cond(&self) -> bool {
-        self.let_token().is_some()
-    }
-}
-
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum FieldKind {
     Name(ast::NameRef),
diff --git a/crates/syntax/src/tests/ast_src.rs b/crates/syntax/src/tests/ast_src.rs
index c0f1d5ef565..aeff851ce4d 100644
--- a/crates/syntax/src/tests/ast_src.rs
+++ b/crates/syntax/src/tests/ast_src.rs
@@ -133,7 +133,6 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
         "CLOSURE_EXPR",
         "IF_EXPR",
         "WHILE_EXPR",
-        "CONDITION",
         "LOOP_EXPR",
         "FOR_EXPR",
         "CONTINUE_EXPR",
@@ -143,6 +142,7 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
         "STMT_LIST",
         "RETURN_EXPR",
         "YIELD_EXPR",
+        "LET_EXPR",
         "MATCH_EXPR",
         "MATCH_ARM_LIST",
         "MATCH_ARM",
diff --git a/crates/syntax/src/validation.rs b/crates/syntax/src/validation.rs
index 8dc47e0bd3f..3ea5844c951 100644
--- a/crates/syntax/src/validation.rs
+++ b/crates/syntax/src/validation.rs
@@ -38,6 +38,7 @@ pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> {
                 ast::PtrType(it) => validate_trait_object_ptr_ty(it, &mut errors),
                 ast::FnPtrType(it) => validate_trait_object_fn_ptr_ret_ty(it, &mut errors),
                 ast::MacroRules(it) => validate_macro_rules(it, &mut errors),
+                ast::LetExpr(it) => validate_let_expr(it, &mut errors),
                 _ => (),
             }
         }
@@ -343,3 +344,33 @@ fn validate_const(const_: ast::Const, errors: &mut Vec<SyntaxError>) {
         errors.push(SyntaxError::new("const globals cannot be mutable", mut_token.text_range()));
     }
 }
+
+fn validate_let_expr(let_: ast::LetExpr, errors: &mut Vec<SyntaxError>) {
+    let mut token = let_.syntax().clone();
+    loop {
+        token = match token.parent() {
+            Some(it) => it,
+            None => break,
+        };
+
+        if ast::ParenExpr::can_cast(token.kind()) {
+            continue;
+        } else if let Some(it) = ast::BinExpr::cast(token.clone()) {
+            if it.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And)) {
+                continue;
+            }
+        } else if ast::IfExpr::can_cast(token.kind())
+            || ast::WhileExpr::can_cast(token.kind())
+            || ast::MatchGuard::can_cast(token.kind())
+        {
+            // It must be part of the condition since the expressions are inside a block.
+            return;
+        }
+
+        break;
+    }
+    errors.push(SyntaxError::new(
+        "`let` expressions are not supported here",
+        let_.syntax().text_range(),
+    ));
+}
diff --git a/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast b/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast
index d4963979c86..50057a02d80 100644
--- a/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast
+++ b/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast
@@ -49,9 +49,8 @@ SOURCE_FILE@0..350
           IF_EXPR@134..257
             IF_KW@134..136 "if"
             WHITESPACE@136..137 " "
-            CONDITION@137..141
-              LITERAL@137..141
-                TRUE_KW@137..141 "true"
+            LITERAL@137..141
+              TRUE_KW@137..141 "true"
             WHITESPACE@141..142 " "
             BLOCK_EXPR@142..257
               STMT_LIST@142..257
@@ -94,9 +93,8 @@ SOURCE_FILE@0..350
         WHILE_EXPR@262..347
           WHILE_KW@262..267 "while"
           WHITESPACE@267..268 " "
-          CONDITION@268..272
-            LITERAL@268..272
-              TRUE_KW@268..272 "true"
+          LITERAL@268..272
+            TRUE_KW@268..272 "true"
           WHITESPACE@272..273 " "
           BLOCK_EXPR@273..347
             STMT_LIST@273..347
diff --git a/crates/syntax/test_data/parser/validation/invalid_let_expr.rast b/crates/syntax/test_data/parser/validation/invalid_let_expr.rast
new file mode 100644
index 00000000000..5b37b597832
--- /dev/null
+++ b/crates/syntax/test_data/parser/validation/invalid_let_expr.rast
@@ -0,0 +1,216 @@
+SOURCE_FILE@0..282

+  FN@0..281

+    FN_KW@0..2 "fn"

+    WHITESPACE@2..3 " "

+    NAME@3..6

+      IDENT@3..6 "foo"

+    PARAM_LIST@6..8

+      L_PAREN@6..7 "("

+      R_PAREN@7..8 ")"

+    WHITESPACE@8..9 " "

+    BLOCK_EXPR@9..281

+      STMT_LIST@9..281

+        L_CURLY@9..10 "{"

+        WHITESPACE@10..15 "\n    "

+        CONST@15..42

+          CONST_KW@15..20 "const"

+          WHITESPACE@20..21 " "

+          UNDERSCORE@21..22 "_"

+          COLON@22..23 ":"

+          WHITESPACE@23..24 " "

+          TUPLE_TYPE@24..26

+            L_PAREN@24..25 "("

+            R_PAREN@25..26 ")"

+          WHITESPACE@26..27 " "

+          EQ@27..28 "="

+          WHITESPACE@28..29 " "

+          LET_EXPR@29..41

+            LET_KW@29..32 "let"

+            WHITESPACE@32..33 " "

+            WILDCARD_PAT@33..34

+              UNDERSCORE@33..34 "_"

+            WHITESPACE@34..35 " "

+            EQ@35..36 "="

+            WHITESPACE@36..37 " "

+            PATH_EXPR@37..41

+              PATH@37..41

+                PATH_SEGMENT@37..41

+                  NAME_REF@37..41

+                    IDENT@37..41 "None"

+          SEMICOLON@41..42 ";"

+        WHITESPACE@42..48 "\n\n    "

+        LET_STMT@48..83

+          LET_KW@48..51 "let"

+          WHITESPACE@51..52 " "

+          WILDCARD_PAT@52..53

+            UNDERSCORE@52..53 "_"

+          WHITESPACE@53..54 " "

+          EQ@54..55 "="

+          WHITESPACE@55..56 " "

+          IF_EXPR@56..82

+            IF_KW@56..58 "if"

+            WHITESPACE@58..59 " "

+            LITERAL@59..63

+              TRUE_KW@59..63 "true"

+            WHITESPACE@63..64 " "

+            BLOCK_EXPR@64..82

+              STMT_LIST@64..82

+                L_CURLY@64..65 "{"

+                WHITESPACE@65..66 " "

+                PAREN_EXPR@66..80

+                  L_PAREN@66..67 "("

+                  LET_EXPR@67..79

+                    LET_KW@67..70 "let"

+                    WHITESPACE@70..71 " "

+                    WILDCARD_PAT@71..72

+                      UNDERSCORE@71..72 "_"

+                    WHITESPACE@72..73 " "

+                    EQ@73..74 "="

+                    WHITESPACE@74..75 " "

+                    PATH_EXPR@75..79

+                      PATH@75..79

+                        PATH_SEGMENT@75..79

+                          NAME_REF@75..79

+                            IDENT@75..79 "None"

+                  R_PAREN@79..80 ")"

+                WHITESPACE@80..81 " "

+                R_CURLY@81..82 "}"

+          SEMICOLON@82..83 ";"

+        WHITESPACE@83..89 "\n\n    "

+        IF_EXPR@89..279

+          IF_KW@89..91 "if"

+          WHITESPACE@91..92 " "

+          BIN_EXPR@92..114

+            LITERAL@92..96

+              TRUE_KW@92..96 "true"

+            WHITESPACE@96..97 " "

+            AMP2@97..99 "&&"

+            WHITESPACE@99..100 " "

+            PAREN_EXPR@100..114

+              L_PAREN@100..101 "("

+              LET_EXPR@101..113

+                LET_KW@101..104 "let"

+                WHITESPACE@104..105 " "

+                WILDCARD_PAT@105..106

+                  UNDERSCORE@105..106 "_"

+                WHITESPACE@106..107 " "

+                EQ@107..108 "="

+                WHITESPACE@108..109 " "

+                PATH_EXPR@109..113

+                  PATH@109..113

+                    PATH_SEGMENT@109..113

+                      NAME_REF@109..113

+                        IDENT@109..113 "None"

+              R_PAREN@113..114 ")"

+          WHITESPACE@114..115 " "

+          BLOCK_EXPR@115..279

+            STMT_LIST@115..279

+              L_CURLY@115..116 "{"

+              WHITESPACE@116..125 "\n        "

+              EXPR_STMT@125..140

+                PAREN_EXPR@125..139

+                  L_PAREN@125..126 "("

+                  LET_EXPR@126..138

+                    LET_KW@126..129 "let"

+                    WHITESPACE@129..130 " "

+                    WILDCARD_PAT@130..131

+                      UNDERSCORE@130..131 "_"

+                    WHITESPACE@131..132 " "

+                    EQ@132..133 "="

+                    WHITESPACE@133..134 " "

+                    PATH_EXPR@134..138

+                      PATH@134..138

+                        PATH_SEGMENT@134..138

+                          NAME_REF@134..138

+                            IDENT@134..138 "None"

+                  R_PAREN@138..139 ")"

+                SEMICOLON@139..140 ";"

+              WHITESPACE@140..149 "\n        "

+              WHILE_EXPR@149..273

+                WHILE_KW@149..154 "while"

+                WHITESPACE@154..155 " "

+                LET_EXPR@155..167

+                  LET_KW@155..158 "let"

+                  WHITESPACE@158..159 " "

+                  WILDCARD_PAT@159..160

+                    UNDERSCORE@159..160 "_"

+                  WHITESPACE@160..161 " "

+                  EQ@161..162 "="

+                  WHITESPACE@162..163 " "

+                  PATH_EXPR@163..167

+                    PATH@163..167

+                      PATH_SEGMENT@163..167

+                        NAME_REF@163..167

+                          IDENT@163..167 "None"

+                WHITESPACE@167..168 " "

+                BLOCK_EXPR@168..273

+                  STMT_LIST@168..273

+                    L_CURLY@168..169 "{"

+                    WHITESPACE@169..182 "\n            "

+                    MATCH_EXPR@182..263

+                      MATCH_KW@182..187 "match"

+                      WHITESPACE@187..188 " "

+                      PATH_EXPR@188..192

+                        PATH@188..192

+                          PATH_SEGMENT@188..192

+                            NAME_REF@188..192

+                              IDENT@188..192 "None"

+                      WHITESPACE@192..193 " "

+                      MATCH_ARM_LIST@193..263

+                        L_CURLY@193..194 "{"

+                        WHITESPACE@194..211 "\n                "

+                        MATCH_ARM@211..249

+                          WILDCARD_PAT@211..212

+                            UNDERSCORE@211..212 "_"

+                          WHITESPACE@212..213 " "

+                          MATCH_GUARD@213..228

+                            IF_KW@213..215 "if"

+                            WHITESPACE@215..216 " "

+                            LET_EXPR@216..228

+                              LET_KW@216..219 "let"

+                              WHITESPACE@219..220 " "

+                              WILDCARD_PAT@220..221

+                                UNDERSCORE@220..221 "_"

+                              WHITESPACE@221..222 " "

+                              EQ@222..223 "="

+                              WHITESPACE@223..224 " "

+                              PATH_EXPR@224..228

+                                PATH@224..228

+                                  PATH_SEGMENT@224..228

+                                    NAME_REF@224..228

+                                      IDENT@224..228 "None"

+                          WHITESPACE@228..229 " "

+                          FAT_ARROW@229..231 "=>"

+                          WHITESPACE@231..232 " "

+                          BLOCK_EXPR@232..249

+                            STMT_LIST@232..249

+                              L_CURLY@232..233 "{"

+                              WHITESPACE@233..234 " "

+                              LET_STMT@234..247

+                                LET_KW@234..237 "let"

+                                WHITESPACE@237..238 " "

+                                WILDCARD_PAT@238..239

+                                  UNDERSCORE@238..239 "_"

+                                WHITESPACE@239..240 " "

+                                EQ@240..241 "="

+                                WHITESPACE@241..242 " "

+                                PATH_EXPR@242..246

+                                  PATH@242..246

+                                    PATH_SEGMENT@242..246

+                                      NAME_REF@242..246

+                                        IDENT@242..246 "None"

+                                SEMICOLON@246..247 ";"

+                              WHITESPACE@247..248 " "

+                              R_CURLY@248..249 "}"

+                        WHITESPACE@249..262 "\n            "

+                        R_CURLY@262..263 "}"

+                    WHITESPACE@263..272 "\n        "

+                    R_CURLY@272..273 "}"

+              WHITESPACE@273..278 "\n    "

+              R_CURLY@278..279 "}"

+        WHITESPACE@279..280 "\n"

+        R_CURLY@280..281 "}"

+  WHITESPACE@281..282 "\n"

+error 29..41: `let` expressions are not supported here

+error 67..79: `let` expressions are not supported here

+error 126..138: `let` expressions are not supported here

diff --git a/crates/syntax/test_data/parser/validation/invalid_let_expr.rs b/crates/syntax/test_data/parser/validation/invalid_let_expr.rs
new file mode 100644
index 00000000000..1515ae5334d
--- /dev/null
+++ b/crates/syntax/test_data/parser/validation/invalid_let_expr.rs
@@ -0,0 +1,14 @@
+fn foo() {
+    const _: () = let _ = None;
+
+    let _ = if true { (let _ = None) };
+
+    if true && (let _ = None) {
+        (let _ = None);
+        while let _ = None {
+            match None {
+                _ if let _ = None => { let _ = None; }
+            }
+        }
+    }
+}