use std::borrow::Cow;
use std::fmt::Write;

use either::Either;
use rustc_abi::WrappingRange;
use rustc_errors::codes::*;
use rustc_errors::{
    Diag, DiagArgValue, DiagMessage, Diagnostic, EmissionGuarantee, Level, MultiSpan,
    Subdiagnostic, msg,
};
use rustc_hir::ConstContext;
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_middle::mir::interpret::{
    CtfeProvenance, ExpectedKind, InterpErrorKind, InvalidMetaKind, InvalidProgramInfo,
    Misalignment, Pointer, PointerKind, ResourceExhaustionInfo, UndefinedBehaviorInfo,
    UnsupportedOpInfo, ValidationErrorInfo,
};
use rustc_middle::ty::{self, Mutability, Ty};
use rustc_span::{Span, Symbol};

use crate::interpret::InternKind;

#[derive(Diagnostic)]
#[diag(
    r#"encountered dangling pointer in final value of {$kind ->
    [static] static
    [static_mut] mutable static
    [const] constant
    [promoted] promoted
    *[other] {""}
}"#
)]
pub(crate) struct DanglingPtrInFinal {
    #[primary_span]
    pub span: Span,
    pub kind: InternKind,
}

#[derive(Diagnostic)]
#[diag(
    "#[thread_local] does not support implicit nested statics, please create explicit static items and refer to them instead"
)]
pub(crate) struct NestedStaticInThreadLocal {
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag(
    r#"encountered mutable pointer in final value of {$kind ->
    [static] static
    [static_mut] mutable static
    [const] constant
    [promoted] promoted
    *[other] {""}
}"#
)]
pub(crate) struct MutablePtrInFinal {
    #[primary_span]
    pub span: Span,
    pub kind: InternKind,
}

#[derive(Diagnostic)]
#[diag("encountered `const_allocate` pointer in final value that was not made global")]
#[note(
    "use `const_make_global` to turn allocated pointers into immutable globals before returning"
)]
pub(crate) struct ConstHeapPtrInFinal {
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag(
    r#"encountered partial pointer in final value of {$kind ->
    [static] static
    [static_mut] mutable static
    [const] constant
    [promoted] promoted
    *[other] {""}
}"#
)]
#[note(
    "while pointers can be broken apart into individual bytes during const-evaluation, only complete pointers (with all their bytes in the right order) are supported in the final value"
)]
pub(crate) struct PartialPtrInFinal {
    #[primary_span]
    pub span: Span,
    pub kind: InternKind,
}

#[derive(Diagnostic)]
#[diag(
    "const function that might be (indirectly) exposed to stable cannot use `#[feature({$gate})]`"
)]
pub(crate) struct UnstableInStableExposed {
    pub gate: String,
    #[primary_span]
    pub span: Span,
    #[help(
        "mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unstable features"
    )]
    pub is_function_call: bool,
    /// Need to duplicate the field so that fluent also provides it as a variable...
    pub is_function_call2: bool,
    #[suggestion(
        "if the {$is_function_call2 ->
            [true] caller
            *[false] function
        } is not (yet) meant to be exposed to stable const contexts, add `#[rustc_const_unstable]`",
        code = "#[rustc_const_unstable(feature = \"...\", issue = \"...\")]\n",
        applicability = "has-placeholders"
    )]
    pub attr_span: Span,
}

#[derive(Diagnostic)]
#[diag("thread-local statics cannot be accessed at compile-time", code = E0625)]
pub(crate) struct ThreadLocalAccessErr {
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag("pointers cannot be cast to integers during const eval")]
#[note("at compile-time, pointers do not have an integer value")]
#[note(
    "avoiding this restriction via `transmute`, `union`, or raw pointers leads to compile-time undefined behavior"
)]
pub(crate) struct RawPtrToIntErr {
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag("pointers cannot be reliably compared during const eval")]
#[note("see issue #53020 <https://github.com/rust-lang/rust/issues/53020> for more information")]
pub(crate) struct RawPtrComparisonErr {
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag("argument to `panic!()` in a const context must have type `&str`")]
pub(crate) struct PanicNonStrErr {
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag(
    r#"function pointer calls are not allowed in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#
)]
pub(crate) struct UnallowedFnPointerCall {
    #[primary_span]
    pub span: Span,
    pub kind: ConstContext,
}

#[derive(Diagnostic)]
#[diag("`{$def_path}` is not yet stable as a const fn")]
pub(crate) struct UnstableConstFn {
    #[primary_span]
    pub span: Span,
    pub def_path: String,
}

#[derive(Diagnostic)]
#[diag("`{$def_path}` is not yet stable as a const trait")]
pub(crate) struct UnstableConstTrait {
    #[primary_span]
    pub span: Span,
    pub def_path: String,
}

#[derive(Diagnostic)]
#[diag("`{$name}` is not yet stable as a const intrinsic")]
pub(crate) struct UnstableIntrinsic {
    #[primary_span]
    pub span: Span,
    pub name: Symbol,
    pub feature: Symbol,
    #[suggestion(
        "add `#![feature({$feature})]` to the crate attributes to enable",
        code = "#![feature({feature})]\n",
        applicability = "machine-applicable"
    )]
    pub suggestion: Span,
}

#[derive(Diagnostic)]
#[diag("`{$def_path}` cannot be (indirectly) exposed to stable")]
#[help(
    "either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`"
)]
pub(crate) struct UnmarkedConstItemExposed {
    #[primary_span]
    pub span: Span,
    pub def_path: String,
}

#[derive(Diagnostic)]
#[diag("intrinsic `{$def_path}` cannot be (indirectly) exposed to stable")]
#[help(
    "mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_intrinsic_const_stable_indirect]` (but this requires team approval)"
)]
pub(crate) struct UnmarkedIntrinsicExposed {
    #[primary_span]
    pub span: Span,
    pub def_path: String,
}

