about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-09-01 01:02:42 +0000
committerbors <bors@rust-lang.org>2021-09-01 01:02:42 +0000
commitc2a408840ad18f74280805535f0b7193528ff3df (patch)
tree5d8815373247ba476a90946678a2c19e713f8780
parenta3956106d12cebec91be0637759e29ab6908b4cd (diff)
parent3ff1d6bbf427cfb3d504092c93f261b49577170e (diff)
downloadrust-c2a408840ad18f74280805535f0b7193528ff3df.tar.gz
rust-c2a408840ad18f74280805535f0b7193528ff3df.zip
Auto merge of #87688 - camsteffen:let-else, r=cjgillot
Introduce `let...else`

Tracking issue: #87335

The trickiest part for me was enforcing the diverging else block with clear diagnostics. Perhaps the obvious solution is to expand to `let _: ! = ..`, but I decided against this because, when a "mismatched type" error is found in typeck, there is no way to trace where in the HIR the expected type originated, AFAICT. In order to pass down this information, I believe we should introduce `Expectation::LetElseNever(HirId)` or maybe add `HirId` to `Expectation::HasType`, but I left that as a future enhancement. For now, I simply assert that the block is `!` with a custom `ObligationCauseCode`, and I think this is clear enough, at least to start. The downside here is that the error points at the entire block rather than the specific expression with the wrong type. I left a todo to this effect.

Overall, I believe this PR is feature-complete with regard to the RFC.
-rw-r--r--compiler/rustc_ast/src/ast.rs33
-rw-r--r--compiler/rustc_ast/src/mut_visit.rs13
-rw-r--r--compiler/rustc_ast/src/util/classify.rs27
-rw-r--r--compiler/rustc_ast/src/visit.rs5
-rw-r--r--compiler/rustc_ast_lowering/src/block.rs185
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs97
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state.rs10
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/debug.rs4
-rw-r--r--compiler/rustc_expand/src/build.rs6
-rw-r--r--compiler/rustc_feature/src/active.rs3
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/mod.rs5
-rw-r--r--compiler/rustc_lint/src/unused.rs43
-rw-r--r--compiler/rustc_middle/src/traits/mod.rs3
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/check_match.rs103
-rw-r--r--compiler/rustc_parse/src/parser/stmt.rs78
-rw-r--r--compiler/rustc_resolve/src/late.rs11
-rw-r--r--compiler/rustc_span/src/hygiene.rs2
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs9
-rw-r--r--compiler/rustc_typeck/src/check/expr.rs21
-rw-r--r--src/test/ui/did_you_mean/issue-40396.stderr4
-rw-r--r--src/test/ui/feature-gates/feature-gate-let_else.rs5
-rw-r--r--src/test/ui/feature-gates/feature-gate-let_else.stderr14
-rw-r--r--src/test/ui/let-else/let-else-bool-binop-init.fixed8
-rw-r--r--src/test/ui/let-else/let-else-bool-binop-init.rs8
-rw-r--r--src/test/ui/let-else/let-else-bool-binop-init.stderr24
-rw-r--r--src/test/ui/let-else/let-else-brace-before-else.fixed26
-rw-r--r--src/test/ui/let-else/let-else-brace-before-else.rs26
-rw-r--r--src/test/ui/let-else/let-else-brace-before-else.stderr46
-rw-r--r--src/test/ui/let-else/let-else-check.rs14
-rw-r--r--src/test/ui/let-else/let-else-check.stderr14
-rw-r--r--src/test/ui/let-else/let-else-irrefutable.rs7
-rw-r--r--src/test/ui/let-else/let-else-irrefutable.stderr12
-rw-r--r--src/test/ui/let-else/let-else-missing-semicolon.rs11
-rw-r--r--src/test/ui/let-else/let-else-missing-semicolon.stderr18
-rw-r--r--src/test/ui/let-else/let-else-non-diverging.rs13
-rw-r--r--src/test/ui/let-else/let-else-non-diverging.stderr44
-rw-r--r--src/test/ui/let-else/let-else-run-pass.rs35
-rw-r--r--src/test/ui/let-else/let-else-scope.rs7
-rw-r--r--src/test/ui/let-else/let-else-scope.stderr9
-rw-r--r--src/test/ui/parser/attr-stmt-expr-attr-bad.stderr16
-rw-r--r--src/test/ui/parser/issue-72253.rs2
-rw-r--r--src/test/ui/parser/issue-72253.stderr4
-rw-r--r--src/test/ui/parser/issue-84117.rs6
-rw-r--r--src/test/ui/parser/issue-84117.stderr12
-rw-r--r--src/test/ui/parser/macro/issue-37234.stderr4
-rw-r--r--src/test/ui/parser/missing-semicolon.rs2
-rw-r--r--src/test/ui/parser/missing-semicolon.stderr4
-rw-r--r--src/test/ui/parser/range-3.rs2
-rw-r--r--src/test/ui/parser/range-3.stderr4
-rw-r--r--src/test/ui/parser/range-4.rs2
-rw-r--r--src/test/ui/parser/range-4.stderr4
-rw-r--r--src/test/ui/pattern/usefulness/top-level-alternation.rs3
-rw-r--r--src/test/ui/pattern/usefulness/top-level-alternation.stderr30
-rw-r--r--src/tools/clippy/clippy_lints/src/non_expressive_names.rs7
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs12
-rw-r--r--src/tools/rustfmt/src/items.rs9
-rw-r--r--src/tools/rustfmt/tests/source/let_else.rs3
-rw-r--r--src/tools/rustfmt/tests/target/let_else.rs3
59 files changed, 901 insertions, 232 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index e1ea464dedb..0632d937c4c 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -1005,13 +1005,42 @@ pub struct Local {
     pub id: NodeId,
     pub pat: P<Pat>,
     pub ty: Option<P<Ty>>,
-    /// Initializer expression to set the value, if any.
-    pub init: Option<P<Expr>>,
+    pub kind: LocalKind,
     pub span: Span,
     pub attrs: AttrVec,
     pub tokens: Option<LazyTokenStream>,
 }
 
+#[derive(Clone, Encodable, Decodable, Debug)]
+pub enum LocalKind {
+    /// Local declaration.
+    /// Example: `let x;`
+    Decl,
+    /// Local declaration with an initializer.
+    /// Example: `let x = y;`
+    Init(P<Expr>),
+    /// Local declaration with an initializer and an `else` clause.
+    /// Example: `let Some(x) = y else { return };`
+    InitElse(P<Expr>, P<Block>),
+}
+
+impl LocalKind {
+    pub fn init(&self) -> Option<&Expr> {
+        match self {
+            Self::Decl => None,
+            Self::Init(i) | Self::InitElse(i, _) => Some(i),
+        }
+    }
+
+    pub fn init_else_opt(&self) -> Option<(&Expr, Option<&Block>)> {
+        match self {
+            Self::Decl => None,
+            Self::Init(init) => Some((init, None)),
+            Self::InitElse(init, els) => Some((init, Some(els))),
+        }
+    }
+}
+
 /// An arm of a 'match'.
 ///
 /// E.g., `0..=10 => { println!("match!") }` as in
diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs
index fb4db6005ac..368a23e3429 100644
--- a/compiler/rustc_ast/src/mut_visit.rs
+++ b/compiler/rustc_ast/src/mut_visit.rs
@@ -571,11 +571,20 @@ pub fn noop_visit_parenthesized_parameter_data<T: MutVisitor>(
 }
 
 pub fn noop_visit_local<T: MutVisitor>(local: &mut P<Local>, vis: &mut T) {
-    let Local { id, pat, ty, init, span, attrs, tokens } = local.deref_mut();
+    let Local { id, pat, ty, kind, span, attrs, tokens } = local.deref_mut();
     vis.visit_id(id);
     vis.visit_pat(pat);
     visit_opt(ty, |ty| vis.visit_ty(ty));
-    visit_opt(init, |init| vis.visit_expr(init));
+    match kind {
+        LocalKind::Decl => {}
+        LocalKind::Init(init) => {
+            vis.visit_expr(init);
+        }
+        LocalKind::InitElse(init, els) => {
+            vis.visit_expr(init);
+            vis.visit_block(els);
+        }
+    }
     vis.visit_span(span);
     visit_thin_attrs(attrs, vis);
     visit_lazy_tts(tokens, vis);
diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs
index 90786520fe8..6ea3db6d303 100644
--- a/compiler/rustc_ast/src/util/classify.rs
+++ b/compiler/rustc_ast/src/util/classify.rs
@@ -23,3 +23,30 @@ pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
             | ast::ExprKind::TryBlock(..)
     )
 }
+
+/// If an expression ends with `}`, returns the innermost expression ending in the `}`
+pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
+    use ast::ExprKind::*;
+
+    loop {
+        match &expr.kind {
+            AddrOf(_, _, e)
+            | Assign(_, e, _)
+            | AssignOp(_, _, e)
+            | Binary(_, _, e)
+            | Box(e)
+            | Break(_, Some(e))
+            | Closure(.., e, _)
+            | Let(_, e, _)
+            | Range(_, Some(e), _)
+            | Ret(Some(e))
+            | Unary(_, e)
+            | Yield(Some(e)) => {
+                expr = e;
+            }
+            Async(..) | Block(..) | ForLoop(..) | If(..) | Loop(..) | Match(..) | Struct(..)
+            | TryBlock(..) | While(..) => break Some(expr),
+            _ => break None,
+        }
+    }
+}
diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs
index dd8927496e0..c30f711b397 100644
--- a/compiler/rustc_ast/src/visit.rs
+++ b/compiler/rustc_ast/src/visit.rs
@@ -242,7 +242,10 @@ pub fn walk_local<'a, V: Visitor<'a>>(visitor: &mut V, local: &'a Local) {
     }
     visitor.visit_pat(&local.pat);
     walk_list!(visitor, visit_ty, &local.ty);
