// Implementation for lints detecting interior mutability in constants.
//
// For `declare_interior_mutable_const` there are three strategies used to
// determine if a value has interior mutability:
// * A type-based check. This is the least accurate, but can always run.
// * A const-eval based check. This is the most accurate, but this requires that the value is
//   defined and does not work with generics.
// * A HIR-tree based check. This is less accurate than const-eval, but it can be applied to generic
//   values.
//
// For `borrow_interior_mutable_const` the same three strategies are applied
// when checking a constant's value, but field and array index projections at
// the borrow site are taken into account as well. As an example: `FOO.bar` may
// have interior mutability, but `FOO.baz` may not. When borrowing `FOO.baz` no
// warning will be issued.
//
// No matter the lint or strategy, a warning should only be issued if a value
// definitely contains interior mutability.

use clippy_config::Conf;
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::is_in_const_context;
use clippy_utils::macros::macro_backtrace;
use clippy_utils::paths::{PathNS, lookup_path_str};
use clippy_utils::ty::{get_field_idx_by_name, implements_trait};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, DefIdSet};
use rustc_hir::{
    Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, Node, StructTailExpr, TraitItem, TraitItemKind, UnOp,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::mir::{ConstValue, UnevaluatedConst};
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::{
    self, AliasTyKind, EarlyBinder, GenericArgs, GenericArgsRef, Instance, Ty, TyCtxt, TypeFolder, TypeSuperFoldable,
    TypeckResults, TypingEnv,
};
use rustc_session::impl_lint_pass;
use rustc_span::{DUMMY_SP, sym};
use std::collections::hash_map::Entry;

declare_clippy_lint! {
    /// ### What it does
    /// Checks for the declaration of named constant which contain interior mutability.
    ///
    /// ### Why is this bad?
    /// Named constants are copied at every use site which means any change to their value
    /// will be lost after the newly created value is dropped. e.g.
    ///
    /// ```rust
    /// use core::sync::atomic::{AtomicUsize, Ordering};
    /// const ATOMIC: AtomicUsize = AtomicUsize::new(0);
    /// fn add_one() -> usize {
    ///     // This will always return `0` since `ATOMIC` is copied before it's used.
    ///     ATOMIC.fetch_add(1, Ordering::AcqRel)
    /// }
    /// ```
    ///
    /// If shared modification of the value is desired, a `static` item is needed instead.
    /// If that is not desired, a `const fn` constructor should be used to make it obvious
    /// at the use site that a new value is created.
    ///
    /// ### Known problems
    /// Prior to `const fn` stabilization this was the only way to provide a value which
    /// could initialize a `static` item (e.g. the `std::sync::ONCE_INIT` constant). In
    /// this case the use of `const` is required and this lint should be suppressed.
    ///
    /// There also exists types which contain private fields with interior mutability, but
    /// no way to both create a value as a constant and modify any mutable field using the
    /// type's public interface (e.g. `bytes::Bytes`). As there is no reasonable way to
    /// scan a crate's interface to see if this is the case, all such types will be linted.
    /// If this happens use the `ignore-interior-mutability` configuration option to allow
    /// the type.
    ///
    /// ### Example
    /// ```no_run
    /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
    ///
    /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
    /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged
    /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct
    /// ```
    ///
    /// Use instead:
    /// ```no_run
    /// # use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
    /// static STATIC_ATOM: AtomicUsize = AtomicUsize::new(15);
    /// STATIC_ATOM.store(9, SeqCst);
    /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub DECLARE_INTERIOR_MUTABLE_CONST,
    suspicious,
    "declaring `const` with interior mutability"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for a borrow of a named constant with interior mutability.
    ///
    /// ### Why is this bad?
    /// Named constants are copied at every use site which means any change to their value
    /// will be lost after the newly created value is dropped. e.g.
    ///
    /// ```rust
    /// use core::sync::atomic::{AtomicUsize, Ordering};
    /// const ATOMIC: AtomicUsize = AtomicUsize::new(0);
    /// fn add_one() -> usize {
    ///     // This will always return `0` since `ATOMIC` is copied before it's borrowed
    ///     // for use by `fetch_add`.
    ///     ATOMIC.fetch_add(1, Ordering::AcqRel)
    /// }
    /// ```
    ///
    /// ### Known problems
    /// This lint does not, and cannot in general, determine if the borrow of the constant
    /// is used in a way which causes a mutation. e.g.
    ///
    /// ```rust
    /// use core::cell::Cell;
    /// const CELL: Cell<usize> = Cell::new(0);
    /// fn get_cell() -> Cell<usize> {
    ///     // This is fine. It borrows a copy of `CELL`, but never mutates it through the
    ///     // borrow.
    ///     CELL.clone()
    /// }
    /// ```
    ///
    /// There also exists types which contain private fields with interior mutability, but
    /// no way to both create a value as a constant and modify any mutable field using the
    /// type's public interface (e.g. `bytes::Bytes`). As there is no reasonable way to
    /// scan a crate's interface to see if this is the case, all such types will be linted.
    /// If this happens use the `ignore-interior-mutability` configuration option to allow
    /// the type.
    ///
    /// ### Example
    /// ```no_run
    /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
    /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
    ///
    /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged
    /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct
    /// ```
    ///
    /// Use instead:
    /// ```no_run
    /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
    /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
    ///
    /// static STATIC_ATOM: AtomicUsize = CONST_ATOM;
    /// STATIC_ATOM.store(9, SeqCst);
    /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub BORROW_INTERIOR_MUTABLE_CONST,
    style,
    "referencing `const` with interior mutability"
}

#[derive(Clone, Copy)]
enum IsFreeze {
    /// The type and all possible values are `Freeze`
    Yes,
    /// The type itself is non-`Freeze`, but not all values are.
    Maybe,
    /// The type and all possible values are non-`Freeze`
    No,
}
impl IsFreeze {
    /// Merges the variants of a sum type (i.e. an enum).
    fn from_variants(iter: impl Iterator<Item = Self>) -> Self {
        iter.fold(Self::Yes, |x, y| match (x, y) {
            (Self::Maybe, _) | (_, Self::Maybe) | (Self::No, Self::Yes) | (Self::Yes, Self::No) => Self::Maybe,
            (Self::No, Self::No) => Self::No,
            (Self::Yes, Self::Yes) => Self::Yes,
        })
    }

    /// Merges the fields of a product type (e.g. a struct or tuple).
    fn from_fields(mut iter: impl Iterator<Item = Self>) -> Self {
        iter.try_fold(Self::Yes, |x, y| match (x, y) {
            (Self::No, _) | (_, Self::No) => None,
            (Self::Maybe, _) | (_, Self::Maybe) => Some(Self::Maybe),
            (Self::Yes, Self::Yes) => Some(Self::Yes),
        })
        .unwrap_or(Self::No)
    }

    /// Checks if this is definitely `Freeze`.
    fn is_freeze(self) -> bool {
        matches!(self, Self::Yes)
    }

    /// Checks if this is definitely not `Freeze`.
    fn is_not_freeze(self) -> bool {
        matches!(self, Self::No)
    }
}

/// What operation caused a borrow to occur.
#[derive(Clone, Copy)]
enum BorrowCause {
    Borrow,
    Deref,
    Index,
    AutoDeref,
    AutoBorrow,
    AutoDerefField,
}
impl BorrowCause {
    fn note(self) -> Option<&'static str> {
        match self {
            Self::Borrow => None,
            Self::Deref => Some("this deref expression is a call to `Deref::deref`"),
            Self::Index => Some("this index expression is a call to `Index::index`"),
            Self::AutoDeref => Some("there is a compiler inserted call to `Deref::deref` here"),
            Self::AutoBorrow => Some("there is a compiler inserted borrow here"),
            Self::AutoDerefField => {
                Some("there is a compiler inserted call to `Deref::deref` when accessing this field")
            },
        }
    }
}

