about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2025-06-26 20:15:17 -0400
committerGitHub <noreply@github.com>2025-06-26 20:15:17 -0400
commit36cde678948400f2dcda71ed09e2072f093b63d5 (patch)
tree1469a5fcbd2aa647383a6ce2e1a08c9541b905ba /compiler
parent0446a0d14bb378dbe9a59f0f3e30a2f14aabd80d (diff)
parentbade3fd0580815f1a5a4abd33244982dc458d54d (diff)
downloadrust-36cde678948400f2dcda71ed09e2072f093b63d5.tar.gz
rust-36cde678948400f2dcda71ed09e2072f093b63d5.zip
Rollup merge of #140942 - RalfJung:const-ref-to-mut, r=oli-obk
const-eval: allow constants to refer to mutable/external memory, but reject such constants as patterns

This fixes https://github.com/rust-lang/rust/issues/140653 by accepting code such as this:
```rust
static FOO: AtomicU32 = AtomicU32::new(0);
const C: &'static AtomicU32 = &FOO;
```
This can be written entirely in safe code, so there can't really be anything wrong with it.

We also accept the much more questionable following code, since it looks very similar to the interpreter:
```rust
static mut FOO2: u32 = 0;
const C2: &'static u32 = unsafe { &mut FOO2 };
```
Using this without causing UB is at least very hard (the details are unclear since it is related to how the aliasing model deals with the staging of const-eval vs runtime code).

If a constant like `C2` is used as a pattern, we emit an error:
```
error: constant BAD_PATTERN cannot be used as pattern
  --> $DIR/const_refs_to_static_fail.rs:30:9
   |
LL |         BAD_PATTERN => {},
   |         ^^^^^^^^^^^
   |
   = note: constants that reference mutable or external memory cannot be used as pattern
```
(If you somehow manage to build a pattern with constant `C`, you'd get the same error, but that should be impossible: we don't have a type that can be used in patterns and that has interior mutability.)

The same treatment is afforded for shared references to `extern static`, for the same reason: the const evaluation is entirely fine with it, we just can't build a pattern for it -- and when using interior mutability, this can be totally sound.

We do still not accept anything where there is an `&mut` in the final value of the const, as that should always require unsafe code and it's hard to imagine a sound use-case that would require this.
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_const_eval/messages.ftl33
-rw-r--r--compiler/rustc_const_eval/src/check_consts/check.rs6
-rw-r--r--compiler/rustc_const_eval/src/check_consts/ops.rs23
-rw-r--r--compiler/rustc_const_eval/src/const_eval/mod.rs7
-rw-r--r--compiler/rustc_const_eval/src/const_eval/valtrees.rs38
-rw-r--r--compiler/rustc_const_eval/src/errors.rs31
-rw-r--r--compiler/rustc_const_eval/src/interpret/validity.rs21
-rw-r--r--compiler/rustc_middle/messages.ftl7
-rw-r--r--compiler/rustc_middle/src/error.rs17
-rw-r--r--compiler/rustc_middle/src/mir/interpret/error.rs52
-rw-r--r--compiler/rustc_middle/src/mir/interpret/mod.rs4
-rw-r--r--compiler/rustc_middle/src/mir/interpret/queries.rs120
-rw-r--r--compiler/rustc_middle/src/query/erase.rs7
-rw-r--r--compiler/rustc_middle/src/ty/consts/valtree.rs8
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs4
-rw-r--r--compiler/rustc_trait_selection/src/traits/mod.rs6
16 files changed, 207 insertions, 177 deletions
diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl
index 7f9abe8aa8e..97b154ad142 100644
--- a/compiler/rustc_const_eval/messages.ftl
+++ b/compiler/rustc_const_eval/messages.ftl
@@ -124,12 +124,13 @@ const_eval_incompatible_return_types =
 const_eval_incompatible_types =
     calling a function with argument of type {$callee_ty} passing data of type {$caller_ty}
 
-const_eval_interior_mutable_ref_escaping =
-    {const_eval_const_context}s cannot refer to interior mutable data
-    .label = this borrow of an interior mutable value may end up in the final value
+const_eval_interior_mutable_borrow_escaping =
+    interior mutable shared borrows of lifetime-extended temporaries in the top-level scope of a {const_eval_const_context} are not allowed
+    .label = this borrow of an interior mutable value refers to a lifetime-extended temporary
     .help = to fix this, the value can be extracted to a separate `static` item and then referenced
     .teach_note =
-        References that escape into the final value of a constant or static must be immutable.
+        This creates a raw pointer to a temporary that has its lifetime extended to last for the entire program.
+        Lifetime-extended temporaries in constants and statics must be immutable.
         This is to avoid accidentally creating shared mutable state.
 
 
@@ -207,33 +208,23 @@ const_eval_long_running =
     .label = the const evaluator is currently interpreting this expression
     .help = the constant being evaluated
 
-const_eval_max_num_nodes_in_const = maximum number of nodes exceeded in constant {$global_const_id}
-
 const_eval_memory_exhausted =
     tried to allocate more memory than available to compiler
 
 const_eval_modified_global =
     modifying a static's initial value from another static's initializer
 
-const_eval_mutable_ptr_in_final = encountered mutable pointer in final value of {const_eval_intern_kind}
-
-const_eval_mutable_raw_escaping =
-    raw mutable pointers are not allowed in the final value of {const_eval_const_context}s
+const_eval_mutable_borrow_escaping =
+    mutable borrows of lifetime-extended temporaries in the top-level scope of a {const_eval_const_context} are not allowed
     .teach_note =
