use crate::errors;
use crate::thir::cx::region::Scope;
use crate::thir::cx::Cx;
use crate::thir::util::UserAnnotatedTyHelpers;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_index::Idx;
use rustc_middle::hir::place::Place as HirPlace;
use rustc_middle::hir::place::PlaceBase as HirPlaceBase;
use rustc_middle::hir::place::ProjectionKind as HirProjectionKind;
use rustc_middle::middle::region;
use rustc_middle::mir::{self, BinOp, BorrowKind, UnOp};
use rustc_middle::thir::*;
use rustc_middle::ty::adjustment::{
    Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, PointerCoercion,
};
use rustc_middle::ty::GenericArgs;
use rustc_middle::ty::{
    self, AdtKind, InlineConstArgs, InlineConstArgsParts, ScalarInt, Ty, UpvarArgs, UserType,
};
use rustc_span::{sym, Span};
use rustc_target::abi::{FieldIdx, FIRST_VARIANT};

impl<'tcx> Cx<'tcx> {
    pub(crate) fn mirror_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) -> ExprId {
        // `mirror_expr` is recursing very deep. Make sure the stack doesn't overflow.
        ensure_sufficient_stack(|| self.mirror_expr_inner(expr))
    }

    pub(crate) fn mirror_exprs(&mut self, exprs: &'tcx [hir::Expr<'tcx>]) -> Box<[ExprId]> {
        exprs.iter().map(|expr| self.mirror_expr_inner(expr)).collect()
    }

    #[instrument(level = "trace", skip(self, hir_expr))]
    pub(super) fn mirror_expr_inner(&mut self, hir_expr: &'tcx hir::Expr<'tcx>) -> ExprId {
        let expr_scope =
            region::Scope { id: hir_expr.hir_id.local_id, data: region::ScopeData::Node };

        trace!(?hir_expr.hir_id, ?hir_expr.span);

        let mut expr = self.make_mirror_unadjusted(hir_expr);

        trace!(?expr.ty);

        // Now apply adjustments, if any.
        if self.apply_adjustments {
            for adjustment in self.typeck_results.expr_adjustments(hir_expr) {
                trace!(?expr, ?adjustment);
                let span = expr.span;
                expr = self.apply_adjustment(hir_expr, expr, adjustment, span);
            }
        }

        trace!(?expr.ty, "after adjustments");

        // Next, wrap this up in the expr's scope.
        expr = Expr {
            temp_lifetime: expr.temp_lifetime,
            ty: expr.ty,
            span: hir_expr.span,
            kind: ExprKind::Scope {
                region_scope: expr_scope,
                value: self.thir.exprs.push(expr),
                lint_level: LintLevel::Explicit(hir_expr.hir_id),
            },
        };

        // Finally, create a destruction scope, if any.
        if let Some(region_scope) =
            self.region_scope_tree.opt_destruction_scope(hir_expr.hir_id.local_id)
        {
            expr = Expr {
                temp_lifetime: expr.temp_lifetime,
                ty: expr.ty,
                span: hir_expr.span,
                kind: ExprKind::Scope {
                    region_scope,
                    value: self.thir.exprs.push(expr),
                    lint_level: LintLevel::Inherited,
                },
            };
        }

        // OK, all done!
        self.thir.exprs.push(expr)
    }

    fn apply_adjustment(
        &mut self,
        hir_expr: &'tcx hir::Expr<'tcx>,
        mut expr: Expr<'tcx>,
        adjustment: &Adjustment<'tcx>,
        mut span: Span,
    ) -> Expr<'tcx> {
        let Expr { temp_lifetime, .. } = expr;

        // Adjust the span from the block, to the last expression of the
        // block. This is a better span when returning a mutable reference
        // with too short a lifetime. The error message will use the span
        // from the assignment to the return place, which should only point
        // at the returned value, not the entire function body.
        //
        // fn return_short_lived<'a>(x: &'a mut i32) -> &'static mut i32 {
        //      x
        //   // ^ error message points at this expression.
        // }
        let mut adjust_span = |expr: &mut Expr<'tcx>| {
            if let ExprKind::Block { block } = expr.kind {
                if let Some(last_expr) = self.thir[block].expr {
                    span = self.thir[last_expr].span;
                    expr.span = span;
                }
            }
        };

        let kind = match adjustment.kind {
            Adjust::Pointer(PointerCoercion::Unsize) => {
                adjust_span(&mut expr);
                ExprKind::PointerCoercion {
                    cast: PointerCoercion::Unsize,
                    source: self.thir.exprs.push(expr),
                }
            }
            Adjust::Pointer(cast) => {
                ExprKind::PointerCoercion { cast, source: self.thir.exprs.push(expr) }
            }
            Adjust::NeverToAny if adjustment.target.is_never() => return expr,
            Adjust::NeverToAny => ExprKind::NeverToAny { source: self.thir.exprs.push(expr) },
            Adjust::Deref(None) => {
                adjust_span(&mut expr);
                ExprKind::Deref { arg: self.thir.exprs.push(expr) }
            }
            Adjust::Deref(Some(deref)) => {
                // We don't need to do call adjust_span here since
                // deref coercions always start with a built-in deref.
                let call = deref.method_call(self.tcx(), expr.ty);

                expr = Expr {
                    temp_lifetime,
                    ty: Ty::new_ref(
                        self.tcx,
                        deref.region,
                        ty::TypeAndMut { ty: expr.ty, mutbl: deref.mutbl },
                    ),
                    span,
                    kind: ExprKind::Borrow {
                        borrow_kind: deref.mutbl.to_borrow_kind(),
                        arg: self.thir.exprs.push(expr),
                    },
                };

                let expr = Box::new([self.thir.exprs.push(expr)]);

                self.overloaded_place(hir_expr, adjustment.target, Some(call), expr, deref.span)
            }
            Adjust::Borrow(AutoBorrow::Ref(_, m)) => ExprKind::Borrow {
                borrow_kind: m.to_borrow_kind(),
                arg: self.thir.exprs.push(expr),
            },
            Adjust::Borrow(AutoBorrow::RawPtr(mutability)) => {
                ExprKind::AddressOf { mutability, arg: self.thir.exprs.push(expr) }
            }
            Adjust::DynStar => ExprKind::Cast { source: self.thir.exprs.push(expr) },
        };

        Expr { temp_lifetime, ty: adjustment.target, span, kind }
    }

    /// Lowers a cast expression.
    ///
    /// Dealing with user type annotations is left to the caller.
    fn mirror_expr_cast(
        &mut self,
        source: &'tcx hir::Expr<'tcx>,
        temp_lifetime: Option<Scope>,
        span: Span,
    ) -> ExprKind<'tcx> {
        let tcx = self.tcx;

        // Check to see if this cast is a "coercion cast", where the cast is actually done
        // using a coercion (or is a no-op).
        if self.typeck_results().is_coercion_cast(source.hir_id) {
            // Convert the lexpr to a vexpr.
            ExprKind::Use { source: self.mirror_expr(source) }
        } else if self.typeck_results().expr_ty(source).is_ref() {
            // Special cased so that we can type check that the element
            // type of the source matches the pointed to type of the
            // destination.
            ExprKind::PointerCoercion {
                source: self.mirror_expr(source),
                cast: PointerCoercion::ArrayToPointer,
            }
        } else {
            // check whether this is casting an enum variant discriminant
            // to prevent cycles, we refer to the discriminant initializer
            // which is always an integer and thus doesn't need to know the
            // enum's layout (or its tag type) to compute it during const eval
            // Example:
            // enum Foo {
            //     A,
            //     B = A as isize + 4,
            // }
            // The correct solution would be to add symbolic computations to miri,
            // so we wouldn't have to compute and store the actual value

            let hir::ExprKind::Path(ref qpath) = source.kind else {
                return ExprKind::Cast { source: self.mirror_expr(source) };
            };

            let res = self.typeck_results().qpath_res(qpath, source.hir_id);
            let ty = self.typeck_results().node_type(source.hir_id);
            let ty::Adt(adt_def, args) = ty.kind() else {
                return ExprKind::Cast { source: self.mirror_expr(source) };
            };

            let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), variant_ctor_id) = res
            else {
                return ExprKind::Cast { source: self.mirror_expr(source) };
            };

            let idx = adt_def.variant_index_with_ctor_id(variant_ctor_id);
            let (discr_did, discr_offset) = adt_def.discriminant_def_for_variant(idx);

            use rustc_middle::ty::util::IntTypeExt;
            let ty = adt_def.repr().discr_type();
            let discr_ty = ty.to_ty(tcx);

            let param_env_ty = self.param_env.and(discr_ty);
            let size = tcx
                .layout_of(param_env_ty)
                .unwrap_or_else(|e| panic!("could not compute layout for {param_env_ty:?}: {e:?}"))
                .size;

            let lit = ScalarInt::try_from_uint(discr_offset as u128, size).unwrap();
            let kind = ExprKind::NonHirLiteral { lit, user_ty: None };
            let offset = self.thir.exprs.push(Expr { temp_lifetime, ty: discr_ty, span, kind });

            let source = match discr_did {
                // in case we are offsetting from a computed discriminant
                // and not the beginning of discriminants (which is always `0`)
                Some(did) => {
                    let kind = ExprKind::NamedConst { def_id: did, args, user_ty: None };
                    let lhs =
                        self.thir.exprs.push(Expr { temp_lifetime, ty: discr_ty, span, kind });
                    let bin = ExprKind::Binary { op: BinOp::Add, lhs, rhs: offset };
                    self.thir.exprs.push(Expr {
                        temp_lifetime,
                        ty: discr_ty,
                        span: span,
                        kind: bin,
                    })
                }
                None => offset,
            };

            ExprKind::Cast { source }
        }
    }

    #[instrument(level = "debug", skip(self), ret)]
    fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> {
        let tcx = self.tcx;
        let expr_ty = self.typeck_results().expr_ty(expr);
        let temp_lifetime =
            self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);

        let kind = match expr.kind {
            // Here comes the interesting stuff:
            hir::ExprKind::MethodCall(segment, receiver, ref args, fn_span) => {
                // Rewrite a.b(c) into UFCS form like Trait::b(a, c)
                let expr = self.method_callee(expr, segment.ident.span, None);
                info!("Using method span: {:?}", expr.span);
                let args = std::iter::once(receiver)
                    .chain(args.iter())
                    .map(|expr| self.mirror_expr(expr))
                    .collect();
                ExprKind::Call {
                    ty: expr.ty,
                    fun: self.thir.exprs.push(expr),
                    args,
                    from_hir_call: true,
                    fn_span,
                }
            }

            hir::ExprKind::Call(ref fun, ref args) => {
                if self.typeck_results().is_method_call(expr) {
                    // The callee is something implementing Fn, FnMut, or FnOnce.
                    // Find the actual method implementation being called and
                    // build the appropriate UFCS call expression with the
                    // callee-object as expr parameter.

                    // rewrite f(u, v) into FnOnce::call_once(f, (u, v))

                    let method = self.method_callee(expr, fun.span, None);

                    let arg_tys = args.iter().map(|e| self.typeck_results().expr_ty_adjusted(e));
                    let tupled_args = Expr {
                        ty: Ty::new_tup_from_iter(tcx, arg_tys),
                        temp_lifetime,
                        span: expr.span,
                        kind: ExprKind::Tuple { fields: self.mirror_exprs(args) },
                    };
                    let tupled_args = self.thir.exprs.push(tupled_args);

                    ExprKind::Call {
                        ty: method.ty,
                        fun: self.thir.exprs.push(method),
                        args: Box::new([self.mirror_expr(fun), tupled_args]),
                        from_hir_call: true,
                        fn_span: expr.span,
                    }
                } else {
                    let attrs = tcx.hir().attrs(expr.hir_id);
                    if attrs.iter().any(|a| a.name_or_empty() == sym::rustc_box) {
                        if attrs.len() != 1 {
                            tcx.sess.emit_err(errors::RustcBoxAttributeError {
                                span: attrs[0].span,
                                reason: errors::RustcBoxAttrReason::Attributes,
                            });
                        } else if let Some(box_item) = tcx.lang_items().owned_box() {
                            if let hir::ExprKind::Path(hir::QPath::TypeRelative(ty, fn_path)) = fun.kind
                                && let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind
                                && path.res.opt_def_id().is_some_and(|did| did == box_item)
                                && fn_path.ident.name == sym::new
                                && let [value] = args
                            {
                                return Expr { temp_lifetime, ty: expr_ty, span: expr.span, kind: ExprKind::Box { value: self.mirror_expr(value) } }
                            } else {
                                tcx.sess.emit_err(errors::RustcBoxAttributeError {
                                    span: expr.span,
                                    reason: errors::RustcBoxAttrReason::NotBoxNew
                                });
                            }
                        } else {
                            tcx.sess.emit_err(errors::RustcBoxAttributeError {
                                span: attrs[0].span,
                                reason: errors::RustcBoxAttrReason::MissingBox,
                            });
                        }
                    }

                    // Tuple-like ADTs are represented as ExprKind::Call. We convert them here.
                    let adt_data = if let hir::ExprKind::Path(ref qpath) = fun.kind
                    && let Some(adt_def) = expr_ty.ty_adt_def() {
                        match qpath {
                            hir::QPath::Resolved(_, ref path) => {
                                match path.res {
                                    Res::Def(DefKind::Ctor(_, CtorKind::Fn), ctor_id) => {
                                        Some((adt_def, adt_def.variant_index_with_ctor_id(ctor_id)))
                                    }
                                    Res::SelfCtor(..) => Some((adt_def, FIRST_VARIANT)),
                                    _ => None,
                                }
                            }
                            hir::QPath::TypeRelative(_ty, _) => {
                                if let Some((DefKind::Ctor(_, CtorKind::Fn), ctor_id)) =
                                    self.typeck_results().type_dependent_def(fun.hir_id)
                                {
                                    Some((adt_def, adt_def.variant_index_with_ctor_id(ctor_id)))
                                } else {
                                    None
                                }

                            }
                            _ => None,
                        }
                    } else {
                        None
                    };
                    if let Some((adt_def, index)) = adt_data {
                        let node_args = self.typeck_results().node_args(fun.hir_id);
                        let user_provided_types = self.typeck_results().user_provided_types();
                        let user_ty =
                            user_provided_types.get(fun.hir_id).copied().map(|mut u_ty| {
                                if let UserType::TypeOf(ref mut did, _) = &mut u_ty.value {
                                    *did = adt_def.did();
                                }
                                Box::new(u_ty)
                            });
                        debug!("make_mirror_unadjusted: (call) user_ty={:?}", user_ty);

                        let field_refs = args
                            .iter()
                            .enumerate()
                            .map(|(idx, e)| FieldExpr {
                                name: FieldIdx::new(idx),
                                expr: self.mirror_expr(e),
                            })
                            .collect();
                        ExprKind::Adt(Box::new(AdtExpr {
                            adt_def,
                            args: node_args,
                            variant_index: index,
                            fields: field_refs,
                            user_ty,
                            base: None,
                        }))
                    } else {
                        ExprKind::Call {
                            ty: self.typeck_results().node_type(fun.hir_id),
                            fun: self.mirror_expr(fun),
                            args: self.mirror_exprs(args),
                            from_hir_call: true,
                            fn_span: expr.span,
                        }
                    }
                }
            }

            hir::ExprKind::AddrOf(hir::BorrowKind::Ref, mutbl, ref arg) => {
                ExprKind::Borrow { borrow_kind: mutbl.to_borrow_kind(), arg: self.mirror_expr(arg) }
            }

            hir::ExprKind::AddrOf(hir::BorrowKind::Raw, mutability, ref arg) => {
                ExprKind::AddressOf { mutability, arg: self.mirror_expr(arg) }
            }

            hir::ExprKind::Block(ref blk, _) => ExprKind::Block { block: self.mirror_block(blk) },

            hir::ExprKind::Assign(ref lhs, ref rhs, _) => {
                ExprKind::Assign { lhs: self.mirror_expr(lhs), rhs: self.mirror_expr(rhs) }
            }

            hir::ExprKind::AssignOp(op, ref lhs, ref rhs) => {
                if self.typeck_results().is_method_call(expr) {
                    let lhs = self.mirror_expr(lhs);
                    let rhs = self.mirror_expr(rhs);
                    self.overloaded_operator(expr, Box::new([lhs, rhs]))
                } else {
                    ExprKind::AssignOp {
                        op: bin_op(op.node),
                        lhs: self.mirror_expr(lhs),
                        rhs: self.mirror_expr(rhs),
                    }
                }
            }

            hir::ExprKind::Lit(ref lit) => ExprKind::Literal { lit, neg: false },

            hir::ExprKind::Binary(op, ref lhs, ref rhs) => {
                if self.typeck_results().is_method_call(expr) {
                    let lhs = self.mirror_expr(lhs);
                    let rhs = self.mirror_expr(rhs);
                    self.overloaded_operator(expr, Box::new([lhs, rhs]))
                } else {
                    // FIXME overflow
                    match op.node {
                        hir::BinOpKind::And => ExprKind::LogicalOp {
                            op: LogicalOp::And,
                            lhs: self.mirror_expr(lhs),
                            rhs: self.mirror_expr(rhs),
                        },
                        hir::BinOpKind::Or => ExprKind::LogicalOp {
                            op: LogicalOp::Or,
                            lhs: self.mirror_expr(lhs),
                            rhs: self.mirror_expr(rhs),
                        },
                        _ => {
                            let op = bin_op(op.node);
                            ExprKind::Binary {
                                op,
                                lhs: self.mirror_expr(lhs),
                                rhs: self.mirror_expr(rhs),
                            }
                        }
                    }
                }
            }

            hir::ExprKind::Index(ref lhs, ref index, brackets_span) => {
                if self.typeck_results().is_method_call(expr) {
                    let lhs = self.mirror_expr(lhs);
                    let index = self.mirror_expr(index);
                    self.overloaded_place(
                        expr,
                        expr_ty,
                        None,
                        Box::new([lhs, index]),
                        brackets_span,
                    )
                } else {
                    ExprKind::Index { lhs: self.mirror_expr(lhs), index: self.mirror_expr(index) }
                }
            }

            hir::ExprKind::Unary(hir::UnOp::Deref, ref arg) => {
                if self.typeck_results().is_method_call(expr) {
                    let arg = self.mirror_expr(arg);
                    self.overloaded_place(expr, expr_ty, None, Box::new([arg]), expr.span)
                } else {
                    ExprKind::Deref { arg: self.mirror_expr(arg) }
                }
            }

            hir::ExprKind::Unary(hir::UnOp::Not, ref arg) => {
                if self.typeck_results().is_method_call(expr) {
                    let arg = self.mirror_expr(arg);
                    self.overloaded_operator(expr, Box::new([arg]))
                } else {
                    ExprKind::Unary { op: UnOp::Not, arg: self.mirror_expr(arg) }
                }
            }

            hir::ExprKind::Unary(hir::UnOp::Neg, ref arg) => {
                if self.typeck_results().is_method_call(expr) {
                    let arg = self.mirror_expr(arg);
                    self.overloaded_operator(expr, Box::new([arg]))
                } else if let hir::ExprKind::Lit(ref lit) = arg.kind {
                    ExprKind::Literal { lit, neg: true }
                } else {
                    ExprKind::Unary { op: UnOp::Neg, arg: self.mirror_expr(arg) }
                }
            }

            hir::ExprKind::Struct(ref qpath, ref fields, ref base) => match expr_ty.kind() {
                ty::Adt(adt, args) => match adt.adt_kind() {
                    AdtKind::Struct | AdtKind::Union => {
                        let user_provided_types = self.typeck_results().user_provided_types();
                        let user_ty = user_provided_types.get(expr.hir_id).copied().map(Box::new);
                        debug!("make_mirror_unadjusted: (struct/union) user_ty={:?}", user_ty);
                        ExprKind::Adt(Box::new(AdtExpr {
                            adt_def: *adt,
                            variant_index: FIRST_VARIANT,
                            args,
                            user_ty,
                            fields: self.field_refs(fields),
                            base: base.map(|base| FruInfo {
                                base: self.mirror_expr(base),
                                field_types: self.typeck_results().fru_field_types()[expr.hir_id]
                                    .iter()
                                    .copied()
                                    .collect(),
                            }),
                        }))
                    }
                    AdtKind::Enum => {
                        let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
                        match res {
                            Res::Def(DefKind::Variant, variant_id) => {
                                assert!(base.is_none());

                                let index = adt.variant_index_with_id(variant_id);
                                let user_provided_types =
                                    self.typeck_results().user_provided_types();
                                let user_ty =
                                    user_provided_types.get(expr.hir_id).copied().map(Box::new);
                                debug!("make_mirror_unadjusted: (variant) user_ty={:?}", user_ty);
                                ExprKind::Adt(Box::new(AdtExpr {
                                    adt_def: *adt,
                                    variant_index: index,
                                    args,
                                    user_ty,
                                    fields: self.field_refs(fields),
                                    base: None,
                                }))
                            }
                            _ => {
                                span_bug!(expr.span, "unexpected res: {:?}", res);
                            }
                        }
                    }
                },
                _ => {
                    span_bug!(expr.span, "unexpected type for struct literal: {:?}", expr_ty);
                }
            },

            hir::ExprKind::Closure { .. } => {
                let closure_ty = self.typeck_results().expr_ty(expr);
                let (def_id, args, movability) = match *closure_ty.kind() {
                    ty::Closure(def_id, args) => (def_id, UpvarArgs::Closure(args), None),
                    ty::Generator(def_id, args, movability) => {
                        (def_id, UpvarArgs::Generator(args), Some(movability))
                    }
                    _ => {
                        span_bug!(expr.span, "closure expr w/o closure type: {:?}", closure_ty);
                    }
                };
                let def_id = def_id.expect_local();

                let upvars = self
                    .tcx
                    .closure_captures(def_id)
                    .iter()
                    .zip(args.upvar_tys())
                    .map(|(captured_place, ty)| {
                        let upvars = self.capture_upvar(expr, captured_place, ty);
                        self.thir.exprs.push(upvars)
                    })
                    .collect();

                // Convert the closure fake reads, if any, from hir `Place` to ExprRef
                let fake_reads = match self.typeck_results.closure_fake_reads.get(&def_id) {
                    Some(fake_reads) => fake_reads
                        .iter()
                        .map(|(place, cause, hir_id)| {
                            let expr = self.convert_captured_hir_place(expr, place.clone());
                            (self.thir.exprs.push(expr), *cause, *hir_id)
                        })
                        .collect(),
                    None => Vec::new(),
                };

                ExprKind::Closure(Box::new(ClosureExpr {
                    closure_id: def_id,
                    args,
                    upvars,
                    movability,
                    fake_reads,
                }))
            }

            hir::ExprKind::Path(ref qpath) => {
                let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
                self.convert_path_expr(expr, res)
            }

            hir::ExprKind::InlineAsm(ref asm) => ExprKind::InlineAsm(Box::new(InlineAsmExpr {
                template: asm.template,
                operands: asm
                    .operands
                    .iter()
                    .map(|(op, _op_sp)| match *op {
                        hir::InlineAsmOperand::In { reg, ref expr } => {
                            InlineAsmOperand::In { reg, expr: self.mirror_expr(expr) }
                        }
                        hir::InlineAsmOperand::Out { reg, late, ref expr } => {
                            InlineAsmOperand::Out {
                                reg,
                                late,
                                expr: expr.map(|expr| self.mirror_expr(expr)),
                            }
                        }
                        hir::InlineAsmOperand::InOut { reg, late, ref expr } => {
                            InlineAsmOperand::InOut { reg, late, expr: self.mirror_expr(expr) }
                        }
                        hir::InlineAsmOperand::SplitInOut {
                            reg,
                            late,
                            ref in_expr,
                            ref out_expr,
                        } => InlineAsmOperand::SplitInOut {
                            reg,
                            late,
                            in_expr: self.mirror_expr(in_expr),
                            out_expr: out_expr.map(|expr| self.mirror_expr(expr)),
                        },
                        hir::InlineAsmOperand::Const { ref anon_const } => {
                            let value = mir::ConstantKind::from_anon_const(
                                tcx,
                                anon_const.def_id,
                                self.param_env,
                            );
                            let span = tcx.def_span(anon_const.def_id);

                            InlineAsmOperand::Const { value, span }
                        }
                        hir::InlineAsmOperand::SymFn { ref anon_const } => {
                            let value = mir::ConstantKind::from_anon_const(
                                tcx,
                                anon_const.def_id,
                                self.param_env,
                            );
                            let span = tcx.def_span(anon_const.def_id);

                            InlineAsmOperand::SymFn { value, span }
                        }
                        hir::InlineAsmOperand::SymStatic { path: _, def_id } => {
                            InlineAsmOperand::SymStatic { def_id }
                        }
                    })
                    .collect(),
                options: asm.options,
                line_spans: asm.line_spans,
            })),

            hir::ExprKind::OffsetOf(_, _) => {
                let data = self.typeck_results.offset_of_data();
                let &(container, ref indices) = data.get(expr.hir_id).unwrap();
                let fields = tcx.mk_fields_from_iter(indices.iter().copied());

                ExprKind::OffsetOf { container, fields }
            }

            hir::ExprKind::ConstBlock(ref anon_const) => {
                let ty = self.typeck_results().node_type(anon_const.hir_id);
                let did = anon_const.def_id.to_def_id();
                let typeck_root_def_id = tcx.typeck_root_def_id(did);
                let parent_args =
                    tcx.erase_regions(GenericArgs::identity_for_item(tcx, typeck_root_def_id));
                let args = InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }).args;

                ExprKind::ConstBlock { did, args }
            }
            // Now comes the rote stuff:
            hir::ExprKind::Repeat(ref v, _) => {
                let ty = self.typeck_results().expr_ty(expr);
                let ty::Array(_, count) = ty.kind() else {
                    span_bug!(expr.span, "unexpected repeat expr ty: {:?}", ty);
                };

                ExprKind::Repeat { value: self.mirror_expr(v), count: *count }
            }
            hir::ExprKind::Ret(v) => ExprKind::Return { value: v.map(|v| self.mirror_expr(v)) },
            hir::ExprKind::Become(call) => ExprKind::Become { value: self.mirror_expr(call) },
            hir::ExprKind::Break(dest, ref value) => match dest.target_id {
                Ok(target_id) => ExprKind::Break {
                    label: region::Scope { id: target_id.local_id, data: region::ScopeData::Node },
                    value: value.map(|value| self.mirror_expr(value)),
                },
                Err(err) => bug!("invalid loop id for break: {}", err),
            },
            hir::ExprKind::Continue(dest) => match dest.target_id {
                Ok(loop_id) => ExprKind::Continue {
                    label: region::Scope { id: loop_id.local_id, data: region::ScopeData::Node },
                },
                Err(err) => bug!("invalid loop id for continue: {}", err),
            },
            hir::ExprKind::Let(let_expr) => ExprKind::Let {
                expr: self.mirror_expr(let_expr.init),
                pat: self.pattern_from_hir(let_expr.pat),
            },
            hir::ExprKind::If(cond, then, else_opt) => ExprKind::If {
                if_then_scope: region::Scope {
                    id: then.hir_id.local_id,
                    data: region::ScopeData::IfThen,
                },
                cond: self.mirror_expr(cond),
                then: self.mirror_expr(then),
                else_opt: else_opt.map(|el| self.mirror_expr(el)),
            },
            hir::ExprKind::Match(ref discr, ref arms, _) => ExprKind::Match {
                scrutinee: self.mirror_expr(discr),
                arms: arms.iter().map(|a| self.convert_arm(a)).collect(),
            },
            hir::ExprKind::Loop(ref body, ..) => {
                let block_ty = self.typeck_results().node_type(body.hir_id);
                let temp_lifetime = self
                    .rvalue_scopes
                    .temporary_scope(self.region_scope_tree, body.hir_id.local_id);
                let block = self.mirror_block(body);
                let body = self.thir.exprs.push(Expr {
                    ty: block_ty,
                    temp_lifetime,
                    span: self.thir[block].span,
                    kind: ExprKind::Block { block },
                });
                ExprKind::Loop { body }
            }
            hir::ExprKind::Field(ref source, ..) => ExprKind::Field {
                lhs: self.mirror_expr(source),
                variant_index: FIRST_VARIANT,
                name: self.typeck_results.field_index(expr.hir_id),
            },
            hir::ExprKind::Cast(ref source, ref cast_ty) => {
                // Check for a user-given type annotation on this `cast`
                let user_provided_types = self.typeck_results.user_provided_types();
                let user_ty = user_provided_types.get(cast_ty.hir_id);

                debug!(
                    "cast({:?}) has ty w/ hir_id {:?} and user provided ty {:?}",
                    expr, cast_ty.hir_id, user_ty,
                );

                let cast = self.mirror_expr_cast(*source, temp_lifetime, expr.span);

                if let Some(user_ty) = user_ty {
                    // NOTE: Creating a new Expr and wrapping a Cast inside of it may be
                    //       inefficient, revisit this when performance becomes an issue.
                    let cast_expr = self.thir.exprs.push(Expr {
                        temp_lifetime,
                        ty: expr_ty,
                        span: expr.span,
                        kind: cast,
                    });
                    debug!("make_mirror_unadjusted: (cast) user_ty={:?}", user_ty);

                    ExprKind::ValueTypeAscription {
                        source: cast_expr,
                        user_ty: Some(Box::new(*user_ty)),
                    }
                } else {
                    cast
                }
            }
            hir::ExprKind::Type(ref source, ref ty) => {
                let user_provided_types = self.typeck_results.user_provided_types();
                let user_ty = user_provided_types.get(ty.hir_id).copied().map(Box::new);
                debug!("make_mirror_unadjusted: (type) user_ty={:?}", user_ty);
                let mirrored = self.mirror_expr(source);
                if source.is_syntactic_place_expr() {
                    ExprKind::PlaceTypeAscription { source: mirrored, user_ty }
                } else {
                    ExprKind::ValueTypeAscription { source: mirrored, user_ty }
                }
            }
            hir::ExprKind::DropTemps(ref source) => {
                ExprKind::Use { source: self.mirror_expr(source) }
            }
            hir::ExprKind::Array(ref fields) => {
                ExprKind::Array { fields: self.mirror_exprs(fields) }
            }
            hir::ExprKind::Tup(ref fields) => ExprKind::Tuple { fields: self.mirror_exprs(fields) },

            hir::ExprKind::Yield(ref v, _) => ExprKind::Yield { value: self.mirror_expr(v) },
            hir::ExprKind::Err(_) => unreachable!(),
        };

        Expr { temp_lifetime, ty: expr_ty, span: expr.span, kind }
    }

    fn user_args_applied_to_res(
        &mut self,
        hir_id: hir::HirId,
        res: Res,
    ) -> Option<Box<ty::CanonicalUserType<'tcx>>> {
        debug!("user_args_applied_to_res: res={:?}", res);
        let user_provided_type = match res {
            // A reference to something callable -- e.g., a fn, method, or
            // a tuple-struct or tuple-variant. This has the type of a
            // `Fn` but with the user-given substitutions.
            Res::Def(DefKind::Fn, _)
            | Res::Def(DefKind::AssocFn, _)
            | Res::Def(DefKind::Ctor(_, CtorKind::Fn), _)
            | Res::Def(DefKind::Const, _)
            | Res::Def(DefKind::AssocConst, _) => {
                self.typeck_results().user_provided_types().get(hir_id).copied().map(Box::new)
            }

            // A unit struct/variant which is used as a value (e.g.,
            // `None`). This has the type of the enum/struct that defines
            // this variant -- but with the substitutions given by the
            // user.
            Res::Def(DefKind::Ctor(_, CtorKind::Const), _) => {
                self.user_args_applied_to_ty_of_hir_id(hir_id).map(Box::new)
            }

            // `Self` is used in expression as a tuple struct constructor or a unit struct constructor
            Res::SelfCtor(_) => self.user_args_applied_to_ty_of_hir_id(hir_id).map(Box::new),

            _ => bug!("user_args_applied_to_res: unexpected res {:?} at {:?}", res, hir_id),
        };
        debug!("user_args_applied_to_res: user_provided_type={:?}", user_provided_type);
        user_provided_type
    }

    fn method_callee(
        &mut self,
        expr: &hir::Expr<'_>,
        span: Span,
        overloaded_callee: Option<Ty<'tcx>>,
    ) -> Expr<'tcx> {
        let temp_lifetime =
            self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
        let (ty, user_ty) = match overloaded_callee {
            Some(fn_def) => (fn_def, None),
            None => {
                let (kind, def_id) =
                    self.typeck_results().type_dependent_def(expr.hir_id).unwrap_or_else(|| {
                        span_bug!(expr.span, "no type-dependent def for method callee")
                    });
                let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(kind, def_id));
                debug!("method_callee: user_ty={:?}", user_ty);
                (
                    Ty::new_fn_def(
                        self.tcx(),
                        def_id,
                        self.typeck_results().node_args(expr.hir_id),
                    ),
                    user_ty,
                )
            }
        };
        Expr { temp_lifetime, ty, span, kind: ExprKind::ZstLiteral { user_ty } }
    }

    fn convert_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) -> ArmId {
        let arm = Arm {
            pattern: self.pattern_from_hir(&arm.pat),
            guard: arm.guard.as_ref().map(|g| match g {
                hir::Guard::If(ref e) => Guard::If(self.mirror_expr(e)),
                hir::Guard::IfLet(ref l) => {
                    Guard::IfLet(self.pattern_from_hir(l.pat), self.mirror_expr(l.init))
                }
            }),
            body: self.mirror_expr(arm.body),
            lint_level: LintLevel::Explicit(arm.hir_id),
            scope: region::Scope { id: arm.hir_id.local_id, data: region::ScopeData::Node },
            span: arm.span,
        };
        self.thir.arms.push(arm)
    }

    fn convert_path_expr(&mut self, expr: &'tcx hir::Expr<'tcx>, res: Res) -> ExprKind<'tcx> {
        let args = self.typeck_results().node_args(expr.hir_id);
        match res {
            // A regular function, constructor function or a constant.
            Res::Def(DefKind::Fn, _)
            | Res::Def(DefKind::AssocFn, _)
            | Res::Def(DefKind::Ctor(_, CtorKind::Fn), _)
            | Res::SelfCtor(_) => {
                let user_ty = self.user_args_applied_to_res(expr.hir_id, res);
                ExprKind::ZstLiteral { user_ty }
            }

            Res::Def(DefKind::ConstParam, def_id) => {
                let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id.expect_local());
                let generics = self.tcx.generics_of(hir_id.owner);
                let index = generics.param_def_id_to_index[&def_id];
                let name = self.tcx.hir().name(hir_id);
                let param = ty::ParamConst::new(index, name);

                ExprKind::ConstParam { param, def_id }
            }

            Res::Def(DefKind::Const, def_id) | Res::Def(DefKind::AssocConst, def_id) => {
                let user_ty = self.user_args_applied_to_res(expr.hir_id, res);
                ExprKind::NamedConst { def_id, args, user_ty }
            }

            Res::Def(DefKind::Ctor(_, CtorKind::Const), def_id) => {
                let user_provided_types = self.typeck_results.user_provided_types();
                let user_ty = user_provided_types.get(expr.hir_id).copied().map(Box::new);
                debug!("convert_path_expr: user_ty={:?}", user_ty);
                let ty = self.typeck_results().node_type(expr.hir_id);
                match ty.kind() {
                    // A unit struct/variant which is used as a value.
                    // We return a completely different ExprKind here to account for this special case.
                    ty::Adt(adt_def, args) => ExprKind::Adt(Box::new(AdtExpr {
                        adt_def: *adt_def,
                        variant_index: adt_def.variant_index_with_ctor_id(def_id),
                        args,
                        user_ty,
                        fields: Box::new([]),
                        base: None,
                    })),
                    _ => bug!("unexpected ty: {:?}", ty),
                }
            }

            // We encode uses of statics as a `*&STATIC` where the `&STATIC` part is
            // a constant reference (or constant raw pointer for `static mut`) in MIR
            Res::Def(DefKind::Static(_), id) => {
                let ty = self.tcx.static_ptr_ty(id);
                let temp_lifetime = self
                    .rvalue_scopes
                    .temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
                let kind = if self.tcx.is_thread_local_static(id) {
                    ExprKind::ThreadLocalRef(id)
                } else {
                    let alloc_id = self.tcx.create_static_alloc(id);
                    ExprKind::StaticRef { alloc_id, ty, def_id: id }
                };
                ExprKind::Deref {
                    arg: self.thir.exprs.push(Expr { ty, temp_lifetime, span: expr.span, kind }),
                }
            }

            Res::Local(var_hir_id) => self.convert_var(var_hir_id),

            _ => span_bug!(expr.span, "res `{:?}` not yet implemented", res),
        }
    }

    fn convert_var(&mut self, var_hir_id: hir::HirId) -> ExprKind<'tcx> {
        // We want upvars here not captures.
        // Captures will be handled in MIR.
        let is_upvar = self
            .tcx
            .upvars_mentioned(self.body_owner)
            .is_some_and(|upvars| upvars.contains_key(&var_hir_id));

        debug!(
            "convert_var({:?}): is_upvar={}, body_owner={:?}",
            var_hir_id, is_upvar, self.body_owner
        );

        if is_upvar {
            ExprKind::UpvarRef {
                closure_def_id: self.body_owner,
                var_hir_id: LocalVarId(var_hir_id),
            }
        } else {
            ExprKind::VarRef { id: LocalVarId(var_hir_id) }
        }
    }

    fn overloaded_operator(
        &mut self,
        expr: &'tcx hir::Expr<'tcx>,
        args: Box<[ExprId]>,
    ) -> ExprKind<'tcx> {
        let fun = self.method_callee(expr, expr.span, None);
        let fun = self.thir.exprs.push(fun);
        ExprKind::Call {
            ty: self.thir[fun].ty,
            fun,
            args,
            from_hir_call: false,
            fn_span: expr.span,
        }
    }

    fn overloaded_place(
        &mut self,
        expr: &'tcx hir::Expr<'tcx>,
        place_ty: Ty<'tcx>,
        overloaded_callee: Option<Ty<'tcx>>,
        args: Box<[ExprId]>,
        span: Span,
    ) -> ExprKind<'tcx> {
        // For an overloaded *x or x[y] expression of type T, the method
        // call returns an &T and we must add the deref so that the types
        // line up (this is because `*x` and `x[y]` represent places):

        // Reconstruct the output assuming it's a reference with the
        // same region and mutability as the receiver. This holds for
        // `Deref(Mut)::Deref(_mut)` and `Index(Mut)::index(_mut)`.
        let ty::Ref(region, _, mutbl) = *self.thir[args[0]].ty.kind() else {
            span_bug!(span, "overloaded_place: receiver is not a reference");
        };
        let ref_ty = Ty::new_ref(self.tcx, region, ty::TypeAndMut { ty: place_ty, mutbl });

        // construct the complete expression `foo()` for the overloaded call,
        // which will yield the &T type
        let temp_lifetime =
            self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
        let fun = self.method_callee(expr, span, overloaded_callee);
        let fun = self.thir.exprs.push(fun);
        let fun_ty = self.thir[fun].ty;
        let ref_expr = self.thir.exprs.push(Expr {
            temp_lifetime,
            ty: ref_ty,
            span,
            kind: ExprKind::Call { ty: fun_ty, fun, args, from_hir_call: false, fn_span: span },
        });

        // construct and return a deref wrapper `*foo()`
        ExprKind::Deref { arg: ref_expr }
    }

    fn convert_captured_hir_place(
        &mut self,
        closure_expr: &'tcx hir::Expr<'tcx>,
        place: HirPlace<'tcx>,
    ) -> Expr<'tcx> {
        let temp_lifetime = self
            .rvalue_scopes
            .temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id);
        let var_ty = place.base_ty;

        // The result of capture analysis in `rustc_hir_analysis/check/upvar.rs`represents a captured path
        // as it's seen for use within the closure and not at the time of closure creation.
        //
        // That is we see expect to see it start from a captured upvar and not something that is local
        // to the closure's parent.
        let var_hir_id = match place.base {
            HirPlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id,
            base => bug!("Expected an upvar, found {:?}", base),
        };

        let mut captured_place_expr = Expr {
            temp_lifetime,
            ty: var_ty,
            span: closure_expr.span,
            kind: self.convert_var(var_hir_id),
        };

        for proj in place.projections.iter() {
            let kind = match proj.kind {
                HirProjectionKind::Deref => {
                    ExprKind::Deref { arg: self.thir.exprs.push(captured_place_expr) }
                }
                HirProjectionKind::Field(field, variant_index) => ExprKind::Field {
                    lhs: self.thir.exprs.push(captured_place_expr),
                    variant_index,
                    name: field,
                },
                HirProjectionKind::OpaqueCast => {
                    ExprKind::Use { source: self.thir.exprs.push(captured_place_expr) }
                }
                HirProjectionKind::Index | HirProjectionKind::Subslice => {
                    // We don't capture these projections, so we can ignore them here
                    continue;
                }
            };

            captured_place_expr =
                Expr { temp_lifetime, ty: proj.ty, span: closure_expr.span, kind };
        }

        captured_place_expr
    }

    fn capture_upvar(
        &mut self,
        closure_expr: &'tcx hir::Expr<'tcx>,
        captured_place: &'tcx ty::CapturedPlace<'tcx>,
        upvar_ty: Ty<'tcx>,
    ) -> Expr<'tcx> {
        let upvar_capture = captured_place.info.capture_kind;
        let captured_place_expr =
            self.convert_captured_hir_place(closure_expr, captured_place.place.clone());
        let temp_lifetime = self
            .rvalue_scopes
            .temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id);

        match upvar_capture {
            ty::UpvarCapture::ByValue => captured_place_expr,
            ty::UpvarCapture::ByRef(upvar_borrow) => {
                let borrow_kind = match upvar_borrow {
                    ty::BorrowKind::ImmBorrow => BorrowKind::Shared,
                    ty::BorrowKind::UniqueImmBorrow => {
                        BorrowKind::Mut { kind: mir::MutBorrowKind::ClosureCapture }
                    }
                    ty::BorrowKind::MutBorrow => {
                        BorrowKind::Mut { kind: mir::MutBorrowKind::Default }
                    }
                };
                Expr {
                    temp_lifetime,
                    ty: upvar_ty,
                    span: closure_expr.span,
                    kind: ExprKind::Borrow {
                        borrow_kind,
                        arg: self.thir.exprs.push(captured_place_expr),
                    },
                }
            }
        }
    }

    /// Converts a list of named fields (i.e., for struct-like struct/enum ADTs) into FieldExpr.
    fn field_refs(&mut self, fields: &'tcx [hir::ExprField<'tcx>]) -> Box<[FieldExpr]> {
        fields
            .iter()
            .map(|field| FieldExpr {
                name: self.typeck_results.field_index(field.hir_id),
                expr: self.mirror_expr(field.expr),
            })
            .collect()
    }
}