#[derive(Diagnostic)]
#[diag("mutable borrows of temporaries that have their lifetime extended until the end of the program are not allowed", code = E0764)]
#[note(
    "temporaries in constants and statics can have their lifetime extended until the end of the program"
)]
#[note("to avoid accidentally creating global mutable state, such temporaries must be immutable")]
#[help(
    "if you really want global mutable state, try replacing the temporary by an interior mutable `static` or a `static mut`"
)]
pub(crate) struct MutableBorrowEscaping {
    #[primary_span]
    #[label("this mutable borrow refers to such a temporary")]
    pub span: Span,
    pub kind: ConstContext,
}

#[derive(Diagnostic)]
#[diag(
    r#"cannot call {$non_or_conditionally}-const formatting macro in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#,
    code = E0015,
)]
pub(crate) struct NonConstFmtMacroCall {
    #[primary_span]
    pub span: Span,
    pub kind: ConstContext,
    pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag(r#"cannot call {$non_or_conditionally}-const {$def_descr} `{$def_path_str}` in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#, code = E0015)]
pub(crate) struct NonConstFnCall {
    #[primary_span]
    pub span: Span,
    pub def_path_str: String,
    pub def_descr: &'static str,
    pub kind: ConstContext,
    pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag(
    r#"cannot call non-const intrinsic `{$name}` in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#
)]
pub(crate) struct NonConstIntrinsic {
    #[primary_span]
    pub span: Span,
    pub name: Symbol,
    pub kind: ConstContext,
}

#[derive(Diagnostic)]
#[diag("{$msg}")]
pub(crate) struct UnallowedOpInConstContext {
    #[primary_span]
    pub span: Span,
    pub msg: String,
}

#[derive(Diagnostic)]
#[diag(r#"inline assembly is not allowed in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#, code = E0015)]
pub(crate) struct UnallowedInlineAsm {
    #[primary_span]
    pub span: Span,
    pub kind: ConstContext,
}

#[derive(Diagnostic)]
#[diag("interior mutable shared borrows of temporaries that have their lifetime extended until the end of the program are not allowed", code = E0492)]
#[note(
    "temporaries in constants and statics can have their lifetime extended until the end of the program"
)]
#[note("to avoid accidentally creating global mutable state, such temporaries must be immutable")]
#[help(
    "if you really want global mutable state, try replacing the temporary by an interior mutable `static` or a `static mut`"
)]
pub(crate) struct InteriorMutableBorrowEscaping {
    #[primary_span]
    #[label("this borrow of an interior mutable value refers to such a temporary")]
    pub span: Span,
    pub kind: ConstContext,
}

#[derive(Diagnostic)]
#[diag("constant evaluation is taking a long time")]
#[note(
    "this lint makes sure the compiler doesn't get stuck due to infinite loops in const eval.
    If your compilation actually takes a long time, you can safely allow the lint"
)]
pub struct LongRunning {
    #[help("the constant being evaluated")]
    pub item_span: Span,
}

#[derive(Diagnostic)]
#[diag("constant evaluation is taking a long time")]
pub struct LongRunningWarn {
    #[primary_span]
    #[label("the const evaluator is currently interpreting this expression")]
    pub span: Span,
    #[help("the constant being evaluated")]
    pub item_span: Span,
    // Used for evading `-Z deduplicate-diagnostics`.
    pub force_duplicate: usize,
}

#[derive(Subdiagnostic)]
#[note("impl defined here, but it is not `const`")]
pub(crate) struct NonConstImplNote {
    #[primary_span]
    pub span: Span,
}

#[derive(Clone)]
pub struct FrameNote {
    pub span: Span,
    pub times: i32,
    pub where_: &'static str,
    pub instance: String,
    pub has_label: bool,
}

impl Subdiagnostic for FrameNote {
    fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
        diag.arg("times", self.times);
        diag.arg("where_", self.where_);
        diag.arg("instance", self.instance);
        let mut span: MultiSpan = self.span.into();
        if self.has_label && !self.span.is_dummy() {
            span.push_span_label(self.span, msg!("the failure occurred here"));
        }
        let msg = diag.eagerly_translate(msg!(
            r#"{$times ->
                [0] inside {$where_ ->
                    [closure] closure
                    [instance] `{$instance}`
                    *[other] {""}
                }
                *[other] [... {$times} additional calls inside {$where_ ->
                    [closure] closure
                    [instance] `{$instance}`
                    *[other] {""}
                } ...]
            }"#
        ));
        diag.remove_arg("times");
        diag.remove_arg("where_");
        diag.remove_arg("instance");
        diag.span_note(span, msg);
    }
}

#[derive(Subdiagnostic)]
#[note(r#"the raw bytes of the constant (size: {$size}, align: {$align}) {"{"}{$bytes}{"}"}"#)]
pub struct RawBytesNote {
    pub size: u64,
    pub align: u64,
    pub bytes: String,
}

// FIXME(fee1-dead) do not use stringly typed `ConstContext`