/// The source of a borrow. Both what caused it and where.
struct BorrowSource<'tcx> {
    expr: &'tcx Expr<'tcx>,
    cause: BorrowCause,
}
impl<'tcx> BorrowSource<'tcx> {
    fn new(tcx: TyCtxt<'tcx>, expr: &'tcx Expr<'tcx>, cause: BorrowCause) -> Self {
        // Custom deref and index impls will always have an auto-borrow inserted since we
        // never work with reference types.
        let (expr, cause) = if matches!(cause, BorrowCause::AutoBorrow)
            && let Node::Expr(parent) = tcx.parent_hir_node(expr.hir_id)
        {
            match parent.kind {
                ExprKind::Unary(UnOp::Deref, _) => (parent, BorrowCause::Deref),
                ExprKind::Index(..) => (parent, BorrowCause::Index),
                ExprKind::Field(..) => (parent, BorrowCause::AutoDerefField),
                _ => (expr, cause),
            }
        } else {
            (expr, cause)
        };
        Self { expr, cause }
    }
}

pub struct NonCopyConst<'tcx> {
    ignore_tys: DefIdSet,
    // Cache checked types. We can recurse through a type multiple times so this
    // can be hit quite frequently.
    freeze_tys: FxHashMap<Ty<'tcx>, IsFreeze>,
}