-    walk_list!(visitor, visit_expr, &local.init);
+    if let Some((init, els)) = local.kind.init_else_opt() {
+        visitor.visit_expr(init);
+        walk_list!(visitor, visit_block, els);
+    }
 }
 
 pub fn walk_label<'a, V: Visitor<'a>>(visitor: &mut V, label: &'a Label) {
diff --git a/compiler/rustc_ast_lowering/src/block.rs b/compiler/rustc_ast_lowering/src/block.rs
new file mode 100644
index 00000000000..ca804ec6758
--- /dev/null
+++ b/compiler/rustc_ast_lowering/src/block.rs
@@ -0,0 +1,185 @@
+use crate::{ImplTraitContext, ImplTraitPosition, LoweringContext};
+use rustc_ast::{AttrVec, Block, BlockCheckMode, Expr, Local, LocalKind, Stmt, StmtKind};
+use rustc_hir as hir;
+use rustc_session::parse::feature_err;
+use rustc_span::symbol::Ident;
+use rustc_span::{sym, DesugaringKind};
+
+use smallvec::SmallVec;
+
+impl<'a, 'hir> LoweringContext<'a, 'hir> {
+    pub(super) fn lower_block(
+        &mut self,
+        b: &Block,
+        targeted_by_break: bool,
+    ) -> &'hir hir::Block<'hir> {
+        self.arena.alloc(self.lower_block_noalloc(b, targeted_by_break))
+    }
+
+    pub(super) fn lower_block_noalloc(
+        &mut self,
+        b: &Block,
+        targeted_by_break: bool,
+    ) -> hir::Block<'hir> {
+        let (stmts, expr) = self.lower_stmts(&b.stmts);
+        let rules = self.lower_block_check_mode(&b.rules);
+        let hir_id = self.lower_node_id(b.id);
+        hir::Block { hir_id, stmts, expr, rules, span: self.lower_span(b.span), targeted_by_break }
+    }
+
+    fn lower_stmts(
+        &mut self,
+        mut ast_stmts: &[Stmt],
+    ) -> (&'hir [hir::Stmt<'hir>], Option<&'hir hir::Expr<'hir>>) {
+        let mut stmts = SmallVec::<[hir::Stmt<'hir>; 8]>::new();
+        let mut expr = None;
+        while let [s, tail @ ..] = ast_stmts {
+            match s.kind {
+                StmtKind::Local(ref local) => {
+                    let hir_id = self.lower_node_id(s.id);
+                    match &local.kind {
+                        LocalKind::InitElse(init, els) => {
+                            let (s, e) = self.lower_let_else(hir_id, local, init, els, tail);
+                            stmts.push(s);
+                            expr = Some(e);
+                            // remaining statements are in let-else expression
+                            break;
+                        }
+                        _ => {
+                            let local = self.lower_local(local);
+                            self.alias_attrs(hir_id, local.hir_id);
+                            let kind = hir::StmtKind::Local(local);
+                            let span = self.lower_span(s.span);
+                            stmts.push(hir::Stmt { hir_id, kind, span });
+                        }
+                    }
+                }
+                StmtKind::Item(ref it) => {
+                    stmts.extend(self.lower_item_id(it).into_iter().enumerate().map(
+                        |(i, item_id)| {
+                            let hir_id = match i {
+                                0 => self.lower_node_id(s.id),
+                                _ => self.next_id(),
+                            };
+                            let kind = hir::StmtKind::Item(item_id);
+                            let span = self.lower_span(s.span);
+                            hir::Stmt { hir_id, kind, span }
+                        },
+                    ));
+                }
+                StmtKind::Expr(ref e) => {
+                    let e = self.lower_expr(e);
+                    if tail.is_empty() {
+                        expr = Some(e);
+                    } else {
+                        let hir_id = self.lower_node_id(s.id);
+                        self.alias_attrs(hir_id, e.hir_id);
+                        let kind = hir::StmtKind::Expr(e);
+                        let span = self.lower_span(s.span);
+                        stmts.push(hir::Stmt { hir_id, kind, span });
+                    }
+                }
+                StmtKind::Semi(ref e) => {
+                    let e = self.lower_expr(e);
+                    let hir_id = self.lower_node_id(s.id);
+                    self.alias_attrs(hir_id, e.hir_id);
+                    let kind = hir::StmtKind::Semi(e);
+                    let span = self.lower_span(s.span);
+                    stmts.push(hir::Stmt { hir_id, kind, span });
+                }
+                StmtKind::Empty => {}
+                StmtKind::MacCall(..) => panic!("shouldn't exist here"),
+            }
+            ast_stmts = &ast_stmts[1..];
+        }
+        (self.arena.alloc_from_iter(stmts), expr)
+    }
+
+    fn lower_local(&mut self, l: &Local) -> &'hir hir::Local<'hir> {
+        let ty = l
+            .ty
+            .as_ref()
+            .map(|t| self.lower_ty(t, ImplTraitContext::Disallowed(ImplTraitPosition::Binding)));
+        let init = l.kind.init().map(|init| self.lower_expr(init));
+        let hir_id = self.lower_node_id(l.id);
+        let pat = self.lower_pat(&l.pat);
+        let span = self.lower_span(l.span);
+        let source = hir::LocalSource::Normal;
+        self.lower_attrs(hir_id, &l.attrs);
+        self.arena.alloc(hir::Local { hir_id, ty, pat, init, span, source })
+    }
+
+    fn lower_block_check_mode(&mut self, b: &BlockCheckMode) -> hir::BlockCheckMode {
+        match *b {
+            BlockCheckMode::Default => hir::BlockCheckMode::DefaultBlock,
+            BlockCheckMode::Unsafe(u) => {
+                hir::BlockCheckMode::UnsafeBlock(self.lower_unsafe_source(u))
+            }
+        }
+    }
+
+    fn lower_let_else(
+        &mut self,
+        stmt_hir_id: hir::HirId,
+        local: &Local,
+        init: &Expr,
+        els: &Block,
+        tail: &[Stmt],
+    ) -> (hir::Stmt<'hir>, &'hir hir::Expr<'hir>) {
+        let ty = local
+            .ty
+            .as_ref()
+            .map(|t| self.lower_ty(t, ImplTraitContext::Disallowed(ImplTraitPosition::Binding)));
+        let span = self.lower_span(local.span);
+        let span = self.mark_span_with_reason(DesugaringKind::LetElse, span, None);
+        let init = Some(self.lower_expr(init));
+        let val = Ident::with_dummy_span(sym::val);
+        let (pat, val_id) =
+            self.pat_ident_binding_mode(span, val, hir::BindingAnnotation::Unannotated);
+        let local_hir_id = self.lower_node_id(local.id);
+        self.lower_attrs(local_hir_id, &local.attrs);
+        // first statement which basically exists for the type annotation
+        let stmt = {
+            let local = self.arena.alloc(hir::Local {
+                hir_id: local_hir_id,
+                ty,
+                pat,
+                init,
+                span,
+                source: hir::LocalSource::Normal,
+            });
+            let kind = hir::StmtKind::Local(local);
+            hir::Stmt { hir_id: stmt_hir_id, kind, span }
+        };
+        let let_expr = {
+            let scrutinee = self.expr_ident(span, val, val_id);
+            let let_kind = hir::ExprKind::Let(self.lower_pat(&local.pat), scrutinee, span);
+            self.arena.alloc(self.expr(span, let_kind, AttrVec::new()))
+        };
+        let then_expr = {
+            let (stmts, expr) = self.lower_stmts(tail);
+            let block = self.block_all(span, stmts, expr);
+            self.arena.alloc(self.expr_block(block, AttrVec::new()))
+        };
+        let else_expr = {
+            let block = self.lower_block(els, false);
+            self.arena.alloc(self.expr_block(block, AttrVec::new()))
+        };
+        self.alias_attrs(else_expr.hir_id, local_hir_id);
+        let if_expr = self.arena.alloc(hir::Expr {
+            hir_id: self.next_id(),
+            span,
+            kind: hir::ExprKind::If(let_expr, then_expr, Some(else_expr)),
+        });
+        if !self.sess.features_untracked().let_else {
+            feature_err(
+                &self.sess.parse_sess,
+                sym::let_else,
+                local.span,
+                "`let...else` statements are unstable",
+            )
+            .emit();
+        }
+        (stmt, if_expr)
+    }
+}
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index 0133acfee10..deb7e742e5c 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -64,7 +64,7 @@ use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_span::{Span, DUMMY_SP};
 use rustc_target::spec::abi::Abi;
 
-use smallvec::{smallvec, SmallVec};
+use smallvec::SmallVec;
 use std::collections::BTreeMap;
 use std::mem;
 use tracing::{debug, trace};
@@ -77,6 +77,7 @@ macro_rules! arena_vec {
 }
 
 mod asm;
+mod block;
 mod expr;
 mod item;
 mod pat;
@@ -1793,24 +1794,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
         )
     }
 
-    fn lower_local(&mut self, l: &Local) -> hir::Local<'hir> {
-        let ty = l
-            .ty
-            .as_ref()
-            .map(|t| self.lower_ty(t, ImplTraitContext::Disallowed(ImplTraitPosition::Binding)));
-        let init = l.init.as_ref().map(|e| self.lower_expr(e));
-        let hir_id = self.lower_node_id(l.id);
-        self.lower_attrs(hir_id, &l.attrs);
-        hir::Local {
-            hir_id,
-            ty,
-            pat: self.lower_pat(&l.pat),
-            init,
-            span: self.lower_span(l.span),
-            source: hir::LocalSource::Normal,
-        }
-    }
-
     fn lower_fn_params_to_names(&mut self, decl: &FnDecl) -> &'hir [Ident] {
         // Skip the `...` (`CVarArgs`) trailing arguments from the AST,
         // as they are not explicit in HIR/Ty function signatures.
@@ -2396,23 +2379,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
         bounds.iter().map(move |bound| self.lower_param_bound(bound, itctx.reborrow()))
     }
 
-    fn lower_block(&mut self, b: &Block, targeted_by_break: bool) -> &'hir hir::Block<'hir> {
-        self.arena.alloc(self.lower_block_noalloc(b, targeted_by_break))
-    }
-
-    fn lower_block_noalloc(&mut self, b: &Block, targeted_by_break: bool) -> hir::Block<'hir> {
-        let (stmts, expr) = match &*b.stmts {
-            [stmts @ .., Stmt { kind: StmtKind::Expr(e), .. }] => (stmts, Some(&*e)),
-            stmts => (stmts, None),
-        };
-        let stmts = self.arena.alloc_from_iter(stmts.iter().flat_map(|stmt| self.lower_stmt(stmt)));
-        let expr = expr.map(|e| self.lower_expr(e));
-        let rules = self.lower_block_check_mode(&b.rules);
-        let hir_id = self.lower_node_id(b.id);
-
-        hir::Block { hir_id, stmts, expr, rules, span: self.lower_span(b.span), targeted_by_break }
-    }
-
     /// Lowers a block directly to an expression, presuming that it
     /// has no attributes and is not targeted by a `break`.
     fn lower_block_expr(&mut self, b: &Block) -> hir::Expr<'hir> {
@@ -2427,65 +2393,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
         })
     }
 
