about summary refs log tree commit diff
path: root/compiler/rustc_const_eval/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-06-02 05:11:49 +0000
committerbors <bors@rust-lang.org>2023-06-02 05:11:49 +0000
commit33c3d101280c8eb3cd8af421bfb56a8afcc3881d (patch)
tree2e65db5c7568d3a9220cca76dec79c036a97d749 /compiler/rustc_const_eval/src
parent774a3d1523bde3a16f8a92dbaac01d211ba911c3 (diff)
parentf6c2bc5c24fd08200c0e4438d981ca58dc71f488 (diff)
downloadrust-33c3d101280c8eb3cd8af421bfb56a8afcc3881d.tar.gz
rust-33c3d101280c8eb3cd8af421bfb56a8afcc3881d.zip
Auto merge of #111677 - fee1-dead-contrib:rustc_const_eval-translatable, r=oli-obk,RalfJung
Use translatable diagnostics in `rustc_const_eval`

This PR:

* adds a `no_span` parameter to `note` / `help` attributes when using `Subdiagnostic` to allow adding notes/helps without using a span
* has minor tweaks and changes to error messages
Diffstat (limited to 'compiler/rustc_const_eval/src')
-rw-r--r--compiler/rustc_const_eval/src/const_eval/error.rs280
-rw-r--r--compiler/rustc_const_eval/src/const_eval/eval_queries.rs97
-rw-r--r--compiler/rustc_const_eval/src/const_eval/machine.rs37
-rw-r--r--compiler/rustc_const_eval/src/const_eval/mod.rs13
-rw-r--r--compiler/rustc_const_eval/src/errors.rs658
-rw-r--r--compiler/rustc_const_eval/src/interpret/cast.rs20
-rw-r--r--compiler/rustc_const_eval/src/interpret/eval_context.rs35
-rw-r--r--compiler/rustc_const_eval/src/interpret/intern.rs27
-rw-r--r--compiler/rustc_const_eval/src/interpret/intrinsics.rs75
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs63
-rw-r--r--compiler/rustc_const_eval/src/interpret/terminator.rs51
-rw-r--r--compiler/rustc_const_eval/src/interpret/validity.rs217
-rw-r--r--compiler/rustc_const_eval/src/lib.rs3
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/ops.rs133
14 files changed, 1201 insertions, 508 deletions
diff --git a/compiler/rustc_const_eval/src/const_eval/error.rs b/compiler/rustc_const_eval/src/const_eval/error.rs
index c591ff75ab8..7890d878d08 100644
--- a/compiler/rustc_const_eval/src/const_eval/error.rs
+++ b/compiler/rustc_const_eval/src/const_eval/error.rs
@@ -1,17 +1,15 @@
-use std::error::Error;
-use std::fmt;
+use std::mem;
 
-use rustc_errors::Diagnostic;
+use rustc_errors::{DiagnosticArgValue, DiagnosticMessage, IntoDiagnostic, IntoDiagnosticArg};
 use rustc_middle::mir::AssertKind;
-use rustc_middle::query::TyCtxtAt;
+use rustc_middle::ty::TyCtxt;
 use rustc_middle::ty::{layout::LayoutError, ConstInt};
-use rustc_span::{Span, Symbol};
+use rustc_span::source_map::Spanned;
+use rustc_span::{ErrorGuaranteed, Span, Symbol};
 
 use super::InterpCx;
-use crate::interpret::{
-    struct_error, ErrorHandled, FrameInfo, InterpError, InterpErrorInfo, Machine, MachineStopType,
-    UnsupportedOpInfo,
-};
+use crate::errors::{self, FrameNote, ReportErrorExt};
+use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, Machine, MachineStopType};
 
 /// The CTFE machine has some custom error kinds.
 #[derive(Clone, Debug)]
@@ -23,7 +21,35 @@ pub enum ConstEvalErrKind {
     Abort(String),
 }
 
-impl MachineStopType for ConstEvalErrKind {}
+impl MachineStopType for ConstEvalErrKind {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        use ConstEvalErrKind::*;
+        match self {
+            ConstAccessesStatic => const_eval_const_accesses_static,
+            ModifiedGlobal => const_eval_modified_global,
+            Panic { .. } => const_eval_panic,
+            AssertFailure(x) => x.diagnostic_message(),
+            Abort(msg) => msg.to_string().into(),
+        }
+    }
+    fn add_args(
+        self: Box<Self>,
+        adder: &mut dyn FnMut(std::borrow::Cow<'static, str>, DiagnosticArgValue<'static>),
+    ) {
+        use ConstEvalErrKind::*;
+        match *self {
+            ConstAccessesStatic | ModifiedGlobal | Abort(_) => {}
+            AssertFailure(kind) => kind.add_args(adder),
+            Panic { msg, line, col, file } => {
+                adder("msg".into(), msg.into_diagnostic_arg());
+                adder("file".into(), file.into_diagnostic_arg());
+                adder("line".into(), line.into_diagnostic_arg());
+                adder("col".into(), col.into_diagnostic_arg());
+            }
+        }
+    }
+}
 
 // The errors become `MachineStop` with plain strings when being raised.
 // `ConstEvalErr` (in `librustc_middle/mir/interpret/error.rs`) knows to
@@ -34,151 +60,117 @@ impl<'tcx> Into<InterpErrorInfo<'tcx>> for ConstEvalErrKind {
     }
 }
 
-impl fmt::Display for ConstEvalErrKind {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        use self::ConstEvalErrKind::*;
-        match self {
-            ConstAccessesStatic => write!(f, "constant accesses static"),
-            ModifiedGlobal => {
-                write!(f, "modifying a static's initial value from another static's initializer")
-            }
-            AssertFailure(msg) => write!(f, "{:?}", msg),
-            Panic { msg, line, col, file } => {
-                write!(f, "the evaluated program panicked at '{}', {}:{}:{}", msg, file, line, col)
-            }
-            Abort(msg) => write!(f, "{}", msg),
-        }
-    }
-}
-
-impl Error for ConstEvalErrKind {}
+pub fn get_span_and_frames<'tcx, 'mir, M: Machine<'mir, 'tcx>>(
+    ecx: &InterpCx<'mir, 'tcx, M>,
+) -> (Span, Vec<errors::FrameNote>)
+where
+    'tcx: 'mir,
+{
+    let mut stacktrace = ecx.generate_stacktrace();
+    // Filter out `requires_caller_location` frames.
+    stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
+    let span = stacktrace.first().map(|f| f.span).unwrap_or(ecx.tcx.span);
 
-/// When const-evaluation errors, this type is constructed with the resulting information,
-/// and then used to emit the error as a lint or hard error.
-#[derive(Debug)]
-pub(super) struct ConstEvalErr<'tcx> {
-    pub span: Span,
-    pub error: InterpError<'tcx>,
-    pub stacktrace: Vec<FrameInfo<'tcx>>,
-}
+    let mut frames = Vec::new();
 
