use std::mem;
use std::ops::ControlFlow;
use std::sync::Arc;

use rustc_ast::node_id::NodeMap;
use rustc_ast::*;
use rustc_ast_pretty::pprust::expr_to_string;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::msg;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{HirId, Target, find_attr};
use rustc_middle::span_bug;
use rustc_middle::ty::TyCtxt;
use rustc_session::errors::report_lit_error;
use rustc_span::{ByteSymbol, DUMMY_SP, DesugaringKind, Ident, Span, Spanned, Symbol, respan, sym};
use thin_vec::{ThinVec, thin_vec};
use visit::{Visitor, walk_expr};

mod closure;

use crate::diagnostics::{
    AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks,
    FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd,
    InvalidLegacyConstGenericArg, MatchArmWithNoBody, MoveExprOnlyInPlainClosures,
    NeverPatternWithBody, NeverPatternWithGuard, UnderscoreExprLhsAssign, UseConstGenericArg,
    YieldInClosure,
};
use crate::{
    AllowReturnTypeNotation, GenericArgsMode, ImplTraitContext, ImplTraitPosition, LoweringContext,
    ParamMode, ResolverAstLoweringExt, TryBlockScope,
};

pub(super) struct WillCreateDefIdsVisitor;

/// A `move(...)` expression found while looking up generated initializers.
struct MoveExprInitializer<'a> {
    /// The `NodeId` of the outer `move(...)` expression.
    id: NodeId,
    /// Span of the `move` token, used for the generated binding name.
    move_kw_span: Span,
    /// The expression inside `move(...)`; e.g. `foo.bar` in `move(foo.bar)`.
    expr: &'a Expr,
}

/// State for `move(...)` expressions found while lowering one plain closure body.
pub(super) struct MoveExprState<'hir> {
    pub(super) bindings: NodeMap<(Ident, HirId)>,
    pub(super) occurrences: Vec<MoveExprOccurrence<'hir>>,
}

impl<'hir> Default for MoveExprState<'hir> {
    fn default() -> Self {
        Self { bindings: NodeMap::default(), occurrences: Vec::new() }
    }
}

pub(super) struct MoveExprOccurrence<'hir> {
    id: NodeId,
    ident: Ident,
    pat: &'hir hir::Pat<'hir>,
    binding: HirId,
    explicit_capture: bool,
}

/// Looks up the initializer expression for each `move(...)` occurrence.
struct MoveExprInitializerFinder<'a> {
    initializers: Vec<MoveExprInitializer<'a>>,
}

impl<'a> MoveExprInitializerFinder<'a> {
    fn collect(expr: &'a Expr) -> Vec<MoveExprInitializer<'a>> {
        let mut this = Self { initializers: Vec::new() };
        this.visit_expr(expr);
        this.initializers
    }
}

impl<'a> Visitor<'a> for MoveExprInitializerFinder<'a> {
    fn visit_expr(&mut self, expr: &'a Expr) {
        match &expr.kind {
            ExprKind::Move(inner, move_kw_span) => {
                self.visit_expr(inner);
                self.initializers.push(MoveExprInitializer {
                    id: expr.id,
                    move_kw_span: *move_kw_span,
                    expr: inner,
                });
            }
            ExprKind::Closure(..) | ExprKind::Gen(..) | ExprKind::ConstBlock(..) => {}
            _ => walk_expr(self, expr),
        }
    }

    fn visit_item(&mut self, _: &'a Item) {}
}

impl<'v> rustc_ast::visit::Visitor<'v> for WillCreateDefIdsVisitor {
    type Result = ControlFlow<Span>;

    fn visit_anon_const(&mut self, c: &'v AnonConst) -> Self::Result {
        ControlFlow::Break(c.value.span)
    }

    fn visit_item(&mut self, item: &'v Item) -> Self::Result {
        ControlFlow::Break(item.span)
    }

    fn visit_expr(&mut self, ex: &'v Expr) -> Self::Result {
        match ex.kind {
            ExprKind::Gen(..) | ExprKind::ConstBlock(..) | ExprKind::Closure(..) => {
                ControlFlow::Break(ex.span)
            }
            _ => walk_expr(self, ex),
        }
    }
}

impl<'hir> LoweringContext<'_, 'hir> {
    fn with_move_expr_bindings<T>(
        &mut self,
        state: Option<MoveExprState<'hir>>,
        f: impl FnOnce(&mut Self) -> T,
    ) -> (T, Option<MoveExprState<'hir>>) {
        self.move_expr_bindings.push(state);
        let result = f(self);
        let state = self.move_expr_bindings.pop().unwrap_or_else(|| {
            span_bug!(DUMMY_SP, "`move_expr_bindings` stack was empty after lowering")
        });
        (result, state)
    }

    fn record_move_expr(
        &mut self,
        id: NodeId,
        inner: &Expr,
        move_kw_span: Span,
        explicit_capture: bool,
    ) -> (Ident, HirId) {
        let index = self
            .move_expr_bindings
            .last()
            .and_then(|state| state.as_ref())
            .map_or(0, |state| state.occurrences.len());
        let ident = Ident::from_str_and_span(&format!("__move_expr_{index}"), move_kw_span);
        let (pat, binding) = self.pat_ident(inner.span, ident);
        let Some(state) = self.move_expr_bindings.last_mut().and_then(|state| state.as_mut())
        else {
            span_bug!(move_kw_span, "`move(...)` lowered without a plain closure body state");
        };
        state.bindings.insert(id, (ident, binding));
        state.occurrences.push(MoveExprOccurrence { id, ident, pat, binding, explicit_capture });
        (ident, binding)
    }