impl_lint_pass!(NonCopyConst<'_> => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTERIOR_MUTABLE_CONST]);

impl<'tcx> NonCopyConst<'tcx> {
    pub fn new(tcx: TyCtxt<'tcx>, conf: &'static Conf) -> Self {
        Self {
            ignore_tys: conf
                .ignore_interior_mutability
                .iter()
                .flat_map(|ignored_ty| lookup_path_str(tcx, PathNS::Type, ignored_ty))
                .collect(),
            freeze_tys: FxHashMap::default(),
        }
    }

    /// Checks if a value of the given type is `Freeze`, or may be depending on the value.
    fn is_ty_freeze(&mut self, tcx: TyCtxt<'tcx>, typing_env: TypingEnv<'tcx>, ty: Ty<'tcx>) -> IsFreeze {
        let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
        match self.freeze_tys.entry(ty) {
            Entry::Occupied(e) => *e.get(),
            Entry::Vacant(e) => {
                let e = e.insert(IsFreeze::Yes);
                if ty.is_freeze(tcx, typing_env) {
                    return IsFreeze::Yes;
                }
                let is_freeze = match *ty.kind() {
                    ty::Adt(adt, _) if adt.is_unsafe_cell() => {
                        *e = IsFreeze::No;
                        return IsFreeze::No;
                    },
                    ty::Adt(adt, _) if self.ignore_tys.contains(&adt.did()) => return IsFreeze::Yes,
                    ty::Adt(adt, args) if adt.is_enum() => IsFreeze::from_variants(adt.variants().iter().map(|v| {
                        IsFreeze::from_fields(
                            v.fields
                                .iter()
                                .map(|f| self.is_ty_freeze(tcx, typing_env, f.ty(tcx, args))),
                        )
                    })),
                    // Workaround for `ManuallyDrop`-like unions.
                    ty::Adt(adt, args)
                        if adt.is_union()
                            && adt.non_enum_variant().fields.iter().any(|f| {
                                tcx.layout_of(typing_env.as_query_input(f.ty(tcx, args)))
                                    .is_ok_and(|l| l.layout.size().bytes() == 0)
                            }) =>
                    {
                        return IsFreeze::Yes;
                    },
                    // Rust doesn't have the concept of an active union field so we have
                    // to treat all fields as active.
                    ty::Adt(adt, args) => IsFreeze::from_fields(
                        adt.non_enum_variant()
                            .fields
                            .iter()
                            .map(|f| self.is_ty_freeze(tcx, typing_env, f.ty(tcx, args))),
                    ),
                    ty::Array(ty, _) | ty::Pat(ty, _) => self.is_ty_freeze(tcx, typing_env, ty),
                    ty::Tuple(tys) => {
                        IsFreeze::from_fields(tys.iter().map(|ty| self.is_ty_freeze(tcx, typing_env, ty)))
                    },
                    // Treat type parameters as though they were `Freeze`.
                    ty::Param(_) | ty::Alias(..) => return IsFreeze::Yes,
                    // TODO: check other types.
                    _ => {
                        *e = IsFreeze::No;
                        return IsFreeze::No;
                    },
                };
                if !is_freeze.is_freeze() {
                    self.freeze_tys.insert(ty, is_freeze);
                }
                is_freeze
            },
        }
    }

    /// Checks if the given constant value is `Freeze`. Returns `Err` if the constant
    /// cannot be read, but the result depends on the value.
    fn is_value_freeze(
        &mut self,
        tcx: TyCtxt<'tcx>,
        typing_env: TypingEnv<'tcx>,
        ty: Ty<'tcx>,
        val: ConstValue,
    ) -> Result<bool, ()> {
        let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
        match self.is_ty_freeze(tcx, typing_env, ty) {
            IsFreeze::Yes => Ok(true),
            IsFreeze::Maybe if matches!(ty.kind(), ty::Adt(..) | ty::Array(..) | ty::Tuple(..)) => {
                for &(val, ty) in tcx
                    .try_destructure_mir_constant_for_user_output(val, ty)
                    .ok_or(())?
                    .fields
                {
                    if !self.is_value_freeze(tcx, typing_env, ty, val)? {
                        return Ok(false);
                    }
                }
                Ok(true)
            },
            IsFreeze::Maybe | IsFreeze::No => Ok(false),
        }
    }

    /// Checks if the given expression creates a value which is `Freeze`.
    ///
    /// This will return `true` if the type is maybe `Freeze`, but it cannot be
    /// determined for certain from the value.
    ///
    /// `typing_env` and `gen_args` are from the constant's use site.
    /// `typeck` and `e` are from the constant's definition site.
    fn is_init_expr_freeze(
        &mut self,
        tcx: TyCtxt<'tcx>,
        typing_env: TypingEnv<'tcx>,
        typeck: &'tcx TypeckResults<'tcx>,
        gen_args: GenericArgsRef<'tcx>,
        e: &'tcx Expr<'tcx>,
    ) -> bool {
        // Make sure to instantiate all types coming from `typeck` with `gen_args`.
        let ty = EarlyBinder::bind(typeck.expr_ty(e)).instantiate(tcx, gen_args);
        let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
        match self.is_ty_freeze(tcx, typing_env, ty) {
            IsFreeze::Yes => true,
            IsFreeze::No => false,
            IsFreeze::Maybe => match e.kind {
                ExprKind::Block(b, _)
                    if !b.targeted_by_break
                        && b.stmts.is_empty()
                        && let Some(e) = b.expr =>
                {
                    self.is_init_expr_freeze(tcx, typing_env, typeck, gen_args, e)
                },
                ExprKind::Path(ref p) => {
                    let res = typeck.qpath_res(p, e.hir_id);
                    let gen_args = EarlyBinder::bind(typeck.node_args(e.hir_id)).instantiate(tcx, gen_args);
                    match res {
                        Res::Def(DefKind::Const | DefKind::AssocConst, did)
                            if let Ok(val) =
                                tcx.const_eval_resolve(typing_env, UnevaluatedConst::new(did, gen_args), DUMMY_SP)
                                && let Ok(is_freeze) = self.is_value_freeze(tcx, typing_env, ty, val) =>
                        {
                            is_freeze
                        },
                        Res::Def(DefKind::Const | DefKind::AssocConst, did)
                            if let Some((typeck, init)) = get_const_hir_value(tcx, typing_env, did, gen_args) =>
                        {
                            self.is_init_expr_freeze(tcx, typing_env, typeck, gen_args, init)
                        },
                        // Either this is a unit constructor, or some unknown value.
                        // In either case we consider the value to be `Freeze`.
                        _ => true,
                    }
                },
                ExprKind::Call(callee, args)
                    if let ExprKind::Path(p) = &callee.kind
                        && let res = typeck.qpath_res(p, callee.hir_id)
                        && matches!(res, Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(_)) =>
                {
                    args.iter()
                        .all(|e| self.is_init_expr_freeze(tcx, typing_env, typeck, gen_args, e))
                },
                ExprKind::Struct(_, fields, StructTailExpr::None) => fields
                    .iter()
                    .all(|f| self.is_init_expr_freeze(tcx, typing_env, typeck, gen_args, f.expr)),
                ExprKind::Tup(exprs) | ExprKind::Array(exprs) => exprs
                    .iter()
                    .all(|e| self.is_init_expr_freeze(tcx, typing_env, typeck, gen_args, e)),
                ExprKind::Repeat(e, _) => self.is_init_expr_freeze(tcx, typing_env, typeck, gen_args, e),
                _ => true,
            },
        }
    }

    /// Checks if the given expression (or a local projection of it) is both borrowed and
    /// definitely a non-`Freeze` type.
    fn is_non_freeze_expr_borrowed(
        &mut self,
        tcx: TyCtxt<'tcx>,
        typing_env: TypingEnv<'tcx>,
        typeck: &'tcx TypeckResults<'tcx>,
        mut src_expr: &'tcx Expr<'tcx>,
    ) -> Option<BorrowSource<'tcx>> {
        let mut parents = tcx.hir_parent_iter(src_expr.hir_id);
        loop {
            let ty = typeck.expr_ty(src_expr);
            // Normalized as we need to check if this is an array later.
            let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
            let is_freeze = self.is_ty_freeze(tcx, typing_env, ty);
            if is_freeze.is_freeze() {
                return None;
            }
            if let [adjust, ..] = typeck.expr_adjustments(src_expr) {
                return does_adjust_borrow(adjust)
                    .filter(|_| is_freeze.is_not_freeze())
                    .map(|cause| BorrowSource::new(tcx, src_expr, cause));
            }
            let Some((_, Node::Expr(use_expr))) = parents.next() else {
                return None;
            };
            match use_expr.kind {
                ExprKind::Field(..) => {},
                ExprKind::Index(..) if ty.is_array() => {},
                ExprKind::AddrOf(..) if is_freeze.is_not_freeze() => {
                    return Some(BorrowSource::new(tcx, use_expr, BorrowCause::Borrow));
                },
                // All other expressions use the value.
                _ => return None,
            }
            src_expr = use_expr;
        }
    }

    /// Checks if the given value (or a local projection of it) is both borrowed and
    /// definitely non-`Freeze`. Returns `Err` if the constant cannot be read, but the
    /// result depends on the value.
    fn is_non_freeze_val_borrowed(
        &mut self,
        tcx: TyCtxt<'tcx>,
        typing_env: TypingEnv<'tcx>,
        typeck: &'tcx TypeckResults<'tcx>,
        mut src_expr: &'tcx Expr<'tcx>,
        mut val: ConstValue,
    ) -> Result<Option<BorrowSource<'tcx>>, ()> {
        let mut parents = tcx.hir_parent_iter(src_expr.hir_id);
        let mut ty = typeck.expr_ty(src_expr);
        loop {
            // Normalized as we need to check if this is an array later.
            ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
            if let [adjust, ..] = typeck.expr_adjustments(src_expr) {
                let res = if let Some(cause) = does_adjust_borrow(adjust)
                    && !self.is_value_freeze(tcx, typing_env, ty, val)?
                {
                    Some(BorrowSource::new(tcx, src_expr, cause))
                } else {
                    None
                };
                return Ok(res);
            }
            // Check only the type here as the result gets cached for each type.
            if self.is_ty_freeze(tcx, typing_env, ty).is_freeze() {
                return Ok(None);
            }
            let Some((_, Node::Expr(use_expr))) = parents.next() else {
                return Ok(None);
            };
            let next_val = match use_expr.kind {
                ExprKind::Field(_, name) => {
                    if let Some(idx) = get_field_idx_by_name(ty, name.name) {
                        tcx.try_destructure_mir_constant_for_user_output(val, ty)
                            .ok_or(())?
                            .fields
                            .get(idx)
                    } else {
                        return Ok(None);
                    }
                },
                ExprKind::Index(_, idx, _) if ty.is_array() => {
                    let val = tcx.try_destructure_mir_constant_for_user_output(val, ty).ok_or(())?;
                    if let Some(Constant::Int(idx)) = ConstEvalCtxt::with_env(tcx, typing_env, typeck).eval(idx) {
                        val.fields.get(idx as usize)
                    } else {
                        // It's some value in the array so check all of them.
                        for &(val, _) in val.fields {
                            if let Some(src) =
                                self.is_non_freeze_val_borrowed(tcx, typing_env, typeck, use_expr, val)?
                            {
                                return Ok(Some(src));
                            }
                        }
                        return Ok(None);
                    }
                },
                ExprKind::AddrOf(..) if !self.is_value_freeze(tcx, typing_env, ty, val)? => {
                    return Ok(Some(BorrowSource::new(tcx, use_expr, BorrowCause::Borrow)));
                },
                // All other expressions use the value.
                _ => return Ok(None),
            };
            src_expr = use_expr;
            if let Some(&(next_val, next_ty)) = next_val {
                ty = next_ty;
                val = next_val;
            } else {
                return Ok(None);
            }
        }
    }

    /// Checks if the given value (or a local projection of it) is both borrowed and
    /// definitely non-`Freeze`.
    ///
    /// `typing_env` and `init_args` are from the constant's use site.
    /// `init_typeck` and `init_expr` are from the constant's definition site.
    #[expect(clippy::too_many_arguments, clippy::too_many_lines)]
    fn is_non_freeze_init_borrowed(
        &mut self,
        tcx: TyCtxt<'tcx>,
        typing_env: TypingEnv<'tcx>,
        typeck: &'tcx TypeckResults<'tcx>,
        mut src_expr: &'tcx Expr<'tcx>,
        mut init_typeck: &'tcx TypeckResults<'tcx>,
        mut init_args: GenericArgsRef<'tcx>,
        mut init_expr: &'tcx Expr<'tcx>,
    ) -> Option<BorrowSource<'tcx>> {
        // Make sure to instantiate all types coming from `init_typeck` with `init_args`.
        let mut parents = tcx.hir_parent_iter(src_expr.hir_id);
        loop {
            // First handle any adjustments since they are cheap to check.
            if let [adjust, ..] = typeck.expr_adjustments(src_expr) {
                return does_adjust_borrow(adjust)
                    .filter(|_| !self.is_init_expr_freeze(tcx, typing_env, init_typeck, init_args, init_expr))
                    .map(|cause| BorrowSource::new(tcx, src_expr, cause));
            }

            // Then read through constants and blocks on the init expression before
            // applying the next use expression.
            loop {
                match init_expr.kind {
                    ExprKind::Block(b, _)
                        if !b.targeted_by_break
                            && b.stmts.is_empty()
                            && let Some(next_init) = b.expr =>
                    {
                        init_expr = next_init;
                    },
                    ExprKind::Path(ref init_path) => {
                        let next_init_args =
                            EarlyBinder::bind(init_typeck.node_args(init_expr.hir_id)).instantiate(tcx, init_args);
                        match init_typeck.qpath_res(init_path, init_expr.hir_id) {
                            Res::Def(DefKind::Ctor(..), _) => return None,
                            Res::Def(DefKind::Const | DefKind::AssocConst, did)
                                if let Ok(val) = tcx.const_eval_resolve(
                                    typing_env,
                                    UnevaluatedConst::new(did, next_init_args),
                                    DUMMY_SP,
                                ) && let Ok(res) =
                                    self.is_non_freeze_val_borrowed(tcx, typing_env, init_typeck, src_expr, val) =>
                            {
                                return res;
                            },
                            Res::Def(DefKind::Const | DefKind::AssocConst, did)
                                if let Some((next_typeck, value)) =
                                    get_const_hir_value(tcx, typing_env, did, next_init_args) =>
                            {
                                init_typeck = next_typeck;
                                init_args = next_init_args;
                                init_expr = value;
                            },
                            // There's no more that we can read from the init expression. Switch to a
                            // type based check.
                            _ => {
                                return self.is_non_freeze_expr_borrowed(tcx, typing_env, typeck, src_expr);
                            },
                        }
                    },
                    _ => break,
                }
            }

            // Then a type check. Note we only check the type here as the result
            // gets cached.
            let ty = typeck.expr_ty(src_expr);
            // Normalized as we need to check if this is an array later.
            let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
            if self.is_ty_freeze(tcx, typing_env, ty).is_freeze() {
                return None;
            }

            // Finally reduce the init expression using the next use expression.
            let Some((_, Node::Expr(use_expr))) = parents.next() else {
                return None;
            };
            init_expr = match &use_expr.kind {
                ExprKind::Field(_, name) => match init_expr.kind {
                    ExprKind::Struct(_, fields, _)
                        if let Some(field) = fields.iter().find(|f| f.ident.name == name.name) =>
                    {
                        field.expr
                    },
                    ExprKind::Tup(fields)
                        if let Ok(idx) = name.as_str().parse::<usize>()
                            && let Some(field) = fields.get(idx) =>
                    {
                        field
                    },
                    ExprKind::Call(callee, args)
                        if let ExprKind::Path(callee_path) = &callee.kind
                            && matches!(
                                init_typeck.qpath_res(callee_path, callee.hir_id),
                                Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(_)
                            )
                            && let Ok(idx) = name.as_str().parse::<usize>()
                            && let Some(arg) = args.get(idx) =>
                    {
                        arg
                    },
                    // Revert to a type based check as we don't know the field's value.
                    _ => return self.is_non_freeze_expr_borrowed(tcx, typing_env, typeck, use_expr),
                },
                ExprKind::Index(_, idx, _) if ty.is_array() => match init_expr.kind {
                    ExprKind::Array(fields) => {
                        if let Some(Constant::Int(idx)) = ConstEvalCtxt::with_env(tcx, typing_env, typeck).eval(idx) {
                            // If the index is out of bounds it means the code
                            // unconditionally panics. In that case there is no borrow.
                            fields.get(idx as usize)?
                        } else {
                            // Unknown index, just run the check for all values.
                            return fields.iter().find_map(|f| {
                                self.is_non_freeze_init_borrowed(
                                    tcx,
                                    typing_env,
                                    typeck,
                                    use_expr,
                                    init_typeck,
                                    init_args,
                                    f,
                                )
                            });
                        }
                    },
                    // Just assume the index expression doesn't panic here.
                    ExprKind::Repeat(field, _) => field,
                    _ => return self.is_non_freeze_expr_borrowed(tcx, typing_env, typeck, use_expr),
                },
                ExprKind::AddrOf(..)
                    if !self.is_init_expr_freeze(tcx, typing_env, init_typeck, init_args, init_expr) =>
                {
                    return Some(BorrowSource::new(tcx, use_expr, BorrowCause::Borrow));
                },
                // All other expressions use the value.
                _ => return None,
            };
            src_expr = use_expr;
        }
    }
}

impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> {
    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
        if let ItemKind::Const(ident, .., body_id) = item.kind
            && !ident.is_special()
            && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
            && match self.is_ty_freeze(cx.tcx, cx.typing_env(), ty) {
                IsFreeze::No => true,
                IsFreeze::Yes => false,
                IsFreeze::Maybe => match cx.tcx.const_eval_poly(item.owner_id.to_def_id()) {
                    Ok(val) if let Ok(is_freeze) = self.is_value_freeze(cx.tcx, cx.typing_env(), ty, val) => !is_freeze,
                    _ => !self.is_init_expr_freeze(
                        cx.tcx,
                        cx.typing_env(),
                        cx.tcx.typeck(item.owner_id),
                        GenericArgs::identity_for_item(cx.tcx, item.owner_id),
                        cx.tcx.hir_body(body_id).value,
                    ),
                },
            }
            && !item.span.in_external_macro(cx.sess().source_map())
            // Only needed when compiling `std`
            && !is_thread_local(cx, item)
        {
            span_lint_and_then(
                cx,
                DECLARE_INTERIOR_MUTABLE_CONST,
                ident.span,
                "named constant with interior mutability",
                |diag| {
                    let Some(sync_trait) = cx.tcx.lang_items().sync_trait() else {
                        return;
                    };
                    if implements_trait(cx, ty, sync_trait, &[]) {
                        diag.help("did you mean to make this a `static` item");
                    } else {
                        diag.help("did you mean to make this a `thread_local!` item");
                    }
                },
            );
        }
    }

    fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
        if let TraitItemKind::Const(_, body_id_opt) = item.kind
            && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
            && match self.is_ty_freeze(cx.tcx, cx.typing_env(), ty) {
                IsFreeze::No => true,
                IsFreeze::Maybe if let Some(body_id) = body_id_opt => {
                    match cx.tcx.const_eval_poly(item.owner_id.to_def_id()) {
                        Ok(val) if let Ok(is_freeze) = self.is_value_freeze(cx.tcx, cx.typing_env(), ty, val) => {
                            !is_freeze
                        },
                        _ => !self.is_init_expr_freeze(
                            cx.tcx,
                            cx.typing_env(),
                            cx.tcx.typeck(item.owner_id),
                            GenericArgs::identity_for_item(cx.tcx, item.owner_id),
                            cx.tcx.hir_body(body_id).value,
                        ),
                    }
                },
                IsFreeze::Yes | IsFreeze::Maybe => false,
            }
            && !item.span.in_external_macro(cx.sess().source_map())
        {
            span_lint(
                cx,
                DECLARE_INTERIOR_MUTABLE_CONST,
                item.ident.span,
                "named constant with interior mutability",
            );
        }
    }

    fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
        if let ImplItemKind::Const(_, body_id) = item.kind
            && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
            && match self.is_ty_freeze(cx.tcx, cx.typing_env(), ty) {
                IsFreeze::Yes => false,
                IsFreeze::No => {
                    // If this is a trait impl, check if the trait definition is the source
                    // of the cell.
                    if let Node::Item(parent_item) = cx.tcx.parent_hir_node(item.hir_id())
                        && let ItemKind::Impl(impl_block) = parent_item.kind
                        && let Some(of_trait) = impl_block.of_trait
                        && let Some(trait_id) = of_trait.trait_ref.trait_def_id()
                    {
                        // Replace all instances of `<Self as Trait>::AssocType` with the
                        // unit type and check again. If the result is the same then the
                        // trait definition is the cause.
                        let ty = (ReplaceAssocFolder {
                            tcx: cx.tcx,
                            trait_id,
                            self_ty: cx.tcx.type_of(parent_item.owner_id).instantiate_identity(),
                        })
                        .fold_ty(cx.tcx.type_of(item.owner_id).instantiate_identity());
                        // `ty` may not be normalizable, but that should be fine.
                        !self.is_ty_freeze(cx.tcx, cx.typing_env(), ty).is_not_freeze()
                    } else {
                        true
                    }
                },
                // Even if this is from a trait, there are values which don't have
                // interior mutability.
                IsFreeze::Maybe => match cx.tcx.const_eval_poly(item.owner_id.to_def_id()) {
                    Ok(val) if let Ok(is_freeze) = self.is_value_freeze(cx.tcx, cx.typing_env(), ty, val) => !is_freeze,
                    _ => !self.is_init_expr_freeze(
                        cx.tcx,
                        cx.typing_env(),
                        cx.tcx.typeck(item.owner_id),
                        GenericArgs::identity_for_item(cx.tcx, item.owner_id),
                        cx.tcx.hir_body(body_id).value,
                    ),
                },
            }
            && !item.span.in_external_macro(cx.sess().source_map())
        {
            span_lint(
                cx,
                DECLARE_INTERIOR_MUTABLE_CONST,
                item.ident.span,
                "named constant with interior mutability",
            );
        }
    }

    fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
        if let ExprKind::Path(qpath) = &e.kind
            && let typeck = cx.typeck_results()
            && let Res::Def(DefKind::Const | DefKind::AssocConst, did) = typeck.qpath_res(qpath, e.hir_id)
            // As of `1.80` constant contexts can't borrow any type with interior mutability
            && !is_in_const_context(cx)
            && !self.is_ty_freeze(cx.tcx, cx.typing_env(), typeck.expr_ty(e)).is_freeze()
            && let Some(borrow_src) = {
                // The extra block helps formatting a lot.
                if let Ok(val) = cx.tcx.const_eval_resolve(
                    cx.typing_env(),
                    UnevaluatedConst::new(did, typeck.node_args(e.hir_id)),
                    DUMMY_SP,
                ) && let Ok(src) = self.is_non_freeze_val_borrowed(cx.tcx, cx.typing_env(), typeck, e, val)
                {
                    src
                } else if let init_args = typeck.node_args(e.hir_id)
                    && let Some((init_typeck, init)) = get_const_hir_value(cx.tcx, cx.typing_env(), did, init_args)
                {
                    self.is_non_freeze_init_borrowed(cx.tcx, cx.typing_env(), typeck, e, init_typeck, init_args, init)
                } else {
                    self.is_non_freeze_expr_borrowed(cx.tcx, cx.typing_env(), typeck, e)
                }
            }
            && !borrow_src.expr.span.in_external_macro(cx.sess().source_map())
        {
            span_lint_and_then(
                cx,
                BORROW_INTERIOR_MUTABLE_CONST,
                borrow_src.expr.span,
                "borrow of a named constant with interior mutability",
                |diag| {
                    if let Some(note) = borrow_src.cause.note() {
                        diag.note(note);
                    }
                    diag.help("this lint can be silenced by assigning the value to a local variable before borrowing");
                },
            );
        }
    }
}