-impl<'tcx> ConstEvalErr<'tcx> {
-    /// Turn an interpreter error into something to report to the user.
-    /// As a side-effect, if RUSTC_CTFE_BACKTRACE is set, this prints the backtrace.
-    /// Should be called only if the error is actually going to be reported!
-    pub fn new<'mir, M: Machine<'mir, 'tcx>>(
-        ecx: &InterpCx<'mir, 'tcx, M>,
-        error: InterpErrorInfo<'tcx>,
-        span: Option<Span>,
-    ) -> ConstEvalErr<'tcx>
-    where
-        'tcx: 'mir,
-    {
-        error.print_backtrace();
-        let mut stacktrace = ecx.generate_stacktrace();
-        // Filter out `requires_caller_location` frames.
-        stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
-        // If `span` is missing, use topmost remaining frame, or else the "root" span from `ecx.tcx`.
-        let span = span.or_else(|| stacktrace.first().map(|f| f.span)).unwrap_or(ecx.tcx.span);
-        ConstEvalErr { error: error.into_kind(), stacktrace, span }
-    }
-
-    pub(super) fn report(&self, tcx: TyCtxtAt<'tcx>, message: &str) -> ErrorHandled {
-        self.report_decorated(tcx, message, |_| {})
-    }
-
-    #[instrument(level = "trace", skip(self, decorate))]
-    pub(super) fn decorate(&self, err: &mut Diagnostic, decorate: impl FnOnce(&mut Diagnostic)) {
-        trace!("reporting const eval failure at {:?}", self.span);
-        // Add some more context for select error types.
-        match self.error {
-            InterpError::Unsupported(
-                UnsupportedOpInfo::ReadPointerAsBytes
-                | UnsupportedOpInfo::PartialPointerOverwrite(_)
-                | UnsupportedOpInfo::PartialPointerCopy(_),
-            ) => {
-                err.help("this code performed an operation that depends on the underlying bytes representing a pointer");
-                err.help("the absolute address of a pointer is not known at compile-time, so such operations are not supported");
+    // Add notes to the backtrace. Don't print a single-line backtrace though.
+    if stacktrace.len() > 1 {
+        // Helper closure to print duplicated lines.
+        let mut add_frame = |mut frame: errors::FrameNote| {
+            frames.push(errors::FrameNote { times: 0, ..frame.clone() });
+            // Don't print [... additional calls ...] if the number of lines is small
+            if frame.times < 3 {
+                let times = frame.times;
+                frame.times = 0;
+                frames.extend(std::iter::repeat(frame).take(times as usize));
+            } else {
+                frames.push(frame);
             }
-            _ => {}
-        }
-        // Add spans for the stacktrace. Don't print a single-line backtrace though.
-        if self.stacktrace.len() > 1 {
-            // Helper closure to print duplicated lines.
-            let mut flush_last_line = |last_frame: Option<(String, _)>, times| {
-                if let Some((line, span)) = last_frame {
-                    err.span_note(span, line.clone());
-                    // Don't print [... additional calls ...] if the number of lines is small
-                    if times < 3 {
-                        for _ in 0..times {
-                            err.span_note(span, line.clone());
-                        }
-                    } else {
-                        err.span_note(
-                            span,
-                            format!("[... {} additional calls {} ...]", times, &line),
-                        );
-                    }
-                }
-            };
+        };
 
-            let mut last_frame = None;
-            let mut times = 0;
-            for frame_info in &self.stacktrace {
-                let frame = (frame_info.to_string(), frame_info.span);
-                if last_frame.as_ref() == Some(&frame) {
-                    times += 1;
-                } else {
-                    flush_last_line(last_frame, times);
+        let mut last_frame: Option<errors::FrameNote> = None;
+        for frame_info in &stacktrace {
+            let frame = frame_info.as_note(*ecx.tcx);
+            match last_frame.as_mut() {
+                Some(last_frame)
+                    if last_frame.span == frame.span
+                        && last_frame.where_ == frame.where_
+                        && last_frame.instance == frame.instance =>
+                {
+                    last_frame.times += 1;
+                }
+                Some(last_frame) => {
+                    add_frame(mem::replace(last_frame, frame));
+                }
+                None => {
                     last_frame = Some(frame);
-                    times = 0;
                 }
             }
-            flush_last_line(last_frame, times);
         }
-        // Let the caller attach any additional information it wants.
-        decorate(err);
+        if let Some(frame) = last_frame {
+            add_frame(frame);
+        }
     }
 
-    /// Create a diagnostic for this const eval error.
-    ///
-    /// Sets the message passed in via `message` and adds span labels with detailed error
-    /// information before handing control back to `decorate` to do any final annotations,
-    /// after which the diagnostic is emitted.
-    ///
-    /// If `lint_root.is_some()` report it as a lint, else report it as a hard error.
-    /// (Except that for some errors, we ignore all that -- see `must_error` below.)
-    #[instrument(skip(self, tcx, decorate), level = "debug")]
-    pub(super) fn report_decorated(
-        &self,
-        tcx: TyCtxtAt<'tcx>,
-        message: &str,
-        decorate: impl FnOnce(&mut Diagnostic),
-    ) -> ErrorHandled {
-        debug!("self.error: {:?}", self.error);
-        // Special handling for certain errors
-        match &self.error {
-            // Don't emit a new diagnostic for these errors
-            err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
-                ErrorHandled::TooGeneric
-            }
-            err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(*error_reported),
-            err_inval!(Layout(LayoutError::SizeOverflow(_))) => {
-                // We must *always* hard error on these, even if the caller wants just a lint.
-                // The `message` makes little sense here, this is a more serious error than the
-                // caller thinks anyway.
-                // See <https://github.com/rust-lang/rust/pull/63152>.
-                let mut err = struct_error(tcx, &self.error.to_string());
-                self.decorate(&mut err, decorate);
-                ErrorHandled::Reported(err.emit().into())
-            }
-            _ => {
-                // Report as hard error.
-                let mut err = struct_error(tcx, message);
-                err.span_label(self.span, self.error.to_string());
-                self.decorate(&mut err, decorate);
-                ErrorHandled::Reported(err.emit().into())
+    (span, frames)
+}
+
+/// Create a diagnostic for a const eval error.
+///
+/// This will use the `mk` function for creating the error which will get passed labels according to
+/// the `InterpError` and the span and a stacktrace of current execution according to
+/// `get_span_and_frames`.
+pub(super) fn report<'tcx, C, F, E>(
+    tcx: TyCtxt<'tcx>,
+    error: InterpError<'tcx>,
+    span: Option<Span>,
+    get_span_and_frames: C,
+    mk: F,
+) -> ErrorHandled
+where
+    C: FnOnce() -> (Span, Vec<FrameNote>),
+    F: FnOnce(Span, Vec<FrameNote>) -> E,
+    E: IntoDiagnostic<'tcx, ErrorGuaranteed>,
+{
+    // Special handling for certain errors
+    match error {
+        // Don't emit a new diagnostic for these errors
+        err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
+            ErrorHandled::TooGeneric
+        }
+        err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(error_reported),
+        err_inval!(Layout(layout_error @ LayoutError::SizeOverflow(_))) => {
+            // We must *always* hard error on these, even if the caller wants just a lint.
+            // The `message` makes little sense here, this is a more serious error than the
+            // caller thinks anyway.
+            // See <https://github.com/rust-lang/rust/pull/63152>.
+            let (our_span, frames) = get_span_and_frames();
+            let span = span.unwrap_or(our_span);
+            let mut err =
+                tcx.sess.create_err(Spanned { span, node: layout_error.into_diagnostic() });
+            err.code(rustc_errors::error_code!(E0080));
+            let Some((mut err, handler)) = err.into_diagnostic() else {
+                    panic!("did not emit diag");
+                };
+            for frame in frames {
+                err.eager_subdiagnostic(handler, frame);
             }
+
+            ErrorHandled::Reported(handler.emit_diagnostic(&mut err).unwrap().into())
+        }
+        _ => {
+            // Report as hard error.
+            let (our_span, frames) = get_span_and_frames();
+            let span = span.unwrap_or(our_span);
+            let err = mk(span, frames);
+            let mut err = tcx.sess.create_err(err);
+
+            let msg = error.diagnostic_message();
+            error.add_args(&tcx.sess.parse_sess.span_diagnostic, &mut err);
+
+            // Use *our* span to label the interp error
+            err.span_label(our_span, msg);
+            ErrorHandled::Reported(err.emit().into())
         }
     }
 }
diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
index e4d34b90018..8b8e8ff58e9 100644
--- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
+++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
@@ -1,12 +1,12 @@
 use crate::const_eval::CheckAlignment;
-use std::borrow::Cow;
+use crate::errors::ConstEvalError;
 
 use either::{Left, Right};
 
 use rustc_hir::def::DefKind;
 use rustc_middle::mir;
-use rustc_middle::mir::interpret::ErrorHandled;
-use rustc_middle::mir::pretty::display_allocation;
+use rustc_middle::mir::interpret::{ErrorHandled, InterpErrorInfo};
+use rustc_middle::mir::pretty::write_allocation_bytes;
 use rustc_middle::traits::Reveal;
 use rustc_middle::ty::layout::LayoutOf;
 use rustc_middle::ty::print::with_no_trimmed_paths;
@@ -14,7 +14,8 @@ use rustc_middle::ty::{self, TyCtxt};
 use rustc_span::source_map::Span;
 use rustc_target::abi::{self, Abi};
 
-use super::{CompileTimeEvalContext, CompileTimeInterpreter, ConstEvalErr};
+use super::{CompileTimeEvalContext, CompileTimeInterpreter};
+use crate::errors;
 use crate::interpret::eval_nullary_intrinsic;
 use crate::interpret::{
     intern_const_alloc_recursive, Allocation, ConstAlloc, ConstValue, CtfeValidationMode, GlobalId,
@@ -22,10 +23,6 @@ use crate::interpret::{
     RefTracking, StackPopCleanup,
 };
 
-const NOTE_ON_UNDEFINED_BEHAVIOR_ERROR: &str = "The rules on what exactly is undefined behavior aren't clear, \
-     so this check might be overzealous. Please open an issue on the rustc \
-     repository if you believe it should not be considered undefined behavior.";
-
 // Returns a pointer to where the result lives
 fn eval_body_using_ecx<'mir, 'tcx>(
     ecx: &mut CompileTimeEvalContext<'mir, 'tcx>,
@@ -253,8 +250,14 @@ pub fn eval_to_const_value_raw_provider<'tcx>(
         };
         return eval_nullary_intrinsic(tcx, key.param_env, def_id, substs).map_err(|error| {
             let span = tcx.def_span(def_id);
-            let error = ConstEvalErr { error: error.into_kind(), stacktrace: vec![], span };
-            error.report(tcx.at(span), "could not evaluate nullary intrinsic")
+
+            super::report(
+                tcx,
+                error.into_kind(),
+                Some(span),
+                || (span, vec![]),
+                |span, _| errors::NullaryIntrinsicError { span },
+            )
         });
     }
 
@@ -318,9 +321,11 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
     let res = ecx.load_mir(cid.instance.def, cid.promoted);
     match res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, &body)) {
         Err(error) => {
-            let err = ConstEvalErr::new(&ecx, error, None);
-            let msg = if is_static {
-                Cow::from("could not evaluate static initializer")
+            let (error, backtrace) = error.into_parts();
+            backtrace.print_backtrace();
+
+            let (kind, instance) = if is_static {
+                ("static", String::new())
             } else {
                 // If the current item has generics, we'd like to enrich the message with the
                 // instance and its substs: to show the actual compile-time values, in addition to
@@ -328,19 +333,29 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
                 let instance = &key.value.instance;
                 if !instance.substs.is_empty() {
                     let instance = with_no_trimmed_paths!(instance.to_string());
-                    let msg = format!("evaluation of `{}` failed", instance);
-                    Cow::from(msg)
+                    ("const_with_path", instance)
                 } else {
-                    Cow::from("evaluation of constant value failed")
+                    ("const", String::new())
                 }
             };
 
-            Err(err.report(ecx.tcx.at(err.span), &msg))
+            Err(super::report(
+                *ecx.tcx,
+                error,
+                None,
+                || super::get_span_and_frames(&ecx),
+                |span, frames| ConstEvalError {
+                    span,
+                    error_kind: kind,
+                    instance,
+                    frame_notes: frames,
+                },
+            ))
         }
         Ok(mplace) => {
             // Since evaluation had no errors, validate the resulting constant.
             // This is a separate `try` block to provide more targeted error reporting.
-            let validation = try {
+            let validation: Result<_, InterpErrorInfo<'_>> = try {
                 let mut ref_tracking = RefTracking::new(mplace);
                 let mut inner = false;
                 while let Some((mplace, path)) = ref_tracking.todo.pop() {
@@ -357,23 +372,37 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
                 }
             };
             let alloc_id = mplace.ptr.provenance.unwrap();
+
+            // Validation failed, report an error. This is always a hard error.
             if let Err(error) = validation {
-                // Validation failed, report an error. This is always a hard error.
-                let err = ConstEvalErr::new(&ecx, error, None);
-                Err(err.report_decorated(
-                    ecx.tcx,
-                    "it is undefined behavior to use this value",
-                    |diag| {
-                        if matches!(err.error, InterpError::UndefinedBehavior(_)) {
-                            diag.note(NOTE_ON_UNDEFINED_BEHAVIOR_ERROR);
-                        }
-                        diag.note(format!(
-                            "the raw bytes of the constant ({}",
-                            display_allocation(
-                                *ecx.tcx,
-                                ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner()
-                            )
-                        ));
+                let (error, backtrace) = error.into_parts();
+                backtrace.print_backtrace();
+
+                let ub_note = matches!(error, InterpError::UndefinedBehavior(_)).then(|| {});
+
+                let alloc = ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner();
+                let mut bytes = String::new();
+                if alloc.size() != abi::Size::ZERO {
+                    bytes = "\n".into();
+                    // FIXME(translation) there might be pieces that are translatable.
+                    write_allocation_bytes(*ecx.tcx, alloc, &mut bytes, "    ").unwrap();
+                }
+                let raw_bytes = errors::RawBytesNote {
+                    size: alloc.size().bytes(),
+                    align: alloc.align.bytes(),
+                    bytes,
+                };
+
+                Err(super::report(
+                    *ecx.tcx,
+                    error,
+                    None,
+                    || super::get_span_and_frames(&ecx),
+                    move |span, frames| errors::UndefinedBehavior {
+                        span,
+                        ub_note,
+                        frames,
+                        raw_bytes,
                     },
                 ))
             } else {
diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs
index a8b6b98c96c..7391f567040 100644
--- a/compiler/rustc_const_eval/src/const_eval/machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/machine.rs
@@ -25,6 +25,7 @@ use crate::interpret::{
     self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx,
     InterpResult, OpTy, PlaceTy, Pointer, Scalar,
 };
+use crate::{errors, fluent_generated as fluent};
 
 use super::error::*;
 
@@ -254,7 +255,10 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
         let target_align = self.read_scalar(&args[1])?.to_target_usize(self)?;
 
         if !target_align.is_power_of_two() {
-            throw_ub_format!("`align_offset` called with non-power-of-two align: {}", target_align);
+            throw_ub_custom!(
+                fluent::const_eval_align_offset_invalid_align,
+                target_align = target_align,
+            );
         }
 
         match self.ptr_try_get_alloc_id(ptr) {
@@ -360,15 +364,18 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
                 "`alignment_check_failed` called when no alignment check requested"
             ),
             CheckAlignment::FutureIncompat => {
-                let err = ConstEvalErr::new(ecx, err, None);
-                ecx.tcx.struct_span_lint_hir(
+                let (_, backtrace) = err.into_parts();
+                backtrace.print_backtrace();
+                let (span, frames) = super::get_span_and_frames(&ecx);
+
+                ecx.tcx.emit_spanned_lint(
                     INVALID_ALIGNMENT,
                     ecx.stack().iter().find_map(|frame| frame.lint_root()).unwrap_or(CRATE_HIR_ID),
-                    err.span,
-                    err.error.to_string(),
-                    |db| {
-                        err.decorate(db, |_| {});
-                        db
+                    span,
+                    errors::AlignmentCheckFailed {
+                        has: has.bytes(),
+                        required: required.bytes(),
+                        frames,
                     },
                 );
                 Ok(())
@@ -482,7 +489,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
 
                 let align = match Align::from_bytes(align) {
                     Ok(a) => a,
-                    Err(err) => throw_ub_format!("align has to be a power of 2, {}", err),
+                    Err(err) => throw_ub_custom!(
+                        fluent::const_eval_invalid_align_details,
+                        name = "const_allocate",
+                        err_kind = err.diag_ident(),
+                        align = err.align()
+                    ),
                 };
 
                 let ptr = ecx.allocate_ptr(
@@ -500,7 +512,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
                 let size = Size::from_bytes(size);
                 let align = match Align::from_bytes(align) {
                     Ok(a) => a,
-                    Err(err) => throw_ub_format!("align has to be a power of 2, {}", err),
+                    Err(err) => throw_ub_custom!(
+                        fluent::const_eval_invalid_align_details,
+                        name = "const_deallocate",
+                        err_kind = err.diag_ident(),
+                        align = err.align()
+                    ),
                 };
 
                 // If an allocation is created in an another const,
diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs
index b59ca8e2070..6e462d3a1e9 100644
--- a/compiler/rustc_const_eval/src/const_eval/mod.rs
+++ b/compiler/rustc_const_eval/src/const_eval/mod.rs
@@ -73,17 +73,8 @@ pub(crate) fn eval_to_valtree<'tcx>(
             let global_const_id = cid.display(tcx);
             match err {
                 ValTreeCreationError::NodesOverflow => {
-                    let msg = format!(
-                        "maximum number of nodes exceeded in constant {}",
-                        &global_const_id
-                    );
-                    let mut diag = match tcx.hir().span_if_local(did) {
-                        Some(span) => {
-                            tcx.sess.create_err(MaxNumNodesInConstErr { span, global_const_id })
-                        }
-                        None => tcx.sess.struct_err(msg),
-                    };
-                    diag.emit();
+                    let span = tcx.hir().span_if_local(did);
+                    tcx.sess.emit_err(MaxNumNodesInConstErr { span, global_const_id });
 
                     Ok(None)
                 }
diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs
index ad2e68e752d..eed3091d481 100644
--- a/compiler/rustc_const_eval/src/errors.rs
+++ b/compiler/rustc_const_eval/src/errors.rs
@@ -1,6 +1,24 @@
+use rustc_errors::{
+    DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Handler,
+    IntoDiagnostic,
+};
 use rustc_hir::ConstContext;
-use rustc_macros::{Diagnostic, LintDiagnostic};
+use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
+use rustc_middle::mir::interpret::{
+    CheckInAllocMsg, ExpectedKind, InterpError, InvalidMetaKind, InvalidProgramInfo, PointerKind,
+    ResourceExhaustionInfo, UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo,
+};
+use rustc_middle::ty::{self, Ty};
 use rustc_span::Span;
+use rustc_target::abi::call::AdjustForForeignAbiError;
+use rustc_target::abi::{Size, WrappingRange};
+
+#[derive(Diagnostic)]
+#[diag(const_eval_dangling_ptr_in_final)]
+pub(crate) struct DanglingPtrInFinal {
+    #[primary_span]
+    pub span: Span,
+}
 
 #[derive(Diagnostic)]
 #[diag(const_eval_unstable_in_stable)]
@@ -92,7 +110,7 @@ pub(crate) struct TransientMutBorrowErrRaw {
 #[diag(const_eval_max_num_nodes_in_const)]
 pub(crate) struct MaxNumNodesInConstErr {
     #[primary_span]
-    pub span: Span,
+    pub span: Option<Span>,
     pub global_const_id: String,
 }
 
@@ -176,6 +194,14 @@ pub(crate) struct UnallowedInlineAsm {
 }
 
 #[derive(Diagnostic)]
+#[diag(const_eval_unsupported_untyped_pointer)]
+#[note]
+pub(crate) struct UnsupportedUntypedPointer {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
 #[diag(const_eval_interior_mutable_data_refer, code = "E0492")]
 pub(crate) struct InteriorMutableDataRefer {
     #[primary_span]
@@ -212,3 +238,631 @@ pub struct LongRunningWarn {
     #[help]
     pub item_span: Span,
 }
+
+#[derive(Diagnostic)]
+#[diag(const_eval_erroneous_constant)]
+pub(crate) struct ErroneousConstUsed {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Subdiagnostic)]
+#[note(const_eval_non_const_impl)]
+pub(crate) struct NonConstImplNote {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Subdiagnostic, PartialEq, Eq, Clone)]
+#[note(const_eval_frame_note)]
+pub struct FrameNote {
+    #[primary_span]
+    pub span: Span,
+    pub times: i32,
+    pub where_: &'static str,
+    pub instance: String,
+}
+
+#[derive(Subdiagnostic)]
+#[note(const_eval_raw_bytes)]
+pub struct RawBytesNote {
+    pub size: u64,
+    pub align: u64,
+    pub bytes: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_for_loop_into_iter_non_const, code = "E0015")]
+pub struct NonConstForLoopIntoIter<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_question_branch_non_const, code = "E0015")]
+pub struct NonConstQuestionBranch<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_question_from_residual_non_const, code = "E0015")]
+pub struct NonConstQuestionFromResidual<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_try_block_from_output_non_const, code = "E0015")]
+pub struct NonConstTryBlockFromOutput<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_await_non_const, code = "E0015")]
+pub struct NonConstAwait<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_closure_non_const, code = "E0015")]
+pub struct NonConstClosure {
+    #[primary_span]
+    pub span: Span,
+    pub kind: ConstContext,
+    #[subdiagnostic]
+    pub note: Option<NonConstClosureNote>,
+}
+
+#[derive(Subdiagnostic)]
+pub enum NonConstClosureNote {
+    #[note(const_eval_closure_fndef_not_const)]
+    FnDef {
+        #[primary_span]
+        span: Span,
+    },
+    #[note(const_eval_fn_ptr_call)]
+    FnPtr,
+    #[note(const_eval_closure_call)]
+    Closure,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(const_eval_consider_dereferencing, 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(const_eval_operator_non_const, code = "E0015")]
+pub struct NonConstOperator {
+    #[primary_span]
+    pub span: Span,
+    pub kind: ConstContext,
+    #[subdiagnostic]
+    pub sugg: Option<ConsiderDereferencing>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_deref_coercion_non_const, code = "E0015")]
+#[note]
+pub struct NonConstDerefCoercion<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+    pub target_ty: Ty<'tcx>,
+    #[note(const_eval_target_note)]
+    pub deref_target: Option<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_live_drop, code = "E0493")]
+pub struct LiveDrop<'tcx> {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+    pub kind: ConstContext,
+    pub dropped_ty: Ty<'tcx>,
+    #[label(const_eval_dropped_at_label)]
+    pub dropped_at: Option<Span>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(const_eval_align_check_failed)]
+pub struct AlignmentCheckFailed {
+    pub has: u64,
+    pub required: u64,
+    #[subdiagnostic]
+    pub frames: Vec<FrameNote>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_error, code = "E0080")]
+pub struct ConstEvalError {
+    #[primary_span]
+    pub span: Span,
+    /// One of "const", "const_with_path", and "static"
+    pub error_kind: &'static str,
+    pub instance: String,
+    #[subdiagnostic]
+    pub frame_notes: Vec<FrameNote>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_nullary_intrinsic_fail)]
+pub struct NullaryIntrinsicError {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_undefined_behavior, code = "E0080")]
+pub struct UndefinedBehavior {
+    #[primary_span]
+    pub span: Span,
+    #[note(const_eval_undefined_behavior_note)]
+    pub ub_note: Option<()>,
+    #[subdiagnostic]
+    pub frames: Vec<FrameNote>,
+    #[subdiagnostic]
+    pub raw_bytes: RawBytesNote,
+}
+
+pub trait ReportErrorExt {
+    /// Returns the diagnostic message for this error.
+    fn diagnostic_message(&self) -> DiagnosticMessage;
+    fn add_args<G: EmissionGuarantee>(
+        self,
+        handler: &Handler,
+        builder: &mut DiagnosticBuilder<'_, G>,
+    );
+
+    fn debug(self) -> String
+    where
+        Self: Sized,
+    {
+        ty::tls::with(move |tcx| {
+            let mut builder = tcx.sess.struct_allow(DiagnosticMessage::Str(String::new().into()));
+            let handler = &tcx.sess.parse_sess.span_diagnostic;
+            let message = self.diagnostic_message();
+            self.add_args(handler, &mut builder);
+            let s = handler.eagerly_translate_to_string(message, builder.args());
+            builder.cancel();
+            s
+        })
+    }
+}
+
+fn bad_pointer_message(msg: CheckInAllocMsg, handler: &Handler) -> String {
+    use crate::fluent_generated::*;
+
+    let msg = match msg {
+        CheckInAllocMsg::DerefTest => const_eval_deref_test,
+        CheckInAllocMsg::MemoryAccessTest => const_eval_memory_access_test,
+        CheckInAllocMsg::PointerArithmeticTest => const_eval_pointer_arithmetic_test,
+        CheckInAllocMsg::OffsetFromTest => const_eval_offset_from_test,
+        CheckInAllocMsg::InboundsTest => const_eval_in_bounds_test,
+    };
+
+    handler.eagerly_translate_to_string(msg, [].into_iter())
+}
+
+impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        use UndefinedBehaviorInfo::*;
+        match self {
+            Ub(msg) => msg.clone().into(),
+            Unreachable => const_eval_unreachable,
+            BoundsCheckFailed { .. } => const_eval_bounds_check_failed,
+            DivisionByZero => const_eval_division_by_zero,
+            RemainderByZero => const_eval_remainder_by_zero,
+            DivisionOverflow => const_eval_division_overflow,
+            RemainderOverflow => const_eval_remainder_overflow,
+            PointerArithOverflow => const_eval_pointer_arithmetic_overflow,
+            InvalidMeta(InvalidMetaKind::SliceTooBig) => const_eval_invalid_meta_slice,
+            InvalidMeta(InvalidMetaKind::TooBig) => const_eval_invalid_meta,
+            UnterminatedCString(_) => const_eval_unterminated_c_string,
+            PointerUseAfterFree(_) => const_eval_pointer_use_after_free,
+            PointerOutOfBounds { ptr_size: Size::ZERO, .. } => const_eval_zst_pointer_out_of_bounds,
+            PointerOutOfBounds { .. } => const_eval_pointer_out_of_bounds,
+            DanglingIntPointer(0, _) => const_eval_dangling_null_pointer,
+            DanglingIntPointer(_, _) => const_eval_dangling_int_pointer,
+            AlignmentCheckFailed { .. } => const_eval_alignment_check_failed,
+            WriteToReadOnly(_) => const_eval_write_to_read_only,
+            DerefFunctionPointer(_) => const_eval_deref_function_pointer,
+            DerefVTablePointer(_) => const_eval_deref_vtable_pointer,
+            InvalidBool(_) => const_eval_invalid_bool,
+            InvalidChar(_) => const_eval_invalid_char,
+            InvalidTag(_) => const_eval_invalid_tag,
+            InvalidFunctionPointer(_) => const_eval_invalid_function_pointer,
+            InvalidVTablePointer(_) => const_eval_invalid_vtable_pointer,
+            InvalidStr(_) => const_eval_invalid_str,
+            InvalidUninitBytes(None) => const_eval_invalid_uninit_bytes_unknown,
+            InvalidUninitBytes(Some(_)) => const_eval_invalid_uninit_bytes,
+            DeadLocal => const_eval_dead_local,
+            ScalarSizeMismatch(_) => const_eval_scalar_size_mismatch,
+            UninhabitedEnumVariantWritten => const_eval_uninhabited_enum_variant_written,
+            Validation(e) => e.diagnostic_message(),
+            Custom(x) => (x.msg)(),
+        }
+    }
+
+    fn add_args<G: EmissionGuarantee>(
+        self,
+        handler: &Handler,
+        builder: &mut DiagnosticBuilder<'_, G>,
+    ) {
+        use UndefinedBehaviorInfo::*;
+        match self {
+            Ub(_)
+            | Unreachable
+            | DivisionByZero
+            | RemainderByZero
+            | DivisionOverflow
+            | RemainderOverflow
+            | PointerArithOverflow
+            | InvalidMeta(InvalidMetaKind::SliceTooBig)
+            | InvalidMeta(InvalidMetaKind::TooBig)
+            | InvalidUninitBytes(None)
+            | DeadLocal
+            | UninhabitedEnumVariantWritten => {}
+            BoundsCheckFailed { len, index } => {
+                builder.set_arg("len", len);
+                builder.set_arg("index", index);
+            }
+            UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
+                builder.set_arg("pointer", ptr);
+            }
+            PointerUseAfterFree(allocation) => {
+                builder.set_arg("allocation", allocation);
+            }
+            PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size, msg } => {
+                builder
+                    .set_arg("alloc_id", alloc_id)
+                    .set_arg("alloc_size", alloc_size.bytes())
+                    .set_arg("ptr_offset", ptr_offset)
+                    .set_arg("ptr_size", ptr_size.bytes())
+                    .set_arg("bad_pointer_message", bad_pointer_message(msg, handler));
+            }
+            DanglingIntPointer(ptr, msg) => {
+                if ptr != 0 {
+                    builder.set_arg("pointer", format!("{ptr:#x}[noalloc]"));
+                }
+
+                builder.set_arg("bad_pointer_message", bad_pointer_message(msg, handler));
+            }
+            AlignmentCheckFailed { required, has } => {
+                builder.set_arg("required", required.bytes());
+                builder.set_arg("has", has.bytes());
+            }
+            WriteToReadOnly(alloc) | DerefFunctionPointer(alloc) | DerefVTablePointer(alloc) => {
+                builder.set_arg("allocation", alloc);
+            }
+            InvalidBool(b) => {
+                builder.set_arg("value", format!("{b:02x}"));
+            }
+            InvalidChar(c) => {
+                builder.set_arg("value", format!("{c:08x}"));
+            }
+            InvalidTag(tag) => {
+                builder.set_arg("tag", format!("{tag:x}"));
+            }
+            InvalidStr(err) => {
+                builder.set_arg("err", format!("{err}"));
+            }
+            InvalidUninitBytes(Some((alloc, info))) => {
+                builder.set_arg("alloc", alloc);
+                builder.set_arg("access", info.access);
+                builder.set_arg("uninit", info.uninit);
+            }
+            ScalarSizeMismatch(info) => {
+                builder.set_arg("target_size", info.target_size);
+                builder.set_arg("data_size", info.data_size);
+            }
+            Validation(e) => e.add_args(handler, builder),
+            Custom(custom) => {
+                (custom.add_args)(&mut |name, value| {
+                    builder.set_arg(name, value);
+                });
+            }
+        }
+    }
+}
+
+impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        use rustc_middle::mir::interpret::ValidationErrorKind::*;
+        match self.kind {
+            PtrToUninhabited { ptr_kind: PointerKind::Box, .. } => const_eval_box_to_uninhabited,
+            PtrToUninhabited { ptr_kind: PointerKind::Ref, .. } => const_eval_ref_to_uninhabited,
+
+            PtrToStatic { ptr_kind: PointerKind::Box } => const_eval_box_to_static,
+            PtrToStatic { ptr_kind: PointerKind::Ref } => const_eval_ref_to_static,
+
+            PtrToMut { ptr_kind: PointerKind::Box } => const_eval_box_to_mut,
+            PtrToMut { ptr_kind: PointerKind::Ref } => const_eval_ref_to_mut,
+
+            ExpectedNonPtr { .. } => const_eval_expected_non_ptr,
+            MutableRefInConst => const_eval_mutable_ref_in_const,
+            NullFnPtr => const_eval_null_fn_ptr,
+            NeverVal => const_eval_never_val,
+            NullablePtrOutOfRange { .. } => const_eval_nullable_ptr_out_of_range,
+            PtrOutOfRange { .. } => const_eval_ptr_out_of_range,
+            OutOfRange { .. } => const_eval_out_of_range,
+            UnsafeCell => const_eval_unsafe_cell,
+            UninhabitedVal { .. } => const_eval_uninhabited_val,
+            InvalidEnumTag { .. } => const_eval_invalid_enum_tag,
+            UninitEnumTag => const_eval_uninit_enum_tag,
+            UninitStr => const_eval_uninit_str,
+            Uninit { expected: ExpectedKind::Bool } => const_eval_uninit_bool,
+            Uninit { expected: ExpectedKind::Reference } => const_eval_uninit_ref,
+            Uninit { expected: ExpectedKind::Box } => const_eval_uninit_box,
+            Uninit { expected: ExpectedKind::RawPtr } => const_eval_uninit_raw_ptr,
+            Uninit { expected: ExpectedKind::InitScalar } => const_eval_uninit_init_scalar,
+            Uninit { expected: ExpectedKind::Char } => const_eval_uninit_char,
+            Uninit { expected: ExpectedKind::Float } => const_eval_uninit_float,
+            Uninit { expected: ExpectedKind::Int } => const_eval_uninit_int,
+            Uninit { expected: ExpectedKind::FnPtr } => const_eval_uninit_fn_ptr,
+            UninitVal => const_eval_uninit,
+            InvalidVTablePtr { .. } => const_eval_invalid_vtable_ptr,
+            InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Box } => {
+                const_eval_invalid_box_slice_meta
+            }
+            InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Ref } => {
+                const_eval_invalid_ref_slice_meta
+            }
+
+            InvalidMetaTooLarge { ptr_kind: PointerKind::Box } => const_eval_invalid_box_meta,
+            InvalidMetaTooLarge { ptr_kind: PointerKind::Ref } => const_eval_invalid_ref_meta,
+            UnalignedPtr { ptr_kind: PointerKind::Ref, .. } => const_eval_unaligned_ref,
+            UnalignedPtr { ptr_kind: PointerKind::Box, .. } => const_eval_unaligned_box,
+
+            NullPtr { ptr_kind: PointerKind::Box } => const_eval_null_box,
+            NullPtr { ptr_kind: PointerKind::Ref } => const_eval_null_ref,
+            DanglingPtrNoProvenance { ptr_kind: PointerKind::Box, .. } => {
+                const_eval_dangling_box_no_provenance
+            }
+            DanglingPtrNoProvenance { ptr_kind: PointerKind::Ref, .. } => {
+                const_eval_dangling_ref_no_provenance
+            }
+            DanglingPtrOutOfBounds { ptr_kind: PointerKind::Box } => {
+                const_eval_dangling_box_out_of_bounds
+            }
+            DanglingPtrOutOfBounds { ptr_kind: PointerKind::Ref } => {
+                const_eval_dangling_ref_out_of_bounds
+            }
+            DanglingPtrUseAfterFree { ptr_kind: PointerKind::Box } => {
+                const_eval_dangling_box_use_after_free
+            }
+            DanglingPtrUseAfterFree { ptr_kind: PointerKind::Ref } => {
+                const_eval_dangling_ref_use_after_free
+            }
+            InvalidBool { .. } => const_eval_validation_invalid_bool,
+            InvalidChar { .. } => const_eval_validation_invalid_char,
+            InvalidFnPtr { .. } => const_eval_invalid_fn_ptr,
+        }
+    }
+
+    fn add_args<G: EmissionGuarantee>(self, handler: &Handler, err: &mut DiagnosticBuilder<'_, G>) {
+        use crate::fluent_generated as fluent;
+        use rustc_middle::mir::interpret::ValidationErrorKind::*;
+
+        let message = if let Some(path) = self.path {
+            handler.eagerly_translate_to_string(
+                fluent::const_eval_invalid_value_with_path,
+                [("path".into(), DiagnosticArgValue::Str(path.into()))].iter().map(|(a, b)| (a, b)),
+            )
+        } else {
+            handler.eagerly_translate_to_string(fluent::const_eval_invalid_value, [].into_iter())
+        };
+
+        err.set_arg("front_matter", message);
+
+        fn add_range_arg<G: EmissionGuarantee>(
+            r: WrappingRange,
+            max_hi: u128,
+            handler: &Handler,
+            err: &mut DiagnosticBuilder<'_, G>,
+        ) {
+            let WrappingRange { start: lo, end: hi } = r;
+            assert!(hi <= max_hi);
+            let msg = if lo > hi {
+                fluent::const_eval_range_wrapping
+            } else if lo == hi {
+                fluent::const_eval_range_singular
+            } else if lo == 0 {
+                assert!(hi < max_hi, "should not be printing if the range covers everything");
+                fluent::const_eval_range_upper
+            } else if hi == max_hi {
+                assert!(lo > 0, "should not be printing if the range covers everything");
+                fluent::const_eval_range_lower
+            } else {
+                fluent::const_eval_range
+            };
+
+            let args = [
+                ("lo".into(), DiagnosticArgValue::Str(lo.to_string().into())),
+                ("hi".into(), DiagnosticArgValue::Str(hi.to_string().into())),
+            ];
+            let args = args.iter().map(|(a, b)| (a, b));
+            let message = handler.eagerly_translate_to_string(msg, args);
+            err.set_arg("in_range", message);
+        }
+
+        match self.kind {
+            PtrToUninhabited { ty, .. } | UninhabitedVal { ty } => {
+                err.set_arg("ty", ty);
+            }
+            ExpectedNonPtr { value }
+            | InvalidEnumTag { value }
+            | InvalidVTablePtr { value }
+            | InvalidBool { value }
+            | InvalidChar { value }
+            | InvalidFnPtr { value } => {
+                err.set_arg("value", value);
+            }
+            NullablePtrOutOfRange { range, max_value } | PtrOutOfRange { range, max_value } => {
+                add_range_arg(range, max_value, handler, err)
+            }
+            OutOfRange { range, max_value, value } => {
+                err.set_arg("value", value);
+                add_range_arg(range, max_value, handler, err);
+            }
+            UnalignedPtr { required_bytes, found_bytes, .. } => {
+                err.set_arg("required_bytes", required_bytes);
+                err.set_arg("found_bytes", found_bytes);
+            }
+            DanglingPtrNoProvenance { pointer, .. } => {
+                err.set_arg("pointer", pointer);
+            }
+            NullPtr { .. }
+            | PtrToStatic { .. }
+            | PtrToMut { .. }
+            | MutableRefInConst
+            | NullFnPtr
+            | NeverVal
+            | UnsafeCell
+            | UninitEnumTag
+            | UninitStr
+            | Uninit { .. }
+            | UninitVal
+            | InvalidMetaSliceTooLarge { .. }
+            | InvalidMetaTooLarge { .. }
+            | DanglingPtrUseAfterFree { .. }
+            | DanglingPtrOutOfBounds { .. } => {}
+        }
+    }
+}
+
+impl ReportErrorExt for UnsupportedOpInfo {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        match self {
+            UnsupportedOpInfo::Unsupported(s) => s.clone().into(),
+            UnsupportedOpInfo::PartialPointerOverwrite(_) => const_eval_partial_pointer_overwrite,
+            UnsupportedOpInfo::PartialPointerCopy(_) => const_eval_partial_pointer_copy,
+            UnsupportedOpInfo::ReadPointerAsBytes => const_eval_read_pointer_as_bytes,
+            UnsupportedOpInfo::ThreadLocalStatic(_) => const_eval_thread_local_static,
+            UnsupportedOpInfo::ReadExternStatic(_) => const_eval_read_extern_static,
+        }
+    }
+    fn add_args<G: EmissionGuarantee>(self, _: &Handler, builder: &mut DiagnosticBuilder<'_, G>) {
+        use crate::fluent_generated::*;
+
+        use UnsupportedOpInfo::*;
+        if let ReadPointerAsBytes | PartialPointerOverwrite(_) | PartialPointerCopy(_) = self {
+            builder.help(const_eval_ptr_as_bytes_1);
+            builder.help(const_eval_ptr_as_bytes_2);
+        }
+        match self {
+            Unsupported(_) | ReadPointerAsBytes => {}
+            PartialPointerOverwrite(ptr) | PartialPointerCopy(ptr) => {
+                builder.set_arg("ptr", ptr);
+            }
+            ThreadLocalStatic(did) | ReadExternStatic(did) => {
+                builder.set_arg("did", format!("{did:?}"));
+            }
+        }
+    }
+}
+
+impl<'tcx> ReportErrorExt for InterpError<'tcx> {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        match self {
+            InterpError::UndefinedBehavior(ub) => ub.diagnostic_message(),
+            InterpError::Unsupported(e) => e.diagnostic_message(),
+            InterpError::InvalidProgram(e) => e.diagnostic_message(),
+            InterpError::ResourceExhaustion(e) => e.diagnostic_message(),
+            InterpError::MachineStop(e) => e.diagnostic_message(),
+        }
+    }
+    fn add_args<G: EmissionGuarantee>(
+        self,
+        handler: &Handler,
+        builder: &mut DiagnosticBuilder<'_, G>,
+    ) {
+        match self {
+            InterpError::UndefinedBehavior(ub) => ub.add_args(handler, builder),
+            InterpError::Unsupported(e) => e.add_args(handler, builder),
+            InterpError::InvalidProgram(e) => e.add_args(handler, builder),
+            InterpError::ResourceExhaustion(e) => e.add_args(handler, builder),
+            InterpError::MachineStop(e) => e.add_args(&mut |name, value| {
+                builder.set_arg(name, value);
+            }),
+        }
+    }
+}
+
+impl<'tcx> ReportErrorExt for InvalidProgramInfo<'tcx> {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        match self {
+            InvalidProgramInfo::TooGeneric => const_eval_too_generic,
+            InvalidProgramInfo::AlreadyReported(_) => const_eval_already_reported,
+            InvalidProgramInfo::Layout(e) => e.diagnostic_message(),
+            InvalidProgramInfo::FnAbiAdjustForForeignAbi(_) => {
+                rustc_middle::error::middle_adjust_for_foreign_abi_error
+            }
+            InvalidProgramInfo::SizeOfUnsizedType(_) => const_eval_size_of_unsized,
+            InvalidProgramInfo::UninitUnsizedLocal => const_eval_uninit_unsized_local,
+        }
+    }
+    fn add_args<G: EmissionGuarantee>(
+        self,
+        handler: &Handler,
+        builder: &mut DiagnosticBuilder<'_, G>,
+    ) {
+        match self {
+            InvalidProgramInfo::TooGeneric
+            | InvalidProgramInfo::AlreadyReported(_)
+            | InvalidProgramInfo::UninitUnsizedLocal => {}
+            InvalidProgramInfo::Layout(e) => {
+                let diag: DiagnosticBuilder<'_, ()> = e.into_diagnostic().into_diagnostic(handler);
+                for (name, val) in diag.args() {
+                    builder.set_arg(name.clone(), val.clone());
+                }
+                diag.cancel();
+            }
+            InvalidProgramInfo::FnAbiAdjustForForeignAbi(
+                AdjustForForeignAbiError::Unsupported { arch, abi },
+            ) => {
+                builder.set_arg("arch", arch);
+                builder.set_arg("abi", abi.name());
+            }
+            InvalidProgramInfo::SizeOfUnsizedType(ty) => {
+                builder.set_arg("ty", ty);
+            }
+        }
+    }
+}
+
+impl ReportErrorExt for ResourceExhaustionInfo {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        match self {
+            ResourceExhaustionInfo::StackFrameLimitReached => const_eval_stack_frame_limit_reached,
+            ResourceExhaustionInfo::MemoryExhausted => const_eval_memory_exhausted,
+            ResourceExhaustionInfo::AddressSpaceFull => const_eval_address_space_full,
+        }
+    }
+    fn add_args<G: EmissionGuarantee>(self, _: &Handler, _: &mut DiagnosticBuilder<'_, G>) {}
+}
diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs
index 163e3f86993..b4db3dff3ff 100644
--- a/compiler/rustc_const_eval/src/interpret/cast.rs
+++ b/compiler/rustc_const_eval/src/interpret/cast.rs
@@ -14,6 +14,8 @@ use super::{
     util::ensure_monomorphic_enough, FnVal, ImmTy, Immediate, InterpCx, Machine, OpTy, PlaceTy,
 };
 