-        Pointers that escape into the final value of a constant or static must be immutable.
+        This creates a reference to a temporary that has its lifetime extended to last for the entire program.
+        Lifetime-extended temporaries in constants and statics must be immutable.
         This is to avoid accidentally creating shared mutable state.
 
 
         If you really want global mutable state, try using an interior mutable `static` or a `static mut`.
 
-const_eval_mutable_ref_escaping =
-    mutable references are not allowed in the final value of {const_eval_const_context}s
-    .teach_note =
-        References that escape into the final value of a constant or static must be immutable.
-        This is to avoid accidentally creating shared mutable state.
-
-
-        If you really want global mutable state, try using an interior mutable `static` or a `static mut`.
+const_eval_mutable_ptr_in_final = encountered mutable pointer in final value of {const_eval_intern_kind}
 
 const_eval_nested_static_in_thread_local = #[thread_local] does not support implicit nested statics, please create explicit static items and refer to them instead
 
@@ -437,9 +428,6 @@ const_eval_unwind_past_top =
 ## (We'd love to sort this differently to make that more clear but tidy won't let us...)
 const_eval_validation_box_to_uninhabited = {$front_matter}: encountered a box pointing to uninhabited type {$ty}
 
-const_eval_validation_const_ref_to_extern = {$front_matter}: encountered reference to `extern` static in `const`
-const_eval_validation_const_ref_to_mutable = {$front_matter}: encountered reference to mutable memory in `const`
-
 const_eval_validation_dangling_box_no_provenance = {$front_matter}: encountered a dangling box ({$pointer} has no provenance)
 const_eval_validation_dangling_box_out_of_bounds = {$front_matter}: encountered a dangling box (going beyond the bounds of its allocation)
 const_eval_validation_dangling_box_use_after_free = {$front_matter}: encountered a dangling box (use-after-free)
@@ -479,6 +467,7 @@ const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid re
 const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object
 const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer
 const_eval_validation_invalid_vtable_trait = {$front_matter}: wrong trait in wide pointer vtable: expected `{$expected_dyn_type}`, but encountered `{$vtable_dyn_type}`