#[derive(Diagnostic)]
#[diag(
    r#"cannot match on `{$ty}` in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#
)]
#[note("`{$ty}` cannot be compared in compile-time, and therefore cannot be used in `match`es")]
pub struct NonConstMatchEq<'tcx> {
    #[primary_span]
    pub span: Span,
    pub ty: Ty<'tcx>,
    pub kind: ConstContext,
    pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag(r#"cannot use `for` loop on `{$ty}` in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#, code = E0015)]
pub struct NonConstForLoopIntoIter<'tcx> {
    #[primary_span]
    pub span: Span,
    pub ty: Ty<'tcx>,
    pub kind: ConstContext,
    pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag(r#"`?` is not allowed on `{$ty}` in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#, code = E0015)]
pub struct NonConstQuestionBranch<'tcx> {
    #[primary_span]
    pub span: Span,
    pub ty: Ty<'tcx>,
    pub kind: ConstContext,
    pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag(r#"`?` is not allowed on `{$ty}` in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#, code = E0015)]
pub struct NonConstQuestionFromResidual<'tcx> {
    #[primary_span]
    pub span: Span,
    pub ty: Ty<'tcx>,
    pub kind: ConstContext,
    pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag(r#"`try` block cannot convert `{$ty}` to the result in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#, code = E0015)]
pub struct NonConstTryBlockFromOutput<'tcx> {
    #[primary_span]
    pub span: Span,
    pub ty: Ty<'tcx>,
    pub kind: ConstContext,
    pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag(r#"cannot convert `{$ty}` into a future in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#, code = E0015)]
pub struct NonConstAwait<'tcx> {
    #[primary_span]
    pub span: Span,
    pub ty: Ty<'tcx>,
    pub kind: ConstContext,
    pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag(r#"cannot call {$non_or_conditionally}-const closure in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#, code = E0015)]
pub struct NonConstClosure {
    #[primary_span]
    pub span: Span,
    pub kind: ConstContext,
    #[subdiagnostic]
    pub note: Option<NonConstClosureNote>,
    pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag(r#"calling const c-variadic functions is unstable in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#, code = E0015)]
pub struct NonConstCVariadicCall {
    #[primary_span]
    pub span: Span,
    pub kind: ConstContext,
}

#[derive(Subdiagnostic)]
pub enum NonConstClosureNote {
    #[note("function defined here, but it is not `const`")]
    FnDef {
        #[primary_span]
        span: Span,
    },
    #[note(
        r#"function pointers need an RFC before allowed to be called in {$kind ->
            [const] constant
            [static] static
            [const_fn] constant function
            *[other] {""}
        }s"#
    )]
    FnPtr,
    #[note(
        r#"closures need an RFC before allowed to be called in {$kind ->
            [const] constant
            [static] static
            [const_fn] constant function
            *[other] {""}
        }s"#
    )]
    Closure,
}

#[derive(Subdiagnostic)]
#[multipart_suggestion("consider dereferencing here", applicability = "machine-applicable")]
pub struct ConsiderDereferencing {
    pub deref: String,
    #[suggestion_part(code = "{deref}")]
    pub span: Span,
    #[suggestion_part(code = "{deref}")]
    pub rhs_span: Span,
}

#[derive(Diagnostic)]
#[diag(r#"cannot call {$non_or_conditionally}-const operator in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#, code = E0015)]
pub struct NonConstOperator {
    #[primary_span]
    pub span: Span,
    pub kind: ConstContext,
    #[subdiagnostic]
    pub sugg: Option<ConsiderDereferencing>,
    pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag(r#"cannot perform {$non_or_conditionally}-const deref coercion on `{$ty}` in {$kind ->
    [const] constant
    [static] static
    [const_fn] constant function
    *[other] {""}
}s"#, code = E0015)]
#[note("attempting to deref into `{$target_ty}`")]
pub struct NonConstDerefCoercion<'tcx> {
    #[primary_span]
    pub span: Span,
    pub ty: Ty<'tcx>,
    pub kind: ConstContext,
    pub target_ty: Ty<'tcx>,
    #[note("deref defined here")]
    pub deref_target: Option<Span>,
    pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag("destructor of `{$dropped_ty}` cannot be evaluated at compile-time", code = E0493)]
pub struct LiveDrop<'tcx> {
    #[primary_span]
    #[label(
        r#"the destructor for this type cannot be evaluated in {$kind ->
            [const] constant
            [static] static
            [const_fn] constant function
            *[other] {""}
        }s"#
    )]
    pub span: Span,
    pub kind: ConstContext,
    pub dropped_ty: Ty<'tcx>,
    #[label("value is dropped here")]
    pub dropped_at: Span,
}

pub trait ReportErrorExt {
    /// Returns the diagnostic message for this error.
    fn diagnostic_message(&self) -> DiagMessage;
    fn add_args<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>);

    fn debug(self) -> String
    where
        Self: Sized,
    {
        ty::tls::with(move |tcx| {
            let dcx = tcx.dcx();
            let mut diag = dcx.struct_allow(DiagMessage::Str(String::new().into()));
            let message = self.diagnostic_message();
            self.add_args(&mut diag);
            let s = dcx.eagerly_translate_to_string(message, diag.args.iter());
            diag.cancel();
            s
        })
    }
}

impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
    fn diagnostic_message(&self) -> DiagMessage {
        use UndefinedBehaviorInfo::*;

        match self {
            Ub(msg) => msg.clone().into(),
            Custom(x) => (x.msg)(),
            ValidationError(e) => e.diagnostic_message(),

            Unreachable => "entering unreachable code".into(),
            BoundsCheckFailed { .. } => msg!("indexing out of bounds: the len is {$len} but the index is {$index}"),
            DivisionByZero => "dividing by zero".into(),
            RemainderByZero => "calculating the remainder with a divisor of zero".into(),
            DivisionOverflow => "overflow in signed division (dividing MIN by -1)".into(),
            RemainderOverflow => "overflow in signed remainder (dividing MIN by -1)".into(),
            PointerArithOverflow => "overflowing pointer arithmetic: the total offset in bytes does not fit in an `isize`".into(),
            ArithOverflow { .. } => msg!("arithmetic overflow in `{$intrinsic}`"),
            ShiftOverflow { .. } => msg!("overflowing shift by {$shift_amount} in `{$intrinsic}`"),
            InvalidMeta(InvalidMetaKind::SliceTooBig) => "invalid metadata in wide pointer: slice is bigger than largest supported object".into(),
            InvalidMeta(InvalidMetaKind::TooBig) => "invalid metadata in wide pointer: total size is bigger than largest supported object".into(),
            UnterminatedCString(_) => "reading a null-terminated string starting at {$pointer} with no null found before end of allocation".into(),
            PointerUseAfterFree(_, _) => msg!(
                "{$operation ->
                    [MemoryAccess] memory access failed
                    [InboundsPointerArithmetic] in-bounds pointer arithmetic failed
                    *[Dereferenceable] pointer not dereferenceable
                }: {$alloc_id} has been freed, so this pointer is dangling"
            ),
            PointerOutOfBounds { .. } => msg!(
                "{$operation ->
                    [MemoryAccess] memory access failed
                    [InboundsPointerArithmetic] in-bounds pointer arithmetic failed
                    *[Dereferenceable] pointer not dereferenceable
                }: {$operation ->
                    [MemoryAccess] attempting to access {$inbounds_size ->
                        [1] 1 byte
                        *[x] {$inbounds_size} bytes
                    }
                    [InboundsPointerArithmetic] attempting to offset pointer by {$inbounds_size ->
                        [1] 1 byte
                        *[x] {$inbounds_size} bytes
                    }
                    *[Dereferenceable] pointer must {$inbounds_size ->
                        [0] point to some allocation
                        [1] be dereferenceable for 1 byte
                        *[x] be dereferenceable for {$inbounds_size} bytes
                    }
                }, but got {$pointer} which {$ptr_offset_is_neg ->
                    [true] points to before the beginning of the allocation
                    *[false] {$inbounds_size_is_neg ->
                        [false] {$alloc_size_minus_ptr_offset ->
                            [0] is at or beyond the end of the allocation of size {$alloc_size ->
                                [1] 1 byte
                                *[x] {$alloc_size} bytes
                            }
                            [1] is only 1 byte from the end of the allocation
                            *[x] is only {$alloc_size_minus_ptr_offset} bytes from the end of the allocation
                        }
                        *[true] {$ptr_offset_abs ->
                            [0] is at the beginning of the allocation
                            *[other] is only {$ptr_offset_abs} bytes from the beginning of the allocation
                        }
                    }
                }"
            ),
            DanglingIntPointer { addr: 0, .. } => msg!(
                "{$operation ->
                    [MemoryAccess] memory access failed
                    [InboundsPointerArithmetic] in-bounds pointer arithmetic failed
                    *[Dereferenceable] pointer not dereferenceable
                }: {$operation ->
                    [MemoryAccess] attempting to access {$inbounds_size ->
                        [1] 1 byte
                        *[x] {$inbounds_size} bytes
                    }
                    [InboundsPointerArithmetic] attempting to offset pointer by {$inbounds_size ->
                        [1] 1 byte
                        *[x] {$inbounds_size} bytes
                    }
                    *[Dereferenceable] pointer must {$inbounds_size ->
                        [0] point to some allocation
                        [1] be dereferenceable for 1 byte
                        *[x] be dereferenceable for {$inbounds_size} bytes
                    }
                }, but got null pointer"),
            DanglingIntPointer { .. } => msg!(
                "{$operation ->
                    [MemoryAccess] memory access failed
                    [InboundsPointerArithmetic] in-bounds pointer arithmetic failed
                    *[Dereferenceable] pointer not dereferenceable
                }: {$operation ->
                    [MemoryAccess] attempting to access {$inbounds_size ->
                        [1] 1 byte
                        *[x] {$inbounds_size} bytes
                    }
                    [InboundsPointerArithmetic] attempting to offset pointer by {$inbounds_size ->
                        [1] 1 byte
                        *[x] {$inbounds_size} bytes
                    }
                    *[Dereferenceable] pointer must {$inbounds_size ->
                        [0] point to some allocation
                        [1] be dereferenceable for 1 byte
                        *[x] be dereferenceable for {$inbounds_size} bytes
                    }
                }, but got {$pointer} which is a dangling pointer (it has no provenance)"),
            AlignmentCheckFailed { .. } => msg!(
                "{$msg ->
                    [AccessedPtr] accessing memory
                    *[other] accessing memory based on pointer
                } with alignment {$has}, but alignment {$required} is required"
            ),
            WriteToReadOnly(_) => msg!("writing to {$allocation} which is read-only"),
            DerefFunctionPointer(_) => msg!("accessing {$allocation} which contains a function"),
            DerefVTablePointer(_) => msg!("accessing {$allocation} which contains a vtable"),
            DerefVaListPointer(_) => msg!("accessing {$allocation} which contains a variable argument list"),
            DerefTypeIdPointer(_) => msg!("accessing {$allocation} which contains a `TypeId`"),
            InvalidBool(_) => msg!("interpreting an invalid 8-bit value as a bool: 0x{$value}"),
            InvalidChar(_) => msg!("interpreting an invalid 32-bit value as a char: 0x{$value}"),
            InvalidTag(_) => msg!("enum value has invalid tag: {$tag}"),
            InvalidFunctionPointer(_) => msg!("using {$pointer} as function pointer but it does not point to a function"),
            InvalidVaListPointer(_) => msg!("using {$pointer} as variable argument list pointer but it does not point to a variable argument list"),
            InvalidVTablePointer(_) => msg!("using {$pointer} as vtable pointer but it does not point to a vtable"),
            InvalidVTableTrait { .. } => msg!("using vtable for `{$vtable_dyn_type}` but `{$expected_dyn_type}` was expected"),
            InvalidStr(_) => msg!("this string is not valid UTF-8: {$err}"),
            InvalidUninitBytes(None) => "using uninitialized data, but this operation requires initialized memory".into(),
            InvalidUninitBytes(Some(_)) => msg!("reading memory at {$alloc}{$access}, but memory is uninitialized at {$uninit}, and this operation requires initialized memory"),
            DeadLocal => "accessing a dead local variable".into(),
            ScalarSizeMismatch(_) => msg!("scalar size mismatch: expected {$target_size} bytes but got {$data_size} bytes instead"),
            UninhabitedEnumVariantWritten(_) => "writing discriminant of an uninhabited enum variant".into(),
            UninhabitedEnumVariantRead(_) => "read discriminant of an uninhabited enum variant".into(),
            InvalidNichedEnumVariantWritten { .. } => {
                msg!("trying to set discriminant of a {$ty} to the niched variant, but the value does not match")
            }
            AbiMismatchArgument { .. } => msg!("calling a function whose parameter #{$arg_idx} has type {$callee_ty} passing argument of type {$caller_ty}"),
            AbiMismatchReturn { .. } => msg!("calling a function with return type {$callee_ty} passing return place of type {$caller_ty}"),
            VaArgOutOfBounds => "more C-variadic arguments read than were passed".into(),
            CVariadicMismatch { ..} => "calling a function where the caller and callee disagree on whether the function is C-variadic".into(),
            CVariadicFixedCountMismatch { .. } => msg!("calling a C-variadic function with {$caller} fixed arguments, but the function expects {$callee}"),
        }
    }

    fn add_args<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
        use UndefinedBehaviorInfo::*;
        match self {
            Ub(_) => {}
            Custom(custom) => {
                (custom.add_args)(&mut |name, value| {
                    diag.arg(name, value);
                });
            }
            ValidationError(e) => e.add_args(diag),

            Unreachable
            | DivisionByZero
            | RemainderByZero
            | DivisionOverflow
            | RemainderOverflow
            | PointerArithOverflow
            | InvalidMeta(InvalidMetaKind::SliceTooBig)
            | InvalidMeta(InvalidMetaKind::TooBig)
            | InvalidUninitBytes(None)
            | DeadLocal
            | VaArgOutOfBounds
            | UninhabitedEnumVariantWritten(_)
            | UninhabitedEnumVariantRead(_) => {}

            ArithOverflow { intrinsic } => {
                diag.arg("intrinsic", intrinsic);
            }
            ShiftOverflow { intrinsic, shift_amount } => {
                diag.arg("intrinsic", intrinsic);
                diag.arg(
                    "shift_amount",
                    match shift_amount {
                        Either::Left(v) => v.to_string(),
                        Either::Right(v) => v.to_string(),
                    },
                );
            }
            BoundsCheckFailed { len, index } => {
                diag.arg("len", len);
                diag.arg("index", index);
            }
            UnterminatedCString(ptr)
            | InvalidFunctionPointer(ptr)
            | InvalidVaListPointer(ptr)
            | InvalidVTablePointer(ptr) => {
                diag.arg("pointer", ptr);
            }
            InvalidVTableTrait { expected_dyn_type, vtable_dyn_type } => {
                diag.arg("expected_dyn_type", expected_dyn_type.to_string());
                diag.arg("vtable_dyn_type", vtable_dyn_type.to_string());
            }
            PointerUseAfterFree(alloc_id, msg) => {
                diag.arg("alloc_id", alloc_id).arg("operation", format!("{:?}", msg));
            }
            PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, inbounds_size, msg } => {
                diag.arg("alloc_size", alloc_size.bytes());
                diag.arg("pointer", {
                    let mut out = format!("{:?}", alloc_id);
                    if ptr_offset > 0 {
                        write!(out, "+{:#x}", ptr_offset).unwrap();
                    } else if ptr_offset < 0 {
                        write!(out, "-{:#x}", ptr_offset.unsigned_abs()).unwrap();
                    }
                    out
                });
                diag.arg("inbounds_size", inbounds_size);
                diag.arg("inbounds_size_is_neg", inbounds_size < 0);
                diag.arg("inbounds_size_abs", inbounds_size.unsigned_abs());
                diag.arg("ptr_offset", ptr_offset);
                diag.arg("ptr_offset_is_neg", ptr_offset < 0);
                diag.arg("ptr_offset_abs", ptr_offset.unsigned_abs());
                diag.arg(
                    "alloc_size_minus_ptr_offset",
                    alloc_size.bytes().saturating_sub(ptr_offset as u64),
                );
                diag.arg("operation", format!("{:?}", msg));
            }
            DanglingIntPointer { addr, inbounds_size, msg } => {
                if addr != 0 {
                    diag.arg(
                        "pointer",
                        Pointer::<Option<CtfeProvenance>>::without_provenance(addr).to_string(),
                    );
                }

                diag.arg("inbounds_size", inbounds_size);
                diag.arg("inbounds_size_is_neg", inbounds_size < 0);
                diag.arg("inbounds_size_abs", inbounds_size.unsigned_abs());
                diag.arg("operation", format!("{:?}", msg));
            }
            AlignmentCheckFailed(Misalignment { required, has }, msg) => {
                diag.arg("required", required.bytes());
                diag.arg("has", has.bytes());
                diag.arg("msg", format!("{msg:?}"));
            }
            WriteToReadOnly(alloc)
            | DerefFunctionPointer(alloc)
            | DerefVTablePointer(alloc)
            | DerefVaListPointer(alloc)
            | DerefTypeIdPointer(alloc) => {
                diag.arg("allocation", alloc);
            }
            InvalidBool(b) => {
                diag.arg("value", format!("{b:02x}"));
            }
            InvalidChar(c) => {
                diag.arg("value", format!("{c:08x}"));
            }
            InvalidTag(tag) => {
                diag.arg("tag", format!("{tag:x}"));
            }
            InvalidStr(err) => {
                diag.arg("err", format!("{err}"));
            }
            InvalidUninitBytes(Some((alloc, info))) => {
                diag.arg("alloc", alloc);
                diag.arg("access", info.access);
                diag.arg("uninit", info.bad);
            }
            ScalarSizeMismatch(info) => {
                diag.arg("target_size", info.target_size);
                diag.arg("data_size", info.data_size);
            }
            InvalidNichedEnumVariantWritten { enum_ty } => {
                diag.arg("ty", enum_ty);
            }
            AbiMismatchArgument { arg_idx, caller_ty, callee_ty } => {
                diag.arg("arg_idx", arg_idx + 1); // adjust for 1-indexed lists in output
                diag.arg("caller_ty", caller_ty);
                diag.arg("callee_ty", callee_ty);
            }
            AbiMismatchReturn { caller_ty, callee_ty } => {
                diag.arg("caller_ty", caller_ty);
                diag.arg("callee_ty", callee_ty);
            }
            CVariadicMismatch { caller_is_c_variadic, callee_is_c_variadic } => {
                diag.arg("caller_is_c_variadic", caller_is_c_variadic);
                diag.arg("callee_is_c_variadic", callee_is_c_variadic);
            }
            CVariadicFixedCountMismatch { caller, callee } => {
                diag.arg("caller", caller);
                diag.arg("callee", callee);
            }
        }
    }
}

impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
    fn diagnostic_message(&self) -> DiagMessage {
        use rustc_middle::mir::interpret::ValidationErrorKind::*;

        match self.kind {
            PtrToUninhabited { ptr_kind: PointerKind::Box, .. } => {
                msg!("{$front_matter}: encountered a box pointing to uninhabited type {$ty}")
            }
            PtrToUninhabited { ptr_kind: PointerKind::Ref(_), .. } => {
                msg!("{$front_matter}: encountered a reference pointing to uninhabited type {$ty}")
            }

            PointerAsInt { .. } => {
                msg!("{$front_matter}: encountered a pointer, but {$expected}")
            }
            PartialPointer => {
                msg!("{$front_matter}: encountered a partial pointer or a mix of pointers")
            }
            MutableRefToImmutable => {
                msg!(
                    "{$front_matter}: encountered mutable reference or box pointing to read-only memory"
                )
            }
            NullFnPtr { .. } => {
                msg!(
                    "{$front_matter}: encountered a {$maybe ->
                        [true] maybe-null
                        *[false] null
                    } function pointer"
                )
            }
            NeverVal => {
                msg!("{$front_matter}: encountered a value of the never type `!`")
            }
            NonnullPtrMaybeNull { .. } => {
                msg!(
                    "{$front_matter}: encountered a maybe-null pointer, but expected something that is definitely non-zero"
                )
            }
            PtrOutOfRange { .. } => {
                msg!(
                    "{$front_matter}: encountered a pointer with unknown absolute address, but expected something that is definitely {$in_range}"
                )
            }
            OutOfRange { .. } => {
                msg!("{$front_matter}: encountered {$value}, but expected something {$in_range}")
            }
            UnsafeCellInImmutable => {
                msg!("{$front_matter}: encountered `UnsafeCell` in read-only memory")
            }
            UninhabitedVal { .. } => {
                msg!("{$front_matter}: encountered a value of uninhabited type `{$ty}`")
            }
            InvalidEnumTag { .. } => {
                msg!("{$front_matter}: encountered {$value}, but expected a valid enum tag")
            }
            UninhabitedEnumVariant => {
                msg!("{$front_matter}: encountered an uninhabited enum variant")
            }
            Uninit { .. } => {
                msg!("{$front_matter}: encountered uninitialized memory, but {$expected}")
            }
            InvalidVTablePtr { .. } => {
                msg!("{$front_matter}: encountered {$value}, but expected a vtable pointer")
            }
            InvalidMetaWrongTrait { .. } => {
                msg!(
                    "{$front_matter}: wrong trait in wide pointer vtable: expected `{$expected_dyn_type}`, but encountered `{$vtable_dyn_type}`"
                )
            }
            InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Box } => {
                msg!(
                    "{$front_matter}: encountered invalid box metadata: slice is bigger than largest supported object"
                )
            }
            InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Ref(_) } => {
                msg!(
                    "{$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object"
                )
            }

            InvalidMetaTooLarge { ptr_kind: PointerKind::Box } => {
                msg!(
                    "{$front_matter}: encountered invalid box metadata: total size is bigger than largest supported object"
                )
            }
            InvalidMetaTooLarge { ptr_kind: PointerKind::Ref(_) } => {
                msg!(
                    "{$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object"
                )
            }
            UnalignedPtr { ptr_kind: PointerKind::Ref(_), .. } => {
                msg!(
                    "{$front_matter}: encountered an unaligned reference (required {$required_bytes} byte alignment but found {$found_bytes})"
                )
            }
            UnalignedPtr { ptr_kind: PointerKind::Box, .. } => {
                msg!(
                    "{$front_matter}: encountered an unaligned box (required {$required_bytes} byte alignment but found {$found_bytes})"
                )
            }

            NullPtr { ptr_kind: PointerKind::Box, .. } => {
                msg!(
                    "{$front_matter}: encountered a {$maybe ->
                        [true] maybe-null
                        *[false] null
                    } box"
                )
            }
            NullPtr { ptr_kind: PointerKind::Ref(_), .. } => {
                msg!(
                    "{$front_matter}: encountered a {$maybe ->
                        [true] maybe-null
                        *[false] null
                    } reference"
                )
            }
            DanglingPtrNoProvenance { ptr_kind: PointerKind::Box, .. } => {
                msg!("{$front_matter}: encountered a dangling box ({$pointer} has no provenance)")
            }
            DanglingPtrNoProvenance { ptr_kind: PointerKind::Ref(_), .. } => {
                msg!(
                    "{$front_matter}: encountered a dangling reference ({$pointer} has no provenance)"
                )
            }
            DanglingPtrOutOfBounds { ptr_kind: PointerKind::Box } => {
                msg!(
                    "{$front_matter}: encountered a dangling box (going beyond the bounds of its allocation)"
                )
            }
            DanglingPtrOutOfBounds { ptr_kind: PointerKind::Ref(_) } => {
                msg!(
                    "{$front_matter}: encountered a dangling reference (going beyond the bounds of its allocation)"
                )
            }
            DanglingPtrUseAfterFree { ptr_kind: PointerKind::Box } => {
                msg!("{$front_matter}: encountered a dangling box (use-after-free)")
            }
            DanglingPtrUseAfterFree { ptr_kind: PointerKind::Ref(_) } => {
                msg!("{$front_matter}: encountered a dangling reference (use-after-free)")
            }
            InvalidBool { .. } => {
                msg!("{$front_matter}: encountered {$value}, but expected a boolean")
            }
            InvalidChar { .. } => {
                msg!(
                    "{$front_matter}: encountered {$value}, but expected a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`)"
                )
            }
            InvalidFnPtr { .. } => {
                msg!("{$front_matter}: encountered {$value}, but expected a function pointer")
            }
        }
    }

    fn add_args<G: EmissionGuarantee>(self, err: &mut Diag<'_, G>) {
        use rustc_errors::msg;
        use rustc_middle::mir::interpret::ValidationErrorKind::*;

        if let PointerAsInt { .. } | PartialPointer = self.kind {
            err.help(msg!("this code performed an operation that depends on the underlying bytes representing a pointer"));
            err.help(msg!("the absolute address of a pointer is not known at compile-time, so such operations are not supported"));
        }

        let message = if let Some(path) = self.path {
            err.dcx.eagerly_translate_to_string(
                msg!("constructing invalid value at {$path}"),
                [("path".into(), DiagArgValue::Str(path.into()))].iter().map(|(a, b)| (a, b)),
            )
        } else {
            err.dcx.eagerly_translate_to_string(msg!("constructing invalid value"), [].into_iter())
        };

        err.arg("front_matter", message);

        fn add_range_arg<G: EmissionGuarantee>(
            r: WrappingRange,
            max_hi: u128,
            err: &mut Diag<'_, G>,
        ) {
            let WrappingRange { start: lo, end: hi } = r;
            assert!(hi <= max_hi);
            let msg = if lo > hi {
                msg!("less or equal to {$hi}, or greater or equal to {$lo}")
            } else if lo == hi {
                msg!("equal to {$lo}")
            } else if lo == 0 {
                assert!(hi < max_hi, "should not be printing if the range covers everything");
                msg!("less or equal to {$hi}")
            } else if hi == max_hi {
                assert!(lo > 0, "should not be printing if the range covers everything");
                msg!("greater or equal to {$lo}")
            } else {
                msg!("in the range {$lo}..={$hi}")
            };

            let args = [
                ("lo".into(), DiagArgValue::Str(lo.to_string().into())),
                ("hi".into(), DiagArgValue::Str(hi.to_string().into())),
            ];
            let args = args.iter().map(|(a, b)| (a, b));
            let message = err.dcx.eagerly_translate_to_string(msg, args);
            err.arg("in_range", message);
        }

        match self.kind {
            PtrToUninhabited { ty, .. } | UninhabitedVal { ty } => {
                err.arg("ty", ty);
            }
            PointerAsInt { expected } | Uninit { expected } => {
                let msg = match expected {
                    ExpectedKind::Reference => msg!("expected a reference"),
                    ExpectedKind::Box => msg!("expected a box"),
                    ExpectedKind::RawPtr => msg!("expected a raw pointer"),
                    ExpectedKind::InitScalar => msg!("expected initialized scalar value"),
                    ExpectedKind::Bool => msg!("expected a boolean"),
                    ExpectedKind::Char => msg!("expected a unicode scalar value"),
                    ExpectedKind::Float => msg!("expected a floating point number"),
                    ExpectedKind::Int => msg!("expected an integer"),
                    ExpectedKind::FnPtr => msg!("expected a function pointer"),
                    ExpectedKind::EnumTag => msg!("expected a valid enum tag"),
                    ExpectedKind::Str => msg!("expected a string"),
                };
                let msg = err.dcx.eagerly_translate_to_string(msg, [].into_iter());
                err.arg("expected", msg);
            }
            InvalidEnumTag { value }
            | InvalidVTablePtr { value }
            | InvalidBool { value }
            | InvalidChar { value }
            | InvalidFnPtr { value } => {
                err.arg("value", value);
            }
            PtrOutOfRange { range, max_value } => add_range_arg(range, max_value, err),
            OutOfRange { range, max_value, value } => {
                err.arg("value", value);
                add_range_arg(range, max_value, err);
            }
            UnalignedPtr { required_bytes, found_bytes, .. } => {
                err.arg("required_bytes", required_bytes);
                err.arg("found_bytes", found_bytes);
            }
            DanglingPtrNoProvenance { pointer, .. } => {
                err.arg("pointer", pointer);
            }
            InvalidMetaWrongTrait { vtable_dyn_type, expected_dyn_type } => {
                err.arg("vtable_dyn_type", vtable_dyn_type.to_string());
                err.arg("expected_dyn_type", expected_dyn_type.to_string());
            }
            NullPtr { maybe, .. } | NullFnPtr { maybe } => {
                err.arg("maybe", maybe);
            }
            MutableRefToImmutable
            | NonnullPtrMaybeNull
            | NeverVal
            | UnsafeCellInImmutable
            | InvalidMetaSliceTooLarge { .. }
            | InvalidMetaTooLarge { .. }
            | DanglingPtrUseAfterFree { .. }
            | DanglingPtrOutOfBounds { .. }
            | UninhabitedEnumVariant
            | PartialPointer => {}
        }
    }
}