struct ReplaceAssocFolder<'tcx> {
    tcx: TyCtxt<'tcx>,
    trait_id: DefId,
    self_ty: Ty<'tcx>,
}
impl<'tcx> TypeFolder<TyCtxt<'tcx>> for ReplaceAssocFolder<'tcx> {
    fn cx(&self) -> TyCtxt<'tcx> {
        self.tcx
    }

    fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
        if let ty::Alias(AliasTyKind::Projection, ty) = ty.kind()
            && ty.trait_def_id(self.tcx) == self.trait_id
            && ty.self_ty() == self.self_ty
        {
            self.tcx.types.unit
        } else {
            ty.super_fold_with(self)
        }
    }
}

fn is_thread_local(cx: &LateContext<'_>, it: &Item<'_>) -> bool {
    macro_backtrace(it.span).any(|macro_call| {
        matches!(
            cx.tcx.get_diagnostic_name(macro_call.def_id),
            Some(sym::thread_local_macro)
        )
    })
}

/// Checks if the adjustment causes a borrow of the original value. Returns
/// `None` if the value is consumed instead of borrowed.
fn does_adjust_borrow(adjust: &Adjustment<'_>) -> Option<BorrowCause> {
    match adjust.kind {
        Adjust::Borrow(_) => Some(BorrowCause::AutoBorrow),
        // Custom deref calls `<T as Deref>::deref(&x)` resulting in a borrow.
        Adjust::Deref(Some(_)) => Some(BorrowCause::AutoDeref),
        // All other adjustments read the value.
        _ => None,
    }
}