+use crate::fluent_generated as fluent;
+
 impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
     pub fn cast(
         &mut self,
@@ -138,12 +140,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 assert!(src.layout.is_sized());
                 assert!(dest.layout.is_sized());
                 if src.layout.size != dest.layout.size {
-                    throw_ub_format!(
-                        "transmuting from {}-byte type to {}-byte type: `{}` -> `{}`",
-                        src.layout.size.bytes(),
-                        dest.layout.size.bytes(),
-                        src.layout.ty,
-                        dest.layout.ty,
+                    let src_bytes = src.layout.size.bytes();
+                    let dest_bytes = dest.layout.size.bytes();
+                    let src_ty = format!("{}", src.layout.ty);
+                    let dest_ty = format!("{}", dest.layout.ty);
+                    throw_ub_custom!(
+                        fluent::const_eval_invalid_transmute,
+                        src_bytes = src_bytes,
+                        dest_bytes = dest_bytes,
+                        src = src_ty,
+                        dest = dest_ty,
                     );
                 }
 
@@ -363,7 +369,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let old_vptr = old_vptr.to_pointer(self)?;
                 let (ty, old_trait) = self.get_ptr_vtable(old_vptr)?;
                 if old_trait != data_a.principal() {
-                    throw_ub_format!("upcast on a pointer whose vtable does not match its type");
+                    throw_ub_custom!(fluent::const_eval_upcast_mismatch);
                 }
                 let new_vptr = self.get_vtable_ptr(ty, data_b.principal())?;
                 self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs
index cd43a53b1b3..36606ff69a6 100644
--- a/compiler/rustc_const_eval/src/interpret/eval_context.rs
+++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs
@@ -1,6 +1,5 @@
 use std::cell::Cell;
-use std::fmt;
-use std::mem;
+use std::{fmt, mem};
 
 use either::{Either, Left, Right};
 
@@ -8,7 +7,7 @@ use hir::CRATE_HIR_ID;
 use rustc_hir::{self as hir, def_id::DefId, definitions::DefPathData};
 use rustc_index::IndexVec;
 use rustc_middle::mir;
-use rustc_middle::mir::interpret::{ErrorHandled, InterpError, ReportedErrorInfo};
+use rustc_middle::mir::interpret::{ErrorHandled, InterpError, InvalidMetaKind, ReportedErrorInfo};
 use rustc_middle::query::TyCtxtAt;
 use rustc_middle::ty::layout::{
     self, FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOf, LayoutOfHelpers,
@@ -25,6 +24,8 @@ use super::{
     MemPlaceMeta, Memory, MemoryKind, Operand, Place, PlaceTy, PointerArithmetic, Provenance,
     Scalar, StackPopJump,
 };
+use crate::errors::{self, ErroneousConstUsed};
+use crate::fluent_generated as fluent;
 use crate::util;
 
 pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
@@ -247,6 +248,7 @@ impl<'mir, 'tcx, Prov: Provenance, Extra> Frame<'mir, 'tcx, Prov, Extra> {
     }
 }
 
+// FIXME: only used by miri, should be removed once translatable.
 impl<'tcx> fmt::Display for FrameInfo<'tcx> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         ty::tls::with(|tcx| {
@@ -264,6 +266,21 @@ impl<'tcx> fmt::Display for FrameInfo<'tcx> {
     }
 }
 
+impl<'tcx> FrameInfo<'tcx> {
+    pub fn as_note(&self, tcx: TyCtxt<'tcx>) -> errors::FrameNote {
+        let span = self.span;
+        if tcx.def_key(self.instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr {
+            errors::FrameNote { where_: "closure", span, instance: String::new(), times: 0 }
+        } else {
+            let instance = format!("{}", self.instance);
+            // Note: this triggers a `good_path_bug` state, which means that if we ever get here
+            // we must emit a diagnostic. We should never display a `FrameInfo` unless we
+            // actually want to emit a warning or error to the user.
+            errors::FrameNote { where_: "instance", span, instance, times: 0 }
+        }
+    }
+}
+
 impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for InterpCx<'mir, 'tcx, M> {
     #[inline]
     fn data_layout(&self) -> &TargetDataLayout {
@@ -620,7 +637,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
 
                 // Check if this brought us over the size limit.
                 if size > self.max_size_of_val() {
-                    throw_ub!(InvalidMeta("total size is bigger than largest supported object"));
+                    throw_ub!(InvalidMeta(InvalidMetaKind::TooBig));
                 }
                 Ok(Some((size, align)))
             }
@@ -638,7 +655,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let size = elem.size.bytes().saturating_mul(len); // we rely on `max_size_of_val` being smaller than `u64::MAX`.
                 let size = Size::from_bytes(size);
                 if size > self.max_size_of_val() {
-                    throw_ub!(InvalidMeta("slice is bigger than largest supported object"));
+                    throw_ub!(InvalidMeta(InvalidMetaKind::SliceTooBig));
                 }
                 Ok(Some((size, elem.align.abi)))
             }
@@ -746,7 +763,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             mir::UnwindAction::Cleanup(block) => Left(mir::Location { block, statement_index: 0 }),
             mir::UnwindAction::Continue => Right(self.frame_mut().body.span),
             mir::UnwindAction::Unreachable => {
-                throw_ub_format!("unwinding past a stack frame that does not allow unwinding")
+                throw_ub_custom!(fluent::const_eval_unreachable_unwind);
             }
             mir::UnwindAction::Terminate => {
                 self.frame_mut().loc = Right(self.frame_mut().body.span);
@@ -785,7 +802,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             }
         );
         if unwinding && self.frame_idx() == 0 {
-            throw_ub_format!("unwinding past the topmost frame of the stack");
+            throw_ub_custom!(fluent::const_eval_unwind_past_top);
         }
 
         // Copy return value. Must of course happen *before* we deallocate the locals.
@@ -873,7 +890,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         // StorageLive expects the local to be dead, and marks it live.
         let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
         if !matches!(old, LocalValue::Dead) {
-            throw_ub_format!("StorageLive on a local that was already live");
+            throw_ub_custom!(fluent::const_eval_double_storage_live);
         }
         Ok(())
     }
@@ -916,7 +933,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 ErrorHandled::Reported(err) => {
                     if !err.is_tainted_by_errors() && let Some(span) = span {
                         // To make it easier to figure out where this error comes from, also add a note at the current location.
-                        self.tcx.sess.span_note_without_error(span, "erroneous constant used");
+                        self.tcx.sess.emit_note(ErroneousConstUsed { span });
                     }
                     err_inval!(AlreadyReported(err))
                 }
diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs
index c2b82ba9b07..7b11ad33091 100644
--- a/compiler/rustc_const_eval/src/interpret/intern.rs
+++ b/compiler/rustc_const_eval/src/interpret/intern.rs
@@ -28,6 +28,7 @@ use super::{
     ValueVisitor,
 };
 use crate::const_eval;
+use crate::errors::{DanglingPtrInFinal, UnsupportedUntypedPointer};
 
 pub trait CompileTimeMachine<'mir, 'tcx, T> = Machine<
         'mir,
@@ -320,10 +321,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
     }
 }
 