impl ReportErrorExt for UnsupportedOpInfo {
    fn diagnostic_message(&self) -> DiagMessage {
        match self {
            UnsupportedOpInfo::Unsupported(s) => s.clone().into(),
            UnsupportedOpInfo::ExternTypeField => {
                "`extern type` field does not have a known offset".into()
            }
            UnsupportedOpInfo::UnsizedLocal => "unsized locals are not supported".into(),
            UnsupportedOpInfo::ReadPartialPointer(_) => {
                msg!("unable to read parts of a pointer from memory at {$ptr}")
            }
            UnsupportedOpInfo::ReadPointerAsInt(_) => "unable to turn pointer into integer".into(),
            UnsupportedOpInfo::ThreadLocalStatic(_) => {
                msg!("cannot access thread local static `{$did}`")
            }
            UnsupportedOpInfo::ExternStatic(_) => {
                msg!("cannot access extern static `{$did}`")
            }
        }
        .into()
    }

    fn add_args<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
        use UnsupportedOpInfo::*;

        if let ReadPointerAsInt(_) | ReadPartialPointer(_) = self {
            diag.help("this code performed an operation that depends on the underlying bytes representing a pointer");
            diag.help("the absolute address of a pointer is not known at compile-time, so such operations are not supported");
        }
        match self {
            // `ReadPointerAsInt(Some(info))` is never printed anyway, it only serves as an error to
            // be further processed by validity checking which then turns it into something nice to
            // print. So it's not worth the effort of having diagnostics that can print the `info`.
            UnsizedLocal
            | UnsupportedOpInfo::ExternTypeField
            | Unsupported(_)
            | ReadPointerAsInt(_) => {}
            ReadPartialPointer(ptr) => {
                diag.arg("ptr", ptr);
            }
            ThreadLocalStatic(did) | ExternStatic(did) => rustc_middle::ty::tls::with(|tcx| {
                diag.arg("did", tcx.def_path_str(did));
            }),
        }
    }
}