-    fn lower_stmt(&mut self, s: &Stmt) -> SmallVec<[hir::Stmt<'hir>; 1]> {
-        let (hir_id, kind) = match s.kind {
-            StmtKind::Local(ref l) => {
-                let l = self.lower_local(l);
-                let hir_id = self.lower_node_id(s.id);
-                self.alias_attrs(hir_id, l.hir_id);
-                return smallvec![hir::Stmt {
-                    hir_id,
-                    kind: hir::StmtKind::Local(self.arena.alloc(l)),
-                    span: self.lower_span(s.span),
-                }];
-            }
-            StmtKind::Item(ref it) => {
-                // Can only use the ID once.
-                let mut id = Some(s.id);
-                return self
-                    .lower_item_id(it)
-                    .into_iter()
-                    .map(|item_id| {
-                        let hir_id = id
-                            .take()
-                            .map(|id| self.lower_node_id(id))
-                            .unwrap_or_else(|| self.next_id());
-
-                        hir::Stmt {
-                            hir_id,
-                            kind: hir::StmtKind::Item(item_id),
-                            span: self.lower_span(s.span),
-                        }
-                    })
-                    .collect();
-            }
-            StmtKind::Expr(ref e) => {
-                let e = self.lower_expr(e);
-                let hir_id = self.lower_node_id(s.id);
-                self.alias_attrs(hir_id, e.hir_id);
-                (hir_id, hir::StmtKind::Expr(e))
-            }
-            StmtKind::Semi(ref e) => {
-                let e = self.lower_expr(e);
-                let hir_id = self.lower_node_id(s.id);
-                self.alias_attrs(hir_id, e.hir_id);
-                (hir_id, hir::StmtKind::Semi(e))
-            }
-            StmtKind::Empty => return smallvec![],
-            StmtKind::MacCall(..) => panic!("shouldn't exist here"),
-        };
-        smallvec![hir::Stmt { hir_id, kind, span: self.lower_span(s.span) }]
-    }
-
-    fn lower_block_check_mode(&mut self, b: &BlockCheckMode) -> hir::BlockCheckMode {
-        match *b {
-            BlockCheckMode::Default => hir::BlockCheckMode::DefaultBlock,
-            BlockCheckMode::Unsafe(u) => {
-                hir::BlockCheckMode::UnsafeBlock(self.lower_unsafe_source(u))
-            }
-        }
-    }
-
     fn lower_unsafe_source(&mut self, u: UnsafeSource) -> hir::UnsafeSource {
         match u {
             CompilerGenerated => hir::UnsafeSource::CompilerGenerated,
diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs
index 949e7a1fbb8..3cf04be160c 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state.rs
@@ -1518,13 +1518,19 @@ impl<'a> State<'a> {
                 self.ibox(INDENT_UNIT);
                 self.print_local_decl(loc);
                 self.end();
-                if let Some(ref init) = loc.init {
+                if let Some((init, els)) = loc.kind.init_else_opt() {
                     self.nbsp();
                     self.word_space("=");
                     self.print_expr(init);
+                    if let Some(els) = els {
+                        self.cbox(INDENT_UNIT);
+                        self.ibox(INDENT_UNIT);
+                        self.s.word(" else ");
+                        self.print_block(els);
+                    }
                 }
                 self.s.word(";");
-                self.end();
+                self.end(); // `let` ibox
             }
             ast::StmtKind::Item(ref item) => self.print_item(item),
             ast::StmtKind::Expr(ref expr) => {
diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs
index cc6dac52d76..14506f296bf 100644
--- a/compiler/rustc_builtin_macros/src/deriving/debug.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs
@@ -3,7 +3,7 @@ use crate::deriving::generic::*;
 use crate::deriving::path_std;
 
 use rustc_ast::ptr::P;
-use rustc_ast::{self as ast, Expr, MetaItem};
+use rustc_ast::{self as ast, Expr, LocalKind, MetaItem};
 use rustc_expand::base::{Annotatable, ExtCtxt};
 use rustc_span::symbol::{sym, Ident};
 use rustc_span::{Span, DUMMY_SP};
@@ -135,8 +135,8 @@ fn stmt_let_underscore(cx: &mut ExtCtxt<'_>, sp: Span, expr: P<ast::Expr>) -> as
     let local = P(ast::Local {
         pat: cx.pat_wild(sp),
         ty: None,
-        init: Some(expr),
         id: ast::DUMMY_NODE_ID,
+        kind: LocalKind::Init(expr),
         span: sp,
         attrs: ast::AttrVec::new(),
         tokens: None,
diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs
index 824df2757ea..1d83ecbfd40 100644
--- a/compiler/rustc_expand/src/build.rs
+++ b/compiler/rustc_expand/src/build.rs
@@ -2,7 +2,7 @@ use crate::base::ExtCtxt;
 
 use rustc_ast::attr;
 use rustc_ast::ptr::P;
-use rustc_ast::{self as ast, AttrVec, BlockCheckMode, Expr, PatKind, UnOp};
+use rustc_ast::{self as ast, AttrVec, BlockCheckMode, Expr, LocalKind, PatKind, UnOp};
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 
@@ -153,8 +153,8 @@ impl<'a> ExtCtxt<'a> {
         let local = P(ast::Local {
             pat,
             ty: None,
-            init: Some(ex),
             id: ast::DUMMY_NODE_ID,
+            kind: LocalKind::Init(ex),
             span: sp,
             attrs: AttrVec::new(),
             tokens: None,
@@ -167,8 +167,8 @@ impl<'a> ExtCtxt<'a> {
         let local = P(ast::Local {
             pat: self.pat_wild(span),
             ty: Some(ty),
-            init: None,
             id: ast::DUMMY_NODE_ID,
+            kind: LocalKind::Decl,
             span,
             attrs: AttrVec::new(),
             tokens: None,
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index 1ff2c75966a..a3807a2bb9f 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -676,6 +676,9 @@ declare_features! (
     /// Allows additional const parameter types, such as `&'static str` or user defined types
     (incomplete, adt_const_params, "1.56.0", Some(44580), None),
 
+    /// Allows `let...else` statements.
+    (active, let_else, "1.56.0", Some(87335), None),
+
     // -------------------------------------------------------------------------
     // feature-group-end: actual feature gates
     // -------------------------------------------------------------------------
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index 299dcf5f17a..d54933841fd 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -781,6 +781,10 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
                     );
                 }
             }
+            ObligationCauseCode::LetElse => {
+                err.help("try adding a diverging expression, such as `return` or `panic!(..)`");
+                err.help("...or use `match` instead of `let...else`");
+            }
             _ => (),
         }
     }
@@ -2592,6 +2596,7 @@ impl<'tcx> ObligationCauseExt<'tcx> for ObligationCause<'tcx> {
             }
             IfExpression { .. } => Error0308("`if` and `else` have incompatible types"),
             IfExpressionWithNoElse => Error0317("`if` may be missing an `else` clause"),
+            LetElse => Error0308("`else` clause of `let...else` does not diverge"),
             MainFunctionType => Error0580("`main` function has wrong type"),
             StartFunctionType => Error0308("`#[start]` function has wrong type"),
             IntrinsicType => Error0308("intrinsic has wrong type"),
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
index be137884b4b..f04ac8dd942 100644
--- a/compiler/rustc_lint/src/unused.rs
+++ b/compiler/rustc_lint/src/unused.rs
@@ -1,7 +1,7 @@
 use crate::Lint;
 use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
 use rustc_ast as ast;
-use rustc_ast::util::parser;
+use rustc_ast::util::{classify, parser};
 use rustc_ast::{ExprKind, StmtKind};
 use rustc_ast_pretty::pprust;
 use rustc_errors::{pluralize, Applicability};
@@ -382,6 +382,7 @@ enum UnusedDelimsCtx {
     FunctionArg,
     MethodArg,
     AssignedValue,
+    AssignedValueLetElse,
     IfCond,
     WhileCond,
     ForIterExpr,
@@ -398,7 +399,9 @@ impl From<UnusedDelimsCtx> for &'static str {
         match ctx {
             UnusedDelimsCtx::FunctionArg => "function argument",
             UnusedDelimsCtx::MethodArg => "method argument",
-            UnusedDelimsCtx::AssignedValue => "assigned value",
+            UnusedDelimsCtx::AssignedValue | UnusedDelimsCtx::AssignedValueLetElse => {
+                "assigned value"
+            }
             UnusedDelimsCtx::IfCond => "`if` condition",
             UnusedDelimsCtx::WhileCond => "`while` condition",
             UnusedDelimsCtx::ForIterExpr => "`for` iterator expression",
@@ -441,14 +444,26 @@ trait UnusedDelimLint {
         right_pos: Option<BytePos>,
     );
 
-    fn is_expr_delims_necessary(inner: &ast::Expr, followed_by_block: bool) -> bool {
+    fn is_expr_delims_necessary(
+        inner: &ast::Expr,
+        followed_by_block: bool,
+        followed_by_else: bool,
+    ) -> bool {
+        if followed_by_else {
+            match inner.kind {
+                ast::ExprKind::Binary(op, ..) if op.node.lazy() => return true,
+                _ if classify::expr_trailing_brace(inner).is_some() => return true,
+                _ => {}
+            }
+        }
+
         // Prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }`
         let lhs_needs_parens = {
             let mut innermost = inner;
             loop {
                 if let ExprKind::Binary(_, lhs, _rhs) = &innermost.kind {
                     innermost = lhs;
-                    if !rustc_ast::util::classify::expr_requires_semi_to_be_stmt(innermost) {
+                    if !classify::expr_requires_semi_to_be_stmt(innermost) {
                         break true;
                     }
                 } else {
@@ -618,15 +633,12 @@ trait UnusedDelimLint {
     fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
         match s.kind {
             StmtKind::Local(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
-                if let Some(ref value) = local.init {
-                    self.check_unused_delims_expr(
-                        cx,
-                        &value,
-                        UnusedDelimsCtx::AssignedValue,
-                        false,
-                        None,
-                        None,
-                    );
+                if let Some((init, els)) = local.kind.init_else_opt() {
+                    let ctx = match els {
+                        None => UnusedDelimsCtx::AssignedValue,
+                        Some(_) => UnusedDelimsCtx::AssignedValueLetElse,
+                    };
+                    self.check_unused_delims_expr(cx, init, ctx, false, None, None);
                 }
             }
             StmtKind::Expr(ref expr) => {
@@ -702,7 +714,8 @@ impl UnusedDelimLint for UnusedParens {
     ) {
         match value.kind {
             ast::ExprKind::Paren(ref inner) => {
-                if !Self::is_expr_delims_necessary(inner, followed_by_block)
+                let followed_by_else = ctx == UnusedDelimsCtx::AssignedValueLetElse;
+                if !Self::is_expr_delims_necessary(inner, followed_by_block, followed_by_else)
                     && value.attrs.is_empty()
                     && !value.span.from_expansion()
                     && (ctx != UnusedDelimsCtx::LetScrutineeExpr
@@ -941,7 +954,7 @@ impl UnusedDelimLint for UnusedBraces {
                 // FIXME(const_generics): handle paths when #67075 is fixed.
                 if let [stmt] = inner.stmts.as_slice() {
                     if let ast::StmtKind::Expr(ref expr) = stmt.kind {
-                        if !Self::is_expr_delims_necessary(expr, followed_by_block)
+                        if !Self::is_expr_delims_necessary(expr, followed_by_block, false)
                             && (ctx != UnusedDelimsCtx::AnonConst
                                 || matches!(expr.kind, ast::ExprKind::Lit(_)))
                             && !cx.sess().source_map().is_multiline(value.span)
diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs
index 676cb7fe41d..74edb17fe32 100644
--- a/compiler/rustc_middle/src/traits/mod.rs
+++ b/compiler/rustc_middle/src/traits/mod.rs
@@ -305,6 +305,9 @@ pub enum ObligationCauseCode<'tcx> {
     /// Intrinsic has wrong type
     IntrinsicType,
 
+    /// A let else block does not diverge
+    LetElse,
+
     /// Method receiver
     MethodReceiver,
 
diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
index 50cbe0f71f5..b34c1e07be7 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
@@ -17,7 +17,7 @@ use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_session::lint::builtin::BINDINGS_WITH_VARIANT_NAME;
 use rustc_session::lint::builtin::{IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS};
 use rustc_session::Session;
-use rustc_span::Span;
+use rustc_span::{DesugaringKind, ExpnKind, Span};
 use std::slice;
 
 crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) {
@@ -118,31 +118,6 @@ impl<'tcx> MatchVisitor<'_, 'tcx> {
         check_for_bindings_named_same_as_variants(self, pat);
     }
 
-    fn let_source(&mut self, pat: &'tcx hir::Pat<'tcx>, _expr: &hir::Expr<'_>) -> LetSource {
-        let hir = self.tcx.hir();
-        let parent = hir.get_parent_node(pat.hir_id);
-        let parent_parent = hir.get_parent_node(parent);
-        let parent_parent_node = hir.get(parent_parent);
-
-        let parent_parent_parent = hir.get_parent_node(parent_parent);
-        let parent_parent_parent_parent = hir.get_parent_node(parent_parent_parent);
-        let parent_parent_parent_parent_node = hir.get(parent_parent_parent_parent);
-
-        if let hir::Node::Expr(hir::Expr {
-            kind: hir::ExprKind::Loop(_, _, hir::LoopSource::While, _),
-            ..
-        }) = parent_parent_parent_parent_node
-        {
-            LetSource::WhileLet
-        } else if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::If { .. }, .. }) =
-            parent_parent_node
-        {
-            LetSource::IfLet
-        } else {
-            LetSource::GenericLet
-        }
-    }
-
     fn lower_pattern<'p>(
         &self,
         cx: &mut MatchCheckCtxt<'p, 'tcx>,
@@ -172,10 +147,9 @@ impl<'tcx> MatchVisitor<'_, 'tcx> {
 
     fn check_let(&mut self, pat: &'tcx hir::Pat<'tcx>, expr: &hir::Expr<'_>, span: Span) {
         self.check_patterns(pat);
-        let ls = self.let_source(pat, expr);
         let mut cx = self.new_cx(expr.hir_id);
         let tpat = self.lower_pattern(&mut cx, pat, &mut false).0;
-        check_let_reachability(&mut cx, ls, pat.hir_id, &tpat, span);
+        check_let_reachability(&mut cx, pat.hir_id, &tpat, span);
     }
 
     fn check_match(
@@ -192,13 +166,7 @@ impl<'tcx> MatchVisitor<'_, 'tcx> {
             if let Some(hir::Guard::IfLet(ref pat, _)) = arm.guard {
                 self.check_patterns(pat);
                 let tpat = self.lower_pattern(&mut cx, pat, &mut false).0;
-                check_let_reachability(
-                    &mut cx,
-                    LetSource::IfLetGuard,
-                    pat.hir_id,
-                    &tpat,
-                    tpat.span,
-                );
+                check_let_reachability(&mut cx, pat.hir_id, &tpat, tpat.span);
             }
         }
 
@@ -397,7 +365,7 @@ fn unreachable_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, catchall: Option<
     });
 }
 
-fn irrefutable_let_pattern(id: HirId, ls: LetSource, span: Span, tcx: TyCtxt<'_>) {
+fn irrefutable_let_pattern(tcx: TyCtxt<'_>, id: HirId, span: Span) {
     macro_rules! emit_diag {
         (
             $lint:expr,
@@ -412,7 +380,12 @@ fn irrefutable_let_pattern(id: HirId, ls: LetSource, span: Span, tcx: TyCtxt<'_>
         }};
     }
 
-    tcx.struct_span_lint_hir(IRREFUTABLE_LET_PATTERNS, id, span, |lint| match ls {
+    let source = let_source(tcx, id);
+    let span = match source {
+        LetSource::LetElse(span) => span,
+        _ => span,
+    };
+    tcx.struct_span_lint_hir(IRREFUTABLE_LET_PATTERNS, id, span, |lint| match source {
         LetSource::GenericLet => {
             emit_diag!(lint, "`let`", "`let` is useless", "removing `let`");
         }
@@ -432,6 +405,14 @@ fn irrefutable_let_pattern(id: HirId, ls: LetSource, span: Span, tcx: TyCtxt<'_>
                 "removing the guard and adding a `let` inside the match arm"
             );
         }
+        LetSource::LetElse(..) => {
+            emit_diag!(
+                lint,
+                "`let...else`",
+                "`else` clause is useless",
+                "removing the `else` clause"
+            );
+        }
         LetSource::WhileLet => {
             emit_diag!(
                 lint,
@@ -445,7 +426,6 @@ fn irrefutable_let_pattern(id: HirId, ls: LetSource, span: Span, tcx: TyCtxt<'_>
 
 fn check_let_reachability<'p, 'tcx>(
     cx: &mut MatchCheckCtxt<'p, 'tcx>,
-    ls: LetSource,
     pat_id: HirId,
     pat: &'p super::Pat<'tcx>,
     span: Span,
@@ -454,13 +434,13 @@ fn check_let_reachability<'p, 'tcx>(
     let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty);
 
     report_arm_reachability(&cx, &report, |arm_index, arm_span, arm_hir_id, _| {
-        match ls {
+        match let_source(cx.tcx, pat_id) {
             LetSource::IfLet | LetSource::WhileLet => {
                 match arm_index {
                     // The arm with the user-specified pattern.
                     0 => unreachable_pattern(cx.tcx, arm_span, arm_hir_id, None),
                     // The arm with the wildcard pattern.
-                    1 => irrefutable_let_pattern(pat_id, ls, arm_span, cx.tcx),
+                    1 => irrefutable_let_pattern(cx.tcx, pat_id, arm_span),
                     _ => bug!(),
                 }
             }
@@ -473,7 +453,7 @@ fn check_let_reachability<'p, 'tcx>(
 
     if report.non_exhaustiveness_witnesses.is_empty() {
         // The match is exhaustive, i.e. the `if let` pattern is irrefutable.
-        irrefutable_let_pattern(pat_id, ls, span, cx.tcx);
+        irrefutable_let_pattern(cx.tcx, pat_id, span);
     }
 }
 
@@ -787,5 +767,46 @@ pub enum LetSource {
     GenericLet,
     IfLet,
     IfLetGuard,
+    LetElse(Span),
     WhileLet,
 }
+
+fn let_source(tcx: TyCtxt<'_>, pat_id: HirId) -> LetSource {
+    let hir = tcx.hir();
+    let parent = hir.get_parent_node(pat_id);
+    match hir.get(parent) {
+        hir::Node::Arm(hir::Arm {
+            guard: Some(hir::Guard::IfLet(&hir::Pat { hir_id, .. }, _)),
+            ..
+        }) if hir_id == pat_id => {
+            return LetSource::IfLetGuard;
+        }
+        hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Let(..), span, .. }) => {
+            let expn_data = span.ctxt().outer_expn_data();
+            if let ExpnKind::Desugaring(DesugaringKind::LetElse) = expn_data.kind {
+                return LetSource::LetElse(expn_data.call_site);
+            }
+        }
+        _ => {}
+    }
+    let parent_parent = hir.get_parent_node(parent);
+    let parent_parent_node = hir.get(parent_parent);
+
+    let parent_parent_parent = hir.get_parent_node(parent_parent);
+    let parent_parent_parent_parent = hir.get_parent_node(parent_parent_parent);
+    let parent_parent_parent_parent_node = hir.get(parent_parent_parent_parent);
+
+    if let hir::Node::Expr(hir::Expr {
+        kind: hir::ExprKind::Loop(_, _, hir::LoopSource::While, _),
+        ..
+    }) = parent_parent_parent_parent_node
+    {
+        LetSource::WhileLet
+    } else if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::If { .. }, .. }) =
+        parent_parent_node
+    {
+        LetSource::IfLet
+    } else {
+        LetSource::GenericLet
+    }
+}
diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs
index 85515bd2a63..068bd36af55 100644
--- a/compiler/rustc_parse/src/parser/stmt.rs
+++ b/compiler/rustc_parse/src/parser/stmt.rs
@@ -11,8 +11,9 @@ use rustc_ast as ast;
 use rustc_ast::ptr::P;
 use rustc_ast::token::{self, TokenKind};
 use rustc_ast::util::classify;
-use rustc_ast::AstLike;
-use rustc_ast::{AttrStyle, AttrVec, Attribute, MacCall, MacCallStmt, MacStmtStyle};
+use rustc_ast::{
+    AstLike, AttrStyle, AttrVec, Attribute, LocalKind, MacCall, MacCallStmt, MacStmtStyle,
+};
 use rustc_ast::{Block, BlockCheckMode, Expr, ExprKind, Local, Stmt};
 use rustc_ast::{StmtKind, DUMMY_NODE_ID};
 use rustc_errors::{Applicability, PResult};
@@ -292,8 +293,65 @@ impl<'a> Parser<'a> {
                 return Err(err);
             }
         };
+        let kind = match init {
+            None => LocalKind::Decl,
+            Some(init) => {
+                if self.eat_keyword(kw::Else) {
+                    let els = self.parse_block()?;
+                    self.check_let_else_init_bool_expr(&init);
+                    self.check_let_else_init_trailing_brace(&init);
+                    LocalKind::InitElse(init, els)
+                } else {
+                    LocalKind::Init(init)
+                }
+            }
+        };
         let hi = if self.token == token::Semi { self.token.span } else { self.prev_token.span };
-        Ok(P(ast::Local { ty, pat, init, id: DUMMY_NODE_ID, span: lo.to(hi), attrs, tokens: None }))
+        Ok(P(ast::Local { ty, pat, kind, id: DUMMY_NODE_ID, span: lo.to(hi), attrs, tokens: None }))
+    }
+
+    fn check_let_else_init_bool_expr(&self, init: &ast::Expr) {
+        if let ast::ExprKind::Binary(op, ..) = init.kind {
+            if op.node.lazy() {
+                let suggs = vec![
+                    (init.span.shrink_to_lo(), "(".to_string()),
+                    (init.span.shrink_to_hi(), ")".to_string()),
+                ];
+                self.struct_span_err(
+                    init.span,
+                    &format!(
+                        "a `{}` expression cannot be directly assigned in `let...else`",
+                        op.node.to_string()
+                    ),
+                )
+                .multipart_suggestion(
+                    "wrap the expression in parenthesis",
+                    suggs,
+                    Applicability::MachineApplicable,
+                )
+                .emit();
+            }
+        }
+    }
+
+    fn check_let_else_init_trailing_brace(&self, init: &ast::Expr) {
+        if let Some(trailing) = classify::expr_trailing_brace(init) {
+            let err_span = trailing.span.with_lo(trailing.span.hi() - BytePos(1));
+            let suggs = vec![
+                (trailing.span.shrink_to_lo(), "(".to_string()),
+                (trailing.span.shrink_to_hi(), ")".to_string()),
+            ];
+            self.struct_span_err(
+                err_span,
+                "right curly brace `}` before `else` in a `let...else` statement not allowed",
+            )
+            .multipart_suggestion(
+                "try wrapping the expression in parenthesis",
+                suggs,
+                Applicability::MachineApplicable,
+            )
+            .emit();
+        }
     }
 
     /// Parses the RHS of a local variable declaration (e.g., `= 14;`).
@@ -495,13 +553,13 @@ impl<'a> Parser<'a> {
             StmtKind::Expr(_) | StmtKind::MacCall(_) => {}
             StmtKind::Local(ref mut local) if let Err(e) = self.expect_semi() => {
                 // We might be at the `,` in `let x = foo<bar, baz>;`. Try to recover.
-                match &mut local.init {
-                    Some(ref mut expr) => {
-                        self.check_mistyped_turbofish_with_multiple_type_params(e, expr)?;
-                        // We found `foo<bar, baz>`, have we fully recovered?
-                        self.expect_semi()?;
-                    }
-                    None => return Err(e),
+                match &mut local.kind {
+                    LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => {
+                            self.check_mistyped_turbofish_with_multiple_type_params(e, expr)?;
+                            // We found `foo<bar, baz>`, have we fully recovered?
+                            self.expect_semi()?;
+                        }
+                        LocalKind::Decl => return Err(e),
                 }
                 eat_semi = false;
             }
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index 0b552aa07f5..5c7b4b02822 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -454,7 +454,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
             _ => Some((
                 local.pat.span,
                 local.ty.as_ref().map(|ty| ty.span),
-                local.init.as_ref().map(|init| init.span),
+                local.kind.init().map(|init| init.span),
             )),
         };
         let original = replace(&mut self.diagnostic_metadata.current_let_binding, local_spans);
@@ -1426,7 +1426,14 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
         walk_list!(self, visit_ty, &local.ty);
 
         // Resolve the initializer.
-        walk_list!(self, visit_expr, &local.init);
+        if let Some((init, els)) = local.kind.init_else_opt() {
+            self.visit_expr(init);
+
+            // Resolve the `else` block
+            if let Some(els) = els {
+                self.visit_block(els);
+            }
+        }
 
         // Resolve the pattern.
         self.resolve_pattern_top(&local.pat, PatternSource::Let);
diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs
index e44a2e96598..c22093c5a42 100644
--- a/compiler/rustc_span/src/hygiene.rs
+++ b/compiler/rustc_span/src/hygiene.rs
@@ -1097,6 +1097,7 @@ pub enum DesugaringKind {
     Async,
     Await,
     ForLoop(ForLoopLoc),
+    LetElse,
 }
 
 /// A location in the desugaring of a `for` loop
@@ -1117,6 +1118,7 @@ impl DesugaringKind {
             DesugaringKind::TryBlock => "`try` block",
             DesugaringKind::OpaqueTy => "`impl Trait`",
             DesugaringKind::ForLoop(_) => "`for` loop",
+            DesugaringKind::LetElse => "`let...else`",
         }
     }
 }
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 19c02ba45c4..899c51e12ff 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -744,6 +744,7 @@ symbols! {
         le,
         len,
         let_chains,
+        let_else,
         lhs,
         lib,
         libc,
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
index 40841a6e32d..db3432b0142 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -1928,7 +1928,11 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
             | ObligationCauseCode::OpaqueType
             | ObligationCauseCode::MiscObligation
             | ObligationCauseCode::WellFormed(..)
-            | ObligationCauseCode::MatchImpl(..) => {}
+            | ObligationCauseCode::MatchImpl(..)
+            | ObligationCauseCode::ReturnType
+            | ObligationCauseCode::ReturnValue(_)
+            | ObligationCauseCode::BlockTailExpression(_)
+            | ObligationCauseCode::LetElse => {}
             ObligationCauseCode::SliceOrArrayElem => {
                 err.note("slice and array elements must have `Sized` type");
             }
@@ -2338,9 +2342,6 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
                     predicate
                 ));
             }
-            ObligationCauseCode::ReturnType
-            | ObligationCauseCode::ReturnValue(_)
-            | ObligationCauseCode::BlockTailExpression(_) => (),
             ObligationCauseCode::TrivialBound => {
                 err.help("see issue #48214");
                 if tcx.sess.opts.unstable_features.is_nightly_build() {
diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs
index 51c646e500c..10f5b000aca 100644
--- a/compiler/rustc_typeck/src/check/expr.rs
+++ b/compiler/rustc_typeck/src/check/expr.rs
@@ -849,7 +849,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         coerce.coerce(self, &self.misc(sp), then_expr, then_ty);
 
         if let Some(else_expr) = opt_else_expr {
-            let else_ty = self.check_expr_with_expectation(else_expr, expected);
+            let else_ty = if sp.desugaring_kind() == Some(DesugaringKind::LetElse) {
+                // todo introduce `check_expr_with_expectation(.., Expectation::LetElse)`
+                //   for errors that point to the offending expression rather than the entire block.
+                //   We could use `check_expr_eq_type(.., tcx.types.never)`, but then there is no
+                //   way to detect that the expected type originated from let-else and provide
+                //   a customized error.
+                let else_ty = self.check_expr(else_expr);
+                let cause = self.cause(else_expr.span, ObligationCauseCode::LetElse);
+
+                if let Some(mut err) =
+                    self.demand_eqtype_with_origin(&cause, self.tcx.types.never, else_ty)
+                {
+                    err.emit();
+                    self.tcx.ty_error()
+                } else {
+                    else_ty
+                }
+            } else {
+                self.check_expr_with_expectation(else_expr, expected)
+            };
             let else_diverges = self.diverges.get();
 
             let opt_suggest_box_span =
diff --git a/src/test/ui/did_you_mean/issue-40396.stderr b/src/test/ui/did_you_mean/issue-40396.stderr
index f4bc5aef82d..d2938435ece 100644
--- a/src/test/ui/did_you_mean/issue-40396.stderr
+++ b/src/test/ui/did_you_mean/issue-40396.stderr
@@ -31,11 +31,11 @@ help: use `::<...>` instead of `<...>` to specify type or const arguments
 LL |     (0..13).collect::<Vec<i32>();
    |                    ++
 
-error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,`
+error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,`
   --> $DIR/issue-40396.rs:11:43
    |
 LL |     let x = std::collections::HashMap<i128, i128>::new();
-   |                                           ^ expected one of 7 possible tokens
+   |                                           ^ expected one of 8 possible tokens
    |
 help: use `::<...>` instead of `<...>` to specify type or const arguments
    |
diff --git a/src/test/ui/feature-gates/feature-gate-let_else.rs b/src/test/ui/feature-gates/feature-gate-let_else.rs
new file mode 100644
index 00000000000..3f04a9dabfd
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-let_else.rs
@@ -0,0 +1,5 @@
+fn main() {
+    let Some(x) = Some(1) else { //~ ERROR `let...else` statements are unstable
+        return;
+    };
+}
diff --git a/src/test/ui/feature-gates/feature-gate-let_else.stderr b/src/test/ui/feature-gates/feature-gate-let_else.stderr
new file mode 100644
index 00000000000..86252604154
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-let_else.stderr
@@ -0,0 +1,14 @@
+error[E0658]: `let...else` statements are unstable
+  --> $DIR/feature-gate-let_else.rs:2:5
+   |
+LL | /     let Some(x) = Some(1) else {
+LL | |         return;
+LL | |     };
+   | |______^
+   |
+   = note: see issue #87335 <https://github.com/rust-lang/rust/issues/87335> for more information
+   = help: add `#![feature(let_else)]` to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/let-else/let-else-bool-binop-init.fixed b/src/test/ui/let-else/let-else-bool-binop-init.fixed
new file mode 100644
index 00000000000..e47f7f23d7e
--- /dev/null
+++ b/src/test/ui/let-else/let-else-bool-binop-init.fixed
@@ -0,0 +1,8 @@
+// run-rustfix
+
+#![feature(let_else)]
+
+fn main() {
+    let true = (true && false) else { return }; //~ ERROR a `&&` expression cannot be directly assigned in `let...else`
+    let true = (true || false) else { return }; //~ ERROR a `||` expression cannot be directly assigned in `let...else`
+}
diff --git a/src/test/ui/let-else/let-else-bool-binop-init.rs b/src/test/ui/let-else/let-else-bool-binop-init.rs
new file mode 100644
index 00000000000..e443fb0d6a3
--- /dev/null
+++ b/src/test/ui/let-else/let-else-bool-binop-init.rs
@@ -0,0 +1,8 @@
+// run-rustfix
+
+#![feature(let_else)]
+
+fn main() {
+    let true = true && false else { return }; //~ ERROR a `&&` expression cannot be directly assigned in `let...else`
+    let true = true || false else { return }; //~ ERROR a `||` expression cannot be directly assigned in `let...else`
+}
diff --git a/src/test/ui/let-else/let-else-bool-binop-init.stderr b/src/test/ui/let-else/let-else-bool-binop-init.stderr
new file mode 100644
index 00000000000..6551e24cc83
--- /dev/null
+++ b/src/test/ui/let-else/let-else-bool-binop-init.stderr
@@ -0,0 +1,24 @@
+error: a `&&` expression cannot be directly assigned in `let...else`
+  --> $DIR/let-else-bool-binop-init.rs:6:16
+   |
+LL |     let true = true && false else { return };
+   |                ^^^^^^^^^^^^^
+   |
+help: wrap the expression in parenthesis
+   |
+LL |     let true = (true && false) else { return };
+   |                +             +
+
+error: a `||` expression cannot be directly assigned in `let...else`
+  --> $DIR/let-else-bool-binop-init.rs:7:16
+   |
+LL |     let true = true || false else { return };
+   |                ^^^^^^^^^^^^^
+   |
+help: wrap the expression in parenthesis
+   |
+LL |     let true = (true || false) else { return };
+   |                +             +
+
+error: aborting due to 2 previous errors
+
diff --git a/src/test/ui/let-else/let-else-brace-before-else.fixed b/src/test/ui/let-else/let-else-brace-before-else.fixed
new file mode 100644
index 00000000000..fb4fd77791e
--- /dev/null
+++ b/src/test/ui/let-else/let-else-brace-before-else.fixed
@@ -0,0 +1,26 @@
+// run-rustfix
+
+#![feature(let_else)]
+
+fn main() {
+    let Some(1) = ({ Some(1) }) else {
+        //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
+        return;
+    };
+    let Some(1) = (loop { break Some(1) }) else {
+        //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
+        return;
+    };
+    let 2 = 1 + (match 1 { n => n }) else {
+        //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
+        return;
+    };
+    let Some(1) = (unsafe { unsafe_fn() }) else {
+        //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
+        return;
+    };
+}
+
+unsafe fn unsafe_fn<T>() -> T {
+    unimplemented!();
+}
diff --git a/src/test/ui/let-else/let-else-brace-before-else.rs b/src/test/ui/let-else/let-else-brace-before-else.rs
new file mode 100644
index 00000000000..c4c5a1ca28b
--- /dev/null
+++ b/src/test/ui/let-else/let-else-brace-before-else.rs
@@ -0,0 +1,26 @@
+// run-rustfix
+
+#![feature(let_else)]
+
+fn main() {
+    let Some(1) = { Some(1) } else {
+        //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
+        return;
+    };
+    let Some(1) = loop { break Some(1) } else {
+        //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
+        return;
+    };
+    let 2 = 1 + match 1 { n => n } else {
+        //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
+        return;
+    };
+    let Some(1) = unsafe { unsafe_fn() } else {
+        //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
+        return;
+    };
+}
+
+unsafe fn unsafe_fn<T>() -> T {
+    unimplemented!();
+}
diff --git a/src/test/ui/let-else/let-else-brace-before-else.stderr b/src/test/ui/let-else/let-else-brace-before-else.stderr
new file mode 100644
index 00000000000..eac029c848b
--- /dev/null
+++ b/src/test/ui/let-else/let-else-brace-before-else.stderr
@@ -0,0 +1,46 @@
+error: right curly brace `}` before `else` in a `let...else` statement not allowed
+  --> $DIR/let-else-brace-before-else.rs:6:29
+   |
+LL |     let Some(1) = { Some(1) } else {
+   |                             ^
+   |
+help: try wrapping the expression in parenthesis
+   |
+LL |     let Some(1) = ({ Some(1) }) else {
+   |                   +           +
+
+error: right curly brace `}` before `else` in a `let...else` statement not allowed
+  --> $DIR/let-else-brace-before-else.rs:10:40
+   |
+LL |     let Some(1) = loop { break Some(1) } else {
+   |                                        ^
+   |
+help: try wrapping the expression in parenthesis
+   |
+LL |     let Some(1) = (loop { break Some(1) }) else {
+   |                   +                      +
+
+error: right curly brace `}` before `else` in a `let...else` statement not allowed
+  --> $DIR/let-else-brace-before-else.rs:14:34
+   |
+LL |     let 2 = 1 + match 1 { n => n } else {
+   |                                  ^
+   |
+help: try wrapping the expression in parenthesis
+   |
+LL |     let 2 = 1 + (match 1 { n => n }) else {
+   |                 +                  +
+
+error: right curly brace `}` before `else` in a `let...else` statement not allowed
+  --> $DIR/let-else-brace-before-else.rs:18:40
+   |
+LL |     let Some(1) = unsafe { unsafe_fn() } else {
+   |                                        ^
+   |
+help: try wrapping the expression in parenthesis
+   |
+LL |     let Some(1) = (unsafe { unsafe_fn() }) else {
+   |                   +                      +
+
+error: aborting due to 4 previous errors
+
diff --git a/src/test/ui/let-else/let-else-check.rs b/src/test/ui/let-else/let-else-check.rs
new file mode 100644
index 00000000000..ab763447ef7
--- /dev/null
+++ b/src/test/ui/let-else/let-else-check.rs
@@ -0,0 +1,14 @@
+#![feature(let_else)]
+
+#![deny(unused_variables)]
+
+fn main() {
+    // type annotation, attributes
+    #[allow(unused_variables)]
+    let Some(_): Option<u32> = Some(Default::default()) else {
+        let x = 1; // OK
+        return;
+    };
+
+    let x = 1; //~ ERROR unused variable: `x`
+}
diff --git a/src/test/ui/let-else/let-else-check.stderr b/src/test/ui/let-else/let-else-check.stderr
new file mode 100644
index 00000000000..50e54d320b0
--- /dev/null
+++ b/src/test/ui/let-else/let-else-check.stderr
@@ -0,0 +1,14 @@
+error: unused variable: `x`
+  --> $DIR/let-else-check.rs:13:9
+   |
+LL |     let x = 1;
+   |         ^ help: if this is intentional, prefix it with an underscore: `_x`
+   |
+note: the lint level is defined here
+  --> $DIR/let-else-check.rs:3:9
+   |
+LL | #![deny(unused_variables)]
+   |         ^^^^^^^^^^^^^^^^
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/let-else/let-else-irrefutable.rs b/src/test/ui/let-else/let-else-irrefutable.rs
new file mode 100644
index 00000000000..b1e09a1248f
--- /dev/null
+++ b/src/test/ui/let-else/let-else-irrefutable.rs
@@ -0,0 +1,7 @@
+// check-pass
+
+#![feature(let_else)]
+
+fn main() {
+    let x = 1 else { return }; //~ WARN irrefutable `let...else` pattern
+}
diff --git a/src/test/ui/let-else/let-else-irrefutable.stderr b/src/test/ui/let-else/let-else-irrefutable.stderr
new file mode 100644
index 00000000000..e030c50d45d
--- /dev/null
+++ b/src/test/ui/let-else/let-else-irrefutable.stderr
@@ -0,0 +1,12 @@
+warning: irrefutable `let...else` pattern
+  --> $DIR/let-else-irrefutable.rs:6:5
+   |
+LL |     let x = 1 else { return };
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(irrefutable_let_patterns)]` on by default
+   = note: this pattern will always match, so the `else` clause is useless
+   = help: consider removing the `else` clause
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/let-else/let-else-missing-semicolon.rs b/src/test/ui/let-else/let-else-missing-semicolon.rs
new file mode 100644
index 00000000000..ed9d79f1ebd
--- /dev/null
+++ b/src/test/ui/let-else/let-else-missing-semicolon.rs
@@ -0,0 +1,11 @@
+#![feature(let_else)]
+
+fn main() {
+    let Some(x) = Some(1) else {
+        return;
+    } //~ ERROR expected `;`, found keyword `let`
+    let _ = "";
+    let Some(x) = Some(1) else {
+        panic!();
+    } //~ ERROR expected `;`, found `}`
+}
diff --git a/src/test/ui/let-else/let-else-missing-semicolon.stderr b/src/test/ui/let-else/let-else-missing-semicolon.stderr
new file mode 100644
index 00000000000..1818a0b1263
--- /dev/null
+++ b/src/test/ui/let-else/let-else-missing-semicolon.stderr
@@ -0,0 +1,18 @@
+error: expected `;`, found keyword `let`
+  --> $DIR/let-else-missing-semicolon.rs:6:6
+   |
+LL |     }
+   |      ^ help: add `;` here
+LL |     let _ = "";
+   |     --- unexpected token
+
+error: expected `;`, found `}`
+  --> $DIR/let-else-missing-semicolon.rs:10:6
+   |
+LL |     }
+   |      ^ help: add `;` here
+LL | }
+   | - unexpected token
+
+error: aborting due to 2 previous errors
+
diff --git a/src/test/ui/let-else/let-else-non-diverging.rs b/src/test/ui/let-else/let-else-non-diverging.rs
new file mode 100644
index 00000000000..a1cee335aee
--- /dev/null
+++ b/src/test/ui/let-else/let-else-non-diverging.rs
@@ -0,0 +1,13 @@
+#![feature(let_else)]
+
+fn main() {
+    let Some(x) = Some(1) else { //~ ERROR does not diverge
+        Some(2)
+    };
+    let Some(x) = Some(1) else { //~ ERROR does not diverge
+        if 1 == 1 {
+            panic!();
+        }
+    };
+    let Some(x) = Some(1) else { Some(2) }; //~ ERROR does not diverge
+}
diff --git a/src/test/ui/let-else/let-else-non-diverging.stderr b/src/test/ui/let-else/let-else-non-diverging.stderr
new file mode 100644
index 00000000000..fd5a18ce7ea
--- /dev/null
+++ b/src/test/ui/let-else/let-else-non-diverging.stderr
@@ -0,0 +1,44 @@
+error[E0308]: `else` clause of `let...else` does not diverge
+  --> $DIR/let-else-non-diverging.rs:12:32
+   |
+LL |     let Some(x) = Some(1) else { Some(2) };
+   |                                ^^^^^^^^^^^ expected `!`, found enum `Option`
+   |
+   = note: expected type `!`
+              found type `Option<{integer}>`
+   = help: try adding a diverging expression, such as `return` or `panic!(..)`
+   = help: ...or use `match` instead of `let...else`
+
+error[E0308]: `else` clause of `let...else` does not diverge
+  --> $DIR/let-else-non-diverging.rs:7:32
+   |
+LL |       let Some(x) = Some(1) else {
+   |  ________________________________^
+LL | |         if 1 == 1 {
+LL | |             panic!();
+LL | |         }
+LL | |     };
+   | |_____^ expected `!`, found `()`
+   |
+   = note: expected type `!`
+              found type `()`
+   = help: try adding a diverging expression, such as `return` or `panic!(..)`
+   = help: ...or use `match` instead of `let...else`
+
+error[E0308]: `else` clause of `let...else` does not diverge
+  --> $DIR/let-else-non-diverging.rs:4:32
+   |
+LL |       let Some(x) = Some(1) else {
+   |  ________________________________^
+LL | |         Some(2)
+LL | |     };
+   | |_____^ expected `!`, found enum `Option`
+   |
+   = note: expected type `!`
+              found type `Option<{integer}>`
+   = help: try adding a diverging expression, such as `return` or `panic!(..)`
+   = help: ...or use `match` instead of `let...else`
+
+error: aborting due to 3 previous errors
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/let-else/let-else-run-pass.rs b/src/test/ui/let-else/let-else-run-pass.rs
new file mode 100644
index 00000000000..5d96623236d
--- /dev/null
+++ b/src/test/ui/let-else/let-else-run-pass.rs
@@ -0,0 +1,35 @@
+// run-pass
+
+#![feature(let_else)]
+
+fn main() {
+    #[allow(dead_code)]
+    enum MyEnum {
+        A(String),
+        B { f: String },
+        C,
+    }
+    // ref binding to non-copy value and or-pattern
+    let (MyEnum::A(ref x) | MyEnum::B { f: ref x }) = (MyEnum::B { f: String::new() }) else {
+        panic!();
+    };
+    assert_eq!(x, "");
+
+    // nested let-else
+    let mut x = 1;
+    loop {
+        let 4 = x else {
+            let 3 = x else {
+                x += 1;
+                continue;
+            };
+            break;
+        };
+        panic!();
+    }
+    assert_eq!(x, 3);
+
+    // else return
+    let Some(1) = Some(2) else { return };
+    panic!();
+}
diff --git a/src/test/ui/let-else/let-else-scope.rs b/src/test/ui/let-else/let-else-scope.rs
new file mode 100644
index 00000000000..f17682db4c3
--- /dev/null
+++ b/src/test/ui/let-else/let-else-scope.rs
@@ -0,0 +1,7 @@
+#![feature(let_else)]
+
+fn main() {
+    let Some(x) = Some(2) else {
+        panic!("{}", x); //~ ERROR cannot find value `x` in this scope
+    };
+}
diff --git a/src/test/ui/let-else/let-else-scope.stderr b/src/test/ui/let-else/let-else-scope.stderr
new file mode 100644
index 00000000000..4b3936eac4b
--- /dev/null
+++ b/src/test/ui/let-else/let-else-scope.stderr
@@ -0,0 +1,9 @@
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/let-else-scope.rs:5:22
+   |
+LL |         panic!("{}", x);
+   |                      ^ not found in this scope
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0425`.
diff --git a/src/test/ui/parser/attr-stmt-expr-attr-bad.stderr b/src/test/ui/parser/attr-stmt-expr-attr-bad.stderr
index 6dfe7aad6ea..cec6980c008 100644
--- a/src/test/ui/parser/attr-stmt-expr-attr-bad.stderr
+++ b/src/test/ui/parser/attr-stmt-expr-attr-bad.stderr
@@ -12,11 +12,11 @@ error: expected expression, found `]`
 LL | #[cfg(FALSE)] fn e() { let _ = [#[attr]]; }
    |                                        ^ expected expression
 
-error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `#`
+error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `#`
   --> $DIR/attr-stmt-expr-attr-bad.rs:9:35
    |
 LL | #[cfg(FALSE)] fn e() { let _ = foo#[attr](); }
-   |                                   ^ expected one of 7 possible tokens
+   |                                   ^ expected one of 8 possible tokens
 
 error: an inner attribute is not permitted in this context
   --> $DIR/attr-stmt-expr-attr-bad.rs:11:36
@@ -70,11 +70,11 @@ LL | #[cfg(FALSE)] fn e() { let _ = -#![attr] 0; }
    |
    = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
 
-error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `#`
+error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `#`
   --> $DIR/attr-stmt-expr-attr-bad.rs:23:34
    |
 LL | #[cfg(FALSE)] fn e() { let _ = x #![attr] as Y; }
-   |                                  ^ expected one of 7 possible tokens
+   |                                  ^ expected one of 8 possible tokens
 
 error: an inner attribute is not permitted in this context
   --> $DIR/attr-stmt-expr-attr-bad.rs:25:35
@@ -372,11 +372,11 @@ error: unexpected token: `#`
 LL | #[cfg(FALSE)] fn e() { let _ = x.#![attr]foo(); }
    |                                  ^
 
-error: expected one of `.`, `;`, `?`, or an operator, found `#`
+error: expected one of `.`, `;`, `?`, `else`, or an operator, found `#`
   --> $DIR/attr-stmt-expr-attr-bad.rs:100:34
    |
 LL | #[cfg(FALSE)] fn e() { let _ = x.#![attr]foo(); }
-   |                                  ^ expected one of `.`, `;`, `?`, or an operator
+   |                                  ^ expected one of `.`, `;`, `?`, `else`, or an operator
 
 error: unexpected token: `#`
   --> $DIR/attr-stmt-expr-attr-bad.rs:103:34
@@ -384,11 +384,11 @@ error: unexpected token: `#`
 LL | #[cfg(FALSE)] fn e() { let _ = x.#[attr]foo(); }
    |                                  ^
 
-error: expected one of `.`, `;`, `?`, or an operator, found `#`
+error: expected one of `.`, `;`, `?`, `else`, or an operator, found `#`
   --> $DIR/attr-stmt-expr-attr-bad.rs:103:34
    |
 LL | #[cfg(FALSE)] fn e() { let _ = x.#[attr]foo(); }
-   |                                  ^ expected one of `.`, `;`, `?`, or an operator
+   |                                  ^ expected one of `.`, `;`, `?`, `else`, or an operator
 
 error: expected statement after outer attribute
   --> $DIR/attr-stmt-expr-attr-bad.rs:108:37
diff --git a/src/test/ui/parser/issue-72253.rs b/src/test/ui/parser/issue-72253.rs
index 6f9af73b039..1446a796fa0 100644
--- a/src/test/ui/parser/issue-72253.rs
+++ b/src/test/ui/parser/issue-72253.rs
@@ -1,6 +1,6 @@
 fn main() {
     let a = std::process::Command::new("echo")
         .arg("1")
-        ,arg("2") //~ ERROR expected one of `.`, `;`, `?`, or an operator, found `,`
+        ,arg("2") //~ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `,`
         .output();
 }
diff --git a/src/test/ui/parser/issue-72253.stderr b/src/test/ui/parser/issue-72253.stderr
index 3819fd92a9e..477fa09f495 100644
--- a/src/test/ui/parser/issue-72253.stderr
+++ b/src/test/ui/parser/issue-72253.stderr
@@ -1,8 +1,8 @@
-error: expected one of `.`, `;`, `?`, or an operator, found `,`
+error: expected one of `.`, `;`, `?`, `else`, or an operator, found `,`
   --> $DIR/issue-72253.rs:4:9
    |
 LL |         .arg("1")
-   |                  - expected one of `.`, `;`, `?`, or an operator
+   |                  - expected one of `.`, `;`, `?`, `else`, or an operator
 LL |         ,arg("2")
    |         ^ unexpected token
 
diff --git a/src/test/ui/parser/issue-84117.rs b/src/test/ui/parser/issue-84117.rs
index 0f200735915..919585877cf 100644
--- a/src/test/ui/parser/issue-84117.rs
+++ b/src/test/ui/parser/issue-84117.rs
@@ -2,8 +2,8 @@ fn main() {
     let outer_local:e_outer<&str, { let inner_local:e_inner<&str, }
     //~^ ERROR expected one of `>`, a const expression
     //~| ERROR expected one of `>`, a const expression, lifetime, or type, found `}`
-    //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,`
-    //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,`
-    //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,`
+    //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,`
+    //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,`
+    //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,`
 }
 //~^ ERROR expected one of `,`, `:`, `=`, or `>`, found `}`
diff --git a/src/test/ui/parser/issue-84117.stderr b/src/test/ui/parser/issue-84117.stderr
index d667a4977d0..5b9cc53baa5 100644
--- a/src/test/ui/parser/issue-84117.stderr
+++ b/src/test/ui/parser/issue-84117.stderr
@@ -7,11 +7,11 @@ LL |     let outer_local:e_outer<&str, { let inner_local:e_inner<&str, }
    |                                         |          help: use `=` if you meant to assign
    |                                         while parsing the type for `inner_local`
 
-error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,`
+error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,`
   --> $DIR/issue-84117.rs:2:65
    |
 LL |     let outer_local:e_outer<&str, { let inner_local:e_inner<&str, }
-   |                                                                 ^ expected one of 7 possible tokens
+   |                                                                 ^ expected one of 8 possible tokens
 
 error: expected one of `,`, `:`, `=`, or `>`, found `}`
   --> $DIR/issue-84117.rs:8:1
@@ -33,17 +33,17 @@ LL |     let outer_local:e_outer<&str, { let inner_local:e_inner<&str, }
    |                                         |          help: use `=` if you meant to assign
    |                                         while parsing the type for `inner_local`
 
-error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,`
+error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,`
   --> $DIR/issue-84117.rs:2:65
    |
 LL |     let outer_local:e_outer<&str, { let inner_local:e_inner<&str, }
-   |                                                                 ^ expected one of 7 possible tokens
+   |                                                                 ^ expected one of 8 possible tokens
 
-error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,`
+error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,`
   --> $DIR/issue-84117.rs:2:33
    |
 LL |     let outer_local:e_outer<&str, { let inner_local:e_inner<&str, }
-   |                                 ^ expected one of 7 possible tokens
+   |                                 ^ expected one of 8 possible tokens
 
 error: aborting due to 6 previous errors
 
diff --git a/src/test/ui/parser/macro/issue-37234.stderr b/src/test/ui/parser/macro/issue-37234.stderr
index f0ec79e5357..8d9636d401c 100644
--- a/src/test/ui/parser/macro/issue-37234.stderr
+++ b/src/test/ui/parser/macro/issue-37234.stderr
@@ -1,8 +1,8 @@
-error: expected one of `.`, `;`, `?`, or an operator, found `""`
+error: expected one of `.`, `;`, `?`, `else`, or an operator, found `""`
   --> $DIR/issue-37234.rs:3:19
    |
 LL |         let x = 5 "";
-   |                   ^^ expected one of `.`, `;`, `?`, or an operator
+   |                   ^^ expected one of `.`, `;`, `?`, `else`, or an operator
 ...
 LL |     failed!();
    |     ---------- in this macro invocation
diff --git a/src/test/ui/parser/missing-semicolon.rs b/src/test/ui/parser/missing-semicolon.rs
index a24dfa761a6..f68d177c01f 100644
--- a/src/test/ui/parser/missing-semicolon.rs
+++ b/src/test/ui/parser/missing-semicolon.rs
@@ -1,6 +1,6 @@
 macro_rules! m {
     ($($e1:expr),*; $($e2:expr),*) => {
-        $( let x = $e1 )*; //~ ERROR expected one of `.`, `;`, `?`, or
+        $( let x = $e1 )*; //~ ERROR expected one of `.`, `;`, `?`, `else`, or
         $( println!("{}", $e2) )*;
     }
 }
diff --git a/src/test/ui/parser/missing-semicolon.stderr b/src/test/ui/parser/missing-semicolon.stderr
index 68f0f440c46..72f76b6fe3f 100644
--- a/src/test/ui/parser/missing-semicolon.stderr
+++ b/src/test/ui/parser/missing-semicolon.stderr
@@ -1,8 +1,8 @@
-error: expected one of `.`, `;`, `?`, or an operator, found keyword `let`
+error: expected one of `.`, `;`, `?`, `else`, or an operator, found keyword `let`
   --> $DIR/missing-semicolon.rs:3:12
    |
 LL |         $( let x = $e1 )*;
-   |            ^^^ expected one of `.`, `;`, `?`, or an operator
+   |            ^^^ expected one of `.`, `;`, `?`, `else`, or an operator
 ...
 LL | fn main() { m!(0, 0; 0, 0); }
    |             --------------- in this macro invocation
diff --git a/src/test/ui/parser/range-3.rs b/src/test/ui/parser/range-3.rs
index 931839fb282..2c917a24e90 100644
--- a/src/test/ui/parser/range-3.rs
+++ b/src/test/ui/parser/range-3.rs
@@ -2,5 +2,5 @@
 
 pub fn main() {
     let r = 1..2..3;
-    //~^ ERROR expected one of `.`, `;`, `?`, or an operator, found `..`
+    //~^ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `..`
 }
diff --git a/src/test/ui/parser/range-3.stderr b/src/test/ui/parser/range-3.stderr
index f866ea59983..340167f1804 100644
--- a/src/test/ui/parser/range-3.stderr
+++ b/src/test/ui/parser/range-3.stderr
@@ -1,8 +1,8 @@
-error: expected one of `.`, `;`, `?`, or an operator, found `..`
+error: expected one of `.`, `;`, `?`, `else`, or an operator, found `..`
   --> $DIR/range-3.rs:4:17
    |
 LL |     let r = 1..2..3;
-   |                 ^^ expected one of `.`, `;`, `?`, or an operator
+   |                 ^^ expected one of `.`, `;`, `?`, `else`, or an operator
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/parser/range-4.rs b/src/test/ui/parser/range-4.rs
index 20af9567205..c970c96de84 100644
--- a/src/test/ui/parser/range-4.rs
+++ b/src/test/ui/parser/range-4.rs
@@ -2,5 +2,5 @@
 
 pub fn main() {
     let r = ..1..2;
-    //~^ ERROR expected one of `.`, `;`, `?`, or an operator, found `..`
+    //~^ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `..`
 }
diff --git a/src/test/ui/parser/range-4.stderr b/src/test/ui/parser/range-4.stderr
index dcb85170c1d..720d489389b 100644
--- a/src/test/ui/parser/range-4.stderr
+++ b/src/test/ui/parser/range-4.stderr
@@ -1,8 +1,8 @@
-error: expected one of `.`, `;`, `?`, or an operator, found `..`
+error: expected one of `.`, `;`, `?`, `else`, or an operator, found `..`
   --> $DIR/range-4.rs:4:16
    |
 LL |     let r = ..1..2;
-   |                ^^ expected one of `.`, `;`, `?`, or an operator
+   |                ^^ expected one of `.`, `;`, `?`, `else`, or an operator
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/pattern/usefulness/top-level-alternation.rs b/src/test/ui/pattern/usefulness/top-level-alternation.rs
index 4b47b978930..076de846129 100644
--- a/src/test/ui/pattern/usefulness/top-level-alternation.rs
+++ b/src/test/ui/pattern/usefulness/top-level-alternation.rs
@@ -1,3 +1,5 @@
+#![feature(let_else)]
+
 #![deny(unreachable_patterns)]
 
 fn main() {
@@ -53,4 +55,5 @@ fn main() {
         1..=2 => {}, //~ ERROR unreachable pattern
         _ => {},
     }
+    let (0 | 0) = 0 else { return }; //~ ERROR unreachable pattern
 }
diff --git a/src/test/ui/pattern/usefulness/top-level-alternation.stderr b/src/test/ui/pattern/usefulness/top-level-alternation.stderr
index 76bc4f8d091..dd5936fdcc4 100644
--- a/src/test/ui/pattern/usefulness/top-level-alternation.stderr
+++ b/src/test/ui/pattern/usefulness/top-level-alternation.stderr
@@ -1,68 +1,74 @@
 error: unreachable pattern
-  --> $DIR/top-level-alternation.rs:4:23
+  --> $DIR/top-level-alternation.rs:6:23
    |
 LL |     while let 0..=2 | 1 = 0 {}
    |                       ^
    |
 note: the lint level is defined here
-  --> $DIR/top-level-alternation.rs:1:9
+  --> $DIR/top-level-alternation.rs:3:9
    |
 LL | #![deny(unreachable_patterns)]
    |         ^^^^^^^^^^^^^^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/top-level-alternation.rs:5:20
+  --> $DIR/top-level-alternation.rs:7:20
    |
 LL |     if let 0..=2 | 1 = 0 {}
    |                    ^
 
 error: unreachable pattern
-  --> $DIR/top-level-alternation.rs:9:15
+  --> $DIR/top-level-alternation.rs:11:15
    |
 LL |             | 0 => {}
    |               ^
 
 error: unreachable pattern
-  --> $DIR/top-level-alternation.rs:14:15
+  --> $DIR/top-level-alternation.rs:16:15
    |
 LL |             | Some(0) => {}
    |               ^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/top-level-alternation.rs:19:9
+  --> $DIR/top-level-alternation.rs:21:9
    |
 LL |         (0, 0) => {}
    |         ^^^^^^
 
 error: unreachable pattern
-  --> $DIR/top-level-alternation.rs:39:9
+  --> $DIR/top-level-alternation.rs:41:9
    |
 LL |         _ => {}
    |         ^
 
 error: unreachable pattern
-  --> $DIR/top-level-alternation.rs:43:9
+  --> $DIR/top-level-alternation.rs:45:9
    |
 LL |         Some(_) => {}
    |         ^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/top-level-alternation.rs:44:9
+  --> $DIR/top-level-alternation.rs:46:9
    |
 LL |         None => {}
    |         ^^^^
 
 error: unreachable pattern
-  --> $DIR/top-level-alternation.rs:49:9
+  --> $DIR/top-level-alternation.rs:51:9
    |
 LL |         None | Some(_) => {}
    |         ^^^^^^^^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/top-level-alternation.rs:53:9
+  --> $DIR/top-level-alternation.rs:55:9
    |
 LL |         1..=2 => {},
    |         ^^^^^
 
-error: aborting due to 10 previous errors
+error: unreachable pattern
+  --> $DIR/top-level-alternation.rs:58:14
+   |
+LL |     let (0 | 0) = 0 else { return };
+   |              ^
+
+error: aborting due to 11 previous errors
 
diff --git a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs
index ac21eb5275f..2ffc00b449d 100644
--- a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs
+++ b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs
@@ -316,8 +316,11 @@ impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> {
 
 impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> {
     fn visit_local(&mut self, local: &'tcx Local) {
-        if let Some(ref init) = local.init {
-            self.apply(|this| walk_expr(this, &**init));
+        if let Some((init, els)) = &local.kind.init_else_opt() {
+            self.apply(|this| walk_expr(this, init));
+            if let Some(els) = els {
+                self.apply(|this| walk_block(this, els));
+            }
         }
         // add the pattern after the expression because the bindings aren't available
         // yet in the init
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs
index 7ea07a15aea..133f6c29f7d 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs
@@ -221,7 +221,7 @@ pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool {
         (Local(l), Local(r)) => {
             eq_pat(&l.pat, &r.pat)
                 && both(&l.ty, &r.ty, |l, r| eq_ty(l, r))
-                && eq_expr_opt(&l.init, &r.init)
+                && eq_local_kind(&l.kind, &r.kind)
                 && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
         },
         (Item(l), Item(r)) => eq_item(l, r, eq_item_kind),
@@ -234,6 +234,16 @@ pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool {
     }
 }
 
+pub fn eq_local_kind(l: &LocalKind, r: &LocalKind) -> bool {
+    use LocalKind::*;
+    match (l, r) {
+        (Decl, Decl) => true,
+        (Init(l), Init(r)) => eq_expr(l, r),
+        (InitElse(li, le), InitElse(ri, re)) => eq_expr(li, ri) && eq_block(le, re),
+        _ => false,
+    }
+}
+
 pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> bool) -> bool {
     eq_id(l.ident, r.ident)
         && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs
index 0542358c6e7..2483d0570d9 100644
--- a/src/tools/rustfmt/src/items.rs
+++ b/src/tools/rustfmt/src/items.rs
@@ -48,7 +48,7 @@ impl Rewrite for ast::Local {
 
         skip_out_of_file_lines_range!(context, self.span);
 
-        if contains_skip(&self.attrs) {
+        if contains_skip(&self.attrs) || matches!(self.kind, ast::LocalKind::InitElse(..)) {
             return None;
         }
 
@@ -97,7 +97,7 @@ impl Rewrite for ast::Local {
                 infix.push_str(&rewrite);
             }
 
-            if self.init.is_some() {
+            if self.kind.init().is_some() {
                 infix.push_str(" =");
             }
 
@@ -106,11 +106,12 @@ impl Rewrite for ast::Local {
 
         result.push_str(&infix);
 
-        if let Some(ref ex) = self.init {
+        if let Some((init, _els)) = self.kind.init_else_opt() {
             // 1 = trailing semicolon;
             let nested_shape = shape.sub_width(1)?;
 
-            result = rewrite_assign_rhs(context, result, &**ex, nested_shape)?;
+            result = rewrite_assign_rhs(context, result, init, nested_shape)?;
+            // todo else
         }
 
         result.push(';');
diff --git a/src/tools/rustfmt/tests/source/let_else.rs b/src/tools/rustfmt/tests/source/let_else.rs
new file mode 100644
index 00000000000..a6e816fb524
--- /dev/null
+++ b/src/tools/rustfmt/tests/source/let_else.rs
@@ -0,0 +1,3 @@
+fn main() {
+    let Some(1) = Some(1) else { return };
+}
diff --git a/src/tools/rustfmt/tests/target/let_else.rs b/src/tools/rustfmt/tests/target/let_else.rs
new file mode 100644
index 00000000000..a6e816fb524
--- /dev/null
+++ b/src/tools/rustfmt/tests/target/let_else.rs
@@ -0,0 +1,3 @@
+fn main() {
+    let Some(1) = Some(1) else { return };
+}