+/// How a constant value should be interned.
 #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
 pub enum InternKind {
     /// The `mutability` of the static, ignoring the type which may have interior mutability.
     Static(hir::Mutability),
+    /// A `const` item
     Constant,
     Promoted,
 }
@@ -388,8 +391,7 @@ pub fn intern_const_alloc_recursive<
                 ecx.tcx.sess.delay_span_bug(
                     ecx.tcx.span,
                     format!(
-                        "error during interning should later cause validation failure: {}",
-                        error
+                        "error during interning should later cause validation failure: {error:?}"
                     ),
                 );
             }
@@ -425,14 +427,16 @@ pub fn intern_const_alloc_recursive<
                     // immutability is so important.
                     alloc.mutability = Mutability::Not;
                 }
+                // If it's a constant, we should not have any "leftovers" as everything
+                // is tracked by const-checking.
+                // FIXME: downgrade this to a warning? It rejects some legitimate consts,
+                // such as `const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;`.
+                //
+                // NOTE: it looks likes this code path is only reachable when we try to intern
+                // something that cannot be promoted, which in constants means values that have
+                // drop glue, such as the example above.
                 InternKind::Constant => {
-                    // If it's a constant, we should not have any "leftovers" as everything
-                    // is tracked by const-checking.
-                    // FIXME: downgrade this to a warning? It rejects some legitimate consts,
-                    // such as `const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;`.
-                    ecx.tcx
-                        .sess
-                        .span_err(ecx.tcx.span, "untyped pointers are not allowed in constant");
+                    ecx.tcx.sess.emit_err(UnsupportedUntypedPointer { span: ecx.tcx.span });
                     // For better errors later, mark the allocation as immutable.
                     alloc.mutability = Mutability::Not;
                 }
