diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs
index 8ba3764bcc317..86e7451da7761 100644
--- a/compiler/rustc_middle/src/query/erase.rs
+++ b/compiler/rustc_middle/src/query/erase.rs
@@ -182,6 +182,10 @@ impl EraseType for ty::Binder<'_, &'_ ty::List<Ty<'_>>> {
     type Result = [u8; size_of::<ty::Binder<'static, &'static ty::List<Ty<'static>>>>()];
 }
 
+impl EraseType for Option<ty::Const<'_>> {
+    type Result = [u8; size_of::<Option<ty::Const<'static>>>()];
+}
+
 impl<T0, T1> EraseType for (&'_ T0, &'_ T1) {
     type Result = [u8; size_of::<(&'static (), &'static ())>()];
 }
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 340c5a769dbc4..235f20d96f59b 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -1111,13 +1111,21 @@ rustc_queries! {
         desc { "getting a &core::panic::Location referring to a span" }
     }
 
-    // FIXME get rid of this with valtrees
     query lit_to_const(
         key: LitToConstInput<'tcx>
-    ) -> Result<ty::Const<'tcx>, LitToConstError> {
+    ) -> Option<ty::Const<'tcx>> {
         desc { "converting literal to const" }
     }
 
+    // FIXME: We could get rid of this if we got rid of float patterns, for
+    // example, but we would also need to make sure that other things like
+    // const type mismatches are handled elsewhere.
+    query lit_to_const_for_patterns(
+        key: LitToConstInput<'tcx>
+    ) -> Result<ty::Const<'tcx>, LitToConstError> {
+        desc { "converting literal to pattern const" }
+    }
+
     query check_match(key: LocalDefId) -> Result<(), rustc_errors::ErrorGuaranteed> {
         desc { |tcx| "match-checking `{}`", tcx.def_path_str(key) }
         cache_on_disk_if { true }
diff --git a/compiler/rustc_middle/src/ty/consts.rs b/compiler/rustc_middle/src/ty/consts.rs
index 2518f0cf2e9ce..25fc3e726bc9d 100644
--- a/compiler/rustc_middle/src/ty/consts.rs
+++ b/compiler/rustc_middle/src/ty/consts.rs
@@ -194,17 +194,10 @@ impl<'tcx> Const<'tcx> {
         };
 
         if let Some(lit_input) = lit_input {
-            // If an error occurred, ignore that it's a literal and leave reporting the error up to
-            // mir.
-            match tcx.at(expr.span).lit_to_const(lit_input) {
-                Ok(c) => return Some(c),
-                Err(e) => {
-                    tcx.sess.delay_span_bug(
-                        expr.span,
-                        format!("Const::from_anon_const: couldn't lit_to_const {e:?}"),
-                    );
-                }
-            }
+            // If we failed to validate the type of the lit (which we sometimes
+            // need to actually build the valtree), then return `None`, which
+            // will make it fall back to using an unevaluated const.
+            return tcx.at(expr.span).lit_to_const(lit_input);
         }
 
         match expr.kind {
diff --git a/compiler/rustc_mir_build/src/lib.rs b/compiler/rustc_mir_build/src/lib.rs
index 099fefbf06871..5220822025e48 100644
--- a/compiler/rustc_mir_build/src/lib.rs
+++ b/compiler/rustc_mir_build/src/lib.rs
@@ -32,6 +32,7 @@ fluent_messages! { "../messages.ftl" }
 pub fn provide(providers: &mut Providers) {
     providers.check_match = thir::pattern::check_match;
     providers.lit_to_const = thir::constant::lit_to_const;
+    providers.lit_to_const_for_patterns = thir::constant::lit_to_const_for_patterns;
     providers.mir_built = build::mir_built;
     providers.closure_saved_names_of_captured_variables =
         build::closure_saved_names_of_captured_variables;
diff --git a/compiler/rustc_mir_build/src/thir/constant.rs b/compiler/rustc_mir_build/src/thir/constant.rs
index fbb74650faaeb..75ef51e939a8c 100644
--- a/compiler/rustc_mir_build/src/thir/constant.rs
+++ b/compiler/rustc_mir_build/src/thir/constant.rs
@@ -8,6 +8,66 @@ use crate::build::parse_float_into_scalar;
 pub(crate) fn lit_to_const<'tcx>(
     tcx: TyCtxt<'tcx>,
     lit_input: LitToConstInput<'tcx>,
+) -> Option<ty::Const<'tcx>> {
+    let LitToConstInput { lit, ty, neg } = lit_input;
+
+    let valtree = match (lit, &ty.kind()) {
+        (ast::LitKind::Str(s, _), ty::Ref(_, inner_ty, _)) if inner_ty.is_str() => {
+            let str_bytes = s.as_str().as_bytes();
+            ty::ValTree::from_raw_bytes(tcx, str_bytes)
+        }
+        (ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _))
+            if matches!(inner_ty.kind(), ty::Slice(_)) =>
+        {
+            let bytes = data as &[u8];
+            ty::ValTree::from_raw_bytes(tcx, bytes)
+        }
+        (ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _)) if inner_ty.is_array() => {
+            let bytes = data as &[u8];
+            ty::ValTree::from_raw_bytes(tcx, bytes)
+        }
+        (ast::LitKind::Byte(n), ty::Uint(ty::UintTy::U8)) => {
+            ty::ValTree::from_scalar_int((*n).into())
+        }
+        (ast::LitKind::CStr(data, _), ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Adt(def, _) if Some(def.did()) == tcx.lang_items().c_str()) =>
+        {
+            let bytes = data as &[u8];
+            ty::ValTree::from_raw_bytes(tcx, bytes)
+        }
+        (ast::LitKind::Int(n, _), ty::Uint(_)) | (ast::LitKind::Int(n, _), ty::Int(_)) => {
+            let n = if neg { (*n as i128).overflowing_neg().0 as u128 } else { *n };
+            let param_ty = ParamEnv::reveal_all().and(ty);
+            let width = tcx
+                .layout_of(param_ty)
+                .unwrap_or_else(|_| bug!("should always be able to compute the layout of a scalar"))
+                .size;
+            trace!("trunc {} with size {} and shift {}", n, width.bits(), 128 - width.bits());
+            let result = width.truncate(n);
+            trace!("trunc result: {}", result);
+
+            let scalar_int = ScalarInt::try_from_uint(result, width)
+                .unwrap_or_else(|| bug!("expected to create ScalarInt from uint {:?}", result));
+
+            ty::ValTree::from_scalar_int(scalar_int)
+        }
+        (ast::LitKind::Bool(b), ty::Bool) => ty::ValTree::from_scalar_int((*b).into()),
+        (ast::LitKind::Char(c), ty::Char) => ty::ValTree::from_scalar_int((*c).into()),
+        // If there's a type mismatch, it may be a legitimate type mismatch, or
+        // it might be an unnormalized type. Return `None`, which falls back
+        // to an unevaluated const, and the error will be caught (or we will see
+        // that it's ok) elsewhere.
+        _ => return None,
+    };
+
+    Some(ty::Const::new_value(tcx, valtree, ty))
+}
+
+/// The old "lit_to_const", which assumes that the type passed in `lit_input`
+/// is normalized, and will error out if that is not true. This should only
+/// be used in pattern building code (and ideally we'd get rid of this altogether).
+pub(crate) fn lit_to_const_for_patterns<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    lit_input: LitToConstInput<'tcx>,
 ) -> Result<ty::Const<'tcx>, LitToConstError> {
     let LitToConstInput { lit, ty, neg } = lit_input;
 
diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
index fe47a1cd78c49..92cb8d846f111 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
@@ -607,7 +607,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
             _ => None,
         };
         if let Some(lit_input) = lit_input {
-            match tcx.at(expr.span).lit_to_const(lit_input) {
+            match tcx.at(expr.span).lit_to_const_for_patterns(lit_input) {
                 Ok(c) => return self.const_to_pat(Const::Ty(c), id, span, None).kind,
                 // If an error occurred, ignore that it's a literal
                 // and leave reporting the error up to const eval of
@@ -676,7 +676,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
 
         let lit_input =
             LitToConstInput { lit: &lit.node, ty: self.typeck_results.expr_ty(expr), neg };
-        match self.tcx.at(expr.span).lit_to_const(lit_input) {
+        match self.tcx.at(expr.span).lit_to_const_for_patterns(lit_input) {
             Ok(constant) => {
                 self.const_to_pat(Const::Ty(constant), expr.hir_id, lit.span, None).kind
             }
diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs
index 383cc996b9e69..49fc8b4dd5080 100644
--- a/compiler/rustc_ty_utils/src/consts.rs
+++ b/compiler/rustc_ty_utils/src/consts.rs
@@ -117,7 +117,11 @@ fn recurse_build<'tcx>(
         }
         &ExprKind::Literal { lit, neg } => {
             let sp = node.span;
-            match tcx.at(sp).lit_to_const(LitToConstInput { lit: &lit.node, ty: node.ty, neg }) {
+            match tcx.at(sp).lit_to_const_for_patterns(LitToConstInput {
+                lit: &lit.node,
+                ty: node.ty,
+                neg,
+            }) {
                 Ok(c) => c,
                 Err(LitToConstError::Reported(guar)) => ty::Const::new_error(tcx, guar, node.ty),
                 Err(LitToConstError::TypeError) => {
diff --git a/tests/ui/const-generics/projection-as-arg-const.rs b/tests/ui/const-generics/projection-as-arg-const.rs
index 903548c75db56..6ba8612f31770 100644
--- a/tests/ui/const-generics/projection-as-arg-const.rs
+++ b/tests/ui/const-generics/projection-as-arg-const.rs
@@ -1,6 +1,7 @@
-// This is currently not possible to use projections as const generics.
-// More information about this available here:
-// https://github.com/rust-lang/rust/pull/104443#discussion_r1029375633
+// build-pass
+
+#![feature(adt_const_params)]
+//~^ WARN the feature `adt_const_params` is incomplete
 
 pub trait Identity {
     type Identity;
@@ -11,7 +12,6 @@ impl<T> Identity for T {
 }
 
 pub fn foo<const X: <i32 as Identity>::Identity>() {
-//~^ ERROR
     assert!(X == 12);
 }
 
diff --git a/tests/ui/const-generics/projection-as-arg-const.stderr b/tests/ui/const-generics/projection-as-arg-const.stderr
index 803ed9c959752..6008e5cda163d 100644
--- a/tests/ui/const-generics/projection-as-arg-const.stderr
+++ b/tests/ui/const-generics/projection-as-arg-const.stderr
@@ -1,11 +1,11 @@
-error: `<i32 as Identity>::Identity` is forbidden as the type of a const generic parameter
-  --> $DIR/projection-as-arg-const.rs:13:21
+warning: the feature `adt_const_params` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/projection-as-arg-const.rs:3:12
    |
-LL | pub fn foo<const X: <i32 as Identity>::Identity>() {
-   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #![feature(adt_const_params)]
+   |            ^^^^^^^^^^^^^^^^
    |
-   = note: the only supported types are integers, `bool` and `char`
-   = help: more complex types are supported with `#![feature(adt_const_params)]`
+   = note: see issue #95174 <https://github.com/rust-lang/rust/issues/95174> for more information
+   = note: `#[warn(incomplete_features)]` on by default
 
-error: aborting due to previous error
+warning: 1 warning emitted