impl<'tcx> ReportErrorExt for InterpErrorKind<'tcx> {
    fn diagnostic_message(&self) -> DiagMessage {
        match self {
            InterpErrorKind::UndefinedBehavior(ub) => ub.diagnostic_message(),
            InterpErrorKind::Unsupported(e) => e.diagnostic_message(),
            InterpErrorKind::InvalidProgram(e) => e.diagnostic_message(),
            InterpErrorKind::ResourceExhaustion(e) => e.diagnostic_message(),
            InterpErrorKind::MachineStop(e) => e.diagnostic_message(),
        }
    }
    fn add_args<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
        match self {
            InterpErrorKind::UndefinedBehavior(ub) => ub.add_args(diag),
            InterpErrorKind::Unsupported(e) => e.add_args(diag),
            InterpErrorKind::InvalidProgram(e) => e.add_args(diag),
            InterpErrorKind::ResourceExhaustion(e) => e.add_args(diag),
            InterpErrorKind::MachineStop(e) => e.add_args(&mut |name, value| {
                diag.arg(name, value);
            }),
        }
    }
}

impl<'tcx> ReportErrorExt for InvalidProgramInfo<'tcx> {
    fn diagnostic_message(&self) -> DiagMessage {
        match self {
            InvalidProgramInfo::TooGeneric => "encountered overly generic constant".into(),
            InvalidProgramInfo::AlreadyReported(_) => {
                "an error has already been reported elsewhere (this should not usually be printed)"
                    .into()
            }
            InvalidProgramInfo::Layout(e) => e.diagnostic_message(),
        }
    }
    fn add_args<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
        match self {
            InvalidProgramInfo::TooGeneric | InvalidProgramInfo::AlreadyReported(_) => {}
            InvalidProgramInfo::Layout(e) => {
                // The level doesn't matter, `dummy_diag` is consumed without it being used.
                let dummy_level = Level::Bug;
                let dummy_diag: Diag<'_, ()> = e.into_diagnostic().into_diag(diag.dcx, dummy_level);
                for (name, val) in dummy_diag.args.iter() {
                    diag.arg(name.clone(), val.clone());
                }
                dummy_diag.cancel();
            }
        }
    }
}

impl ReportErrorExt for ResourceExhaustionInfo {
    fn diagnostic_message(&self) -> DiagMessage {
        match self {
            ResourceExhaustionInfo::StackFrameLimitReached => {
                "reached the configured maximum number of stack frames"
            }
            ResourceExhaustionInfo::MemoryExhausted => {
                "tried to allocate more memory than available to compiler"
            }
            ResourceExhaustionInfo::AddressSpaceFull => {
                "there are no more free addresses in the address space"
            }
            ResourceExhaustionInfo::Interrupted => "compilation was interrupted",
        }
        .into()
    }
    fn add_args<G: EmissionGuarantee>(self, _: &mut Diag<'_, G>) {}
}

impl rustc_errors::IntoDiagArg for InternKind {
    fn into_diag_arg(self, _: &mut Option<std::path::PathBuf>) -> DiagArgValue {
        DiagArgValue::Str(Cow::Borrowed(match self {
            InternKind::Static(Mutability::Not) => "static",
            InternKind::Static(Mutability::Mut) => "static_mut",
            InternKind::Constant => "const",
            InternKind::Promoted => "promoted",
        }))
    }
}