@@ -447,10 +451,7 @@ pub fn intern_const_alloc_recursive<
         } else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) {
             // Codegen does not like dangling pointers, and generally `tcx` assumes that
             // all allocations referenced anywhere actually exist. So, make sure we error here.
-            let reported = ecx
-                .tcx
-                .sess
-                .span_err(ecx.tcx.span, "encountered dangling pointer in final constant");
+            let reported = ecx.tcx.sess.emit_err(DanglingPtrInFinal { span: ecx.tcx.span });
             return Err(reported);
         } else if ecx.tcx.try_get_global_alloc(alloc_id).is_none() {
             // We have hit an `AllocId` that is neither in local or global memory and isn't
diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
index a77c699c22f..fb24cf48a9a 100644
--- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs
+++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
@@ -22,6 +22,8 @@ use super::{
     Pointer,
 };
 
+use crate::fluent_generated as fluent;
+
 mod caller_location;
 
 fn numeric_intrinsic<Prov>(name: Symbol, bits: u128, kind: Primitive) -> Scalar<Prov> {
@@ -198,15 +200,18 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                         ty
                     ),
                 };
-                let (nonzero, intrinsic_name) = match intrinsic_name {
+                let (nonzero, actual_intrinsic_name) = match intrinsic_name {
                     sym::cttz_nonzero => (true, sym::cttz),
                     sym::ctlz_nonzero => (true, sym::ctlz),
                     other => (false, other),
                 };
                 if nonzero && bits == 0 {
-                    throw_ub_format!("`{}_nonzero` called on 0", intrinsic_name);
+                    throw_ub_custom!(
+                        fluent::const_eval_call_nonzero_intrinsic,
+                        name = intrinsic_name,
+                    );
                 }
-                let out_val = numeric_intrinsic(intrinsic_name, bits, kind);
+                let out_val = numeric_intrinsic(actual_intrinsic_name, bits, kind);
                 self.write_scalar(out_val, dest)?;
             }
             sym::saturating_add | sym::saturating_sub => {
@@ -253,9 +258,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     let layout = self.layout_of(substs.type_at(0))?;
                     let r_val = r.to_scalar().to_bits(layout.size)?;
                     if let sym::unchecked_shl | sym::unchecked_shr = intrinsic_name {
-                        throw_ub_format!("overflowing shift by {} in `{}`", r_val, intrinsic_name);
+                        throw_ub_custom!(
+                            fluent::const_eval_overflow_shift,
+                            val = r_val,
+                            name = intrinsic_name
+                        );
                     } else {
-                        throw_ub_format!("overflow executing `{}`", intrinsic_name);
+                        throw_ub_custom!(fluent::const_eval_overflow, name = intrinsic_name);
                     }
                 }
                 self.write_scalar(val, dest)?;