    fn lower_exprs(&mut self, exprs: &[Box<Expr>]) -> &'hir [hir::Expr<'hir>] {
        self.arena.alloc_from_iter(exprs.iter().map(|x| self.lower_expr_mut(x)))
    }

    pub(super) fn lower_expr(&mut self, e: &Expr) -> &'hir hir::Expr<'hir> {
        self.arena.alloc(self.lower_expr_mut(e))
    }

    pub(super) fn lower_expr_mut(&mut self, e: &Expr) -> hir::Expr<'hir> {
        ensure_sufficient_stack(|| {
            let mut span = self.lower_span(e.span);
            match &e.kind {
                // Parenthesis expression does not have a HirId and is handled specially.
                ExprKind::Paren(ex) => {
                    let mut ex = self.lower_expr_mut(ex);
                    // Include parens in span, but only if it is a super-span.
                    if e.span.contains(ex.span) {
                        ex.span = self.lower_span(e.span.with_ctxt(ex.span.ctxt()));
                    }
                    // Merge attributes into the inner expression.
                    if !e.attrs.is_empty() {
                        let old_attrs = self.attrs.get(&ex.hir_id.local_id).copied().unwrap_or(&[]);
                        let new_attrs = self
                            .lower_attrs_vec(&e.attrs, e.span, ex.hir_id, Target::from_expr(e))
                            .into_iter()
                            .chain(old_attrs.iter().cloned());
                        let new_attrs = &*self.arena.alloc_from_iter(new_attrs);
                        if new_attrs.is_empty() {
                            return ex;
                        }
                        self.attrs.insert(ex.hir_id.local_id, new_attrs);
                    }
                    return ex;
                }
                // Desugar `ExprForLoop`
                // from: `[opt_ident]: for await? <pat> in <iter> <body>`
                //
                // This also needs special handling because the HirId of the returned `hir::Expr` will not
                // correspond to the `e.id`, so `lower_expr_for` handles attribute lowering itself.
                ExprKind::ForLoop { pat, iter, body, label, kind } => {
                    return self.lower_expr_for(e, pat, iter, body, *label, *kind);
                }
                ExprKind::Closure(closure) => return self.lower_expr_closure_expr(e, closure),
                _ => (),
            }

            let expr_hir_id = self.lower_node_id(e.id);
            self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e));

            let kind = match &e.kind {
                ExprKind::Array(exprs) => hir::ExprKind::Array(self.lower_exprs(exprs)),
                ExprKind::ConstBlock(c) => hir::ExprKind::ConstBlock(self.lower_const_block(c)),
                ExprKind::Repeat(expr, count) => {
                    let expr = self.lower_expr(expr);
                    let count = self.lower_array_length_to_const_arg(count);
                    hir::ExprKind::Repeat(expr, count)
                }
                ExprKind::Tup(elts) => hir::ExprKind::Tup(self.lower_exprs(elts)),
                ExprKind::Call(f, args) => {
                    if let Some(legacy_args) = self.resolver.legacy_const_generic_args(f, self.tcx)
                    {
                        self.lower_legacy_const_generics((**f).clone(), args.clone(), &legacy_args)
                    } else {
                        let f = self.lower_expr(f);
                        hir::ExprKind::Call(f, self.lower_exprs(args))
                    }
                }
                ExprKind::MethodCall(MethodCall { seg, receiver, args, span }) => {
                    let hir_seg = self.arena.alloc(self.lower_path_segment(
                        e.span,
                        seg,
                        ParamMode::Optional,
                        GenericArgsMode::Err,
                        ImplTraitContext::Disallowed(ImplTraitPosition::Path),
                        // Method calls can't have bound modifiers
                        None,
                    ));
                    let receiver = self.lower_expr(receiver);
                    let args =
                        self.arena.alloc_from_iter(args.iter().map(|x| self.lower_expr_mut(x)));
                    hir::ExprKind::MethodCall(hir_seg, receiver, args, self.lower_span(*span))
                }
                ExprKind::Binary(binop, lhs, rhs) => {
                    let binop = self.lower_binop(*binop);
                    let lhs = self.lower_expr(lhs);
                    let rhs = self.lower_expr(rhs);
                    hir::ExprKind::Binary(binop, lhs, rhs)
                }
                ExprKind::Unary(op, ohs) => {
                    let op = self.lower_unop(*op);
                    let ohs = self.lower_expr(ohs);
                    hir::ExprKind::Unary(op, ohs)
                }
                ExprKind::Lit(token_lit) => hir::ExprKind::Lit(self.lower_lit(token_lit, e.span)),
                ExprKind::IncludedBytes(byte_sym) => {
                    let lit = respan(
                        self.lower_span(e.span),
                        LitKind::ByteStr(*byte_sym, StrStyle::Cooked),
                    );
                    hir::ExprKind::Lit(lit)
                }
                ExprKind::Cast(expr, ty) => {
                    let expr = self.lower_expr(expr);
                    let ty = self
                        .lower_ty_alloc(ty, ImplTraitContext::Disallowed(ImplTraitPosition::Cast));
                    hir::ExprKind::Cast(expr, ty)
                }
                ExprKind::Type(expr, ty) => {
                    let expr = self.lower_expr(expr);
                    let ty = self
                        .lower_ty_alloc(ty, ImplTraitContext::Disallowed(ImplTraitPosition::Cast));
                    hir::ExprKind::Type(expr, ty)
                }
                ExprKind::AddrOf(k, m, ohs) => {
                    let ohs = self.lower_expr(ohs);
                    hir::ExprKind::AddrOf(*k, *m, ohs)
                }
                ExprKind::Let(pat, scrutinee, span, recovered) => {
                    hir::ExprKind::Let(self.arena.alloc(hir::LetExpr {
                        span: self.lower_span(*span),
                        pat: self.lower_pat(pat),
                        ty: None,
                        init: self.lower_expr(scrutinee),
                        recovered: *recovered,
                    }))
                }
                ExprKind::If(cond, then, else_opt) => {
                    self.lower_expr_if(cond, then, else_opt.as_deref())
                }
                ExprKind::While(cond, body, opt_label) => {
                    self.with_loop_scope(expr_hir_id, |this| {
                        let span =
                            this.mark_span_with_reason(DesugaringKind::WhileLoop, e.span, None);
                        let opt_label = this.lower_label(*opt_label, e.id, expr_hir_id);
                        this.lower_expr_while_in_loop_scope(span, cond, body, opt_label)
                    })
                }
                ExprKind::Loop(body, opt_label, span) => {
                    self.with_loop_scope(expr_hir_id, |this| {
                        let opt_label = this.lower_label(*opt_label, e.id, expr_hir_id);
                        hir::ExprKind::Loop(
                            this.lower_block(body, false),
                            opt_label,
                            hir::LoopSource::Loop,
                            this.lower_span(*span),
                        )
                    })
                }
                ExprKind::TryBlock(body, opt_ty) => {
                    self.lower_expr_try_block(body, opt_ty.as_deref())
                }
                ExprKind::Match(expr, arms, kind) => hir::ExprKind::Match(
                    self.lower_expr(expr),
                    self.arena.alloc_from_iter(arms.iter().map(|x| self.lower_arm(x))),
                    match kind {
                        MatchKind::Prefix => hir::MatchSource::Normal,
                        MatchKind::Postfix => hir::MatchSource::Postfix,
                    },
                ),
                ExprKind::Await(expr, await_kw_span) => self.lower_expr_await(*await_kw_span, expr),
                ExprKind::Move(inner, move_kw_span) => {
                    if !self.tcx.features().move_expr() {
                        return self.expr_err(*move_kw_span, self.dcx().has_errors().unwrap());
                    }
                    if let Some(state) = self.move_expr_bindings.last().and_then(Option::as_ref) {
                        let existing = state.bindings.get(&e.id).copied();
                        let (ident, binding) = existing.unwrap_or_else(|| {
                            for nested in MoveExprInitializerFinder::collect(inner) {
                                self.record_move_expr(
                                    nested.id,
                                    nested.expr,
                                    nested.move_kw_span,
                                    false,
                                );
                            }
                            self.record_move_expr(e.id, inner, *move_kw_span, true)
                        });
                        hir::ExprKind::Path(hir::QPath::Resolved(
                            None,
                            self.arena.alloc(hir::Path {
                                span: self.lower_span(e.span),
                                res: Res::Local(binding),
                                segments: arena_vec![
                                    self;
                                    hir::PathSegment::new(
                                        self.lower_ident(ident),
                                        self.next_id(),
                                        Res::Local(binding),
                                    )
                                ],
                            }),
                        ))
                    } else {
                        let guar = self
                            .dcx()
                            .emit_err(MoveExprOnlyInPlainClosures { span: *move_kw_span });
                        hir::ExprKind::Err(guar)
                    }
                }
                ExprKind::Use(expr, use_kw_span) => self.lower_expr_use(*use_kw_span, expr),
                ExprKind::Gen(capture_clause, block, genblock_kind, decl_span) => {
                    let desugaring_kind = match genblock_kind {
                        GenBlockKind::Async => hir::CoroutineDesugaring::Async,
                        GenBlockKind::Gen => hir::CoroutineDesugaring::Gen,
                        GenBlockKind::AsyncGen => hir::CoroutineDesugaring::AsyncGen,
                    };
                    self.make_desugared_coroutine_expr(
                        *capture_clause,
                        e.id,
                        None,
                        *decl_span,
                        e.span,
                        desugaring_kind,
                        hir::CoroutineSource::Block,
                        |this| {
                            this.with_new_scopes(e.span, |this| {
                                let (expr, _) = this.with_move_expr_bindings(None, |this| {
                                    this.lower_block_expr(block)
                                });
                                expr
                            })
                        },
                    )
                }
                ExprKind::Block(blk, opt_label) => {
                    // Different from loops, label of block resolves to block id rather than
                    // expr node id.
                    let block_hir_id = self.lower_node_id(blk.id);
                    let opt_label = self.lower_label(*opt_label, blk.id, block_hir_id);
                    let hir_block = self.arena.alloc(self.lower_block_noalloc(
                        block_hir_id,
                        blk,
                        opt_label.is_some(),
                    ));
                    hir::ExprKind::Block(hir_block, opt_label)
                }
                ExprKind::Assign(el, er, span) => self.lower_expr_assign(el, er, *span, e.span),
                ExprKind::AssignOp(op, el, er) => hir::ExprKind::AssignOp(
                    self.lower_assign_op(*op),
                    self.lower_expr(el),
                    self.lower_expr(er),
                ),
                ExprKind::Field(el, ident) => {
                    hir::ExprKind::Field(self.lower_expr(el), self.lower_ident(*ident))
                }
                ExprKind::Index(el, er, brackets_span) => hir::ExprKind::Index(
                    self.lower_expr(el),
                    self.lower_expr(er),
                    self.lower_span(*brackets_span),
                ),
                ExprKind::Range(e1, e2, lims) => {
                    span = self.mark_span_with_reason(DesugaringKind::RangeExpr, span, None);
                    self.lower_expr_range(span, e1.as_deref(), e2.as_deref(), *lims)
                }
                ExprKind::Underscore => {
                    let guar = self.dcx().emit_err(UnderscoreExprLhsAssign { span: e.span });
                    hir::ExprKind::Err(guar)
                }
                ExprKind::Path(qself, path) => {
                    let qpath = self.lower_qpath(
                        e.id,
                        qself,
                        path,
                        ParamMode::Optional,
                        AllowReturnTypeNotation::No,
                        ImplTraitContext::Disallowed(ImplTraitPosition::Path),
                        None,
                    );
                    hir::ExprKind::Path(qpath)
                }
                ExprKind::Break(opt_label, opt_expr) => {
                    let opt_expr = opt_expr.as_ref().map(|x| self.lower_expr(x));
                    hir::ExprKind::Break(self.lower_jump_destination(e.id, *opt_label), opt_expr)
                }
                ExprKind::Continue(opt_label) => {
                    hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label))
                }
                ExprKind::Ret(e) => {
                    let expr = e.as_ref().map(|x| self.lower_expr(x));
                    self.checked_return(expr)
                }
                ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
                ExprKind::Become(sub_expr) => {
                    let sub_expr = self.lower_expr(sub_expr);
                    hir::ExprKind::Become(sub_expr)
                }
                ExprKind::InlineAsm(asm) => {
                    hir::ExprKind::InlineAsm(self.lower_inline_asm(e.span, asm))
                }
                ExprKind::FormatArgs(fmt) => self.lower_format_args(e.span, fmt),
                ExprKind::OffsetOf(container, fields) => hir::ExprKind::OffsetOf(
                    self.lower_ty_alloc(
                        container,
                        ImplTraitContext::Disallowed(ImplTraitPosition::OffsetOf),
                    ),
                    self.arena.alloc_from_iter(fields.iter().map(|&ident| self.lower_ident(ident))),
                ),
                ExprKind::Struct(se) => {
                    let rest = match se.rest {
                        StructRest::Base(ref e) => hir::StructTailExpr::Base(self.lower_expr(e)),
                        StructRest::Rest(sp) => {
                            hir::StructTailExpr::DefaultFields(self.lower_span(sp))
                        }
                        StructRest::None => hir::StructTailExpr::None,
                        StructRest::NoneWithError(guar) => hir::StructTailExpr::NoneWithError(guar),
                    };
                    hir::ExprKind::Struct(
                        self.arena.alloc(self.lower_qpath(
                            e.id,
                            &se.qself,
                            &se.path,
                            ParamMode::Optional,
                            AllowReturnTypeNotation::No,
                            ImplTraitContext::Disallowed(ImplTraitPosition::Path),
                            None,
                        )),
                        self.arena
                            .alloc_from_iter(se.fields.iter().map(|x| self.lower_expr_field(x))),
                        rest,
                    )
                }
                ExprKind::Yield(kind) => self.lower_expr_yield(e.span, kind.expr().map(|x| &**x)),
                ExprKind::Err(guar) => hir::ExprKind::Err(*guar),

                ExprKind::UnsafeBinderCast(kind, expr, ty) => hir::ExprKind::UnsafeBinderCast(
                    *kind,
                    self.lower_expr(expr),
                    ty.as_ref().map(|ty| {
                        self.lower_ty_alloc(
                            ty,
                            ImplTraitContext::Disallowed(ImplTraitPosition::Cast),
                        )
                    }),
                ),

                ExprKind::Dummy => {
                    span_bug!(e.span, "lowered ExprKind::Dummy")
                }

                ExprKind::Try(sub_expr) => self.lower_expr_try(e.span, sub_expr),

                ExprKind::Paren(_) | ExprKind::ForLoop { .. } | ExprKind::Closure(..) => {
                    unreachable!("already handled")
                }

                ExprKind::MacCall(_) => panic!("{:?} shouldn't exist here", e.span),
            };

            hir::Expr { hir_id: expr_hir_id, kind, span }
        })
    }

    pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock {
        self.with_new_scopes(c.value.span, |this| {
            let def_id = this.local_def_id(c.id);
            let hir_id = this.lower_node_id(c.id);
            let (body, _) = this.with_move_expr_bindings(None, |this| {
                this.lower_const_body(c.value.span, Some(&c.value))
            });
            hir::ConstBlock { def_id, hir_id, body }
        })
    }

    pub(crate) fn lower_lit(&mut self, token_lit: &token::Lit, span: Span) -> hir::Lit {
        let lit_kind = match LitKind::from_token_lit(*token_lit) {
            Ok(lit_kind) => lit_kind,
            Err(err) => {
                let guar = report_lit_error(&self.tcx.sess.psess, err, *token_lit, span);
                LitKind::Err(guar)
            }
        };
        respan(self.lower_span(span), lit_kind)
    }

    fn lower_unop(&mut self, u: UnOp) -> hir::UnOp {
        match u {
            UnOp::Deref => hir::UnOp::Deref,
            UnOp::Not => hir::UnOp::Not,
            UnOp::Neg => hir::UnOp::Neg,
        }
    }

    fn lower_binop(&mut self, b: BinOp) -> BinOp {
        Spanned { node: b.node, span: self.lower_span(b.span) }
    }

    fn lower_assign_op(&mut self, a: AssignOp) -> AssignOp {
        Spanned { node: a.node, span: self.lower_span(a.span) }
    }

    fn lower_legacy_const_generics(
        &mut self,
        mut f: Expr,
        args: ThinVec<Box<Expr>>,
        legacy_args_idx: &[usize],
    ) -> hir::ExprKind<'hir> {
        let ExprKind::Path(None, path) = &mut f.kind else {
            unreachable!();
        };

        let mut error = None;
        let mut invalid_expr_error = |tcx: TyCtxt<'_>, span| {
            // Avoid emitting the error multiple times.
            if error.is_none() {
                let mut const_args = vec![];
                let mut other_args = vec![];
                for (idx, arg) in args.iter().enumerate() {
                    if legacy_args_idx.contains(&idx) {
                        const_args.push(format!("{{ {} }}", expr_to_string(arg)));
                    } else {
                        other_args.push(expr_to_string(arg));
                    }
                }
                let suggestion = UseConstGenericArg {
                    end_of_fn: f.span.shrink_to_hi(),
                    const_args: const_args.join(", "),
                    other_args: other_args.join(", "),
                    call_args: args[0].span.to(args.last().unwrap().span),
                };
                error = Some(tcx.dcx().emit_err(InvalidLegacyConstGenericArg { span, suggestion }));
            }
            error.unwrap()
        };

        // Split the arguments into const generics and normal arguments
        let mut real_args = vec![];
        let mut generic_args = ThinVec::new();
        for (idx, arg) in args.iter().cloned().enumerate() {
            if legacy_args_idx.contains(&idx) {
                let node_id = self.next_node_id();
                self.create_def(node_id, None, DefKind::AnonConst, f.span);
                let const_value =
                    if let ControlFlow::Break(span) = WillCreateDefIdsVisitor.visit_expr(&arg) {
                        Box::new(Expr {
                            id: self.next_node_id(),
                            kind: ExprKind::Err(invalid_expr_error(self.tcx, span)),
                            span: f.span,
                            attrs: [].into(),
                            tokens: None,
                        })
                    } else {
                        arg
                    };

                let anon_const = AnonConst {
                    id: node_id,
                    value: const_value,
                    mgca_disambiguation: MgcaDisambiguation::AnonConst,
                };
                generic_args.push(AngleBracketedArg::Arg(GenericArg::Const(anon_const)));
            } else {
                real_args.push(arg);
            }
        }

        // Add generic args to the last element of the path.
        let last_segment = path.segments.last_mut().unwrap();
        assert!(last_segment.args.is_none());
        last_segment.args = Some(Box::new(GenericArgs::AngleBracketed(AngleBracketedArgs {
            span: DUMMY_SP,
            args: generic_args,
        })));

        // Now lower everything as normal.
        let f = self.lower_expr(&f);
        hir::ExprKind::Call(f, self.lower_exprs(&real_args))
    }

    fn lower_expr_if(
        &mut self,
        cond: &Expr,
        then: &Block,
        else_opt: Option<&Expr>,
    ) -> hir::ExprKind<'hir> {
        let lowered_cond = self.lower_expr(cond);
        let then_expr = self.lower_block_expr(then);
        if let Some(rslt) = else_opt {
            hir::ExprKind::If(
                lowered_cond,
                self.arena.alloc(then_expr),
                Some(self.lower_expr(rslt)),
            )
        } else {
            hir::ExprKind::If(lowered_cond, self.arena.alloc(then_expr), None)
        }
    }

    // We desugar: `'label: while $cond $body` into:
    //
    // ```
    // 'label: loop {
    //   if { let _t = $cond; _t } {
    //     $body
    //   }
    //   else {
    //     break;
    //   }
    // }
    // ```
    //
    // Wrap in a construct equivalent to `{ let _t = $cond; _t }`
    // to preserve drop semantics since `while $cond { ... }` does not
    // let temporaries live outside of `cond`.
    fn lower_expr_while_in_loop_scope(
        &mut self,
        span: Span,
        cond: &Expr,
        body: &Block,
        opt_label: Option<Label>,
    ) -> hir::ExprKind<'hir> {
        let lowered_cond = self.with_loop_condition_scope(|t| t.lower_expr(cond));
        let then = self.lower_block_expr(body);
        let expr_break = self.expr_break(span);
        let stmt_break = self.stmt_expr(span, expr_break);
        let else_blk = self.block_all(span, arena_vec![self; stmt_break], None);
        let else_expr = self.arena.alloc(self.expr_block(else_blk));
        let if_kind = hir::ExprKind::If(lowered_cond, self.arena.alloc(then), Some(else_expr));
        let if_expr = self.expr(span, if_kind);
        let block = self.block_expr(self.arena.alloc(if_expr));
        let span = self.lower_span(span.with_hi(cond.span.hi()));
        hir::ExprKind::Loop(block, opt_label, hir::LoopSource::While, span)
    }

    /// Desugar `try { <stmts>; <expr> }` into `{ <stmts>; ::std::ops::Try::from_output(<expr>) }`,
    /// `try { <stmts>; }` into `{ <stmts>; ::std::ops::Try::from_output(()) }`
    /// and save the block id to use it as a break target for desugaring of the `?` operator.
    fn lower_expr_try_block(&mut self, body: &Block, opt_ty: Option<&Ty>) -> hir::ExprKind<'hir> {
        let body_hir_id = self.lower_node_id(body.id);
        let new_scope = if opt_ty.is_some() {
            TryBlockScope::Heterogeneous(body_hir_id)
        } else {
            TryBlockScope::Homogeneous(body_hir_id)
        };
        let whole_block = self.with_try_block_scope(new_scope, |this| {
            let mut block = this.lower_block_noalloc(body_hir_id, body, true);

            // Final expression of the block (if present) or `()` with span at the end of block
            let (try_span, tail_expr) = if let Some(expr) = block.expr.take() {
                (
                    this.mark_span_with_reason(
                        DesugaringKind::TryBlock,
                        expr.span,
                        Some(Arc::clone(&this.allow_try_trait)),
                    ),
                    expr,
                )
            } else {
                let try_span = this.mark_span_with_reason(
                    DesugaringKind::TryBlock,
                    this.tcx.sess.source_map().end_point(body.span),
                    Some(Arc::clone(&this.allow_try_trait)),
                );

                (try_span, this.expr_unit(try_span))
            };

            let ok_wrapped_span =
                this.mark_span_with_reason(DesugaringKind::TryBlock, tail_expr.span, None);

            // `::std::ops::Try::from_output($tail_expr)`
            block.expr = Some(this.wrap_in_try_constructor(
                hir::LangItem::TryTraitFromOutput,
                try_span,
                tail_expr,
                ok_wrapped_span,
            ));

            this.arena.alloc(block)
        });

        if let Some(ty) = opt_ty {
            let ty = self.lower_ty_alloc(ty, ImplTraitContext::Disallowed(ImplTraitPosition::Path));
            let block_expr = self.arena.alloc(self.expr_block(whole_block));
            hir::ExprKind::Type(block_expr, ty)
        } else {
            hir::ExprKind::Block(whole_block, None)
        }
    }

    fn wrap_in_try_constructor(
        &mut self,
        lang_item: hir::LangItem,
        method_span: Span,
        expr: &'hir hir::Expr<'hir>,
        overall_span: Span,
    ) -> &'hir hir::Expr<'hir> {
        let constructor = self.arena.alloc(self.expr_lang_item_path(method_span, lang_item));
        self.expr_call(overall_span, constructor, std::slice::from_ref(expr))
    }

    fn lower_arm(&mut self, arm: &Arm) -> hir::Arm<'hir> {
        let pat = self.lower_pat(&arm.pat);
        let guard = arm.guard.as_ref().map(|guard| self.lower_expr(&guard.cond));
        let hir_id = self.next_id();
        let span = self.lower_span(arm.span);
        self.lower_attrs(hir_id, &arm.attrs, arm.span, Target::Arm);
        let is_never_pattern = pat.is_never_pattern();
        // We need to lower the body even if it's unneeded for never pattern in match,
        // ensure that we can get HirId for DefId if need (issue #137708).
        let body = arm.body.as_ref().map(|x| self.lower_expr(x));
        let body = if let Some(body) = body
            && !is_never_pattern
        {
            body
        } else {
            // Either `body.is_none()` or `is_never_pattern` here.
            if !is_never_pattern {
                if self.tcx.features().never_patterns() {
                    // If the feature is off we already emitted the error after parsing.
                    let suggestion = span.shrink_to_hi();
                    self.dcx().emit_err(MatchArmWithNoBody { span, suggestion });
                }
            } else if let Some(body) = &arm.body {
                self.dcx().emit_err(NeverPatternWithBody { span: body.span });
            } else if let Some(g) = &arm.guard {
                self.dcx().emit_err(NeverPatternWithGuard { span: g.span() });
            }

            // We add a fake `loop {}` arm body so that it typecks to `!`. The mir lowering of never
            // patterns ensures this loop is not reachable.
            let block = self.arena.alloc(hir::Block {
                stmts: &[],
                expr: None,
                hir_id: self.next_id(),
                rules: hir::BlockCheckMode::DefaultBlock,
                span,
                targeted_by_break: false,
            });
            self.arena.alloc(hir::Expr {
                hir_id: self.next_id(),
                kind: hir::ExprKind::Loop(block, None, hir::LoopSource::Loop, span),
                span,
            })
        };
        hir::Arm { hir_id, pat, guard, body, span }
    }

    fn lower_capture_clause(&mut self, capture_clause: CaptureBy) -> CaptureBy {
        match capture_clause {
            CaptureBy::Ref => CaptureBy::Ref,
            CaptureBy::Use { use_kw } => CaptureBy::Use { use_kw: self.lower_span(use_kw) },
            CaptureBy::Value { move_kw } => CaptureBy::Value { move_kw: self.lower_span(move_kw) },
        }
    }

    /// Lower/desugar a coroutine construct.
    ///
    /// In particular, this creates the correct async resume argument and `_task_context`.
    ///
    /// This results in:
    ///
    /// ```text
    /// static move? |<_task_context?>| -> <return_ty> {
    ///     <body>
    /// }
    /// ```
    pub(super) fn make_desugared_coroutine_expr(
        &mut self,
        capture_clause: CaptureBy,
        closure_node_id: NodeId,
        return_ty: Option<hir::FnRetTy<'hir>>,
        fn_decl_span: Span,
        span: Span,
        desugaring_kind: hir::CoroutineDesugaring,
        coroutine_source: hir::CoroutineSource,
        body: impl FnOnce(&mut Self) -> hir::Expr<'hir>,
    ) -> hir::ExprKind<'hir> {
        let closure_def_id = self.local_def_id(closure_node_id);
        let coroutine_kind = hir::CoroutineKind::Desugared(desugaring_kind, coroutine_source);

        // The `async` desugaring takes a resume argument and maintains a `task_context`,
        // whereas a generator does not.
        let (inputs, params, task_context): (&[_], &[_], _) = match desugaring_kind {
            hir::CoroutineDesugaring::Async | hir::CoroutineDesugaring::AsyncGen => {
                // Resume argument type: `ResumeTy`
                let unstable_span = self.mark_span_with_reason(
                    DesugaringKind::Async,
                    self.lower_span(span),
                    Some(Arc::clone(&self.allow_gen_future)),
                );
                let resume_ty =
                    self.make_lang_item_qpath(hir::LangItem::ResumeTy, unstable_span, None);
                let input_ty = hir::Ty {
                    hir_id: self.next_id(),
                    kind: hir::TyKind::Path(resume_ty),
                    span: unstable_span,
                };
                let inputs = arena_vec![self; input_ty];

                // Lower the argument pattern/ident. The ident is used again in the `.await` lowering.
                let (pat, task_context_hid) = self.pat_ident_binding_mode(
                    span,
                    Ident::with_dummy_span(sym::_task_context),
                    hir::BindingMode::MUT,
                );
                let param = hir::Param {
                    hir_id: self.next_id(),
                    pat,
                    ty_span: self.lower_span(span),
                    span: self.lower_span(span),
                };
                let params = arena_vec![self; param];

                (inputs, params, Some(task_context_hid))
            }
            hir::CoroutineDesugaring::Gen => (&[], &[], None),
        };

        let output =
            return_ty.unwrap_or_else(|| hir::FnRetTy::DefaultReturn(self.lower_span(span)));

        let fn_decl = self.arena.alloc(hir::FnDecl {
            inputs,
            output,
            fn_decl_kind: hir::FnDeclFlags::default(),
        });

        let body = self.lower_body(move |this| {
            this.coroutine_kind = Some(coroutine_kind);

            let old_ctx = this.task_context;
            if task_context.is_some() {
                this.task_context = task_context;
            }
            let res = body(this);
            this.task_context = old_ctx;

            (params, res)
        });

        // `static |<_task_context?>| -> <return_ty> { <body> }`:
        hir::ExprKind::Closure(self.arena.alloc(hir::Closure {
            def_id: closure_def_id,
            binder: hir::ClosureBinder::Default,
            capture_clause: self.lower_capture_clause(capture_clause),
            bound_generic_params: &[],
            fn_decl,
            body,
            fn_decl_span: self.lower_span(fn_decl_span),
            fn_arg_span: None,
            kind: hir::ClosureKind::Coroutine(coroutine_kind),
            constness: hir::Constness::NotConst,
            explicit_captures: &[],
        }))
    }

    /// Forwards a possible `#[track_caller]` annotation from `outer_hir_id` to
    /// `inner_hir_id` in case the `async_fn_track_caller` feature is enabled.
    pub(super) fn maybe_forward_track_caller(
        &mut self,
        span: Span,
        outer_hir_id: HirId,
        inner_hir_id: HirId,
    ) {
        if self.tcx.features().async_fn_track_caller()
            && let Some(attrs) = self.attrs.get(&outer_hir_id.local_id)
            && find_attr!(*attrs, TrackCaller(_))
        {
            let unstable_span = self.mark_span_with_reason(
                DesugaringKind::Async,
                span,
                Some(Arc::clone(&self.allow_gen_future)),
            );
            self.lower_attrs(
                inner_hir_id,
                &[Attribute {
                    kind: AttrKind::Normal(Box::new(NormalAttr::from_ident(Ident::new(
                        sym::track_caller,
                        span,
                    )))),
                    id: self.tcx.sess.psess.attr_id_generator.mk_attr_id(),
                    style: AttrStyle::Outer,
                    span: unstable_span,
                }],
                span,
                Target::Fn,
            );
        }
    }

    /// Desugar `<expr>.await` into:
    /// ```ignore (pseudo-rust)
    /// match ::std::future::IntoFuture::into_future(<expr>) {
    ///     mut __awaitee => loop {
    ///         match unsafe { ::std::future::Future::poll(
    ///             <::std::pin::Pin>::new_unchecked(&mut __awaitee),
    ///             ::std::future::get_context(task_context),
    ///         ) } {
    ///             ::std::task::Poll::Ready(result) => break result,
    ///             ::std::task::Poll::Pending => {}
    ///         }
    ///         task_context = yield ();
    ///     }
    /// }
    /// ```
    fn lower_expr_await(&mut self, await_kw_span: Span, expr: &Expr) -> hir::ExprKind<'hir> {
        let expr = self.arena.alloc(self.lower_expr_mut(expr));
        self.make_lowered_await(await_kw_span, expr, FutureKind::Future)
    }

    /// Takes an expr that has already been lowered and generates a desugared await loop around it
    fn make_lowered_await(
        &mut self,
        await_kw_span: Span,
        expr: &'hir hir::Expr<'hir>,
        await_kind: FutureKind,
    ) -> hir::ExprKind<'hir> {
        let full_span = expr.span.to(await_kw_span);

        let is_async_gen = match self.coroutine_kind {
            Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)) => false,
            Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::AsyncGen, _)) => true,
            Some(hir::CoroutineKind::Coroutine(_))
            | Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _))
            | None => {
                // Lower to a block `{ EXPR; <error> }` so that the awaited expr
                // is not accidentally orphaned.
                let stmt_id = self.next_id();
                let expr_err = self.expr(
                    expr.span,
                    hir::ExprKind::Err(self.dcx().emit_err(AwaitOnlyInAsyncFnAndBlocks {
                        await_kw_span,
                        item_span: self.current_item,
                    })),
                );
                return hir::ExprKind::Block(
                    self.block_all(
                        expr.span,
                        arena_vec![self; hir::Stmt {
                            hir_id: stmt_id,
                            kind: hir::StmtKind::Semi(expr),
                            span: expr.span,
                        }],
                        Some(self.arena.alloc(expr_err)),
                    ),
                    None,
                );
            }
        };

        let features = match await_kind {
            FutureKind::Future if is_async_gen => Some(Arc::clone(&self.allow_async_gen)),
            FutureKind::Future => None,
            FutureKind::AsyncIterator => Some(Arc::clone(&self.allow_for_await)),
        };
        let span = self.mark_span_with_reason(DesugaringKind::Await, await_kw_span, features);
        let gen_future_span = self.mark_span_with_reason(
            DesugaringKind::Await,
            full_span,
            Some(Arc::clone(&self.allow_gen_future)),
        );
        let expr_hir_id = expr.hir_id;

        // Note that the name of this binding must not be changed to something else because
        // debuggers and debugger extensions expect it to be called `__awaitee`. They use
        // this name to identify what is being awaited by a suspended async functions.
        let awaitee_ident = Ident::with_dummy_span(sym::__awaitee);
        let (awaitee_pat, awaitee_pat_hid) =
            self.pat_ident_binding_mode(gen_future_span, awaitee_ident, hir::BindingMode::MUT);

        let task_context_ident = Ident::with_dummy_span(sym::_task_context);

        // unsafe {
        //     ::std::future::Future::poll(
        //         ::std::pin::Pin::new_unchecked(&mut __awaitee),
        //         ::std::future::get_context(task_context),
        //     )
        // }
        let poll_expr = {
            let awaitee = self.expr_ident(span, awaitee_ident, awaitee_pat_hid);
            let ref_mut_awaitee = self.expr_mut_addr_of(span, awaitee);

            let Some(task_context_hid) = self.task_context else {
                unreachable!("use of `await` outside of an async context.");
            };

            let task_context = self.expr_ident_mut(span, task_context_ident, task_context_hid);

            let new_unchecked = self.expr_call_lang_item_fn_mut(
                span,
                hir::LangItem::PinNewUnchecked,
                arena_vec![self; ref_mut_awaitee],
            );
            let get_context = self.expr_call_lang_item_fn_mut(
                gen_future_span,
                hir::LangItem::GetContext,
                arena_vec![self; task_context],
            );
            let call = match await_kind {
                FutureKind::Future => self.expr_call_lang_item_fn(
                    span,
                    hir::LangItem::FuturePoll,
                    arena_vec![self; new_unchecked, get_context],
                ),
                FutureKind::AsyncIterator => self.expr_call_lang_item_fn(
                    span,
                    hir::LangItem::AsyncIteratorPollNext,
                    arena_vec![self; new_unchecked, get_context],
                ),
            };
            self.arena.alloc(self.expr_unsafe(span, call))
        };

        // `::std::task::Poll::Ready(result) => break result`
        let loop_node_id = self.next_node_id();
        let loop_hir_id = self.lower_node_id(loop_node_id);
        let ready_arm = {
            let x_ident = Ident::with_dummy_span(sym::result);
            let (x_pat, x_pat_hid) = self.pat_ident(gen_future_span, x_ident);
            let x_expr = self.expr_ident(gen_future_span, x_ident, x_pat_hid);
            let ready_field = self.single_pat_field(gen_future_span, x_pat);
            let ready_pat = self.pat_lang_item_variant(span, hir::LangItem::PollReady, ready_field);
            let break_x = self.with_loop_scope(loop_hir_id, move |this| {
                let expr_break =
                    hir::ExprKind::Break(this.lower_loop_destination(None), Some(x_expr));
                this.arena.alloc(this.expr(gen_future_span, expr_break))
            });
            self.arm(ready_pat, break_x, span)
        };

        // `::std::task::Poll::Pending => {}`
        let pending_arm = {
            let pending_pat = self.pat_lang_item_variant(span, hir::LangItem::PollPending, &[]);
            let empty_block = self.expr_block_empty(span);
            self.arm(pending_pat, empty_block, span)
        };

        let inner_match_stmt = {
            let match_expr = self.expr_match(
                span,
                poll_expr,
                arena_vec![self; ready_arm, pending_arm],
                hir::MatchSource::AwaitDesugar,
            );
            self.stmt_expr(span, match_expr)
        };

        // Depending on `async` of `async gen`:
        // async     - task_context = yield ();
        // async gen - task_context = yield ASYNC_GEN_PENDING;
        let yield_stmt = {
            let yielded = if is_async_gen {
                self.arena.alloc(self.expr_lang_item_path(span, hir::LangItem::AsyncGenPending))
            } else {
                self.expr_unit(span)
            };

            let yield_expr = self.expr(
                span,
                hir::ExprKind::Yield(yielded, hir::YieldSource::Await { expr: Some(expr_hir_id) }),
            );
            let yield_expr = self.arena.alloc(yield_expr);

            let Some(task_context_hid) = self.task_context else {
                unreachable!("use of `await` outside of an async context.");
            };

            let lhs = self.expr_ident(span, task_context_ident, task_context_hid);
            let assign =
                self.expr(span, hir::ExprKind::Assign(lhs, yield_expr, self.lower_span(span)));
            self.stmt_expr(span, assign)
        };

        let loop_block = self.block_all(span, arena_vec![self; inner_match_stmt, yield_stmt], None);

        // loop { .. }
        let loop_expr = self.arena.alloc(hir::Expr {
            hir_id: loop_hir_id,
            kind: hir::ExprKind::Loop(
                loop_block,
                None,
                hir::LoopSource::Loop,
                self.lower_span(span),
            ),
            span: self.lower_span(span),
        });

        // mut __awaitee => loop { ... }
        let awaitee_arm = self.arm(awaitee_pat, loop_expr, span);

        // `match ::std::future::IntoFuture::into_future(<expr>) { ... }`
        let into_future_expr = match await_kind {
            FutureKind::Future => self.expr_call_lang_item_fn(
                span,
                hir::LangItem::IntoFutureIntoFuture,
                arena_vec![self; *expr],
            ),
            // Not needed for `for await` because we expect to have already called
            // `IntoAsyncIterator::into_async_iter` on it.
            FutureKind::AsyncIterator => expr,
        };

        // match <into_future_expr> {
        //     mut __awaitee => loop { .. }
        // }
        hir::ExprKind::Match(
            into_future_expr,
            arena_vec![self; awaitee_arm],
            hir::MatchSource::AwaitDesugar,
        )
    }

    fn lower_expr_use(&mut self, use_kw_span: Span, expr: &Expr) -> hir::ExprKind<'hir> {
        hir::ExprKind::Use(self.lower_expr(expr), self.lower_span(use_kw_span))
    }

    /// Destructure the LHS of complex assignments.
    /// For instance, lower `(a, b) = t` to `{ let (lhs1, lhs2) = t; a = lhs1; b = lhs2; }`.
    fn lower_expr_assign(
        &mut self,
        lhs: &Expr,
        rhs: &Expr,
        eq_sign_span: Span,
        whole_span: Span,
    ) -> hir::ExprKind<'hir> {
        // Return early in case of an ordinary assignment.
        fn is_ordinary(lower_ctx: &mut LoweringContext<'_, '_>, lhs: &Expr) -> bool {
            match &lhs.kind {
                ExprKind::Array(..)
                | ExprKind::Struct(..)
                | ExprKind::Tup(..)
                | ExprKind::Underscore => false,
                // Check for unit struct constructor.
                ExprKind::Path(..) => lower_ctx.extract_unit_struct_path(lhs).is_none(),
                // Check for tuple struct constructor.
                ExprKind::Call(callee, ..) => lower_ctx.extract_tuple_struct_path(callee).is_none(),
                ExprKind::Paren(e) => {
                    match e.kind {
                        // We special-case `(..)` for consistency with patterns.
                        ExprKind::Range(None, None, RangeLimits::HalfOpen) => false,
                        _ => is_ordinary(lower_ctx, e),
                    }
                }
                _ => true,
            }
        }
        if is_ordinary(self, lhs) {
            return hir::ExprKind::Assign(
                self.lower_expr(lhs),
                self.lower_expr(rhs),
                self.lower_span(eq_sign_span),
            );
        }

        let mut assignments = vec![];

        // The LHS becomes a pattern: `(lhs1, lhs2)`.
        let pat = self.destructure_assign(lhs, eq_sign_span, &mut assignments);
        let rhs = self.lower_expr(rhs);

        // Introduce a `let` for destructuring: `let (lhs1, lhs2) = t`.
        let destructure_let =
            self.stmt_let_pat(None, whole_span, Some(rhs), pat, hir::LocalSource::AssignDesugar);

        // `a = lhs1; b = lhs2;`.
        let stmts = self.arena.alloc_from_iter(std::iter::once(destructure_let).chain(assignments));

        // Wrap everything in a block.
        hir::ExprKind::Block(self.block_all(whole_span, stmts, None), None)
    }

    /// If the given expression is a path to a tuple struct, returns that path.
    /// It is not a complete check, but just tries to reject most paths early
    /// if they are not tuple structs.
    /// Type checking will take care of the full validation later.
    fn extract_tuple_struct_path<'a>(
        &mut self,
        expr: &'a Expr,
    ) -> Option<(&'a Option<Box<QSelf>>, &'a Path)> {
        if let ExprKind::Path(qself, path) = &expr.kind {
            // Does the path resolve to something disallowed in a tuple struct/variant pattern?
            if let Some(partial_res) = self.get_partial_res(expr.id) {
                if let Some(res) = partial_res.full_res()
                    && !res.expected_in_tuple_struct_pat()
                {
                    return None;
                }
            }
            return Some((qself, path));
        }
        None
    }

    /// If the given expression is a path to a unit struct, returns that path.
    /// It is not a complete check, but just tries to reject most paths early
    /// if they are not unit structs.
    /// Type checking will take care of the full validation later.
    fn extract_unit_struct_path<'a>(
        &mut self,
        expr: &'a Expr,
    ) -> Option<(&'a Option<Box<QSelf>>, &'a Path)> {
        if let ExprKind::Path(qself, path) = &expr.kind {
            // Does the path resolve to something disallowed in a unit struct/variant pattern?
            if let Some(partial_res) = self.get_partial_res(expr.id) {
                if let Some(res) = partial_res.full_res()
                    && !res.expected_in_unit_struct_pat()
                {
                    return None;
                }
            }
            return Some((qself, path));
        }
        None
    }

    /// Convert the LHS of a destructuring assignment to a pattern.
    /// Each sub-assignment is recorded in `assignments`.
    fn destructure_assign(
        &mut self,
        lhs: &Expr,
        eq_sign_span: Span,
        assignments: &mut Vec<hir::Stmt<'hir>>,
    ) -> &'hir hir::Pat<'hir> {
        self.arena.alloc(self.destructure_assign_mut(lhs, eq_sign_span, assignments))
    }

    fn destructure_assign_mut(
        &mut self,
        lhs: &Expr,
        eq_sign_span: Span,
        assignments: &mut Vec<hir::Stmt<'hir>>,
    ) -> hir::Pat<'hir> {
        match &lhs.kind {
            // Underscore pattern.
            ExprKind::Underscore => {
                return self.pat_without_dbm(lhs.span, hir::PatKind::Wild);
            }
            // Slice patterns.
            ExprKind::Array(elements) => {
                let (pats, rest) =
                    self.destructure_sequence(elements, "slice", eq_sign_span, assignments);
                let slice_pat = if let Some((i, span)) = rest {
                    let (before, after) = pats.split_at(i);
                    hir::PatKind::Slice(
                        before,
                        Some(self.arena.alloc(self.pat_without_dbm(span, hir::PatKind::Wild))),
                        after,
                    )
                } else {
                    hir::PatKind::Slice(pats, None, &[])
                };
                return self.pat_without_dbm(lhs.span, slice_pat);
            }
            // Tuple structs.
            ExprKind::Call(callee, args) => {
                if let Some((qself, path)) = self.extract_tuple_struct_path(callee) {
                    let (pats, rest) = self.destructure_sequence(
                        args,
                        "tuple struct or variant",
                        eq_sign_span,
                        assignments,
                    );
                    let qpath = self.lower_qpath(
                        callee.id,
                        qself,
                        path,
                        ParamMode::Optional,
                        AllowReturnTypeNotation::No,
                        ImplTraitContext::Disallowed(ImplTraitPosition::Path),
                        None,
                    );
                    // Destructure like a tuple struct.
                    let tuple_struct_pat = hir::PatKind::TupleStruct(
                        qpath,
                        pats,
                        hir::DotDotPos::new(rest.map(|r| r.0)),
                    );
                    return self.pat_without_dbm(lhs.span, tuple_struct_pat);
                }
            }
            // Unit structs and enum variants.
            ExprKind::Path(..) => {
                if let Some((qself, path)) = self.extract_unit_struct_path(lhs) {
                    let qpath = self.lower_qpath(
                        lhs.id,
                        qself,
                        path,
                        ParamMode::Optional,
                        AllowReturnTypeNotation::No,
                        ImplTraitContext::Disallowed(ImplTraitPosition::Path),
                        None,
                    );
                    // Destructure like a unit struct.
                    let unit_struct_pat = hir::PatKind::Expr(self.arena.alloc(hir::PatExpr {
                        kind: hir::PatExprKind::Path(qpath),
                        hir_id: self.next_id(),
                        span: self.lower_span(lhs.span),
                    }));
                    return self.pat_without_dbm(lhs.span, unit_struct_pat);
                }
            }
            // Structs.
            ExprKind::Struct(se) => {
                let field_pats = self.arena.alloc_from_iter(se.fields.iter().map(|f| {
                    let pat = self.destructure_assign(&f.expr, eq_sign_span, assignments);
                    hir::PatField {
                        hir_id: self.next_id(),
                        ident: self.lower_ident(f.ident),
                        pat,
                        is_shorthand: f.is_shorthand,
                        span: self.lower_span(f.span),
                    }
                }));
                let qpath = self.lower_qpath(
                    lhs.id,
                    &se.qself,
                    &se.path,
                    ParamMode::Optional,
                    AllowReturnTypeNotation::No,
                    ImplTraitContext::Disallowed(ImplTraitPosition::Path),
                    None,
                );
                let fields_omitted = match &se.rest {
                    StructRest::Base(e) => {
                        self.dcx().emit_err(FunctionalRecordUpdateDestructuringAssignment {
                            span: e.span,
                        });
                        Some(self.lower_span(e.span))
                    }
                    StructRest::Rest(span) => Some(self.lower_span(*span)),
                    StructRest::None | StructRest::NoneWithError(_) => None,
                };
                let struct_pat = hir::PatKind::Struct(qpath, field_pats, fields_omitted);
                return self.pat_without_dbm(lhs.span, struct_pat);
            }
            // Tuples.
            ExprKind::Tup(elements) => {
                let (pats, rest) =
                    self.destructure_sequence(elements, "tuple", eq_sign_span, assignments);
                let tuple_pat = hir::PatKind::Tuple(pats, hir::DotDotPos::new(rest.map(|r| r.0)));
                return self.pat_without_dbm(lhs.span, tuple_pat);
            }
            ExprKind::Paren(e) => {
                // We special-case `(..)` for consistency with patterns.
                if let ExprKind::Range(None, None, RangeLimits::HalfOpen) = e.kind {
                    let tuple_pat = hir::PatKind::Tuple(&[], hir::DotDotPos::new(Some(0)));
                    return self.pat_without_dbm(lhs.span, tuple_pat);
                } else {
                    return self.destructure_assign_mut(e, eq_sign_span, assignments);
                }
            }
            _ => {}
        }
        // Treat all other cases as normal lvalue.
        let ident = Ident::new(sym::lhs, self.lower_span(lhs.span));
        let (pat, binding) = self.pat_ident_mut(lhs.span, ident);
        let ident = self.expr_ident(lhs.span, ident, binding);
        let assign =
            hir::ExprKind::Assign(self.lower_expr(lhs), ident, self.lower_span(eq_sign_span));
        let expr = self.expr(lhs.span, assign);
        assignments.push(self.stmt_expr(lhs.span, expr));
        pat
    }

    /// Destructure a sequence of expressions occurring on the LHS of an assignment.
    /// Such a sequence occurs in a tuple (struct)/slice.
    /// Return a sequence of corresponding patterns, and the index and the span of `..` if it
    /// exists.
    /// Each sub-assignment is recorded in `assignments`.
    fn destructure_sequence(
        &mut self,
        elements: &[Box<Expr>],
        ctx: &str,
        eq_sign_span: Span,
        assignments: &mut Vec<hir::Stmt<'hir>>,
    ) -> (&'hir [hir::Pat<'hir>], Option<(usize, Span)>) {
        let mut rest = None;
        let elements =
            self.arena.alloc_from_iter(elements.iter().enumerate().filter_map(|(i, e)| {
                // Check for `..` pattern.
                if let ExprKind::Range(None, None, RangeLimits::HalfOpen) = e.kind {
                    if let Some((_, prev_span)) = rest {
                        self.ban_extra_rest_pat(e.span, prev_span, ctx);
                    } else {
                        rest = Some((i, e.span));
                    }
                    None
                } else {
                    Some(self.destructure_assign_mut(e, eq_sign_span, assignments))
                }
            }));
        (elements, rest)
    }

    /// Desugar `<start>..=<end>` into `std::ops::RangeInclusive::new(<start>, <end>)`.
    fn lower_expr_range_closed(&mut self, span: Span, e1: &Expr, e2: &Expr) -> hir::ExprKind<'hir> {
        let e1 = self.lower_expr_mut(e1);
        let e2 = self.lower_expr_mut(e2);
        let fn_path = self.make_lang_item_qpath(hir::LangItem::RangeInclusiveNew, span, None);
        let fn_expr = self.arena.alloc(self.expr(span, hir::ExprKind::Path(fn_path)));
        hir::ExprKind::Call(fn_expr, arena_vec![self; e1, e2])
    }

    fn lower_expr_range(
        &mut self,
        span: Span,
        e1: Option<&Expr>,
        e2: Option<&Expr>,
        lims: RangeLimits,
    ) -> hir::ExprKind<'hir> {
        use rustc_ast::RangeLimits::*;

        let lang_item = match (e1, e2, lims) {
            (None, None, HalfOpen) => hir::LangItem::RangeFull,
            (Some(..), None, HalfOpen) => {
                if self.tcx.features().new_range() {
                    hir::LangItem::RangeFromCopy
                } else {
                    hir::LangItem::RangeFrom
                }
            }
            (None, Some(..), HalfOpen) => hir::LangItem::RangeTo,
            (Some(..), Some(..), HalfOpen) => {
                if self.tcx.features().new_range() {
                    hir::LangItem::RangeCopy
                } else {
                    hir::LangItem::Range
                }
            }
            (None, Some(..), Closed) => {
                if self.tcx.features().new_range() {
                    hir::LangItem::RangeToInclusiveCopy
                } else {
                    hir::LangItem::RangeToInclusive
                }
            }
            (Some(e1), Some(e2), Closed) => {
                if self.tcx.features().new_range() {
                    hir::LangItem::RangeInclusiveCopy
                } else {
                    return self.lower_expr_range_closed(span, e1, e2);
                }
            }
            (start, None, Closed) => {
                self.dcx().emit_err(InclusiveRangeWithNoEnd { span });
                match start {
                    Some(..) => {
                        if self.tcx.features().new_range() {
                            hir::LangItem::RangeFromCopy
                        } else {
                            hir::LangItem::RangeFrom
                        }
                    }
                    None => hir::LangItem::RangeFull,
                }
            }
        };

        let fields = self.arena.alloc_from_iter(
            e1.iter()
                .map(|e| (sym::start, e))
                .chain(e2.iter().map(|e| {
                    (
                        if matches!(
                            lang_item,
                            hir::LangItem::RangeInclusiveCopy | hir::LangItem::RangeToInclusiveCopy
                        ) {
                            sym::last
                        } else {
                            sym::end
                        },
                        e,
                    )
                }))
                .map(|(s, e)| {
                    let span = self.lower_span(e.span);
                    let span = self.mark_span_with_reason(DesugaringKind::RangeExpr, span, None);
                    let expr = self.lower_expr(e);
                    let ident = Ident::new(s, span);
                    self.expr_field(ident, expr, span)
                }),
        );

        hir::ExprKind::Struct(
            self.arena.alloc(self.make_lang_item_qpath(lang_item, span, None)),
            fields,
            hir::StructTailExpr::None,
        )
    }

    // Record labelled expr's HirId so that we can retrieve it in `lower_jump_destination` without
    // lowering node id again.
    fn lower_label(
        &mut self,
        opt_label: Option<Label>,
        dest_id: NodeId,
        dest_hir_id: hir::HirId,
    ) -> Option<Label> {
        let label = opt_label?;
        self.ident_and_label_to_local_id.insert(dest_id, dest_hir_id.local_id);
        Some(Label { ident: self.lower_ident(label.ident) })
    }

    fn lower_loop_destination(&mut self, destination: Option<(NodeId, Label)>) -> hir::Destination {
        let target_id = match destination {
            Some((id, _)) => {
                if let Some(loop_id) = self.owner.get_label_res(id) {
                    let local_id = self.ident_and_label_to_local_id[&loop_id];
                    let loop_hir_id = HirId { owner: self.current_hir_id_owner, local_id };
                    Ok(loop_hir_id)
                } else {
                    Err(hir::LoopIdError::UnresolvedLabel)
                }
            }
            None => {
                self.loop_scope.map(|id| Ok(id)).unwrap_or(Err(hir::LoopIdError::OutsideLoopScope))
            }
        };
        let label = destination
            .map(|(_, label)| label)
            .map(|label| Label { ident: self.lower_ident(label.ident) });
        hir::Destination { label, target_id }
    }

    fn lower_jump_destination(&mut self, id: NodeId, opt_label: Option<Label>) -> hir::Destination {
        if self.is_in_loop_condition && opt_label.is_none() {
            hir::Destination {
                label: None,
                target_id: Err(hir::LoopIdError::UnlabeledCfInWhileCondition),
            }
        } else {
            self.lower_loop_destination(opt_label.map(|label| (id, label)))
        }
    }

    fn with_try_block_scope<T>(
        &mut self,
        scope: TryBlockScope,
        f: impl FnOnce(&mut Self) -> T,
    ) -> T {
        let old_scope = mem::replace(&mut self.try_block_scope, scope);
        let result = f(self);
        self.try_block_scope = old_scope;
        result
    }

    fn with_loop_scope<T>(&mut self, loop_id: hir::HirId, f: impl FnOnce(&mut Self) -> T) -> T {
        // We're no longer in the base loop's condition; we're in another loop.
        let was_in_loop_condition = self.is_in_loop_condition;
        self.is_in_loop_condition = false;

        let old_scope = self.loop_scope.replace(loop_id);
        let result = f(self);
        self.loop_scope = old_scope;

        self.is_in_loop_condition = was_in_loop_condition;

        result
    }

    fn with_loop_condition_scope<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
        let was_in_loop_condition = self.is_in_loop_condition;
        self.is_in_loop_condition = true;

        let result = f(self);

        self.is_in_loop_condition = was_in_loop_condition;

        result
    }

    fn lower_expr_field(&mut self, f: &ExprField) -> hir::ExprField<'hir> {
        let hir_id = self.lower_node_id(f.id);
        self.lower_attrs(hir_id, &f.attrs, f.span, Target::ExprField);
        hir::ExprField {
            hir_id,
            ident: self.lower_ident(f.ident),
            expr: self.lower_expr(&f.expr),
            span: self.lower_span(f.span),
            is_shorthand: f.is_shorthand,
        }
    }

    fn lower_expr_yield(&mut self, span: Span, opt_expr: Option<&Expr>) -> hir::ExprKind<'hir> {
        let yielded =
            opt_expr.as_ref().map(|x| self.lower_expr(x)).unwrap_or_else(|| self.expr_unit(span));

        if !self.tcx.features().yield_expr()
            && !self.tcx.features().coroutines()
            && !self.tcx.features().gen_blocks()
        {
            rustc_session::errors::feature_err(
                &self.tcx.sess,
                sym::yield_expr,
                span,
                msg!("yield syntax is experimental"),
            )
            .emit();
        }

        let is_async_gen = match self.coroutine_kind {
            Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _)) => false,
            Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::AsyncGen, _)) => true,
            Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)) => {
                // Lower to a block `{ EXPR; <error> }` so that the awaited expr
                // is not accidentally orphaned.
                let stmt_id = self.next_id();
                let expr_err = self.expr(
                    yielded.span,
                    hir::ExprKind::Err(self.dcx().emit_err(AsyncCoroutinesNotSupported { span })),
                );
                return hir::ExprKind::Block(
                    self.block_all(
                        yielded.span,
                        arena_vec![self; hir::Stmt {
                            hir_id: stmt_id,
                            kind: hir::StmtKind::Semi(yielded),
                            span: yielded.span,
                        }],
                        Some(self.arena.alloc(expr_err)),
                    ),
                    None,
                );
            }
            Some(hir::CoroutineKind::Coroutine(_)) => false,
            None => {
                let suggestion = self.current_item.map(|s| s.shrink_to_lo());
                self.dcx().emit_err(YieldInClosure { span, suggestion });
                self.coroutine_kind = Some(hir::CoroutineKind::Coroutine(Movability::Movable));

                false
            }
        };

        if is_async_gen {
            // `yield $expr` is transformed into `task_context = yield async_gen_ready($expr)`.
            // This ensures that we store our resumed `ResumeContext` correctly, and also that
            // the apparent value of the `yield` expression is `()`.
            let desugar_span = self.mark_span_with_reason(
                DesugaringKind::Async,
                span,
                Some(Arc::clone(&self.allow_async_gen)),
            );
            let wrapped_yielded = self.expr_call_lang_item_fn(
                desugar_span,
                hir::LangItem::AsyncGenReady,
                std::slice::from_ref(yielded),
            );
            let yield_expr = self.arena.alloc(
                self.expr(span, hir::ExprKind::Yield(wrapped_yielded, hir::YieldSource::Yield)),
            );

            let Some(task_context_hid) = self.task_context else {
                unreachable!("use of `await` outside of an async context.");
            };
            let task_context_ident = Ident::with_dummy_span(sym::_task_context);
            let lhs = self.expr_ident(desugar_span, task_context_ident, task_context_hid);

            hir::ExprKind::Assign(lhs, yield_expr, self.lower_span(span))
        } else {
            hir::ExprKind::Yield(yielded, hir::YieldSource::Yield)
        }
    }

    /// Desugar `ExprForLoop` from: `[opt_ident]: for <pat> in <head> <body>` into:
    /// ```ignore (pseudo-rust)
    /// {
    ///     let result = match IntoIterator::into_iter(<head>) {
    ///         mut iter => {
    ///             [opt_ident]: loop {
    ///                 match Iterator::next(&mut iter) {
    ///                     None => break,
    ///                     Some(<pat>) => <body>,
    ///                 };
    ///             }
    ///         }
    ///     };
    ///     result
    /// }
    /// ```
    fn lower_expr_for(
        &mut self,
        e: &Expr,
        pat: &Pat,
        head: &Expr,
        body: &Block,
        opt_label: Option<Label>,
        loop_kind: ForLoopKind,
    ) -> hir::Expr<'hir> {
        let head = self.lower_expr_mut(head);
        let pat = self.lower_pat(pat);
        let for_span =
            self.mark_span_with_reason(DesugaringKind::ForLoop, self.lower_span(e.span), None);
        let for_ctxt = for_span.ctxt();

        // Try to point both the head and pat spans to their position in the for loop
        // rather than inside a macro.
        let head_span =
            head.span.find_ancestor_in_same_ctxt(e.span).unwrap_or(head.span).with_ctxt(for_ctxt);
        let pat_span =
            pat.span.find_ancestor_in_same_ctxt(e.span).unwrap_or(pat.span).with_ctxt(for_ctxt);

        let loop_hir_id = self.lower_node_id(e.id);
        let label = self.lower_label(opt_label, e.id, loop_hir_id);

        // `None => break`
        let none_arm = {
            let break_expr =
                self.with_loop_scope(loop_hir_id, |this| this.expr_break_alloc(for_span));
            let pat = self.pat_none(for_span);
            self.arm(pat, break_expr, for_span)
        };

        // Some(<pat>) => <body>,
        let some_arm = {
            let some_pat = self.pat_some(pat_span, pat);
            let body_block =
                self.with_loop_scope(loop_hir_id, |this| this.lower_block(body, false));
            let body_expr = self.arena.alloc(self.expr_block(body_block));
            self.arm(some_pat, body_expr, for_span)
        };

        // `mut iter`
        let iter = Ident::with_dummy_span(sym::iter);
        let (iter_pat, iter_pat_nid) =
            self.pat_ident_binding_mode(head_span, iter, hir::BindingMode::MUT);

        let match_expr = {
            let iter = self.expr_ident(head_span, iter, iter_pat_nid);
            let next_expr = match loop_kind {
                ForLoopKind::For => {
                    // `Iterator::next(&mut iter)`
                    let ref_mut_iter = self.expr_mut_addr_of(head_span, iter);
                    self.expr_call_lang_item_fn(
                        head_span,
                        hir::LangItem::IteratorNext,
                        arena_vec![self; ref_mut_iter],
                    )
                }
                ForLoopKind::ForAwait => {
                    // we'll generate `unsafe { Pin::new_unchecked(&mut iter) })` and then pass this
                    // to make_lowered_await with `FutureKind::AsyncIterator` which will generator
                    // calls to `poll_next`. In user code, this would probably be a call to
                    // `Pin::as_mut` but here it's easy enough to do `new_unchecked`.

                    // `&mut iter`
                    let iter = self.expr_mut_addr_of(head_span, iter);
                    // `Pin::new_unchecked(...)`
                    let iter = self.arena.alloc(self.expr_call_lang_item_fn_mut(
                        head_span,
                        hir::LangItem::PinNewUnchecked,
                        arena_vec![self; iter],
                    ));
                    // `unsafe { ... }`
                    let iter = self.arena.alloc(self.expr_unsafe(head_span, iter));
                    let kind = self.make_lowered_await(head_span, iter, FutureKind::AsyncIterator);
                    self.arena.alloc(hir::Expr { hir_id: self.next_id(), kind, span: head_span })
                }
            };
            let arms = arena_vec![self; none_arm, some_arm];

            // `match $next_expr { ... }`
            self.expr_match(head_span, next_expr, arms, hir::MatchSource::ForLoopDesugar)
        };
        let match_stmt = self.stmt_expr(for_span, match_expr);

        let loop_block = self.block_all(for_span, arena_vec![self; match_stmt], None);

        // `[opt_ident]: loop { ... }`
        let kind = hir::ExprKind::Loop(
            loop_block,
            label,
            hir::LoopSource::ForLoop,
            self.lower_span(for_span.with_hi(head.span.hi())),
        );
        let loop_expr = self.arena.alloc(hir::Expr { hir_id: loop_hir_id, kind, span: for_span });

        // `mut iter => { ... }`
        let iter_arm = self.arm(iter_pat, loop_expr, for_span);

        let match_expr = match loop_kind {
            ForLoopKind::For => {
                // `::std::iter::IntoIterator::into_iter(<head>)`
                let into_iter_expr = self.expr_call_lang_item_fn(
                    head_span,
                    hir::LangItem::IntoIterIntoIter,
                    arena_vec![self; head],
                );

                self.arena.alloc(self.expr_match(
                    for_span,
                    into_iter_expr,
                    arena_vec![self; iter_arm],
                    hir::MatchSource::ForLoopDesugar,
                ))
            }
            // `match into_async_iter(<head>) { ref mut iter => match unsafe { Pin::new_unchecked(iter) } { ... } }`
            ForLoopKind::ForAwait => {
                let iter_ident = iter;
                let (async_iter_pat, async_iter_pat_id) =
                    self.pat_ident_binding_mode(head_span, iter_ident, hir::BindingMode::REF_MUT);
                let iter = self.expr_ident_mut(head_span, iter_ident, async_iter_pat_id);
                // `Pin::new_unchecked(...)`
                let iter = self.arena.alloc(self.expr_call_lang_item_fn_mut(
                    head_span,
                    hir::LangItem::PinNewUnchecked,
                    arena_vec![self; iter],
                ));
                // `unsafe { ... }`
                let iter = self.arena.alloc(self.expr_unsafe(head_span, iter));
                let inner_match_expr = self.arena.alloc(self.expr_match(
                    for_span,
                    iter,
                    arena_vec![self; iter_arm],
                    hir::MatchSource::ForLoopDesugar,
                ));

                // `::core::async_iter::IntoAsyncIterator::into_async_iter(<head>)`
                let iter = self.expr_call_lang_item_fn(
                    head_span,
                    hir::LangItem::IntoAsyncIterIntoIter,
                    arena_vec![self; head],
                );
                let iter_arm = self.arm(async_iter_pat, inner_match_expr, for_span);
                self.arena.alloc(self.expr_match(
                    for_span,
                    iter,
                    arena_vec![self; iter_arm],
                    hir::MatchSource::ForLoopDesugar,
                ))
            }
        };

        // This is effectively `{ let _result = ...; _result }`.
        // The construct was introduced in #21984 and is necessary to make sure that
        // temporaries in the `head` expression are dropped and do not leak to the
        // surrounding scope of the `match` since the `match` is not a terminating scope.
        //
        // Also, add the attributes to the outer returned expr node.
        let expr = self.expr_drop_temps_mut(for_span, match_expr);
        self.lower_attrs(expr.hir_id, &e.attrs, e.span, Target::from_expr(e));
        expr
    }

    /// Desugar `ExprKind::Try` from: `<expr>?` into:
    /// ```ignore (pseudo-rust)
    /// match Try::branch(<expr>) {
    ///     ControlFlow::Continue(val) => #[allow(unreachable_code)] val,,
    ///     ControlFlow::Break(residual) =>
    ///         #[allow(unreachable_code)]
    ///         // If there is an enclosing `try {...}`:
    ///         break 'catch_target Residual::into_try_type(residual),
    ///         // Otherwise:
    ///         return Try::from_residual(residual),
    /// }
    /// ```
    fn lower_expr_try(&mut self, span: Span, sub_expr: &Expr) -> hir::ExprKind<'hir> {
        let unstable_span = self.mark_span_with_reason(
            DesugaringKind::QuestionMark,
            span,
            Some(Arc::clone(&self.allow_try_trait)),
        );
        let try_span = self.tcx.sess.source_map().end_point(span);
        let try_span = self.mark_span_with_reason(
            DesugaringKind::QuestionMark,
            try_span,
            Some(Arc::clone(&self.allow_try_trait)),
        );

        // `Try::branch(<expr>)`
        let scrutinee = {
            // expand <expr>
            let sub_expr = self.lower_expr_mut(sub_expr);

            self.expr_call_lang_item_fn(
                unstable_span,
                hir::LangItem::TryTraitBranch,
                arena_vec![self; sub_expr],
            )
        };

        let attrs: AttrVec = thin_vec![self.unreachable_code_attr(try_span)];

        // `ControlFlow::Continue(val) => #[allow(unreachable_code)] val,`
        let continue_arm = {
            let val_ident = Ident::with_dummy_span(sym::val);
            let (val_pat, val_pat_nid) = self.pat_ident(span, val_ident);
            let val_expr = self.expr_ident(span, val_ident, val_pat_nid);
            self.lower_attrs(val_expr.hir_id, &attrs, span, Target::Expression);
            let continue_pat = self.pat_cf_continue(unstable_span, val_pat);
            self.arm(continue_pat, val_expr, try_span)
        };

        // `ControlFlow::Break(residual) =>
        //     #[allow(unreachable_code)]
        //     return Try::from_residual(residual),`
        let break_arm = {
            let residual_ident = Ident::with_dummy_span(sym::residual);
            let (residual_local, residual_local_nid) = self.pat_ident(try_span, residual_ident);
            let residual_expr = self.expr_ident_mut(try_span, residual_ident, residual_local_nid);

            let (constructor_item, target_id) = match self.try_block_scope {
                TryBlockScope::Function => {
                    (hir::LangItem::TryTraitFromResidual, Err(hir::LoopIdError::OutsideLoopScope))
                }
                TryBlockScope::Homogeneous(block_id) => {
                    (hir::LangItem::ResidualIntoTryType, Ok(block_id))
                }
                TryBlockScope::Heterogeneous(block_id) => {
                    (hir::LangItem::TryTraitFromResidual, Ok(block_id))
                }
            };
            let from_residual_expr = self.wrap_in_try_constructor(
                constructor_item,
                try_span,
                self.arena.alloc(residual_expr),
                unstable_span,
            );
            let ret_expr = if target_id.is_ok() {
                self.arena.alloc(self.expr(
                    try_span,
                    hir::ExprKind::Break(
                        hir::Destination { label: None, target_id },
                        Some(from_residual_expr),
                    ),
                ))
            } else {
                let ret_expr = self.checked_return(Some(from_residual_expr));
                self.arena.alloc(self.expr(try_span, ret_expr))
            };
            self.lower_attrs(ret_expr.hir_id, &attrs, span, Target::Expression);

            let break_pat = self.pat_cf_break(try_span, residual_local);
            self.arm(break_pat, ret_expr, try_span)
        };

        hir::ExprKind::Match(
            scrutinee,
            arena_vec![self; break_arm, continue_arm],
            hir::MatchSource::TryDesugar(scrutinee.hir_id),
        )
    }

    /// Desugar `ExprKind::Yeet` from: `do yeet <expr>` into:
    /// ```ignore(illustrative)
    /// // If there is an enclosing `try {...}`:
    /// break 'catch_target FromResidual::from_residual(Yeet(residual));
    /// // Otherwise:
    /// return FromResidual::from_residual(Yeet(residual));
    /// ```
    /// But to simplify this, there's a `from_yeet` lang item function which
    /// handles the combined `FromResidual::from_residual(Yeet(residual))`.
    fn lower_expr_yeet(&mut self, span: Span, sub_expr: Option<&Expr>) -> hir::ExprKind<'hir> {
        // The expression (if present) or `()` otherwise.
        let (yeeted_span, yeeted_expr) = if let Some(sub_expr) = sub_expr {
            (sub_expr.span, self.lower_expr(sub_expr))
        } else {
            (self.mark_span_with_reason(DesugaringKind::YeetExpr, span, None), self.expr_unit(span))
        };

        let unstable_span = self.mark_span_with_reason(
            DesugaringKind::YeetExpr,
            span,
            Some(Arc::clone(&self.allow_try_trait)),
        );

        let from_yeet_expr = self.wrap_in_try_constructor(
            hir::LangItem::TryTraitFromYeet,
            unstable_span,
            yeeted_expr,
            yeeted_span,
        );

        match self.try_block_scope {
            TryBlockScope::Homogeneous(block_id) | TryBlockScope::Heterogeneous(block_id) => {
                hir::ExprKind::Break(
                    hir::Destination { label: None, target_id: Ok(block_id) },
                    Some(from_yeet_expr),
                )
            }
            TryBlockScope::Function => self.checked_return(Some(from_yeet_expr)),
        }
    }

    // =========================================================================
    // Helper methods for building HIR.
    // =========================================================================

    /// Wrap the given `expr` in a terminating scope using `hir::ExprKind::DropTemps`.
    ///
    /// In terms of drop order, it has the same effect as wrapping `expr` in
    /// `{ let _t = $expr; _t }` but should provide better compile-time performance.
    ///
    /// The drop order can be important, e.g. to drop temporaries from an `async fn`
    /// body before its parameters.
    pub(super) fn expr_drop_temps(
        &mut self,
        span: Span,
        expr: &'hir hir::Expr<'hir>,
    ) -> &'hir hir::Expr<'hir> {
        self.arena.alloc(self.expr_drop_temps_mut(span, expr))
    }

    pub(super) fn expr_drop_temps_mut(
        &mut self,
        span: Span,
        expr: &'hir hir::Expr<'hir>,
    ) -> hir::Expr<'hir> {
        self.expr(span, hir::ExprKind::DropTemps(expr))
    }

    pub(super) fn expr_match(
        &mut self,
        span: Span,
        arg: &'hir hir::Expr<'hir>,
        arms: &'hir [hir::Arm<'hir>],
        source: hir::MatchSource,
    ) -> hir::Expr<'hir> {
        self.expr(span, hir::ExprKind::Match(arg, arms, source))
    }

    fn expr_break(&mut self, span: Span) -> hir::Expr<'hir> {
        let expr_break = hir::ExprKind::Break(self.lower_loop_destination(None), None);
        self.expr(span, expr_break)
    }

    fn expr_break_alloc(&mut self, span: Span) -> &'hir hir::Expr<'hir> {
        let expr_break = self.expr_break(span);
        self.arena.alloc(expr_break)
    }

    fn expr_mut_addr_of(&mut self, span: Span, e: &'hir hir::Expr<'hir>) -> hir::Expr<'hir> {
        self.expr(span, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e))
    }

    pub(super) fn expr_unit(&mut self, sp: Span) -> &'hir hir::Expr<'hir> {
        self.arena.alloc(self.expr(sp, hir::ExprKind::Tup(&[])))
    }

    pub(super) fn expr_str(&mut self, sp: Span, value: Symbol) -> hir::Expr<'hir> {
        let lit = hir::Lit {
            span: self.lower_span(sp),
            node: ast::LitKind::Str(value, ast::StrStyle::Cooked),
        };
        self.expr(sp, hir::ExprKind::Lit(lit))
    }

    pub(super) fn expr_byte_str(&mut self, sp: Span, value: ByteSymbol) -> hir::Expr<'hir> {
        let lit = hir::Lit {
            span: self.lower_span(sp),
            node: ast::LitKind::ByteStr(value, ast::StrStyle::Cooked),
        };
        self.expr(sp, hir::ExprKind::Lit(lit))
    }

    pub(super) fn expr_call_mut(
        &mut self,
        span: Span,
        e: &'hir hir::Expr<'hir>,
        args: &'hir [hir::Expr<'hir>],
    ) -> hir::Expr<'hir> {
        self.expr(span, hir::ExprKind::Call(e, args))
    }

    pub(super) fn expr_struct(
        &mut self,
        span: Span,
        path: &'hir hir::QPath<'hir>,
        fields: &'hir [hir::ExprField<'hir>],
    ) -> hir::Expr<'hir> {
        self.expr(span, hir::ExprKind::Struct(path, fields, rustc_hir::StructTailExpr::None))
    }

    pub(super) fn expr_enum_variant(
        &mut self,
        span: Span,
        path: &'hir hir::QPath<'hir>,
        fields: &'hir [hir::Expr<'hir>],
    ) -> hir::Expr<'hir> {
        let fields = self.arena.alloc_from_iter(fields.into_iter().enumerate().map(|(i, f)| {
            hir::ExprField {
                hir_id: self.next_id(),
                ident: Ident::from_str(&i.to_string()),
                expr: f,
                span: f.span,
                is_shorthand: false,
            }
        }));
        self.expr_struct(span, path, fields)
    }

    pub(super) fn expr_enum_variant_lang_item(
        &mut self,
        span: Span,
        lang_item: hir::LangItem,
        fields: &'hir [hir::Expr<'hir>],
    ) -> hir::Expr<'hir> {
        let path = self.arena.alloc(self.make_lang_item_qpath(lang_item, span, None));
        self.expr_enum_variant(span, path, fields)
    }

    pub(super) fn expr_call(
        &mut self,
        span: Span,
        e: &'hir hir::Expr<'hir>,
        args: &'hir [hir::Expr<'hir>],
    ) -> &'hir hir::Expr<'hir> {
        self.arena.alloc(self.expr_call_mut(span, e, args))
    }

    pub(super) fn expr_call_lang_item_fn_mut(
        &mut self,
        span: Span,
        lang_item: hir::LangItem,
        args: &'hir [hir::Expr<'hir>],
    ) -> hir::Expr<'hir> {
        let path = self.arena.alloc(self.expr_lang_item_path(span, lang_item));
        self.expr_call_mut(span, path, args)
    }

    pub(super) fn expr_call_lang_item_fn(
        &mut self,
        span: Span,
        lang_item: hir::LangItem,
        args: &'hir [hir::Expr<'hir>],
    ) -> &'hir hir::Expr<'hir> {
        self.arena.alloc(self.expr_call_lang_item_fn_mut(span, lang_item, args))
    }

    pub(super) fn expr_lang_item_path(
        &mut self,
        span: Span,
        lang_item: hir::LangItem,
    ) -> hir::Expr<'hir> {
        let qpath = self.make_lang_item_qpath(lang_item, self.lower_span(span), None);
        self.expr(span, hir::ExprKind::Path(qpath))
    }

    /// `<LangItem>::name`
    pub(super) fn expr_lang_item_type_relative(
        &mut self,
        span: Span,
        lang_item: hir::LangItem,
        name: Symbol,
    ) -> hir::Expr<'hir> {
        let qpath = self.make_lang_item_qpath(lang_item, self.lower_span(span), None);
        let path = hir::ExprKind::Path(hir::QPath::TypeRelative(
            self.arena.alloc(self.ty(span, hir::TyKind::Path(qpath))),
            self.arena.alloc(hir::PathSegment::new(
                Ident::new(name, self.lower_span(span)),
                self.next_id(),
                Res::Err,
            )),
        ));
        self.expr(span, path)
    }

    pub(super) fn expr_ident(
        &mut self,
        sp: Span,
        ident: Ident,
        binding: HirId,
    ) -> &'hir hir::Expr<'hir> {
        self.arena.alloc(self.expr_ident_mut(sp, ident, binding))
    }

    pub(super) fn expr_ident_mut(
        &mut self,
        span: Span,
        ident: Ident,
        binding: HirId,
    ) -> hir::Expr<'hir> {
        let hir_id = self.next_id();
        let res = Res::Local(binding);
        let expr_path = hir::ExprKind::Path(hir::QPath::Resolved(
            None,
            self.arena.alloc(hir::Path {
                span: self.lower_span(span),
                res,
                segments: arena_vec![self; hir::PathSegment::new(self.lower_ident(ident), hir_id, res)],
            }),
        ));

        self.expr(span, expr_path)
    }

    pub(super) fn expr_unsafe(
        &mut self,
        span: Span,
        expr: &'hir hir::Expr<'hir>,
    ) -> hir::Expr<'hir> {
        let hir_id = self.next_id();
        self.expr(
            span,
            hir::ExprKind::Block(
                self.arena.alloc(hir::Block {
                    stmts: &[],
                    expr: Some(expr),
                    hir_id,
                    rules: hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::CompilerGenerated),
                    span: self.lower_span(span),
                    targeted_by_break: false,
                }),
                None,
            ),
        )
    }

    fn expr_block_empty(&mut self, span: Span) -> &'hir hir::Expr<'hir> {
        let blk = self.block_all(span, &[], None);
        let expr = self.expr_block(blk);
        self.arena.alloc(expr)
    }

    pub(super) fn expr_block(&mut self, b: &'hir hir::Block<'hir>) -> hir::Expr<'hir> {
        self.expr(b.span, hir::ExprKind::Block(b, None))
    }

    /// Wrap an expression in a block, and wrap that block in an expression again.
    /// Useful for constructing if-expressions, which require expressions of
    /// kind block.
    pub(super) fn block_expr_block(
        &mut self,
        expr: &'hir hir::Expr<'hir>,
    ) -> &'hir hir::Expr<'hir> {
        let b = self.block_expr(expr);
        self.arena.alloc(self.expr_block(b))
    }

    pub(super) fn expr_ref(&mut self, span: Span, expr: &'hir hir::Expr<'hir>) -> hir::Expr<'hir> {
        self.expr(span, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, expr))
    }

    pub(super) fn expr_bool_literal(&mut self, span: Span, val: bool) -> hir::Expr<'hir> {
        self.expr(span, hir::ExprKind::Lit(Spanned { node: LitKind::Bool(val), span }))
    }

    pub(super) fn expr(&mut self, span: Span, kind: hir::ExprKind<'hir>) -> hir::Expr<'hir> {
        let hir_id = self.next_id();
        hir::Expr { hir_id, kind, span: self.lower_span(span) }
    }

    pub(super) fn expr_field(
        &mut self,
        ident: Ident,
        expr: &'hir hir::Expr<'hir>,
        span: Span,
    ) -> hir::ExprField<'hir> {
        hir::ExprField {
            hir_id: self.next_id(),
            ident,
            span: self.lower_span(span),
            expr,
            is_shorthand: false,
        }
    }

    pub(super) fn arm(
        &mut self,
        pat: &'hir hir::Pat<'hir>,
        expr: &'hir hir::Expr<'hir>,
        span: Span,
    ) -> hir::Arm<'hir> {
        hir::Arm {
            hir_id: self.next_id(),
            pat,
            guard: None,
            span: self.lower_span(span),
            body: expr,
        }
    }

    /// `#[allow(unreachable_code)]`
    pub(super) fn unreachable_code_attr(&mut self, span: Span) -> Attribute {
        let attr = attr::mk_attr_nested_word(
            &self.tcx.sess.psess.attr_id_generator,
            AttrStyle::Outer,
            Safety::Default,
            sym::allow,
            sym::unreachable_code,
            span,
        );
        attr
    }
}

/// Used by [`LoweringContext::make_lowered_await`] to customize the desugaring based on what kind
/// of future we are awaiting.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum FutureKind {
    /// We are awaiting a normal future
    Future,
    /// We are awaiting something that's known to be an AsyncIterator (i.e. we are in the header of
    /// a `for await` loop)
    AsyncIterator,
}