+const_eval_validation_mutable_ref_in_const = {$front_matter}: encountered mutable reference in `const` value
 const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory
 const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!`
 const_eval_validation_null_box = {$front_matter}: encountered a null box
diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs
index 576b174369d..c151e8acf92 100644
--- a/compiler/rustc_const_eval/src/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/check_consts/check.rs
@@ -595,11 +595,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                     self.const_kind() == hir::ConstContext::Static(hir::Mutability::Mut);
 
                 if !is_allowed && self.place_may_escape(place) {
-                    self.check_op(ops::EscapingMutBorrow(if matches!(rvalue, Rvalue::Ref(..)) {
-                        hir::BorrowKind::Ref
-                    } else {
-                        hir::BorrowKind::Raw
-                    }));
+                    self.check_op(ops::EscapingMutBorrow);
                 }
             }
 
diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs
index f5b7a6066c8..02edff8f632 100644
--- a/compiler/rustc_const_eval/src/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/check_consts/ops.rs
@@ -567,7 +567,7 @@ impl<'tcx> NonConstOp<'tcx> for EscapingCellBorrow {
         DiagImportance::Secondary
     }
     fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
-        ccx.dcx().create_err(errors::InteriorMutableRefEscaping {
+        ccx.dcx().create_err(errors::InteriorMutableBorrowEscaping {
             span,
             opt_help: matches!(ccx.const_kind(), hir::ConstContext::Static(_)),
             kind: ccx.const_kind(),
@@ -580,7 +580,7 @@ impl<'tcx> NonConstOp<'tcx> for EscapingCellBorrow {
 /// This op is for `&mut` borrows in the trailing expression of a constant
 /// which uses the "enclosing scopes rule" to leak its locals into anonymous
 /// static or const items.
-pub(crate) struct EscapingMutBorrow(pub hir::BorrowKind);
+pub(crate) struct EscapingMutBorrow;
 
 impl<'tcx> NonConstOp<'tcx> for EscapingMutBorrow {
     fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
@@ -594,20 +594,11 @@ impl<'tcx> NonConstOp<'tcx> for EscapingMutBorrow {
     }
 
     fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
-        match self.0 {
-            hir::BorrowKind::Raw => ccx.tcx.dcx().create_err(errors::MutableRawEscaping {
-                span,
-                kind: ccx.const_kind(),
-                teach: ccx.tcx.sess.teach(E0764),
-            }),
-            hir::BorrowKind::Ref | hir::BorrowKind::Pin => {
-                ccx.dcx().create_err(errors::MutableRefEscaping {
-                    span,
-                    kind: ccx.const_kind(),
-                    teach: ccx.tcx.sess.teach(E0764),
-                })
-            }
-        }
+        ccx.dcx().create_err(errors::MutableBorrowEscaping {
+            span,
+            kind: ccx.const_kind(),
+            teach: ccx.tcx.sess.teach(E0764),
+        })
     }
 }
 
diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs
index 6fd0b9d26e3..73cb1e77436 100644
--- a/compiler/rustc_const_eval/src/const_eval/mod.rs
+++ b/compiler/rustc_const_eval/src/const_eval/mod.rs
@@ -26,13 +26,6 @@ pub(crate) use self::valtrees::{eval_to_valtree, valtree_to_const_value};
 // We forbid type-level constants that contain more than `VALTREE_MAX_NODES` nodes.
 const VALTREE_MAX_NODES: usize = 100000;
 
-pub(crate) enum ValTreeCreationError<'tcx> {
-    NodesOverflow,
-    /// Values of this type, or this particular value, are not supported as valtrees.
-    NonSupportedType(Ty<'tcx>),
-}
-pub(crate) type ValTreeCreationResult<'tcx> = Result<ty::ValTree<'tcx>, ValTreeCreationError<'tcx>>;
-
 #[instrument(skip(tcx), level = "debug")]
 pub(crate) fn try_destructure_mir_constant_for_user_output<'tcx>(
     tcx: TyCtxt<'tcx>,
diff --git a/compiler/rustc_const_eval/src/const_eval/valtrees.rs b/compiler/rustc_const_eval/src/const_eval/valtrees.rs
index 58d230af683..b3d2e1a0598 100644
--- a/compiler/rustc_const_eval/src/const_eval/valtrees.rs
+++ b/compiler/rustc_const_eval/src/const_eval/valtrees.rs
@@ -1,17 +1,16 @@
 use rustc_abi::{BackendRepr, FieldIdx, VariantIdx};
 use rustc_data_structures::stack::ensure_sufficient_stack;
-use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId, ReportedErrorInfo};
+use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId, ValTreeCreationError};
 use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout};
 use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_middle::{bug, mir};
 use rustc_span::DUMMY_SP;
 use tracing::{debug, instrument, trace};
 
+use super::VALTREE_MAX_NODES;
 use super::eval_queries::{mk_eval_cx_to_read_const_val, op_to_const};
 use super::machine::CompileTimeInterpCx;
-use super::{VALTREE_MAX_NODES, ValTreeCreationError, ValTreeCreationResult};
 use crate::const_eval::CanAccessMutGlobal;
-use crate::errors::MaxNumNodesInConstErr;
 use crate::interpret::{
     ImmTy, Immediate, InternKind, MPlaceTy, MemPlaceMeta, MemoryKind, PlaceTy, Projectable, Scalar,
     intern_const_alloc_recursive,
@@ -24,7 +23,7 @@ fn branches<'tcx>(
     field_count: usize,
     variant: Option<VariantIdx>,
     num_nodes: &mut usize,
-) -> ValTreeCreationResult<'tcx> {
+) -> EvalToValTreeResult<'tcx> {
     let place = match variant {
         Some(variant) => ecx.project_downcast(place, variant).unwrap(),
         None => place.clone(),
@@ -58,7 +57,7 @@ fn slice_branches<'tcx>(
     ecx: &CompileTimeInterpCx<'tcx>,
     place: &MPlaceTy<'tcx>,
     num_nodes: &mut usize,
-) -> ValTreeCreationResult<'tcx> {
+) -> EvalToValTreeResult<'tcx> {
     let n = place.len(ecx).unwrap_or_else(|_| panic!("expected to use len of place {place:?}"));
 
     let mut elems = Vec::with_capacity(n as usize);
@@ -76,7 +75,7 @@ fn const_to_valtree_inner<'tcx>(
     ecx: &CompileTimeInterpCx<'tcx>,
     place: &MPlaceTy<'tcx>,
     num_nodes: &mut usize,
-) -> ValTreeCreationResult<'tcx> {
+) -> EvalToValTreeResult<'tcx> {
     let tcx = *ecx.tcx;
     let ty = place.layout.ty;
     debug!("ty kind: {:?}", ty.kind());
@@ -91,7 +90,7 @@ fn const_to_valtree_inner<'tcx>(
             Ok(ty::ValTree::zst(tcx))
         }
         ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => {
-            let val = ecx.read_immediate(place).unwrap();
+            let val = ecx.read_immediate(place).report_err()?;
             let val = val.to_scalar_int().unwrap();
             *num_nodes += 1;
 
@@ -113,7 +112,7 @@ fn const_to_valtree_inner<'tcx>(
             // equality at compile-time (see `ptr_guaranteed_cmp`).
             // However we allow those that are just integers in disguise.
             // First, get the pointer. Remember it might be wide!
-            let val = ecx.read_immediate(place).unwrap();
+            let val = ecx.read_immediate(place).report_err()?;
             // We could allow wide raw pointers where both sides are integers in the future,
             // but for now we reject them.
             if matches!(val.layout.backend_repr, BackendRepr::ScalarPair(..)) {
@@ -134,7 +133,7 @@ fn const_to_valtree_inner<'tcx>(
         ty::FnPtr(..) => Err(ValTreeCreationError::NonSupportedType(ty)),
 
         ty::Ref(_, _, _)  => {
-            let derefd_place = ecx.deref_pointer(place).unwrap();
+            let derefd_place = ecx.deref_pointer(place).report_err()?;
             const_to_valtree_inner(ecx, &derefd_place, num_nodes)
         }
 
@@ -158,7 +157,7 @@ fn const_to_valtree_inner<'tcx>(
                 bug!("uninhabited types should have errored and never gotten converted to valtree")
             }
 
-            let variant = ecx.read_discriminant(place).unwrap();
+            let variant = ecx.read_discriminant(place).report_err()?;
             branches(ecx, place, def.variant(variant).fields.len(), def.is_enum().then_some(variant), num_nodes)
         }
 
@@ -249,24 +248,7 @@ pub(crate) fn eval_to_valtree<'tcx>(
     debug!(?place);
 
     let mut num_nodes = 0;
-    let valtree_result = const_to_valtree_inner(&ecx, &place, &mut num_nodes);
-
-    match valtree_result {
-        Ok(valtree) => Ok(Ok(valtree)),
-        Err(err) => {
-            let did = cid.instance.def_id();
-            let global_const_id = cid.display(tcx);
-            let span = tcx.hir_span_if_local(did);
-            match err {
-                ValTreeCreationError::NodesOverflow => {
-                    let handled =
-                        tcx.dcx().emit_err(MaxNumNodesInConstErr { span, global_const_id });
-                    Err(ReportedErrorInfo::allowed_in_infallible(handled).into())
-                }
-                ValTreeCreationError::NonSupportedType(ty) => Ok(Err(ty)),
-            }
-        }
-    }
+    const_to_valtree_inner(&ecx, &place, &mut num_nodes)
 }
 
 /// Converts a `ValTree` to a `ConstValue`, which is needed after mir
diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs
index 609749554ce..b2c3103c34a 100644
--- a/compiler/rustc_const_eval/src/errors.rs
+++ b/compiler/rustc_const_eval/src/errors.rs
@@ -93,14 +93,6 @@ pub(crate) struct PanicNonStrErr {
 }
 
 #[derive(Diagnostic)]
-#[diag(const_eval_max_num_nodes_in_const)]
-pub(crate) struct MaxNumNodesInConstErr {
-    #[primary_span]
-    pub span: Option<Span>,
-    pub global_const_id: String,
-}
-
-#[derive(Diagnostic)]
 #[diag(const_eval_unallowed_fn_pointer_call)]
 pub(crate) struct UnallowedFnPointerCall {
     #[primary_span]
@@ -158,8 +150,8 @@ pub(crate) struct UnmarkedIntrinsicExposed {
 }
 
 #[derive(Diagnostic)]
-#[diag(const_eval_mutable_ref_escaping, code = E0764)]
-pub(crate) struct MutableRefEscaping {
+#[diag(const_eval_mutable_borrow_escaping, code = E0764)]
+pub(crate) struct MutableBorrowEscaping {
     #[primary_span]
     pub span: Span,
     pub kind: ConstContext,
@@ -168,15 +160,6 @@ pub(crate) struct MutableRefEscaping {
 }
 
 #[derive(Diagnostic)]
-#[diag(const_eval_mutable_raw_escaping, code = E0764)]
-pub(crate) struct MutableRawEscaping {
-    #[primary_span]
-    pub span: Span,
-    pub kind: ConstContext,
-    #[note(const_eval_teach_note)]
-    pub teach: bool,
-}
-#[derive(Diagnostic)]
 #[diag(const_eval_non_const_fmt_macro_call, code = E0015)]
 pub(crate) struct NonConstFmtMacroCall {
     #[primary_span]
@@ -233,8 +216,8 @@ pub(crate) struct UnallowedInlineAsm {
 }
 
 #[derive(Diagnostic)]
-#[diag(const_eval_interior_mutable_ref_escaping, code = E0492)]
-pub(crate) struct InteriorMutableRefEscaping {
+#[diag(const_eval_interior_mutable_borrow_escaping, code = E0492)]
+pub(crate) struct InteriorMutableBorrowEscaping {
     #[primary_span]
     #[label]
     pub span: Span,
@@ -655,9 +638,8 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
 
             PointerAsInt { .. } => const_eval_validation_pointer_as_int,
             PartialPointer => const_eval_validation_partial_pointer,
-            ConstRefToMutable => const_eval_validation_const_ref_to_mutable,
-            ConstRefToExtern => const_eval_validation_const_ref_to_extern,
             MutableRefToImmutable => const_eval_validation_mutable_ref_to_immutable,
+            MutableRefInConst => const_eval_validation_mutable_ref_in_const,
             NullFnPtr => const_eval_validation_null_fn_ptr,
             NeverVal => const_eval_validation_never_val,
             NullablePtrOutOfRange { .. } => const_eval_validation_nullable_ptr_out_of_range,
@@ -815,9 +797,8 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
                 err.arg("expected_dyn_type", expected_dyn_type.to_string());
             }
             NullPtr { .. }
-            | ConstRefToMutable
-            | ConstRefToExtern
             | MutableRefToImmutable
+            | MutableRefInConst
             | NullFnPtr
             | NeverVal
             | UnsafeCellInImmutable
diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs
index 7d76d925ef2..7e26b7f3d96 100644
--- a/compiler/rustc_const_eval/src/interpret/validity.rs
+++ b/compiler/rustc_const_eval/src/interpret/validity.rs
@@ -570,6 +570,8 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                     };
                     let (size, _align) =
                         global_alloc.size_and_align(*self.ecx.tcx, self.ecx.typing_env);
+                    let alloc_actual_mutbl =
+                        global_alloc.mutability(*self.ecx.tcx, self.ecx.typing_env);
 
                     if let GlobalAlloc::Static(did) = global_alloc {
                         let DefKind::Static { nested, .. } = self.ecx.tcx.def_kind(did) else {
@@ -597,9 +599,11 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                                 skip_recursive_check = !nested;
                             }
                             CtfeValidationMode::Const { .. } => {
-                                // We can't recursively validate `extern static`, so we better reject them.
-                                if self.ecx.tcx.is_foreign_item(did) {
-                                    throw_validation_failure!(self.path, ConstRefToExtern);
+                                // If this is mutable memory or an `extern static`, there's no point in checking it -- we'd
+                                // just get errors trying to read the value.
+                                if alloc_actual_mutbl.is_mut() || self.ecx.tcx.is_foreign_item(did)
+                                {
+                                    skip_recursive_check = true;
                                 }
                             }
                         }
@@ -618,9 +622,6 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                                 mutbl
                             }
                         };
-                        // Determine what it actually points to.
-                        let alloc_actual_mutbl =
-                            global_alloc.mutability(*self.ecx.tcx, self.ecx.typing_env);
                         // Mutable pointer to immutable memory is no good.
                         if ptr_expected_mutbl == Mutability::Mut
                             && alloc_actual_mutbl == Mutability::Not
@@ -628,12 +629,10 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                             // This can actually occur with transmutes.
                             throw_validation_failure!(self.path, MutableRefToImmutable);
                         }
-                        // In a const, everything must be completely immutable.
+                        // In a const, any kind of mutable reference is not good.
                         if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) {
-                            if ptr_expected_mutbl == Mutability::Mut
-                                || alloc_actual_mutbl == Mutability::Mut
-                            {
-                                throw_validation_failure!(self.path, ConstRefToMutable);
+                            if ptr_expected_mutbl == Mutability::Mut {
+                                throw_validation_failure!(self.path, MutableRefInConst);
                             }
                         }
                     }
diff --git a/compiler/rustc_middle/messages.ftl b/compiler/rustc_middle/messages.ftl
index 3d27e587b6c..0073deb18da 100644
--- a/compiler/rustc_middle/messages.ftl
+++ b/compiler/rustc_middle/messages.ftl
@@ -78,6 +78,11 @@ middle_erroneous_constant = erroneous constant encountered
 middle_failed_writing_file =
     failed to write file {$path}: {$error}"
 
+# Note: We only mention patterns here since the error can only occur with references, and those
+# are forbidden in const generics.
+middle_invalid_const_in_valtree = constant {$global_const_id} cannot be used as pattern
+    .note = constants that reference mutable or external memory cannot be used as pattern
+
 middle_layout_cycle =
     a cycle occurred during layout computation
 
@@ -95,6 +100,8 @@ middle_layout_too_generic = the type `{$ty}` does not have a fixed layout
 middle_layout_unknown =
     the type `{$ty}` has an unknown layout
 
+middle_max_num_nodes_in_valtree = maximum number of nodes exceeded in constant {$global_const_id}
+
 middle_opaque_hidden_type_mismatch =
     concrete type differs from previous defining opaque type use
     .label = expected `{$self_ty}`, got `{$other_ty}`
diff --git a/compiler/rustc_middle/src/error.rs b/compiler/rustc_middle/src/error.rs
index 6c6b12fed67..f36ae831653 100644
--- a/compiler/rustc_middle/src/error.rs
+++ b/compiler/rustc_middle/src/error.rs
@@ -170,3 +170,20 @@ pub(crate) struct TypeLengthLimit {
     pub path: PathBuf,
     pub type_length: usize,
 }
+
+#[derive(Diagnostic)]
+#[diag(middle_max_num_nodes_in_valtree)]
+pub(crate) struct MaxNumNodesInValtree {
+    #[primary_span]
+    pub span: Span,
+    pub global_const_id: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(middle_invalid_const_in_valtree)]
+#[note]
+pub(crate) struct InvalidConstInValtree {
+    #[primary_span]
+    pub span: Span,
+    pub global_const_id: String,
+}
diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs
index 6ff3cac049b..41a166083d0 100644
--- a/compiler/rustc_middle/src/mir/interpret/error.rs
+++ b/compiler/rustc_middle/src/mir/interpret/error.rs
@@ -35,7 +35,7 @@ impl From<ReportedErrorInfo> for ErrorHandled {
 }
 
 impl ErrorHandled {
-    pub fn with_span(self, span: Span) -> Self {
+    pub(crate) fn with_span(self, span: Span) -> Self {
         match self {
             ErrorHandled::Reported(err, _span) => ErrorHandled::Reported(err, span),
             ErrorHandled::TooGeneric(_span) => ErrorHandled::TooGeneric(span),
@@ -94,14 +94,51 @@ impl From<ReportedErrorInfo> for ErrorGuaranteed {
     }
 }
 
+/// An error type for the `const_to_valtree` query. Some error should be reported with a "use-site span",
+/// which means the query cannot emit the error, so those errors are represented as dedicated variants here.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
+pub enum ValTreeCreationError<'tcx> {
+    /// The constant is too big to be valtree'd.
+    NodesOverflow,
+    /// The constant references mutable or external memory, so it cannot be valtree'd.
+    InvalidConst,
+    /// Values of this type, or this particular value, are not supported as valtrees.
+    NonSupportedType(Ty<'tcx>),
+    /// The error has already been handled by const evaluation.
+    ErrorHandled(ErrorHandled),
+}
+
+impl<'tcx> From<ErrorHandled> for ValTreeCreationError<'tcx> {
+    fn from(err: ErrorHandled) -> Self {
+        ValTreeCreationError::ErrorHandled(err)
+    }
+}
+
+impl<'tcx> From<InterpErrorInfo<'tcx>> for ValTreeCreationError<'tcx> {
+    fn from(err: InterpErrorInfo<'tcx>) -> Self {
+        // An error ocurred outside the const-eval query, as part of constructing the valtree. We
+        // don't currently preserve the details of this error, since `InterpErrorInfo` cannot be put
+        // into a query result and it can only be access of some mutable or external memory.
+        let (_kind, backtrace) = err.into_parts();
+        backtrace.print_backtrace();
+        ValTreeCreationError::InvalidConst
+    }
+}
+
+impl<'tcx> ValTreeCreationError<'tcx> {
+    pub(crate) fn with_span(self, span: Span) -> Self {
+        use ValTreeCreationError::*;
+        match self {
+            ErrorHandled(handled) => ErrorHandled(handled.with_span(span)),
+            other => other,
+        }
+    }
+}
+
 pub type EvalToAllocationRawResult<'tcx> = Result<ConstAlloc<'tcx>, ErrorHandled>;
 pub type EvalStaticInitializerRawResult<'tcx> = Result<ConstAllocation<'tcx>, ErrorHandled>;
 pub type EvalToConstValueResult<'tcx> = Result<ConstValue<'tcx>, ErrorHandled>;
-/// `Ok(Err(ty))` indicates the constant was fine, but the valtree couldn't be constructed
-/// because the value contains something of type `ty` that is not valtree-compatible.
-/// The caller can then show an appropriate error; the query does not have the
-/// necessary context to give good user-facing errors for this case.
-pub type EvalToValTreeResult<'tcx> = Result<Result<ValTree<'tcx>, Ty<'tcx>>, ErrorHandled>;
+pub type EvalToValTreeResult<'tcx> = Result<ValTree<'tcx>, ValTreeCreationError<'tcx>>;
 
 #[cfg(target_pointer_width = "64")]
 rustc_data_structures::static_assert_size!(InterpErrorInfo<'_>, 8);
@@ -450,10 +487,9 @@ pub enum ValidationErrorKind<'tcx> {
         ptr_kind: PointerKind,
         ty: Ty<'tcx>,
     },
-    ConstRefToMutable,
-    ConstRefToExtern,
     MutableRefToImmutable,
     UnsafeCellInImmutable,
+    MutableRefInConst,
     NullFnPtr,
     NeverVal,
     NullablePtrOutOfRange {
diff --git a/compiler/rustc_middle/src/mir/interpret/mod.rs b/compiler/rustc_middle/src/mir/interpret/mod.rs
index c2438af6a1e..ea2f84d46d7 100644
--- a/compiler/rustc_middle/src/mir/interpret/mod.rs
+++ b/compiler/rustc_middle/src/mir/interpret/mod.rs
@@ -38,8 +38,8 @@ pub use self::error::{
     EvalToAllocationRawResult, EvalToConstValueResult, EvalToValTreeResult, ExpectedKind,
     InterpErrorInfo, InterpErrorKind, InterpResult, InvalidMetaKind, InvalidProgramInfo,
     MachineStopType, Misalignment, PointerKind, ReportedErrorInfo, ResourceExhaustionInfo,
-    ScalarSizeMismatch, UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo,
-    ValidationErrorKind, interp_ok,
+    ScalarSizeMismatch, UndefinedBehaviorInfo, UnsupportedOpInfo, ValTreeCreationError,
+    ValidationErrorInfo, ValidationErrorKind, interp_ok,
 };
 pub use self::pointer::{CtfeProvenance, Pointer, PointerArithmetic, Provenance};
 pub use self::value::Scalar;
diff --git a/compiler/rustc_middle/src/mir/interpret/queries.rs b/compiler/rustc_middle/src/mir/interpret/queries.rs
index 97db45a70d7..e25f35c59c2 100644
--- a/compiler/rustc_middle/src/mir/interpret/queries.rs
+++ b/compiler/rustc_middle/src/mir/interpret/queries.rs
@@ -5,11 +5,11 @@ use rustc_span::{DUMMY_SP, Span};
 use tracing::{debug, instrument};
 
 use super::{
-    ErrorHandled, EvalToAllocationRawResult, EvalToConstValueResult, EvalToValTreeResult, GlobalId,
-    ReportedErrorInfo,
+    ErrorHandled, EvalToAllocationRawResult, EvalToConstValueResult, GlobalId, ReportedErrorInfo,
 };
-use crate::mir;
-use crate::ty::{self, GenericArgs, TyCtxt, TypeVisitableExt};
+use crate::mir::interpret::ValTreeCreationError;
+use crate::ty::{self, ConstToValTreeResult, GenericArgs, TyCtxt, TypeVisitableExt};
+use crate::{error, mir};
 
 impl<'tcx> TyCtxt<'tcx> {
     /// Evaluates a constant without providing any generic parameters. This is useful to evaluate consts
@@ -92,7 +92,7 @@ impl<'tcx> TyCtxt<'tcx> {
         typing_env: ty::TypingEnv<'tcx>,
         ct: ty::UnevaluatedConst<'tcx>,
         span: Span,
-    ) -> EvalToValTreeResult<'tcx> {
+    ) -> ConstToValTreeResult<'tcx> {
         // Cannot resolve `Unevaluated` constants that contain inference
         // variables. We reject those here since `resolve`
         // would fail otherwise.
@@ -103,47 +103,54 @@ impl<'tcx> TyCtxt<'tcx> {
             bug!("did not expect inference variables here");
         }
 
-        match ty::Instance::try_resolve(self, typing_env, ct.def, ct.args) {
-            Ok(Some(instance)) => {
-                let cid = GlobalId { instance, promoted: None };
-                self.const_eval_global_id_for_typeck(typing_env, cid, span).inspect(|_| {
-                    // We are emitting the lint here instead of in `is_const_evaluatable`
-                    // as we normalize obligations before checking them, and normalization
-                    // uses this function to evaluate this constant.
-                    //
-                    // @lcnr believes that successfully evaluating even though there are
-                    // used generic parameters is a bug of evaluation, so checking for it
-                    // here does feel somewhat sensible.
-                    if !self.features().generic_const_exprs()
-                        && ct.args.has_non_region_param()
-                        // We only FCW for anon consts as repeat expr counts with anon consts are the only place
-                        // that we have a back compat hack for. We don't need to check this is a const argument
-                        // as only anon consts as const args should get evaluated "for the type system".
-                        //
-                        // If we don't *only* FCW anon consts we can wind up incorrectly FCW'ing uses of assoc
-                        // consts in pattern positions. #140447
-                        && self.def_kind(instance.def_id()) == DefKind::AnonConst
-                    {
-                        let mir_body = self.mir_for_ctfe(instance.def_id());
-                        if mir_body.is_polymorphic {
-                            let Some(local_def_id) = ct.def.as_local() else { return };
-                            self.node_span_lint(
-                                lint::builtin::CONST_EVALUATABLE_UNCHECKED,
-                                self.local_def_id_to_hir_id(local_def_id),
-                                self.def_span(ct.def),
-                                |lint| { lint.primary_message("cannot use constants which depend on generic parameters in types"); },
-                            )
-                        }
-                    }
-                })
-            }
+        let cid = match ty::Instance::try_resolve(self, typing_env, ct.def, ct.args) {
+            Ok(Some(instance)) => GlobalId { instance, promoted: None },
             // For errors during resolution, we deliberately do not point at the usage site of the constant,
             // since for these errors the place the constant is used shouldn't matter.
-            Ok(None) => Err(ErrorHandled::TooGeneric(DUMMY_SP)),
+            Ok(None) => return Err(ErrorHandled::TooGeneric(DUMMY_SP).into()),
             Err(err) => {
-                Err(ErrorHandled::Reported(ReportedErrorInfo::non_const_eval_error(err), DUMMY_SP))
+                return Err(ErrorHandled::Reported(
+                    ReportedErrorInfo::non_const_eval_error(err),
+                    DUMMY_SP,
+                )
+                .into());
             }
-        }
+        };
+
+        self.const_eval_global_id_for_typeck(typing_env, cid, span).inspect(|_| {
+            // We are emitting the lint here instead of in `is_const_evaluatable`
+            // as we normalize obligations before checking them, and normalization
+            // uses this function to evaluate this constant.
+            //
+            // @lcnr believes that successfully evaluating even though there are
+            // used generic parameters is a bug of evaluation, so checking for it
+            // here does feel somewhat sensible.
+            if !self.features().generic_const_exprs()
+                && ct.args.has_non_region_param()
+                // We only FCW for anon consts as repeat expr counts with anon consts are the only place
+                // that we have a back compat hack for. We don't need to check this is a const argument
+                // as only anon consts as const args should get evaluated "for the type system".
+                //
+                // If we don't *only* FCW anon consts we can wind up incorrectly FCW'ing uses of assoc
+                // consts in pattern positions. #140447
+                && self.def_kind(cid.instance.def_id()) == DefKind::AnonConst
+            {
+                let mir_body = self.mir_for_ctfe(cid.instance.def_id());
+                if mir_body.is_polymorphic {
+                    let Some(local_def_id) = ct.def.as_local() else { return };
+                    self.node_span_lint(
+                        lint::builtin::CONST_EVALUATABLE_UNCHECKED,
+                        self.local_def_id_to_hir_id(local_def_id),
+                        self.def_span(ct.def),
+                        |lint| {
+                            lint.primary_message(
+                                "cannot use constants which depend on generic parameters in types",
+                            );
+                        },
+                    )
+                }
+            }
+        })
     }
 
     pub fn const_eval_instance(
@@ -182,17 +189,42 @@ impl<'tcx> TyCtxt<'tcx> {
         typing_env: ty::TypingEnv<'tcx>,
         cid: GlobalId<'tcx>,
         span: Span,
-    ) -> EvalToValTreeResult<'tcx> {
+    ) -> ConstToValTreeResult<'tcx> {
         // Const-eval shouldn't depend on lifetimes at all, so we can erase them, which should
         // improve caching of queries.
         let inputs =
             self.erase_regions(typing_env.with_post_analysis_normalized(self).as_query_input(cid));
         debug!(?inputs);
-        if !span.is_dummy() {
+        let res = if !span.is_dummy() {
             // The query doesn't know where it is being invoked, so we need to fix the span.
             self.at(span).eval_to_valtree(inputs).map_err(|e| e.with_span(span))
         } else {
             self.eval_to_valtree(inputs)
+        };
+        match res {
+            Ok(valtree) => Ok(Ok(valtree)),
+            Err(err) => {
+                match err {
+                    // Let the caller decide how to handle this.
+                    ValTreeCreationError::NonSupportedType(ty) => Ok(Err(ty)),
+                    // Report the others.
+                    ValTreeCreationError::NodesOverflow => {
+                        let handled = self.dcx().emit_err(error::MaxNumNodesInValtree {
+                            span,
+                            global_const_id: cid.display(self),
+                        });
+                        Err(ReportedErrorInfo::allowed_in_infallible(handled).into())
+                    }
+                    ValTreeCreationError::InvalidConst => {
+                        let handled = self.dcx().emit_err(error::InvalidConstInValtree {
+                            span,
+                            global_const_id: cid.display(self),
+                        });
+                        Err(ReportedErrorInfo::allowed_in_infallible(handled).into())
+                    }
+                    ValTreeCreationError::ErrorHandled(handled) => Err(handled),
+                }
+            }
         }
     }
 }
diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs
index 7c998761a9d..26a31cb055e 100644
--- a/compiler/rustc_middle/src/query/erase.rs
+++ b/compiler/rustc_middle/src/query/erase.rs
@@ -4,6 +4,7 @@ use std::mem::MaybeUninit;
 
 use rustc_span::ErrorGuaranteed;
 
+use crate::mir::interpret::EvalToValTreeResult;
 use crate::query::CyclePlaceholder;
 use crate::ty::adjustment::CoerceUnsizedInfo;
 use crate::ty::{self, Ty, TyCtxt};
@@ -156,10 +157,8 @@ impl EraseType for Result<mir::ConstValue<'_>, mir::interpret::ErrorHandled> {
     type Result = [u8; size_of::<Result<mir::ConstValue<'static>, mir::interpret::ErrorHandled>>()];
 }
 
-impl EraseType for Result<Result<ty::ValTree<'_>, Ty<'_>>, mir::interpret::ErrorHandled> {
-    type Result = [u8; size_of::<
-        Result<Result<ty::ValTree<'static>, Ty<'static>>, mir::interpret::ErrorHandled>,
-    >()];
+impl EraseType for EvalToValTreeResult<'_> {
+    type Result = [u8; size_of::<EvalToValTreeResult<'static>>()];
 }
 
 impl EraseType for Result<&'_ ty::List<Ty<'_>>, ty::util::AlwaysRequiresDrop> {
diff --git a/compiler/rustc_middle/src/ty/consts/valtree.rs b/compiler/rustc_middle/src/ty/consts/valtree.rs
index 2f21d19e03c..d95006dcf4a 100644
--- a/compiler/rustc_middle/src/ty/consts/valtree.rs
+++ b/compiler/rustc_middle/src/ty/consts/valtree.rs
@@ -5,7 +5,7 @@ use rustc_data_structures::intern::Interned;
 use rustc_macros::{HashStable, Lift, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable};
 
 use super::ScalarInt;
-use crate::mir::interpret::Scalar;
+use crate::mir::interpret::{ErrorHandled, Scalar};
 use crate::ty::{self, Ty, TyCtxt};
 
 /// This datastructure is used to represent the value of constants used in the type system.
@@ -124,6 +124,12 @@ impl fmt::Debug for ValTree<'_> {
     }
 }
 
+/// `Ok(Err(ty))` indicates the constant was fine, but the valtree couldn't be constructed
+/// because the value contains something of type `ty` that is not valtree-compatible.
+/// The caller can then show an appropriate error; the query does not have the
+/// necessary context to give good user-facing errors for this case.
+pub type ConstToValTreeResult<'tcx> = Result<Result<ValTree<'tcx>, Ty<'tcx>>, ErrorHandled>;
+
 /// A type-level constant value.
 ///
 /// Represents a typed, fully evaluated constant.
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index 97408e31854..40ea2a23a70 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -75,8 +75,8 @@ pub use self::closure::{
     place_to_string_for_capture,
 };
 pub use self::consts::{
-    AnonConstKind, AtomicOrdering, Const, ConstInt, ConstKind, Expr, ExprKind, ScalarInt,
-    UnevaluatedConst, ValTree, ValTreeKind, Value,
+    AnonConstKind, AtomicOrdering, Const, ConstInt, ConstKind, ConstToValTreeResult, Expr,
+    ExprKind, ScalarInt, UnevaluatedConst, ValTree, ValTreeKind, Value,
 };
 pub use self::context::{
     CtxtInterners, CurrentGcx, DeducedParamAttrs, Feed, FreeRegionInfo, GlobalCtxt, Lift, TyCtxt,
diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs
index 999ef97683c..43806d3977b 100644
--- a/compiler/rustc_trait_selection/src/traits/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/mod.rs
@@ -36,8 +36,8 @@ use rustc_middle::ty::{
     self, GenericArgs, GenericArgsRef, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
     TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypingMode, Upcast,
 };
+use rustc_span::Span;
 use rustc_span::def_id::DefId;
-use rustc_span::{DUMMY_SP, Span};
 use tracing::{debug, instrument};
 
 pub use self::coherence::{
@@ -644,7 +644,9 @@ pub fn try_evaluate_const<'tcx>(
             let erased_uv = tcx.erase_regions(uv);
 
             use rustc_middle::mir::interpret::ErrorHandled;
-            match tcx.const_eval_resolve_for_typeck(typing_env, erased_uv, DUMMY_SP) {
+            // FIXME: `def_span` will point at the definition of this const; ideally, we'd point at
+            // where it gets used as a const generic.
+            match tcx.const_eval_resolve_for_typeck(typing_env, erased_uv, tcx.def_span(uv.def)) {
                 Ok(Ok(val)) => Ok(ty::Const::new_value(
                     tcx,
                     val,