@@ -314,17 +323,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                         (Err(_), _) | (_, Err(_)) => {
                             // We managed to find a valid allocation for one pointer, but not the other.
                             // That means they are definitely not pointing to the same allocation.
-                            throw_ub_format!(
-                                "`{}` called on pointers into different allocations",
-                                intrinsic_name
+                            throw_ub_custom!(
+                                fluent::const_eval_different_allocations,
+                                name = intrinsic_name,
                             );
                         }
                         (Ok((a_alloc_id, a_offset, _)), Ok((b_alloc_id, b_offset, _))) => {
                             // Found allocation for both. They must be into the same allocation.
                             if a_alloc_id != b_alloc_id {
-                                throw_ub_format!(
-                                    "`{}` called on pointers into different allocations",
-                                    intrinsic_name
+                                throw_ub_custom!(
+                                    fluent::const_eval_different_allocations,
+                                    name = intrinsic_name,
                                 );
                             }
                             // Use these offsets for distance calculation.
@@ -344,11 +353,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     if overflowed {
                         // a < b
                         if intrinsic_name == sym::ptr_offset_from_unsigned {
-                            throw_ub_format!(
-                                "`{}` called when first pointer has smaller offset than second: {} < {}",
-                                intrinsic_name,
-                                a_offset,
-                                b_offset,
+                            throw_ub_custom!(
+                                fluent::const_eval_unsigned_offset_from_overflow,
+                                a_offset = a_offset,
+                                b_offset = b_offset,
                             );
                         }
                         // The signed form of the intrinsic allows this. If we interpret the
@@ -356,9 +364,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                         // seems *positive*, they were more than isize::MAX apart.
                         let dist = val.to_target_isize(self)?;
                         if dist >= 0 {
-                            throw_ub_format!(
-                                "`{}` called when first pointer is too far before second",
-                                intrinsic_name
+                            throw_ub_custom!(
+                                fluent::const_eval_offset_from_underflow,
+                                name = intrinsic_name,
                             );
                         }
                         dist
@@ -368,9 +376,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                         // If converting to isize produced a *negative* result, we had an overflow
                         // because they were more than isize::MAX apart.
                         if dist < 0 {
-                            throw_ub_format!(
-                                "`{}` called when first pointer is too far ahead of second",
-                                intrinsic_name
+                            throw_ub_custom!(
+                                fluent::const_eval_offset_from_overflow,
+                                name = intrinsic_name,
                             );
                         }
                         dist
@@ -513,7 +521,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let op = self.eval_operand(op, None)?;
                 let cond = self.read_scalar(&op)?.to_bool()?;
                 if !cond {
-                    throw_ub_format!("`assume` called with `false`");
+                    throw_ub_custom!(fluent::const_eval_assume_false);
                 }
                 Ok(())
             }
@@ -542,7 +550,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         let (res, overflow, _ty) = self.overflowing_binary_op(BinOp::Rem, &a, &b)?;
         assert!(!overflow); // All overflow is UB, so this should never return on overflow.
         if res.assert_bits(a.layout.size) != 0 {
-            throw_ub_format!("exact_div: {} cannot be divided by {} without remainder", a, b)
+            throw_ub_custom!(
+                fluent::const_eval_exact_div_has_remainder,
+                a = format!("{a}"),
+                b = format!("{b}")
+            )
         }
         // `Rem` says this is all right, so we can let `Div` do its job.
         self.binop_ignore_overflow(BinOp::Div, &a, &b, dest)
@@ -638,9 +650,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         // `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
         // but no actual allocation can be big enough for the difference to be noticeable.
         let size = size.checked_mul(count, self).ok_or_else(|| {
-            err_ub_format!(
-                "overflow computing total size of `{}`",
-                if nonoverlapping { "copy_nonoverlapping" } else { "copy" }
+            err_ub_custom!(
+                fluent::const_eval_size_overflow,
+                name = if nonoverlapping { "copy_nonoverlapping" } else { "copy" }
             )
         })?;
 
@@ -664,10 +676,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
 
         // `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
         // but no actual allocation can be big enough for the difference to be noticeable.
-        let len = layout
-            .size
-            .checked_mul(count, self)
-            .ok_or_else(|| err_ub_format!("overflow computing total size of `write_bytes`"))?;
+        let len = layout.size.checked_mul(count, self).ok_or_else(|| {
+            err_ub_custom!(fluent::const_eval_size_overflow, name = "write_bytes")
+        })?;
 
         let bytes = std::iter::repeat(byte).take(len.bytes_usize());
         self.write_bytes_ptr(dst, bytes)
@@ -691,7 +702,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 return Ok(&[]);
             };
             if alloc_ref.has_provenance() {
-                throw_ub_format!("`raw_eq` on bytes with provenance");
+                throw_ub_custom!(fluent::const_eval_raw_eq_with_provenance);
             }
             alloc_ref.get_bytes_strip_provenance()
         };
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index d5b6a581a79..1125d8d1f0e 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -19,6 +19,7 @@ use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TyCtxt};
 use rustc_target::abi::{Align, HasDataLayout, Size};
 
 use crate::const_eval::CheckAlignment;
+use crate::fluent_generated as fluent;
 
 use super::{
     alloc_range, AllocBytes, AllocId, AllocMap, AllocRange, Allocation, CheckInAllocMsg,
@@ -200,7 +201,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         align: Align,
         kind: MemoryKind<M::MemoryKind>,
     ) -> InterpResult<'tcx, Pointer<M::Provenance>> {
-        let alloc = Allocation::uninit(size, align, M::PANIC_ON_ALLOC_FAIL)?;
+        let alloc = if M::PANIC_ON_ALLOC_FAIL {
+            Allocation::uninit(size, align)
+        } else {
+            Allocation::try_uninit(size, align)?
+        };
         self.allocate_raw_ptr(alloc, kind)
     }
 
@@ -242,9 +247,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
     ) -> InterpResult<'tcx, Pointer<M::Provenance>> {
         let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr)?;
         if offset.bytes() != 0 {
-            throw_ub_format!(
-                "reallocating {:?} which does not point to the beginning of an object",
-                ptr
+            throw_ub_custom!(
+                fluent::const_eval_realloc_or_alloc_with_offset,
+                ptr = format!("{ptr:?}"),
+                kind = "realloc"
             );
         }
 
@@ -280,9 +286,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         trace!("deallocating: {alloc_id:?}");
 
         if offset.bytes() != 0 {
-            throw_ub_format!(
-                "deallocating {:?} which does not point to the beginning of an object",
-                ptr
+            throw_ub_custom!(
+                fluent::const_eval_realloc_or_alloc_with_offset,
+                ptr = format!("{ptr:?}"),
+                kind = "dealloc",
             );
         }
 
@@ -290,13 +297,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             // Deallocating global memory -- always an error
             return Err(match self.tcx.try_get_global_alloc(alloc_id) {
                 Some(GlobalAlloc::Function(..)) => {
-                    err_ub_format!("deallocating {alloc_id:?}, which is a function")
+                    err_ub_custom!(
+                        fluent::const_eval_invalid_dealloc,
+                        alloc_id = alloc_id,
+                        kind = "fn",
+                    )
                 }
                 Some(GlobalAlloc::VTable(..)) => {
-                    err_ub_format!("deallocating {alloc_id:?}, which is a vtable")
+                    err_ub_custom!(
+                        fluent::const_eval_invalid_dealloc,
+                        alloc_id = alloc_id,
+                        kind = "vtable",
+                    )
                 }
                 Some(GlobalAlloc::Static(..) | GlobalAlloc::Memory(..)) => {
-                    err_ub_format!("deallocating {alloc_id:?}, which is static memory")
+                    err_ub_custom!(
+                        fluent::const_eval_invalid_dealloc,
+                        alloc_id = alloc_id,
+                        kind = "static_mem"
+                    )
                 }
                 None => err_ub!(PointerUseAfterFree(alloc_id)),
             }
@@ -304,21 +323,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         };
 
         if alloc.mutability.is_not() {
-            throw_ub_format!("deallocating immutable allocation {alloc_id:?}");
+            throw_ub_custom!(fluent::const_eval_dealloc_immutable, alloc = alloc_id,);
         }
         if alloc_kind != kind {
-            throw_ub_format!(
-                "deallocating {alloc_id:?}, which is {alloc_kind} memory, using {kind} deallocation operation"
+            throw_ub_custom!(
+                fluent::const_eval_dealloc_kind_mismatch,
+                alloc = alloc_id,
+                alloc_kind = format!("{alloc_kind}"),
+                kind = format!("{kind}"),
             );
         }
         if let Some((size, align)) = old_size_and_align {
             if size != alloc.size() || align != alloc.align {
-                throw_ub_format!(
-                    "incorrect layout on deallocation: {alloc_id:?} has size {} and alignment {}, but gave size {} and alignment {}",
-                    alloc.size().bytes(),
-                    alloc.align.bytes(),
-                    size.bytes(),
-                    align.bytes(),
+                throw_ub_custom!(
+                    fluent::const_eval_dealloc_incorrect_layout,
+                    alloc = alloc_id,
+                    size = alloc.size().bytes(),
+                    align = alloc.align.bytes(),
+                    size_found = size.bytes(),
+                    align_found = align.bytes(),
                 )
             }
         }
@@ -1166,7 +1189,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     if (src_offset <= dest_offset && src_offset + size > dest_offset)
                         || (dest_offset <= src_offset && dest_offset + size > src_offset)
                     {
-                        throw_ub_format!("copy_nonoverlapping called on overlapping ranges")
+                        throw_ub_custom!(fluent::const_eval_copy_nonoverlapping_overlapping);
                     }
                 }
 
diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs
index 586e8f063ee..7269ff8d53c 100644
--- a/compiler/rustc_const_eval/src/interpret/terminator.rs
+++ b/compiler/rustc_const_eval/src/interpret/terminator.rs
@@ -15,6 +15,7 @@ use super::{
     FnVal, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, Operand,
     PlaceTy, Scalar, StackPopCleanup,
 };
+use crate::fluent_generated as fluent;
 
 impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
     pub(super) fn eval_terminator(
@@ -172,7 +173,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             InlineAsm { template, ref operands, options, destination, .. } => {
                 M::eval_inline_asm(self, template, operands, options)?;
                 if options.contains(InlineAsmOptions::NORETURN) {
-                    throw_ub_format!("returned from noreturn inline assembly");
+                    throw_ub_custom!(fluent::const_eval_noreturn_asm_returned);
                 }
                 self.go_to_block(
                     destination
@@ -288,15 +289,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             return Ok(());
         }
         // Find next caller arg.
-        let (caller_arg, caller_abi) = caller_args.next().ok_or_else(|| {
-            err_ub_format!("calling a function with fewer arguments than it requires")
-        })?;
+        let Some((caller_arg, caller_abi)) = caller_args.next() else {
+            throw_ub_custom!(fluent::const_eval_not_enough_caller_args);
+        };
         // Now, check
         if !Self::check_argument_compat(caller_abi, callee_abi) {
-            throw_ub_format!(
-                "calling a function with argument of type {:?} passing data of type {:?}",
-                callee_arg.layout.ty,
-                caller_arg.layout.ty
+            let callee_ty = format!("{}", callee_arg.layout.ty);
+            let caller_ty = format!("{}", caller_arg.layout.ty);
+            throw_ub_custom!(
+                fluent::const_eval_incompatible_types,
+                callee_ty = callee_ty,
+                caller_ty = caller_ty,
             )
         }
         // Special handling for unsized parameters.
@@ -398,10 +401,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
 
                 if M::enforce_abi(self) {
                     if caller_fn_abi.conv != callee_fn_abi.conv {
-                        throw_ub_format!(
-                            "calling a function with calling convention {:?} using calling convention {:?}",
-                            callee_fn_abi.conv,
-                            caller_fn_abi.conv
+                        throw_ub_custom!(
+                            fluent::const_eval_incompatible_calling_conventions,
+                            callee_conv = format!("{:?}", callee_fn_abi.conv),
+                            caller_conv = format!("{:?}", caller_fn_abi.conv),
                         )
                     }
                 }
@@ -508,15 +511,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                         "mismatch between callee ABI and callee body arguments"
                     );
                     if caller_args.next().is_some() {
-                        throw_ub_format!("calling a function with more arguments than it expected")
+                        throw_ub_custom!(fluent::const_eval_too_many_caller_args);
                     }
                     // Don't forget to check the return type!
                     if !Self::check_argument_compat(&caller_fn_abi.ret, &callee_fn_abi.ret) {
-                        throw_ub_format!(
-                            "calling a function with return type {:?} passing \
-                                    return place of type {:?}",
-                            callee_fn_abi.ret.layout.ty,
-                            caller_fn_abi.ret.layout.ty,
+                        let callee_ty = format!("{}", callee_fn_abi.ret.layout.ty);
+                        let caller_ty = format!("{}", caller_fn_abi.ret.layout.ty);
+                        throw_ub_custom!(
+                            fluent::const_eval_incompatible_return_types,
+                            callee_ty = callee_ty,
+                            caller_ty = caller_ty,
                         )
                     }
                 };
@@ -587,9 +591,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     let (recv, vptr) = self.unpack_dyn_star(&receiver_place.into())?;
                     let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
                     if dyn_trait != data.principal() {
-                        throw_ub_format!(
-                            "`dyn*` call on a pointer whose vtable does not match its type"
-                        );
+                        throw_ub_custom!(fluent::const_eval_dyn_star_call_vtable_mismatch);
                     }
                     let recv = recv.assert_mem_place(); // we passed an MPlaceTy to `unpack_dyn_star` so we definitely still have one
 
@@ -609,9 +611,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     let vptr = receiver_place.meta.unwrap_meta().to_pointer(self)?;
                     let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
                     if dyn_trait != data.principal() {
-                        throw_ub_format!(
-                            "`dyn` call on a pointer whose vtable does not match its type"
-                        );
+                        throw_ub_custom!(fluent::const_eval_dyn_call_vtable_mismatch);
                     }
 
                     // It might be surprising that we use a pointer as the receiver even if this
@@ -623,7 +623,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 // Now determine the actual method to call. We can do that in two different ways and
                 // compare them to ensure everything fits.
                 let Some(ty::VtblEntry::Method(fn_inst)) = self.get_vtable_entries(vptr)?.get(idx).copied() else {
-                    throw_ub_format!("`dyn` call trying to call something that is not a method")
+                    // FIXME(fee1-dead) these could be variants of the UB info enum instead of this
+                    throw_ub_custom!(fluent::const_eval_dyn_call_not_a_method);
                 };
                 trace!("Virtual call dispatches to {fn_inst:#?}");
                 if cfg!(debug_assertions) {
diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs
index 01b77289937..21c655988a0 100644
--- a/compiler/rustc_const_eval/src/interpret/validity.rs
+++ b/compiler/rustc_const_eval/src/interpret/validity.rs
@@ -4,7 +4,7 @@
 //! That's useful because it means other passes (e.g. promotion) can rely on `const`s
 //! to be const-safe.
 
-use std::fmt::{Display, Write};
+use std::fmt::Write;
 use std::num::NonZeroUsize;
 
 use either::{Left, Right};
@@ -12,7 +12,10 @@ use either::{Left, Right};
 use rustc_ast::Mutability;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir as hir;
-use rustc_middle::mir::interpret::InterpError;
+use rustc_middle::mir::interpret::{
+    ExpectedKind, InterpError, InvalidMetaKind, PointerKind, ValidationErrorInfo,
+    ValidationErrorKind, ValidationErrorKind::*,
+};
 use rustc_middle::ty;
 use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
 use rustc_span::symbol::{sym, Symbol};
@@ -30,14 +33,7 @@ use super::{
 };
 
 macro_rules! throw_validation_failure {
-    ($where:expr, { $( $what_fmt:tt )* } $( expected { $( $expected_fmt:tt )* } )?) => {{
-        let mut msg = String::new();
-        msg.push_str("encountered ");
-        write!(&mut msg, $($what_fmt)*).unwrap();
-        $(
-            msg.push_str(", but expected ");
-            write!(&mut msg, $($expected_fmt)*).unwrap();
-        )?
+    ($where:expr, $kind: expr) => {{
         let where_ = &$where;
         let path = if !where_.is_empty() {
             let mut path = String::new();
@@ -46,7 +42,8 @@ macro_rules! throw_validation_failure {
         } else {
             None
         };
-        throw_ub!(ValidationFailure { path, msg })
+
+        throw_ub!(Validation(ValidationErrorInfo { path, kind: $kind }))
     }};
 }
 
@@ -82,22 +79,22 @@ macro_rules! throw_validation_failure {
 ///
 macro_rules! try_validation {
     ($e:expr, $where:expr,
-    $( $( $p:pat_param )|+ => { $( $what_fmt:tt )* } $( expected { $( $expected_fmt:tt )* } )? ),+ $(,)?
+    $( $( $p:pat_param )|+ => $kind: expr ),+ $(,)?
     ) => {{
         match $e {
             Ok(x) => x,
             // We catch the error and turn it into a validation failure. We are okay with
             // allocation here as this can only slow down builds that fail anyway.
-            Err(e) => match e.kind() {
+            Err(e) => match e.into_parts() {
                 $(
-                    InterpError::UndefinedBehavior($($p)|+) =>
+                    (InterpError::UndefinedBehavior($($p)|+), _) =>
                        throw_validation_failure!(
                             $where,
-                            { $( $what_fmt )* } $( expected { $( $expected_fmt )* } )?
+                            $kind
                         )
                 ),+,
                 #[allow(unreachable_patterns)]
-                _ => Err::<!, _>(e)?,
+                (e, rest) => Err::<!, _>($crate::interpret::InterpErrorInfo::from_parts(e, rest))?,
             }
         }
     }};
@@ -160,6 +157,7 @@ impl<T: Copy + Eq + Hash + std::fmt::Debug, PATH: Default> RefTracking<T, PATH>
     }
 }
 
+// FIXME make this translatable as well?
 /// Format a path
 fn write_path(out: &mut String, path: &[PathElem]) {
     use self::PathElem::*;
@@ -185,26 +183,6 @@ fn write_path(out: &mut String, path: &[PathElem]) {
     }
 }
 
-// Formats such that a sentence like "expected something {}" to mean
-// "expected something <in the given range>" makes sense.
-fn wrapping_range_format(r: WrappingRange, max_hi: u128) -> String {
-    let WrappingRange { start: lo, end: hi } = r;
-    assert!(hi <= max_hi);
-    if lo > hi {
-        format!("less or equal to {}, or greater or equal to {}", hi, lo)
-    } else if lo == hi {
-        format!("equal to {}", lo)
-    } else if lo == 0 {
-        assert!(hi < max_hi, "should not be printing if the range covers everything");
-        format!("less or equal to {}", hi)
-    } else if hi == max_hi {
-        assert!(lo > 0, "should not be printing if the range covers everything");
-        format!("greater or equal to {}", lo)
-    } else {
-        format!("in the range {:?}", r)
-    }
-}
-
 struct ValidityVisitor<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> {
     /// The `path` may be pushed to, but the part that is present when a function
     /// starts must not be changed!  `visit_fields` and `visit_array` rely on
@@ -311,19 +289,19 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
     fn read_immediate(
         &self,
         op: &OpTy<'tcx, M::Provenance>,
-        expected: impl Display,
+        expected: ExpectedKind,
     ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> {
         Ok(try_validation!(
             self.ecx.read_immediate(op),
             self.path,
-            InvalidUninitBytes(None) => { "uninitialized memory" } expected { "{expected}" }
+            InvalidUninitBytes(None) => Uninit { expected }
         ))
     }
 
     fn read_scalar(
         &self,
         op: &OpTy<'tcx, M::Provenance>,
-        expected: impl Display,
+        expected: ExpectedKind,
     ) -> InterpResult<'tcx, Scalar<M::Provenance>> {
         Ok(self.read_immediate(op, expected)?.to_scalar())
     }
@@ -342,8 +320,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                     self.ecx.get_ptr_vtable(vtable),
                     self.path,
                     DanglingIntPointer(..) |
-                    InvalidVTablePointer(..) =>
-                        { "{vtable}" } expected { "a vtable pointer" },
+                    InvalidVTablePointer(..) => InvalidVTablePtr { value: format!("{vtable}") }
                 );
                 // FIXME: check if the type/trait match what ty::Dynamic says?
             }
@@ -366,10 +343,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
     fn check_safe_pointer(
         &mut self,
         value: &OpTy<'tcx, M::Provenance>,
-        kind: &str,
+        ptr_kind: PointerKind,
     ) -> InterpResult<'tcx> {
-        let place =
-            self.ecx.ref_to_mplace(&self.read_immediate(value, format_args!("a {kind}"))?)?;
+        let place = self.ecx.ref_to_mplace(&self.read_immediate(value, ptr_kind.into())?)?;
         // Handle wide pointers.
         // Check metadata early, for better diagnostics
         if place.layout.is_unsized() {
@@ -379,7 +355,10 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
         let size_and_align = try_validation!(
             self.ecx.size_and_align_of_mplace(&place),
             self.path,
-            InvalidMeta(msg) => { "invalid {} metadata: {}", kind, msg },
+            InvalidMeta(msg) => match msg {
+                InvalidMetaKind::SliceTooBig => InvalidMetaSliceTooLarge { ptr_kind },
+                InvalidMetaKind::TooBig => InvalidMetaTooLarge { ptr_kind },
+            }
         );
         let (size, align) = size_and_align
             // for the purpose of validity, consider foreign types to have
@@ -395,31 +374,30 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 CheckInAllocMsg::InboundsTest, // will anyway be replaced by validity message
             ),
             self.path,
-            AlignmentCheckFailed { required, has } =>
-                {
-                    "an unaligned {kind} (required {} byte alignment but found {})",
-                    required.bytes(),
-                    has.bytes(),
-                },
-            DanglingIntPointer(0, _) =>
-                { "a null {kind}" },
-            DanglingIntPointer(i, _) =>
-                {
-                    "a dangling {kind} ({pointer} has no provenance)",
-                    pointer = Pointer::<Option<AllocId>>::from_addr_invalid(*i),
-                },
-            PointerOutOfBounds { .. } =>
-                { "a dangling {kind} (going beyond the bounds of its allocation)" },
+            AlignmentCheckFailed { required, has } => UnalignedPtr {
+                ptr_kind,
+                required_bytes: required.bytes(),
+                found_bytes: has.bytes()
+            },
+            DanglingIntPointer(0, _) => NullPtr { ptr_kind },
+            DanglingIntPointer(i, _) => DanglingPtrNoProvenance {
+                ptr_kind,
+                // FIXME this says "null pointer" when null but we need translate
+                pointer: format!("{}", Pointer::<Option<AllocId>>::from_addr_invalid(i))
+            },
+            PointerOutOfBounds { .. } => DanglingPtrOutOfBounds {
+                ptr_kind
+            },
             // This cannot happen during const-eval (because interning already detects
             // dangling pointers), but it can happen in Miri.
-            PointerUseAfterFree(..) =>
-                { "a dangling {kind} (use-after-free)" },
+            PointerUseAfterFree(..) => DanglingPtrUseAfterFree {
+                ptr_kind,
+            },
         );
         // Do not allow pointers to uninhabited types.
         if place.layout.abi.is_uninhabited() {
-            throw_validation_failure!(self.path,
-                { "a {kind} pointing to uninhabited type {}", place.layout.ty }
-            )
+            let ty = place.layout.ty;
+            throw_validation_failure!(self.path, PtrToUninhabited { ptr_kind, ty })
         }
         // Recursive checking
         if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() {
@@ -441,9 +419,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                             // this check is so important.
                             // This check is reachable when the const just referenced the static,
                             // but never read it (so we never entered `before_access_global`).
-                            throw_validation_failure!(self.path,
-                                { "a {} pointing to a static variable in a constant", kind }
-                            );
+                            throw_validation_failure!(self.path, PtrToStatic { ptr_kind });
                         }
                         // We skip recursively checking other statics. These statics must be sound by
                         // themselves, and the only way to get broken statics here is by using
@@ -464,9 +440,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                             // This should be unreachable, but if someone manages to copy a pointer
                             // out of a `static`, then that pointer might point to mutable memory,
                             // and we would catch that here.
-                            throw_validation_failure!(self.path,
-                                { "a {} pointing to mutable memory in a constant", kind }
-                            );
+                            throw_validation_failure!(self.path, PtrToMut { ptr_kind });
                         }
                     }
                     // Nothing to check for these.
@@ -496,22 +470,24 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
         let ty = value.layout.ty;
         match ty.kind() {
             ty::Bool => {
-                let value = self.read_scalar(value, "a boolean")?;
+                let value = self.read_scalar(value, ExpectedKind::Bool)?;
                 try_validation!(
                     value.to_bool(),
                     self.path,
-                    InvalidBool(..) =>
-                        { "{:x}", value } expected { "a boolean" },
+                    InvalidBool(..) => ValidationErrorKind::InvalidBool {
+                        value: format!("{value:x}"),
+                    }
                 );
                 Ok(true)
             }
             ty::Char => {
-                let value = self.read_scalar(value, "a unicode scalar value")?;
+                let value = self.read_scalar(value, ExpectedKind::Char)?;
                 try_validation!(
                     value.to_char(),
                     self.path,
-                    InvalidChar(..) =>
-                        { "{:x}", value } expected { "a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`)" },
+                    InvalidChar(..) => ValidationErrorKind::InvalidChar {
+                        value: format!("{value:x}"),
+                    }
                 );
                 Ok(true)
             }
@@ -521,16 +497,17 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 let value = self.read_scalar(
                     value,
                     if matches!(ty.kind(), ty::Float(..)) {
-                        "a floating point number"
+                        ExpectedKind::Float
                     } else {
-                        "an integer"
+                        ExpectedKind::Int
                     },
                 )?;
                 // As a special exception we *do* match on a `Scalar` here, since we truly want
                 // to know its underlying representation (and *not* cast it to an integer).
                 if matches!(value, Scalar::Ptr(..)) {
-                    throw_validation_failure!(self.path,
-                        { "{:x}", value } expected { "plain (non-pointer) bytes" }
+                    throw_validation_failure!(
+                        self.path,
+                        ExpectedNonPtr { value: format!("{value:x}") }
                     )
                 }
                 Ok(true)
@@ -540,7 +517,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 // actually enforce the strict rules for raw pointers (mostly because
                 // that lets us re-use `ref_to_mplace`).
                 let place =
-                    self.ecx.ref_to_mplace(&self.read_immediate(value, "a raw pointer")?)?;
+                    self.ecx.ref_to_mplace(&self.read_immediate(value, ExpectedKind::RawPtr)?)?;
                 if place.layout.is_unsized() {
                     self.check_wide_ptr_meta(place.meta, place.layout)?;
                 }
@@ -554,14 +531,14 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                     // a ZST).
                     let layout = self.ecx.layout_of(*ty)?;
                     if !layout.is_zst() {
-                        throw_validation_failure!(self.path, { "mutable reference in a `const`" });
+                        throw_validation_failure!(self.path, MutableRefInConst);
                     }
                 }
-                self.check_safe_pointer(value, "reference")?;
+                self.check_safe_pointer(value, PointerKind::Ref)?;
                 Ok(true)
             }
             ty::FnPtr(_sig) => {
-                let value = self.read_scalar(value, "a function pointer")?;
+                let value = self.read_scalar(value, ExpectedKind::FnPtr)?;
 
                 // If we check references recursively, also check that this points to a function.
                 if let Some(_) = self.ref_tracking {
@@ -570,19 +547,20 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                         self.ecx.get_ptr_fn(ptr),
                         self.path,
                         DanglingIntPointer(..) |
-                        InvalidFunctionPointer(..) =>
-                            { "{ptr}" } expected { "a function pointer" },
+                        InvalidFunctionPointer(..) => InvalidFnPtr {
+                            value: format!("{ptr}"),
+                        },
                     );
                     // FIXME: Check if the signature matches
                 } else {
                     // Otherwise (for standalone Miri), we have to still check it to be non-null.
                     if self.ecx.scalar_may_be_null(value)? {
-                        throw_validation_failure!(self.path, { "a null function pointer" });
+                        throw_validation_failure!(self.path, NullFnPtr);
                     }
                 }
                 Ok(true)
             }
-            ty::Never => throw_validation_failure!(self.path, { "a value of the never type `!`" }),
+            ty::Never => throw_validation_failure!(self.path, NeverVal),
             ty::Foreign(..) | ty::FnDef(..) => {
                 // Nothing to check.
                 Ok(true)
@@ -629,12 +607,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 if start == 1 && end == max_value {
                     // Only null is the niche. So make sure the ptr is NOT null.
                     if self.ecx.scalar_may_be_null(scalar)? {
-                        throw_validation_failure!(self.path,
-                            { "a potentially null pointer" }
-                            expected {
-                                "something that cannot possibly fail to be {}",
-                                wrapping_range_format(valid_range, max_value)
-                            }
+                        throw_validation_failure!(
+                            self.path,
+                            NullablePtrOutOfRange { range: valid_range, max_value }
                         )
                     } else {
                         return Ok(());
@@ -645,12 +620,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 } else {
                     // Conservatively, we reject, because the pointer *could* have a bad
                     // value.
-                    throw_validation_failure!(self.path,
-                        { "a pointer" }
-                        expected {
-                            "something that cannot possibly fail to be {}",
-                            wrapping_range_format(valid_range, max_value)
-                        }
+                    throw_validation_failure!(
+                        self.path,
+                        PtrOutOfRange { range: valid_range, max_value }
                     )
                 }
             }
@@ -659,9 +631,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
         if valid_range.contains(bits) {
             Ok(())
         } else {
-            throw_validation_failure!(self.path,
-                { "{}", bits }
-                expected { "something {}", wrapping_range_format(valid_range, max_value) }
+            throw_validation_failure!(
+                self.path,
+                OutOfRange { value: format!("{bits}"), range: valid_range, max_value }
             )
         }
     }
@@ -685,10 +657,11 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
             Ok(try_validation!(
                 this.ecx.read_discriminant(op),
                 this.path,
-                InvalidTag(val) =>
-                    { "{:x}", val } expected { "a valid enum tag" },
-                InvalidUninitBytes(None) =>
-                    { "uninitialized bytes" } expected { "a valid enum tag" },
+                InvalidTag(val) => InvalidEnumTag {
+                    value: format!("{val:x}"),
+                },
+
+                InvalidUninitBytes(None) => UninitEnumTag,
             )
             .1)
         })
@@ -730,7 +703,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
         // Special check preventing `UnsafeCell` inside unions in the inner part of constants.
         if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. })) {
             if !op.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) {
-                throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" });
+                throw_validation_failure!(self.path, UnsafeCell);
             }
         }
         Ok(())
@@ -738,7 +711,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
 
     #[inline]
     fn visit_box(&mut self, op: &OpTy<'tcx, M::Provenance>) -> InterpResult<'tcx> {
-        self.check_safe_pointer(op, "box")?;
+        self.check_safe_pointer(op, PointerKind::Box)?;
         Ok(())
     }
 
@@ -756,7 +729,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
             if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. }))
                 && def.is_unsafe_cell()
             {
-                throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" });
+                throw_validation_failure!(self.path, UnsafeCell);
             }
         }
 
@@ -775,14 +748,13 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
         // MyNewtype and then the scalar in there).
         match op.layout.abi {
             Abi::Uninhabited => {
-                throw_validation_failure!(self.path,
-                    { "a value of uninhabited type {:?}", op.layout.ty }
-                );
+                let ty = op.layout.ty;
+                throw_validation_failure!(self.path, UninhabitedVal { ty });
             }
             Abi::Scalar(scalar_layout) => {
                 if !scalar_layout.is_uninit_valid() {
                     // There is something to check here.
-                    let scalar = self.read_scalar(op, "initialized scalar value")?;
+                    let scalar = self.read_scalar(op, ExpectedKind::InitScalar)?;
                     self.visit_scalar(scalar, scalar_layout)?;
                 }
             }
@@ -792,7 +764,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
                 // the other must be init.
                 if !a_layout.is_uninit_valid() && !b_layout.is_uninit_valid() {
                     let (a, b) =
-                        self.read_immediate(op, "initialized scalar value")?.to_scalar_pair();
+                        self.read_immediate(op, ExpectedKind::InitScalar)?.to_scalar_pair();
                     self.visit_scalar(a, a_layout)?;
                     self.visit_scalar(b, b_layout)?;
                 }
@@ -822,7 +794,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
                 try_validation!(
                     self.ecx.read_bytes_ptr_strip_provenance(mplace.ptr, Size::from_bytes(len)),
                     self.path,
-                    InvalidUninitBytes(..) => { "uninitialized data in `str`" },
+                    InvalidUninitBytes(..) => { UninitStr },
                 );
             }
             ty::Array(tys, ..) | ty::Slice(tys)
@@ -852,7 +824,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
                     Left(mplace) => mplace,
                     Right(imm) => match *imm {
                         Immediate::Uninit =>
-                            throw_validation_failure!(self.path, { "uninitialized bytes" }),
+                            throw_validation_failure!(self.path, UninitVal),
                         Immediate::Scalar(..) | Immediate::ScalarPair(..) =>
                             bug!("arrays/slices can never have Scalar/ScalarPair layout"),
                     }
@@ -888,7 +860,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
                                 .unwrap();
                                 self.path.push(PathElem::ArrayElem(i));
 
-                                throw_validation_failure!(self.path, { "uninitialized bytes" })
+                                throw_validation_failure!(self.path, UninitVal)
                             }
 
                             // Propagate upwards (that will also check for unexpected errors).