trait ToBorrowKind {
    fn to_borrow_kind(&self) -> BorrowKind;
}

impl ToBorrowKind for AutoBorrowMutability {
    fn to_borrow_kind(&self) -> BorrowKind {
        use rustc_middle::ty::adjustment::AllowTwoPhase;
        match *self {
            AutoBorrowMutability::Mut { allow_two_phase_borrow } => BorrowKind::Mut {
                kind: match allow_two_phase_borrow {
                    AllowTwoPhase::Yes => mir::MutBorrowKind::TwoPhaseBorrow,
                    AllowTwoPhase::No => mir::MutBorrowKind::Default,
                },
            },
            AutoBorrowMutability::Not => BorrowKind::Shared,
        }
    }
}

impl ToBorrowKind for hir::Mutability {
    fn to_borrow_kind(&self) -> BorrowKind {
        match *self {
            hir::Mutability::Mut => BorrowKind::Mut { kind: mir::MutBorrowKind::Default },
            hir::Mutability::Not => BorrowKind::Shared,
        }
    }
}

fn bin_op(op: hir::BinOpKind) -> BinOp {
    match op {
        hir::BinOpKind::Add => BinOp::Add,
        hir::BinOpKind::Sub => BinOp::Sub,
        hir::BinOpKind::Mul => BinOp::Mul,
        hir::BinOpKind::Div => BinOp::Div,
        hir::BinOpKind::Rem => BinOp::Rem,
        hir::BinOpKind::BitXor => BinOp::BitXor,
        hir::BinOpKind::BitAnd => BinOp::BitAnd,
        hir::BinOpKind::BitOr => BinOp::BitOr,
        hir::BinOpKind::Shl => BinOp::Shl,
        hir::BinOpKind::Shr => BinOp::Shr,
        hir::BinOpKind::Eq => BinOp::Eq,
        hir::BinOpKind::Lt => BinOp::Lt,
        hir::BinOpKind::Le => BinOp::Le,
        hir::BinOpKind::Ne => BinOp::Ne,
        hir::BinOpKind::Ge => BinOp::Ge,
        hir::BinOpKind::Gt => BinOp::Gt,
        _ => bug!("no equivalent for ast binop {:?}", op),
    }
}