/// Attempts to get the value of a constant as a HIR expression. Also gets the
/// `TypeckResults` associated with the constant's body.
fn get_const_hir_value<'tcx>(
    tcx: TyCtxt<'tcx>,
    typing_env: TypingEnv<'tcx>,
    did: DefId,
    args: GenericArgsRef<'tcx>,
) -> Option<(&'tcx TypeckResults<'tcx>, &'tcx Expr<'tcx>)> {
    let did = did.as_local()?;
    let (did, body_id) = match tcx.hir_node(tcx.local_def_id_to_hir_id(did)) {
        Node::Item(item) if let ItemKind::Const(.., body_id) = item.kind => (did, body_id),
        Node::ImplItem(item) if let ImplItemKind::Const(.., body_id) = item.kind => (did, body_id),
        Node::TraitItem(_)
            if let Ok(Some(inst)) = Instance::try_resolve(tcx, typing_env, did.into(), args)
                && let Some(did) = inst.def_id().as_local() =>
        {
            match tcx.hir_node(tcx.local_def_id_to_hir_id(did)) {
                Node::ImplItem(item) if let ImplItemKind::Const(.., body_id) = item.kind => (did, body_id),
                Node::TraitItem(item) if let TraitItemKind::Const(.., Some(body_id)) = item.kind => (did, body_id),
                _ => return None,
            }
        },
        _ => return None,
    };
    Some((tcx.typeck(did), tcx.hir_body(body_id).value))
}