@@ -929,12 +901,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         match visitor.visit_value(&op) {
             Ok(()) => Ok(()),
             // Pass through validation failures.
-            Err(err) if matches!(err.kind(), err_ub!(ValidationFailure { .. })) => Err(err),
+            Err(err) if matches!(err.kind(), err_ub!(Validation { .. })) => Err(err),
             // Complain about any other kind of UB error -- those are bad because we'd like to
             // report them in a way that shows *where* in the value the issue lies.
             Err(err) if matches!(err.kind(), InterpError::UndefinedBehavior(_)) => {
-                err.print_backtrace();
-                bug!("Unexpected Undefined Behavior error during validation: {}", err);
+                let (err, backtrace) = err.into_parts();
+                backtrace.print_backtrace();
+                bug!("Unexpected Undefined Behavior error during validation: {err:?}");
             }
             // Pass through everything else.
             Err(err) => Err(err),
diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs
index 0c48d99915a..8314f53ba57 100644
--- a/compiler/rustc_const_eval/src/lib.rs
+++ b/compiler/rustc_const_eval/src/lib.rs
@@ -4,6 +4,7 @@ Rust MIR: a lowered representation of Rust.
 
 */
 
+#![deny(rustc::untranslatable_diagnostic)]
 #![feature(assert_matches)]
 #![feature(box_patterns)]
 #![feature(decl_macro)]
@@ -33,6 +34,8 @@ pub mod interpret;
 pub mod transform;
 pub mod util;
 
+pub use errors::ReportErrorExt;
+
 use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage};
 use rustc_fluent_macro::fluent_messages;
 use rustc_middle::query::Providers;
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
index 21f3c2c8917..236e43bdfcc 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
@@ -2,9 +2,7 @@
 
 use hir::def_id::LocalDefId;
 use hir::{ConstContext, LangItem};
-use rustc_errors::{
-    error_code, struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed,
-};
+use rustc_errors::{error_code, DiagnosticBuilder, ErrorGuaranteed};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_infer::infer::TyCtxtInferExt;
@@ -152,7 +150,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
 
                     if let Ok(Some(ImplSource::UserDefined(data))) = implsrc {
                         let span = tcx.def_span(data.impl_def_id);
-                        err.span_note(span, "impl defined here, but it is not `const`");
+                        err.subdiagnostic(errors::NonConstImplNote { span });
                     }
                 }
                 _ => {}
@@ -166,26 +164,30 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
         let mut err = match call_kind {
             CallKind::Normal { desugaring: Some((kind, self_ty)), .. } => {
                 macro_rules! error {
-                    ($fmt:literal) => {
-                        struct_span_err!(tcx.sess, span, E0015, $fmt, self_ty, ccx.const_kind())
+                    ($err:ident) => {
+                        tcx.sess.create_err(errors::$err {
+                            span,
+                            ty: self_ty,
+                            kind: ccx.const_kind(),
+                        })
                     };
                 }
 
                 let mut err = match kind {
                     CallDesugaringKind::ForLoopIntoIter => {
-                        error!("cannot convert `{}` into an iterator in {}s")
+                        error!(NonConstForLoopIntoIter)
                     }
                     CallDesugaringKind::QuestionBranch => {
-                        error!("`?` cannot determine the branch of `{}` in {}s")
+                        error!(NonConstQuestionBranch)
                     }
                     CallDesugaringKind::QuestionFromResidual => {
-                        error!("`?` cannot convert from residual of `{}` in {}s")
+                        error!(NonConstQuestionFromResidual)
                     }
                     CallDesugaringKind::TryBlockFromOutput => {
-                        error!("`try` block cannot convert `{}` to the result in {}s")
+                        error!(NonConstTryBlockFromOutput)
                     }
                     CallDesugaringKind::Await => {
-                        error!("cannot convert `{}` into a future in {}s")
+                        error!(NonConstAwait)
                     }
                 };
 
@@ -193,49 +195,31 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
                 err
             }
             CallKind::FnCall { fn_trait_id, self_ty } => {
-                let mut err = struct_span_err!(
-                    tcx.sess,
-                    span,
-                    E0015,
-                    "cannot call non-const closure in {}s",
-                    ccx.const_kind(),
-                );
-
-                match self_ty.kind() {
+                let note = match self_ty.kind() {
                     FnDef(def_id, ..) => {
                         let span = tcx.def_span(*def_id);
                         if ccx.tcx.is_const_fn_raw(*def_id) {
                             span_bug!(span, "calling const FnDef errored when it shouldn't");
                         }
 
-                        err.span_note(span, "function defined here, but it is not `const`");
-                    }
-                    FnPtr(..) => {
-                        err.note(format!(
-                            "function pointers need an RFC before allowed to be called in {}s",
-                            ccx.const_kind()
-                        ));
+                        Some(errors::NonConstClosureNote::FnDef { span })
                     }
-                    Closure(..) => {
-                        err.note(format!(
-                            "closures need an RFC before allowed to be called in {}s",
-                            ccx.const_kind()
-                        ));
-                    }
-                    _ => {}
-                }
+                    FnPtr(..) => Some(errors::NonConstClosureNote::FnPtr),
+                    Closure(..) => Some(errors::NonConstClosureNote::Closure),
+                    _ => None,
+                };
+
+                let mut err = tcx.sess.create_err(errors::NonConstClosure {
+                    span,
+                    kind: ccx.const_kind(),
+                    note,
+                });
 
                 diag_trait(&mut err, self_ty, fn_trait_id);
                 err
             }
             CallKind::Operator { trait_id, self_ty, .. } => {
-                let mut err = struct_span_err!(
-                    tcx.sess,
-                    span,
-                    E0015,
-                    "cannot call non-const operator in {}s",
-                    ccx.const_kind()
-                );
+                let mut sugg = None;
 
                 if Some(trait_id) == ccx.tcx.lang_items().eq_trait() {
                     match (substs[0].unpack(), substs[1].unpack()) {
@@ -260,14 +244,11 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
                                         let rhs_pos =
                                             span.lo() + BytePos::from_usize(eq_idx + 2 + rhs_idx);
                                         let rhs_span = span.with_lo(rhs_pos).with_hi(rhs_pos);
-                                        err.multipart_suggestion(
-                                            "consider dereferencing here",
-                                            vec![
-                                                (span.shrink_to_lo(), deref.clone()),
-                                                (rhs_span, deref),
-                                            ],
-                                            Applicability::MachineApplicable,
-                                        );
+                                        sugg = Some(errors::ConsiderDereferencing {
+                                            deref,
+                                            span: span.shrink_to_lo(),
+                                            rhs_span,
+                                        });
                                     }
                                 }
                             }
@@ -275,26 +256,29 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
                         _ => {}
                     }
                 }
-
+                let mut err = tcx.sess.create_err(errors::NonConstOperator {
+                    span,
+                    kind: ccx.const_kind(),
+                    sugg,
+                });
                 diag_trait(&mut err, self_ty, trait_id);
                 err
             }
             CallKind::DerefCoercion { deref_target, deref_target_ty, self_ty } => {
-                let mut err = struct_span_err!(
-                    tcx.sess,
-                    span,
-                    E0015,
-                    "cannot perform deref coercion on `{}` in {}s",
-                    self_ty,
-                    ccx.const_kind()
-                );
-
-                err.note(format!("attempting to deref into `{}`", deref_target_ty));
-
                 // Check first whether the source is accessible (issue #87060)
-                if tcx.sess.source_map().is_span_accessible(deref_target) {
-                    err.span_note(deref_target, "deref defined here");
-                }
+                let target = if tcx.sess.source_map().is_span_accessible(deref_target) {
+                    Some(deref_target)
+                } else {
+                    None
+                };
+
+                let mut err = tcx.sess.create_err(errors::NonConstDerefCoercion {
+                    span,
+                    ty: self_ty,
+                    kind: ccx.const_kind(),
+                    target_ty: deref_target_ty,
+                    deref_target: target,
+                });
 
                 diag_trait(&mut err, self_ty, tcx.require_lang_item(LangItem::Deref, Some(span)));
                 err
@@ -432,21 +416,12 @@ impl<'tcx> NonConstOp<'tcx> for LiveDrop<'tcx> {
         ccx: &ConstCx<'_, 'tcx>,
         span: Span,
     ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
-        let mut err = struct_span_err!(
-            ccx.tcx.sess,
-            span,
-            E0493,
-            "destructor of `{}` cannot be evaluated at compile-time",
-            self.dropped_ty,
-        );
-        err.span_label(
+        ccx.tcx.sess.create_err(errors::LiveDrop {
             span,
-            format!("the destructor for this type cannot be evaluated in {}s", ccx.const_kind()),
-        );
-        if let Some(span) = self.dropped_at {
-            err.span_label(span, "value is dropped here");
-        }
-        err
+            dropped_ty: self.dropped_ty,
+            kind: ccx.const_kind(),
+            dropped_at: self.dropped_at,
+        })
     }
 }