about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_const_eval/src/interpret/operand.rs2
-rw-r--r--compiler/rustc_infer/src/infer/freshen.rs1
-rw-r--r--compiler/rustc_infer/src/infer/mod.rs58
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder.rs6
-rw-r--r--compiler/rustc_metadata/src/rmeta/mod.rs2
-rw-r--r--compiler/rustc_middle/src/mir/pretty.rs1
-rw-r--r--compiler/rustc_middle/src/mir/syntax.rs6
-rw-r--r--compiler/rustc_middle/src/mir/type_foldable.rs2
-rw-r--r--compiler/rustc_middle/src/query/mod.rs13
-rw-r--r--compiler/rustc_middle/src/ty/abstract_const.rs200
-rw-r--r--compiler/rustc_middle/src/ty/codec.rs27
-rw-r--r--compiler/rustc_middle/src/ty/consts/kind.rs19
-rw-r--r--compiler/rustc_middle/src/ty/context.rs14
-rw-r--r--compiler/rustc_middle/src/ty/fast_reject.rs9
-rw-r--r--compiler/rustc_middle/src/ty/flags.rs20
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs2
-rw-r--r--compiler/rustc_middle/src/ty/parameterized.rs2
-rw-r--r--compiler/rustc_middle/src/ty/print/pretty.rs3
-rw-r--r--compiler/rustc_middle/src/ty/relate.rs65
-rw-r--r--compiler/rustc_middle/src/ty/structural_impls.rs6
-rw-r--r--compiler/rustc_middle/src/ty/walk.rs18
-rw-r--r--compiler/rustc_privacy/src/lib.rs15
-rw-r--r--compiler/rustc_query_impl/src/on_disk_cache.rs6
-rw-r--r--compiler/rustc_symbol_mangling/src/v0.rs1
-rw-r--r--compiler/rustc_trait_selection/src/traits/const_evaluatable.rs304
-rw-r--r--compiler/rustc_trait_selection/src/traits/fulfill.rs43
-rw-r--r--compiler/rustc_trait_selection/src/traits/mod.rs4
-rw-r--r--compiler/rustc_trait_selection/src/traits/object_safety.rs21
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/mod.rs66
-rw-r--r--compiler/rustc_trait_selection/src/traits/wf.rs5
-rw-r--r--compiler/rustc_ty_utils/src/consts.rs552
-rw-r--r--src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/const_equate_assoc_consts.rs27
-rw-r--r--src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.rs15
-rw-r--r--src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.stderr10
-rw-r--r--src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/dropck_unifies_assoc_consts.rs20
-rw-r--r--src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/unifies_evaluatable.rs18
-rw-r--r--src/test/ui/const-generics/invariant.rs3
-rw-r--r--src/test/ui/const-generics/issues/issue-83765.stderr6
-rw-r--r--src/test/ui/const-generics/issues/issue-85031-2.rs (renamed from src/test/incremental/const-generics/try_unify_abstract_const_regression_tests/issue-85031-2.rs)8
-rw-r--r--src/test/ui/const-generics/issues/issue-85031-2.stderr14
40 files changed, 802 insertions, 812 deletions
diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs
index 3eb2b3a0b1b..221e359d24a 100644
--- a/compiler/rustc_const_eval/src/interpret/operand.rs
+++ b/compiler/rustc_const_eval/src/interpret/operand.rs
@@ -561,6 +561,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             ty::ConstKind::Param(_) | ty::ConstKind::Placeholder(..) => {
                 throw_inval!(TooGeneric)
             }
+            // FIXME(generic_const_exprs): `ConstKind::Expr` should be able to be evaluated
+            ty::ConstKind::Expr(_) => throw_inval!(TooGeneric),
             ty::ConstKind::Error(reported) => {
                 throw_inval!(AlreadyReported(reported))
             }
diff --git a/compiler/rustc_infer/src/infer/freshen.rs b/compiler/rustc_infer/src/infer/freshen.rs
index ff5d1a05a70..27a94ec5e30 100644
--- a/compiler/rustc_infer/src/infer/freshen.rs
+++ b/compiler/rustc_infer/src/infer/freshen.rs
@@ -248,6 +248,7 @@ impl<'a, 'tcx> TypeFolder<'tcx> for TypeFreshener<'a, 'tcx> {
             ty::ConstKind::Param(_)
             | ty::ConstKind::Value(_)
             | ty::ConstKind::Unevaluated(..)
+            | ty::ConstKind::Expr(..)
             | ty::ConstKind::Error(_) => ct.super_fold_with(self),
         }
     }
diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs
index 2798477d181..67feb83faac 100644
--- a/compiler/rustc_infer/src/infer/mod.rs
+++ b/compiler/rustc_infer/src/infer/mod.rs
@@ -23,7 +23,6 @@ use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKin
 use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult};
 use rustc_middle::mir::ConstraintCategory;
 use rustc_middle::traits::select;
-use rustc_middle::ty::abstract_const::{AbstractConst, FailureKind};
 use rustc_middle::ty::error::{ExpectedFound, TypeError};
 use rustc_middle::ty::fold::BoundVarReplacerDelegate;
 use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
@@ -713,32 +712,6 @@ impl<'tcx> InferCtxt<'tcx> {
         TypeErrCtxt { infcx: self, typeck_results: None, fallback_has_occurred: false }
     }
 
-    /// calls `tcx.try_unify_abstract_consts` after
-    /// canonicalizing the consts.
-    #[instrument(skip(self), level = "debug")]
-    pub fn try_unify_abstract_consts(
-        &self,
-        a: ty::UnevaluatedConst<'tcx>,
-        b: ty::UnevaluatedConst<'tcx>,
-        param_env: ty::ParamEnv<'tcx>,
-    ) -> bool {
-        // Reject any attempt to unify two unevaluated constants that contain inference
-        // variables, since inference variables in queries lead to ICEs.
-        if a.substs.has_non_region_infer()
-            || b.substs.has_non_region_infer()
-            || param_env.has_non_region_infer()
-        {
-            debug!("a or b or param_env contain infer vars in its substs -> cannot unify");
-            return false;
-        }
-
-        let param_env_and = param_env.and((a, b));
-        let erased = self.tcx.erase_regions(param_env_and);
-        debug!("after erase_regions: {:?}", erased);
-
-        self.tcx.try_unify_abstract_consts(erased)
-    }
-
     pub fn is_in_snapshot(&self) -> bool {
         self.in_snapshot.get()
     }
@@ -1646,26 +1619,25 @@ impl<'tcx> InferCtxt<'tcx> {
 
         // Postpone the evaluation of constants whose substs depend on inference
         // variables
+        let tcx = self.tcx;
         if substs.has_non_region_infer() {
-            let ac = AbstractConst::new(self.tcx, unevaluated);
-            match ac {
-                Ok(None) => {
-                    substs = InternalSubsts::identity_for_item(self.tcx, unevaluated.def.did);
-                    param_env = self.tcx.param_env(unevaluated.def.did);
-                }
-                Ok(Some(ct)) => {
-                    if ct.unify_failure_kind(self.tcx) == FailureKind::Concrete {
-                        substs = replace_param_and_infer_substs_with_placeholder(self.tcx, substs);
-                    } else {
-                        return Err(ErrorHandled::TooGeneric);
-                    }
+            if let Some(ct) = tcx.bound_abstract_const(unevaluated.def)? {
+                let ct = tcx.expand_abstract_consts(ct.subst(tcx, substs));
+                if let Err(e) = ct.error_reported() {
+                    return Err(ErrorHandled::Reported(e));
+                } else if ct.has_non_region_infer() || ct.has_non_region_param() {
+                    return Err(ErrorHandled::TooGeneric);
+                } else {
+                    substs = replace_param_and_infer_substs_with_placeholder(tcx, substs);
                 }
-                Err(guar) => return Err(ErrorHandled::Reported(guar)),
+            } else {
+                substs = InternalSubsts::identity_for_item(tcx, unevaluated.def.did);
+                param_env = tcx.param_env(unevaluated.def.did);
             }
         }
 
-        let param_env_erased = self.tcx.erase_regions(param_env);
-        let substs_erased = self.tcx.erase_regions(substs);
+        let param_env_erased = tcx.erase_regions(param_env);
+        let substs_erased = tcx.erase_regions(substs);
         debug!(?param_env_erased);
         debug!(?substs_erased);
 
@@ -1673,7 +1645,7 @@ impl<'tcx> InferCtxt<'tcx> {
 
         // The return value is the evaluated value which doesn't contain any reference to inference
         // variables, thus we don't need to substitute back the original values.
-        self.tcx.const_eval_resolve_for_typeck(param_env_erased, unevaluated, span)
+        tcx.const_eval_resolve_for_typeck(param_env_erased, unevaluated, span)
     }
 
     /// `ty_or_const_infer_var_changed` is equivalent to one of these two:
diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index ac4b5126190..3fae6694add 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -644,12 +644,6 @@ impl<'a, 'tcx> Decodable<DecodeContext<'a, 'tcx>> for Symbol {
     }
 }
 
-impl<'a, 'tcx> Decodable<DecodeContext<'a, 'tcx>> for &'tcx [ty::abstract_const::Node<'tcx>] {
-    fn decode(d: &mut DecodeContext<'a, 'tcx>) -> Self {
-        ty::codec::RefDecodable::decode(d)
-    }
-}
-
 impl<'a, 'tcx> Decodable<DecodeContext<'a, 'tcx>> for &'tcx [(ty::Predicate<'tcx>, Span)] {
     fn decode(d: &mut DecodeContext<'a, 'tcx>) -> Self {
         ty::codec::RefDecodable::decode(d)
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index 3e0b5fa6dd9..c51b8f96c71 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -366,7 +366,7 @@ define_tables! {
     mir_for_ctfe: Table<DefIndex, LazyValue<mir::Body<'static>>>,
     promoted_mir: Table<DefIndex, LazyValue<IndexVec<mir::Promoted, mir::Body<'static>>>>,
     // FIXME(compiler-errors): Why isn't this a LazyArray?
-    thir_abstract_const: Table<DefIndex, LazyValue<&'static [ty::abstract_const::Node<'static>]>>,
+    thir_abstract_const: Table<DefIndex, LazyValue<ty::Const<'static>>>,
     impl_parent: Table<DefIndex, RawDefId>,
     impl_polarity: Table<DefIndex, ty::ImplPolarity>,
     constness: Table<DefIndex, hir::Constness>,
diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs
index 2b3f2c02411..1cac656674d 100644
--- a/compiler/rustc_middle/src/mir/pretty.rs
+++ b/compiler/rustc_middle/src/mir/pretty.rs
@@ -476,6 +476,7 @@ impl<'tcx> Visitor<'tcx> for ExtraComments<'tcx> {
                     // These variants shouldn't exist in the MIR.
                     ty::ConstKind::Placeholder(_)
                     | ty::ConstKind::Infer(_)
+                    | ty::ConstKind::Expr(_)
                     | ty::ConstKind::Bound(..) => bug!("unexpected MIR constant: {:?}", literal),
                 },
                 ConstantKind::Unevaluated(uv, _) => {
diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs
index fed943169df..f2030b91b9b 100644
--- a/compiler/rustc_middle/src/mir/syntax.rs
+++ b/compiler/rustc_middle/src/mir/syntax.rs
@@ -1185,7 +1185,8 @@ pub enum NullOp {
     AlignOf,
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(HashStable, TyEncodable, TyDecodable, TypeFoldable, TypeVisitable)]
 pub enum UnOp {
     /// The `!` operator for logical inversion
     Not,
@@ -1193,7 +1194,8 @@ pub enum UnOp {
     Neg,
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
+#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
+#[derive(TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub enum BinOp {
     /// The `+` operator (addition)
     Add,
diff --git a/compiler/rustc_middle/src/mir/type_foldable.rs b/compiler/rustc_middle/src/mir/type_foldable.rs
index 4c0974f86fb..0705b4cff53 100644
--- a/compiler/rustc_middle/src/mir/type_foldable.rs
+++ b/compiler/rustc_middle/src/mir/type_foldable.rs
@@ -16,9 +16,7 @@ TrivialTypeTraversalAndLiftImpls! {
     UserTypeAnnotationIndex,
     BorrowKind,
     CastKind,
-    BinOp,
     NullOp,
-    UnOp,
     hir::Movability,
     BasicBlock,
     SwitchTargets,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index d86bdbd63d8..36cdb50958c 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -400,7 +400,7 @@ rustc_queries! {
     /// Try to build an abstract representation of the given constant.
     query thir_abstract_const(
         key: DefId
-    ) -> Result<Option<&'tcx [ty::abstract_const::Node<'tcx>]>, ErrorGuaranteed> {
+    ) -> Result<Option<ty::Const<'tcx>>, ErrorGuaranteed> {
         desc {
             |tcx| "building an abstract representation for `{}`", tcx.def_path_str(key),
         }
@@ -409,7 +409,7 @@ rustc_queries! {
     /// Try to build an abstract representation of the given constant.
     query thir_abstract_const_of_const_arg(
         key: (LocalDefId, DefId)
-    ) -> Result<Option<&'tcx [ty::abstract_const::Node<'tcx>]>, ErrorGuaranteed> {
+    ) -> Result<Option<ty::Const<'tcx>>, ErrorGuaranteed> {
         desc {
             |tcx|
             "building an abstract representation for the const argument `{}`",
@@ -417,15 +417,6 @@ rustc_queries! {
         }
     }
 
-    query try_unify_abstract_consts(key:
-        ty::ParamEnvAnd<'tcx, (ty::UnevaluatedConst<'tcx>, ty::UnevaluatedConst<'tcx>
-    )>) -> bool {
-        desc {
-            |tcx| "trying to unify the generic constants `{}` and `{}`",
-            tcx.def_path_str(key.value.0.def.did), tcx.def_path_str(key.value.1.def.did)
-        }
-    }
-
     query mir_drops_elaborated_and_const_checked(
         key: ty::WithOptConstParam<LocalDefId>
     ) -> &'tcx Steal<mir::Body<'tcx>> {
diff --git a/compiler/rustc_middle/src/ty/abstract_const.rs b/compiler/rustc_middle/src/ty/abstract_const.rs
index e5bcd5fb27a..5de758ad9ba 100644
--- a/compiler/rustc_middle/src/ty/abstract_const.rs
+++ b/compiler/rustc_middle/src/ty/abstract_const.rs
@@ -1,98 +1,13 @@
 //! A subset of a mir body used for const evaluatability checking.
-use crate::mir;
-use crate::ty::visit::TypeVisitable;
-use crate::ty::{self, EarlyBinder, SubstsRef, Ty, TyCtxt};
+use crate::ty::{
+    self, Const, EarlyBinder, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
+    TypeVisitable,
+};
 use rustc_errors::ErrorGuaranteed;
 use rustc_hir::def_id::DefId;
-use std::cmp;
-use std::ops::ControlFlow;
 
-rustc_index::newtype_index! {
-    /// An index into an `AbstractConst`.
-    pub struct NodeId {
-        derive [HashStable]
-        DEBUG_FORMAT = "n{}",
-    }
-}
-
-/// A tree representing an anonymous constant.
-///
-/// This is only able to represent a subset of `MIR`,
-/// and should not leak any information about desugarings.
-#[derive(Debug, Clone, Copy)]
-pub struct AbstractConst<'tcx> {
-    // FIXME: Consider adding something like `IndexSlice`
-    // and use this here.
-    inner: &'tcx [Node<'tcx>],
-    substs: SubstsRef<'tcx>,
-}
-
-impl<'tcx> AbstractConst<'tcx> {
-    pub fn new(
-        tcx: TyCtxt<'tcx>,
-        uv: ty::UnevaluatedConst<'tcx>,
-    ) -> Result<Option<AbstractConst<'tcx>>, ErrorGuaranteed> {
-        let inner = tcx.thir_abstract_const_opt_const_arg(uv.def)?;
-        debug!("AbstractConst::new({:?}) = {:?}", uv, inner);
-        Ok(inner.map(|inner| AbstractConst { inner, substs: tcx.erase_regions(uv.substs) }))
-    }
-
-    pub fn from_const(
-        tcx: TyCtxt<'tcx>,
-        ct: ty::Const<'tcx>,
-    ) -> Result<Option<AbstractConst<'tcx>>, ErrorGuaranteed> {
-        match ct.kind() {
-            ty::ConstKind::Unevaluated(uv) => AbstractConst::new(tcx, uv),
-            ty::ConstKind::Error(reported) => Err(reported),
-            _ => Ok(None),
-        }
-    }
-
-    #[inline]
-    pub fn subtree(self, node: NodeId) -> AbstractConst<'tcx> {
-        AbstractConst { inner: &self.inner[..=node.index()], substs: self.substs }
-    }
-
-    #[inline]
-    pub fn root(self, tcx: TyCtxt<'tcx>) -> Node<'tcx> {
-        let node = self.inner.last().copied().unwrap();
-        match node {
-            Node::Leaf(leaf) => Node::Leaf(EarlyBinder(leaf).subst(tcx, self.substs)),
-            Node::Cast(kind, operand, ty) => {
-                Node::Cast(kind, operand, EarlyBinder(ty).subst(tcx, self.substs))
-            }
-            // Don't perform substitution on the following as they can't directly contain generic params
-            Node::Binop(_, _, _) | Node::UnaryOp(_, _) | Node::FunctionCall(_, _) => node,
-        }
-    }
-
-    pub fn unify_failure_kind(self, tcx: TyCtxt<'tcx>) -> FailureKind {
-        let mut failure_kind = FailureKind::Concrete;
-        walk_abstract_const::<!, _>(tcx, self, |node| {
-            match node.root(tcx) {
-                Node::Leaf(leaf) => {
-                    if leaf.has_non_region_infer() {
-                        failure_kind = FailureKind::MentionsInfer;
-                    } else if leaf.has_non_region_param() {
-                        failure_kind = cmp::min(failure_kind, FailureKind::MentionsParam);
-                    }
-                }
-                Node::Cast(_, _, ty) => {
-                    if ty.has_non_region_infer() {
-                        failure_kind = FailureKind::MentionsInfer;
-                    } else if ty.has_non_region_param() {
-                        failure_kind = cmp::min(failure_kind, FailureKind::MentionsParam);
-                    }
-                }
-                Node::Binop(_, _, _) | Node::UnaryOp(_, _) | Node::FunctionCall(_, _) => {}
-            }
-            ControlFlow::CONTINUE
-        });
-        failure_kind
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
+#[derive(Hash, Debug, Clone, Copy, Ord, PartialOrd, PartialEq, Eq)]
+#[derive(TyDecodable, TyEncodable, HashStable, TypeVisitable, TypeFoldable)]
 pub enum CastKind {
     /// thir::ExprKind::As
     As,
@@ -100,16 +15,6 @@ pub enum CastKind {
     Use,
 }
 
-/// A node of an `AbstractConst`.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
-pub enum Node<'tcx> {
-    Leaf(ty::Const<'tcx>),
-    Binop(mir::BinOp, NodeId, NodeId),
-    UnaryOp(mir::UnOp, NodeId),
-    FunctionCall(NodeId, &'tcx [NodeId]),
-    Cast(CastKind, NodeId, Ty<'tcx>),
-}
-
 #[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
 pub enum NotConstEvaluatable {
     Error(ErrorGuaranteed),
@@ -127,68 +32,53 @@ TrivialTypeTraversalAndLiftImpls! {
     NotConstEvaluatable,
 }
 
+pub type BoundAbstractConst<'tcx> = Result<Option<EarlyBinder<ty::Const<'tcx>>>, ErrorGuaranteed>;
+
 impl<'tcx> TyCtxt<'tcx> {
-    #[inline]
-    pub fn thir_abstract_const_opt_const_arg(
+    /// Returns a const without substs applied
+    pub fn bound_abstract_const(
         self,
-        def: ty::WithOptConstParam<DefId>,
-    ) -> Result<Option<&'tcx [Node<'tcx>]>, ErrorGuaranteed> {
-        if let Some((did, param_did)) = def.as_const_arg() {
+        uv: ty::WithOptConstParam<DefId>,
+    ) -> BoundAbstractConst<'tcx> {
+        let ac = if let Some((did, param_did)) = uv.as_const_arg() {
             self.thir_abstract_const_of_const_arg((did, param_did))
         } else {
-            self.thir_abstract_const(def.did)
-        }
+            self.thir_abstract_const(uv.did)
+        };
+        Ok(ac?.map(|ac| EarlyBinder(ac)))
     }
-}
 
-#[instrument(skip(tcx, f), level = "debug")]
-pub fn walk_abstract_const<'tcx, R, F>(
-    tcx: TyCtxt<'tcx>,
-    ct: AbstractConst<'tcx>,
-    mut f: F,
-) -> ControlFlow<R>
-where
-    F: FnMut(AbstractConst<'tcx>) -> ControlFlow<R>,
-{
-    #[instrument(skip(tcx, f), level = "debug")]
-    fn recurse<'tcx, R>(
-        tcx: TyCtxt<'tcx>,
-        ct: AbstractConst<'tcx>,
-        f: &mut dyn FnMut(AbstractConst<'tcx>) -> ControlFlow<R>,
-    ) -> ControlFlow<R> {
-        f(ct)?;
-        let root = ct.root(tcx);
-        debug!(?root);
-        match root {
-            Node::Leaf(_) => ControlFlow::CONTINUE,
-            Node::Binop(_, l, r) => {
-                recurse(tcx, ct.subtree(l), f)?;
-                recurse(tcx, ct.subtree(r), f)
+    pub fn expand_abstract_consts<T: TypeFoldable<'tcx>>(self, ac: T) -> T {
+        struct Expander<'tcx> {
+            tcx: TyCtxt<'tcx>,
+        }
+
+        impl<'tcx> TypeFolder<'tcx> for Expander<'tcx> {
+            fn tcx(&self) -> TyCtxt<'tcx> {
+                self.tcx
             }
-            Node::UnaryOp(_, v) => recurse(tcx, ct.subtree(v), f),
-            Node::FunctionCall(func, args) => {
-                recurse(tcx, ct.subtree(func), f)?;
-                args.iter().try_for_each(|&arg| recurse(tcx, ct.subtree(arg), f))
+            fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
+                if ty.has_type_flags(ty::TypeFlags::HAS_CT_PROJECTION) {
+                    ty.super_fold_with(self)
+                } else {
+                    ty
+                }
+            }
+            fn fold_const(&mut self, c: Const<'tcx>) -> Const<'tcx> {
+                let ct = match c.kind() {
+                    ty::ConstKind::Unevaluated(uv) => match self.tcx.bound_abstract_const(uv.def) {
+                        Err(e) => self.tcx.const_error_with_guaranteed(c.ty(), e),
+                        Ok(Some(bac)) => {
+                            let substs = self.tcx.erase_regions(uv.substs);
+                            bac.subst(self.tcx, substs)
+                        }
+                        Ok(None) => c,
+                    },
+                    _ => c,
+                };
+                ct.super_fold_with(self)
             }
-            Node::Cast(_, operand, _) => recurse(tcx, ct.subtree(operand), f),
         }
+        ac.fold_with(&mut Expander { tcx: self })
     }
-
-    recurse(tcx, ct, &mut f)
-}
-
-// We were unable to unify the abstract constant with
-// a constant found in the caller bounds, there are
-// now three possible cases here.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
-pub enum FailureKind {
-    /// The abstract const still references an inference
-    /// variable, in this case we return `TooGeneric`.
-    MentionsInfer,
-    /// The abstract const references a generic parameter,
-    /// this means that we emit an error here.
-    MentionsParam,
-    /// The substs are concrete enough that we can simply
-    /// try and evaluate the given constant.
-    Concrete,
 }
diff --git a/compiler/rustc_middle/src/ty/codec.rs b/compiler/rustc_middle/src/ty/codec.rs
index b469eebfad9..b22b3961f34 100644
--- a/compiler/rustc_middle/src/ty/codec.rs
+++ b/compiler/rustc_middle/src/ty/codec.rs
@@ -346,33 +346,22 @@ impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D>
 }
 
 impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D>
-    for [ty::abstract_const::Node<'tcx>]
-{
-    fn decode(decoder: &mut D) -> &'tcx Self {
-        decoder.interner().arena.alloc_from_iter(
-            (0..decoder.read_usize()).map(|_| Decodable::decode(decoder)).collect::<Vec<_>>(),
-        )
-    }
-}
-
-impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D>
-    for [ty::abstract_const::NodeId]
+    for ty::List<ty::BoundVariableKind>
 {
     fn decode(decoder: &mut D) -> &'tcx Self {
-        decoder.interner().arena.alloc_from_iter(
-            (0..decoder.read_usize()).map(|_| Decodable::decode(decoder)).collect::<Vec<_>>(),
+        let len = decoder.read_usize();
+        decoder.interner().mk_bound_variable_kinds(
+            (0..len).map::<ty::BoundVariableKind, _>(|_| Decodable::decode(decoder)),
         )
     }
 }
 
-impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D>
-    for ty::List<ty::BoundVariableKind>
-{
+impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D> for ty::List<ty::Const<'tcx>> {
     fn decode(decoder: &mut D) -> &'tcx Self {
         let len = decoder.read_usize();
-        decoder.interner().mk_bound_variable_kinds(
-            (0..len).map::<ty::BoundVariableKind, _>(|_| Decodable::decode(decoder)),
-        )
+        decoder
+            .interner()
+            .mk_const_list((0..len).map::<ty::Const<'tcx>, _>(|_| Decodable::decode(decoder)))
     }
 }
 
diff --git a/compiler/rustc_middle/src/ty/consts/kind.rs b/compiler/rustc_middle/src/ty/consts/kind.rs
index 321cba693d9..de63dae8a3d 100644
--- a/compiler/rustc_middle/src/ty/consts/kind.rs
+++ b/compiler/rustc_middle/src/ty/consts/kind.rs
@@ -1,10 +1,12 @@
 use std::convert::TryInto;
 
+use super::Const;
 use crate::mir;
 use crate::mir::interpret::{AllocId, ConstValue, Scalar};
+use crate::ty::abstract_const::CastKind;
 use crate::ty::subst::{InternalSubsts, SubstsRef};
 use crate::ty::ParamEnv;
-use crate::ty::{self, TyCtxt, TypeVisitable};
+use crate::ty::{self, List, Ty, TyCtxt, TypeVisitable};
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_errors::ErrorGuaranteed;
 use rustc_hir::def_id::DefId;
@@ -70,9 +72,24 @@ pub enum ConstKind<'tcx> {
     /// A placeholder for a const which could not be computed; this is
     /// propagated to avoid useless error messages.
     Error(ErrorGuaranteed),
+
+    /// Expr which contains an expression which has partially evaluated items.
+    Expr(Expr<'tcx>),
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+#[derive(HashStable, TyEncodable, TyDecodable, TypeVisitable, TypeFoldable)]
+pub enum Expr<'tcx> {
+    Binop(mir::BinOp, Const<'tcx>, Const<'tcx>),
+    UnOp(mir::UnOp, Const<'tcx>),
+    FunctionCall(Const<'tcx>, &'tcx List<Const<'tcx>>),
+    Cast(CastKind, Const<'tcx>, Ty<'tcx>),
 }
 
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
+static_assert_size!(Expr<'_>, 24);
+
+#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
 static_assert_size!(ConstKind<'_>, 32);
 
 impl<'tcx> ConstKind<'tcx> {
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index bede0ec142e..fbbf3f312e7 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -137,6 +137,7 @@ pub struct CtxtInterners<'tcx> {
     // Specifically use a speedy hash algorithm for these hash sets, since
     // they're accessed quite often.
     type_: InternedSet<'tcx, WithStableHash<TyS<'tcx>>>,
+    const_lists: InternedSet<'tcx, List<ty::Const<'tcx>>>,
     substs: InternedSet<'tcx, InternalSubsts<'tcx>>,
     canonical_var_infos: InternedSet<'tcx, List<CanonicalVarInfo<'tcx>>>,
     region: InternedSet<'tcx, RegionKind<'tcx>>,
@@ -157,6 +158,7 @@ impl<'tcx> CtxtInterners<'tcx> {
         CtxtInterners {
             arena,
             type_: Default::default(),
+            const_lists: Default::default(),
             substs: Default::default(),
             region: Default::default(),
             poly_existential_predicates: Default::default(),
@@ -2261,6 +2263,7 @@ macro_rules! slice_interners {
 }
 
 slice_interners!(
+    const_lists: _intern_const_list(Const<'tcx>),
     substs: _intern_substs(GenericArg<'tcx>),
     canonical_var_infos: _intern_canonical_var_infos(CanonicalVarInfo<'tcx>),
     poly_existential_predicates:
@@ -2716,6 +2719,17 @@ impl<'tcx> TyCtxt<'tcx> {
         }
     }
 
+    pub fn mk_const_list<I: InternAs<ty::Const<'tcx>, &'tcx List<ty::Const<'tcx>>>>(
+        self,
+        iter: I,
+    ) -> I::Output {
+        iter.intern_with(|xs| self.intern_const_list(xs))
+    }
+
+    pub fn intern_const_list(self, cs: &[ty::Const<'tcx>]) -> &'tcx List<ty::Const<'tcx>> {
+        if cs.is_empty() { List::empty() } else { self._intern_const_list(cs) }
+    }
+
     pub fn intern_type_list(self, ts: &[Ty<'tcx>]) -> &'tcx List<Ty<'tcx>> {
         if ts.is_empty() {
             List::empty()
diff --git a/compiler/rustc_middle/src/ty/fast_reject.rs b/compiler/rustc_middle/src/ty/fast_reject.rs
index 1ee4985cf8d..c9c09c93a3e 100644
--- a/compiler/rustc_middle/src/ty/fast_reject.rs
+++ b/compiler/rustc_middle/src/ty/fast_reject.rs
@@ -356,7 +356,10 @@ impl DeepRejectCtxt {
 
     pub fn consts_may_unify(self, obligation_ct: ty::Const<'_>, impl_ct: ty::Const<'_>) -> bool {
         match impl_ct.kind() {
-            ty::ConstKind::Param(_) | ty::ConstKind::Unevaluated(_) | ty::ConstKind::Error(_) => {
+            ty::ConstKind::Expr(_)
+            | ty::ConstKind::Param(_)
+            | ty::ConstKind::Unevaluated(_)
+            | ty::ConstKind::Error(_) => {
                 return true;
             }
             ty::ConstKind::Value(_) => {}
@@ -374,7 +377,9 @@ impl DeepRejectCtxt {
 
             // As we don't necessarily eagerly evaluate constants,
             // they might unify with any value.
-            ty::ConstKind::Unevaluated(_) | ty::ConstKind::Error(_) => true,
+            ty::ConstKind::Expr(_) | ty::ConstKind::Unevaluated(_) | ty::ConstKind::Error(_) => {
+                true
+            }
             ty::ConstKind::Value(obl) => match k {
                 ty::ConstKind::Value(imp) => obl == imp,
                 _ => true,
diff --git a/compiler/rustc_middle/src/ty/flags.rs b/compiler/rustc_middle/src/ty/flags.rs
index 61f3a0685e7..1e9fe779b76 100644
--- a/compiler/rustc_middle/src/ty/flags.rs
+++ b/compiler/rustc_middle/src/ty/flags.rs
@@ -313,6 +313,26 @@ impl FlagComputation {
                 self.add_flags(TypeFlags::STILL_FURTHER_SPECIALIZABLE);
             }
             ty::ConstKind::Value(_) => {}
+            ty::ConstKind::Expr(e) => {
+                use ty::Expr;
+                match e {
+                    Expr::Binop(_, l, r) => {
+                        self.add_const(l);
+                        self.add_const(r);
+                    }
+                    Expr::UnOp(_, v) => self.add_const(v),
+                    Expr::FunctionCall(f, args) => {
+                        self.add_const(f);
+                        for arg in args {
+                            self.add_const(arg);
+                        }
+                    }
+                    Expr::Cast(_, c, t) => {
+                        self.add_ty(t);
+                        self.add_const(c);
+                    }
+                }
+            }
             ty::ConstKind::Error(_) => self.add_flags(TypeFlags::HAS_ERROR),
         }
     }
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index f20fa3c32ad..e7dc2b36faf 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -77,7 +77,7 @@ pub use self::closure::{
     CAPTURE_STRUCT_LOCAL,
 };
 pub use self::consts::{
-    Const, ConstInt, ConstKind, ConstS, InferConst, ScalarInt, UnevaluatedConst, ValTree,
+    Const, ConstInt, ConstKind, ConstS, Expr, InferConst, ScalarInt, UnevaluatedConst, ValTree,
 };
 pub use self::context::{
     tls, CanonicalUserType, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations,
diff --git a/compiler/rustc_middle/src/ty/parameterized.rs b/compiler/rustc_middle/src/ty/parameterized.rs
index d6025248081..b2bcf0e29cd 100644
--- a/compiler/rustc_middle/src/ty/parameterized.rs
+++ b/compiler/rustc_middle/src/ty/parameterized.rs
@@ -4,7 +4,6 @@ use rustc_index::vec::{Idx, IndexVec};
 
 use crate::middle::exported_symbols::ExportedSymbol;
 use crate::mir::Body;
-use crate::ty::abstract_const::Node;
 use crate::ty::{
     self, Const, FnSig, GeneratorDiagnosticData, GenericPredicates, Predicate, TraitRef, Ty,
 };
@@ -124,6 +123,5 @@ parameterized_over_tcx! {
     Predicate,
     GeneratorDiagnosticData,
     Body,
-    Node,
     ExportedSymbol,
 }
diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs
index a90b63cc05b..d5e196b2e9f 100644
--- a/compiler/rustc_middle/src/ty/print/pretty.rs
+++ b/compiler/rustc_middle/src/ty/print/pretty.rs
@@ -1253,6 +1253,9 @@ pub trait PrettyPrinter<'tcx>:
                 self.pretty_print_bound_var(debruijn, bound_var)?
             }
             ty::ConstKind::Placeholder(placeholder) => p!(write("Placeholder({:?})", placeholder)),
+            // FIXME(generic_const_exprs):
+            // write out some legible representation of an abstract const?
+            ty::ConstKind::Expr(_) => p!("[Const Expr]"),
             ty::ConstKind::Error(_) => p!("[const error]"),
         };
         Ok(self)
diff --git a/compiler/rustc_middle/src/ty/relate.rs b/compiler/rustc_middle/src/ty/relate.rs
index 3d47b71b7ce..e6340040e9c 100644
--- a/compiler/rustc_middle/src/ty/relate.rs
+++ b/compiler/rustc_middle/src/ty/relate.rs
@@ -5,7 +5,7 @@
 //! subtyping, type equality, etc.
 
 use crate::ty::error::{ExpectedFound, TypeError};
-use crate::ty::{self, ImplSubject, Term, TermKind, Ty, TyCtxt, TypeFoldable};
+use crate::ty::{self, Expr, ImplSubject, Term, TermKind, Ty, TyCtxt, TypeFoldable};
 use crate::ty::{GenericArg, GenericArgKind, SubstsRef};
 use rustc_hir as ast;
 use rustc_hir::def_id::DefId;
@@ -613,7 +613,10 @@ pub fn super_relate_consts<'tcx, R: TypeRelation<'tcx>>(
     if a_ty != b_ty {
         relation.tcx().sess.delay_span_bug(
             DUMMY_SP,
-            &format!("cannot relate constants of different types: {} != {}", a_ty, b_ty),
+            &format!(
+                "cannot relate constants ({:?}, {:?}) of different types: {} != {}",
+                a, b, a_ty, b_ty
+            ),
         );
     }
 
@@ -623,11 +626,16 @@ pub fn super_relate_consts<'tcx, R: TypeRelation<'tcx>>(
     // an unnormalized (i.e. unevaluated) const in the param-env.
     // FIXME(generic_const_exprs): Once we always lazily unify unevaluated constants
     // these `eval` calls can be removed.
-    if !relation.tcx().features().generic_const_exprs {
+    if !tcx.features().generic_const_exprs {
         a = a.eval(tcx, relation.param_env());
         b = b.eval(tcx, relation.param_env());
     }
 
+    if tcx.features().generic_const_exprs {
+        a = tcx.expand_abstract_consts(a);
+        b = tcx.expand_abstract_consts(b);
+    }
+
     // Currently, the values that can be unified are primitive types,
     // and those that derive both `PartialEq` and `Eq`, corresponding
     // to structural-match types.
@@ -644,16 +652,11 @@ pub fn super_relate_consts<'tcx, R: TypeRelation<'tcx>>(
         (ty::ConstKind::Placeholder(p1), ty::ConstKind::Placeholder(p2)) => p1 == p2,
         (ty::ConstKind::Value(a_val), ty::ConstKind::Value(b_val)) => a_val == b_val,
 
-        (ty::ConstKind::Unevaluated(au), ty::ConstKind::Unevaluated(bu))
-            if tcx.features().generic_const_exprs =>
-        {
-            tcx.try_unify_abstract_consts(relation.param_env().and((au, bu)))
-        }
-
         // While this is slightly incorrect, it shouldn't matter for `min_const_generics`
         // and is the better alternative to waiting until `generic_const_exprs` can
         // be stabilized.
         (ty::ConstKind::Unevaluated(au), ty::ConstKind::Unevaluated(bu)) if au.def == bu.def => {
+            assert_eq!(a.ty(), b.ty());
             let substs = relation.relate_with_variance(
                 ty::Variance::Invariant,
                 ty::VarianceDiagInfo::default(),
@@ -665,6 +668,50 @@ pub fn super_relate_consts<'tcx, R: TypeRelation<'tcx>>(
                 a.ty(),
             ));
         }
+        // Before calling relate on exprs, it is necessary to ensure that the nested consts
+        // have identical types.
+        (ty::ConstKind::Expr(ae), ty::ConstKind::Expr(be)) => {
+            let r = relation;
+
+            // FIXME(generic_const_exprs): is it possible to relate two consts which are not identical
+            // exprs? Should we care about that?
+            let expr = match (ae, be) {
+                (Expr::Binop(a_op, al, ar), Expr::Binop(b_op, bl, br))
+                    if a_op == b_op && al.ty() == bl.ty() && ar.ty() == br.ty() =>
+                {
+                    Expr::Binop(a_op, r.consts(al, bl)?, r.consts(ar, br)?)
+                }
+                (Expr::UnOp(a_op, av), Expr::UnOp(b_op, bv))
+                    if a_op == b_op && av.ty() == bv.ty() =>
+                {
+                    Expr::UnOp(a_op, r.consts(av, bv)?)
+                }
+                (Expr::Cast(ak, av, at), Expr::Cast(bk, bv, bt))
+                    if ak == bk && av.ty() == bv.ty() =>
+                {
+                    Expr::Cast(ak, r.consts(av, bv)?, r.tys(at, bt)?)
+                }
+                (Expr::FunctionCall(af, aa), Expr::FunctionCall(bf, ba))
+                    if aa.len() == ba.len()
+                        && af.ty() == bf.ty()
+                        && aa
+                            .iter()
+                            .zip(ba.iter())
+                            .all(|(a_arg, b_arg)| a_arg.ty() == b_arg.ty()) =>
+                {
+                    let func = r.consts(af, bf)?;
+                    let mut related_args = Vec::with_capacity(aa.len());
+                    for (a_arg, b_arg) in aa.iter().zip(ba.iter()) {
+                        related_args.push(r.consts(a_arg, b_arg)?);
+                    }
+                    let related_args = tcx.mk_const_list(related_args.iter());
+                    Expr::FunctionCall(func, related_args)
+                }
+                _ => return Err(TypeError::ConstMismatch(expected_found(r, a, b))),
+            };
+            let kind = ty::ConstKind::Expr(expr);
+            return Ok(tcx.mk_const(kind, a.ty()));
+        }
         _ => false,
     };
     if is_match { Ok(a) } else { Err(TypeError::ConstMismatch(expected_found(relation, a, b))) }
diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs
index c8baac1ec20..9f70b4f1ffd 100644
--- a/compiler/rustc_middle/src/ty/structural_impls.rs
+++ b/compiler/rustc_middle/src/ty/structural_impls.rs
@@ -601,6 +601,12 @@ impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::List<ty::PolyExistentialPredicate<'t
     }
 }
 
+impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::List<ty::Const<'tcx>> {
+    fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
+        ty::util::fold_list(self, folder, |tcx, v| tcx.mk_const_list(v.iter()))
+    }
+}
+
 impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::List<ProjectionKind> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         ty::util::fold_list(self, folder, |tcx, v| tcx.intern_projs(v))
diff --git a/compiler/rustc_middle/src/ty/walk.rs b/compiler/rustc_middle/src/ty/walk.rs
index 91db9698c41..4fab5abe909 100644
--- a/compiler/rustc_middle/src/ty/walk.rs
+++ b/compiler/rustc_middle/src/ty/walk.rs
@@ -214,6 +214,24 @@ fn push_inner<'tcx>(stack: &mut TypeWalkerStack<'tcx>, parent: GenericArg<'tcx>)
                 | ty::ConstKind::Value(_)
                 | ty::ConstKind::Error(_) => {}
 
+                ty::ConstKind::Expr(expr) => match expr {
+                    ty::Expr::UnOp(_, v) => push_inner(stack, v.into()),
+                    ty::Expr::Binop(_, l, r) => {
+                        push_inner(stack, r.into());
+                        push_inner(stack, l.into())
+                    }
+                    ty::Expr::FunctionCall(func, args) => {
+                        for a in args.iter().rev() {
+                            push_inner(stack, a.into());
+                        }
+                        push_inner(stack, func.into());
+                    }
+                    ty::Expr::Cast(_, c, t) => {
+                        push_inner(stack, t.into());
+                        push_inner(stack, c.into());
+                    }
+                },
+
                 ty::ConstKind::Unevaluated(ct) => {
                     stack.extend(ct.substs.iter().rev());
                 }
diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs
index 550ed9b6378..f2177a7c283 100644
--- a/compiler/rustc_privacy/src/lib.rs
+++ b/compiler/rustc_privacy/src/lib.rs
@@ -3,6 +3,7 @@
 #![feature(control_flow_enum)]
 #![feature(rustc_private)]
 #![feature(try_blocks)]
+#![feature(let_chains)]
 #![recursion_limit = "256"]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
@@ -25,7 +26,6 @@ use rustc_middle::bug;
 use rustc_middle::hir::nested_filter;
 use rustc_middle::middle::privacy::{EffectiveVisibilities, Level};
 use rustc_middle::span_bug;
-use rustc_middle::ty::abstract_const::{walk_abstract_const, AbstractConst, Node as ACNode};
 use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::subst::InternalSubsts;
 use rustc_middle::ty::{self, Const, DefIdTree, GenericParamDefKind};
@@ -288,19 +288,8 @@ where
     }
 
     fn visit_const(&mut self, c: Const<'tcx>) -> ControlFlow<Self::BreakTy> {
-        self.visit_ty(c.ty())?;
         let tcx = self.def_id_visitor.tcx();
-        if let Ok(Some(ct)) = AbstractConst::from_const(tcx, c) {
-            walk_abstract_const(tcx, ct, |node| match node.root(tcx) {
-                ACNode::Leaf(leaf) => self.visit_const(leaf),
-                ACNode::Cast(_, _, ty) => self.visit_ty(ty),
-                ACNode::Binop(..) | ACNode::UnaryOp(..) | ACNode::FunctionCall(_, _) => {
-                    ControlFlow::CONTINUE
-                }
-            })
-        } else {
-            ControlFlow::CONTINUE
-        }
+        tcx.expand_abstract_consts(c).super_visit_with(self)
     }
 }
 
diff --git a/compiler/rustc_query_impl/src/on_disk_cache.rs b/compiler/rustc_query_impl/src/on_disk_cache.rs
index eaed9aeb850..c61d2a9c2d0 100644
--- a/compiler/rustc_query_impl/src/on_disk_cache.rs
+++ b/compiler/rustc_query_impl/src/on_disk_cache.rs
@@ -812,12 +812,6 @@ impl<'a, 'tcx> Decodable<CacheDecoder<'a, 'tcx>>
     }
 }
 
-impl<'a, 'tcx> Decodable<CacheDecoder<'a, 'tcx>> for &'tcx [ty::abstract_const::Node<'tcx>] {
-    fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self {
-        RefDecodable::decode(d)
-    }
-}
-
 impl<'a, 'tcx> Decodable<CacheDecoder<'a, 'tcx>> for &'tcx [(ty::Predicate<'tcx>, Span)] {
     fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self {
         RefDecodable::decode(d)
diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs
index d1a2aee207d..b6378af7ba0 100644
--- a/compiler/rustc_symbol_mangling/src/v0.rs
+++ b/compiler/rustc_symbol_mangling/src/v0.rs
@@ -575,6 +575,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
             // a path), even for it we still need to encode a placeholder, as
             // the path could refer back to e.g. an `impl` using the constant.
             ty::ConstKind::Unevaluated(_)
+            | ty::ConstKind::Expr(_)
             | ty::ConstKind::Param(_)
             | ty::ConstKind::Infer(_)
             | ty::ConstKind::Bound(..)
diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs
index db3ddc9208a..e9e65336299 100644
--- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs
+++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs
@@ -8,152 +8,18 @@
 //! In this case we try to build an abstract representation of this constant using
 //! `thir_abstract_const` which can then be checked for structural equality with other
 //! generic constants mentioned in the `caller_bounds` of the current environment.
-use rustc_errors::ErrorGuaranteed;
+use rustc_hir::def::DefKind;
 use rustc_infer::infer::InferCtxt;
 use rustc_middle::mir::interpret::ErrorHandled;
-use rustc_middle::ty::abstract_const::{
-    walk_abstract_const, AbstractConst, FailureKind, Node, NotConstEvaluatable,
-};
-use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
-use rustc_span::Span;
-
-use std::iter;
-use std::ops::ControlFlow;
-
-pub struct ConstUnifyCtxt<'tcx> {
-    pub tcx: TyCtxt<'tcx>,
-    pub param_env: ty::ParamEnv<'tcx>,
-}
-
-impl<'tcx> ConstUnifyCtxt<'tcx> {
-    // Substitutes generics repeatedly to allow AbstractConsts to unify where a
-    // ConstKind::Unevaluated could be turned into an AbstractConst that would unify e.g.
-    // Param(N) should unify with Param(T), substs: [Unevaluated("T2", [Unevaluated("T3", [Param(N)])])]
-    #[inline]
-    #[instrument(skip(self), level = "debug")]
-    fn try_replace_substs_in_root(
-        &self,
-        mut abstr_const: AbstractConst<'tcx>,
-    ) -> Option<AbstractConst<'tcx>> {
-        while let Node::Leaf(ct) = abstr_const.root(self.tcx) {
-            match AbstractConst::from_const(self.tcx, ct) {
-                Ok(Some(act)) => abstr_const = act,
-                Ok(None) => break,
-                Err(_) => return None,
-            }
-        }
-
-        Some(abstr_const)
-    }
-
-    /// Tries to unify two abstract constants using structural equality.
-    #[instrument(skip(self), level = "debug")]
-    pub fn try_unify(&self, a: AbstractConst<'tcx>, b: AbstractConst<'tcx>) -> bool {
-        let a = if let Some(a) = self.try_replace_substs_in_root(a) {
-            a
-        } else {
-            return true;
-        };
-
-        let b = if let Some(b) = self.try_replace_substs_in_root(b) {
-            b
-        } else {
-            return true;
-        };
 
-        let a_root = a.root(self.tcx);
-        let b_root = b.root(self.tcx);
-        debug!(?a_root, ?b_root);
-
-        match (a_root, b_root) {
-            (Node::Leaf(a_ct), Node::Leaf(b_ct)) => {
-                let a_ct = a_ct.eval(self.tcx, self.param_env);
-                debug!("a_ct evaluated: {:?}", a_ct);
-                let b_ct = b_ct.eval(self.tcx, self.param_env);
-                debug!("b_ct evaluated: {:?}", b_ct);
-
-                if a_ct.ty() != b_ct.ty() {
-                    return false;
-                }
-
-                match (a_ct.kind(), b_ct.kind()) {
-                    // We can just unify errors with everything to reduce the amount of
-                    // emitted errors here.
-                    (ty::ConstKind::Error(_), _) | (_, ty::ConstKind::Error(_)) => true,
-                    (ty::ConstKind::Param(a_param), ty::ConstKind::Param(b_param)) => {
-                        a_param == b_param
-                    }
-                    (ty::ConstKind::Value(a_val), ty::ConstKind::Value(b_val)) => a_val == b_val,
-                    // If we have `fn a<const N: usize>() -> [u8; N + 1]` and `fn b<const M: usize>() -> [u8; 1 + M]`
-                    // we do not want to use `assert_eq!(a(), b())` to infer that `N` and `M` have to be `1`. This
-                    // means that we only allow inference variables if they are equal.
-                    (ty::ConstKind::Infer(a_val), ty::ConstKind::Infer(b_val)) => a_val == b_val,
-                    // We expand generic anonymous constants at the start of this function, so this
-                    // branch should only be taking when dealing with associated constants, at
-                    // which point directly comparing them seems like the desired behavior.
-                    //
-                    // FIXME(generic_const_exprs): This isn't actually the case.
-                    // We also take this branch for concrete anonymous constants and
-                    // expand generic anonymous constants with concrete substs.
-                    (ty::ConstKind::Unevaluated(a_uv), ty::ConstKind::Unevaluated(b_uv)) => {
-                        a_uv == b_uv
-                    }
-                    // FIXME(generic_const_exprs): We may want to either actually try
-                    // to evaluate `a_ct` and `b_ct` if they are fully concrete or something like
-                    // this, for now we just return false here.
-                    _ => false,
-                }
-            }
-            (Node::Binop(a_op, al, ar), Node::Binop(b_op, bl, br)) if a_op == b_op => {
-                self.try_unify(a.subtree(al), b.subtree(bl))
-                    && self.try_unify(a.subtree(ar), b.subtree(br))
-            }
-            (Node::UnaryOp(a_op, av), Node::UnaryOp(b_op, bv)) if a_op == b_op => {
-                self.try_unify(a.subtree(av), b.subtree(bv))
-            }
-            (Node::FunctionCall(a_f, a_args), Node::FunctionCall(b_f, b_args))
-                if a_args.len() == b_args.len() =>
-            {
-                self.try_unify(a.subtree(a_f), b.subtree(b_f))
-                    && iter::zip(a_args, b_args)
-                        .all(|(&an, &bn)| self.try_unify(a.subtree(an), b.subtree(bn)))
-            }
-            (Node::Cast(a_kind, a_operand, a_ty), Node::Cast(b_kind, b_operand, b_ty))
-                if (a_ty == b_ty) && (a_kind == b_kind) =>
-            {
-                self.try_unify(a.subtree(a_operand), b.subtree(b_operand))
-            }
-            // use this over `_ => false` to make adding variants to `Node` less error prone
-            (Node::Cast(..), _)
-            | (Node::FunctionCall(..), _)
-            | (Node::UnaryOp(..), _)
-            | (Node::Binop(..), _)
-            | (Node::Leaf(..), _) => false,
-        }
-    }
-}
+use rustc_middle::traits::ObligationCause;
+use rustc_middle::ty::abstract_const::NotConstEvaluatable;
+use rustc_middle::ty::{self, TyCtxt, TypeVisitable, TypeVisitor};
 
-#[instrument(skip(tcx), level = "debug")]
-pub fn try_unify_abstract_consts<'tcx>(
-    tcx: TyCtxt<'tcx>,
-    (a, b): (ty::UnevaluatedConst<'tcx>, ty::UnevaluatedConst<'tcx>),
-    param_env: ty::ParamEnv<'tcx>,
-) -> bool {
-    (|| {
-        if let Some(a) = AbstractConst::new(tcx, a)? {
-            if let Some(b) = AbstractConst::new(tcx, b)? {
-                let const_unify_ctxt = ConstUnifyCtxt { tcx, param_env };
-                return Ok(const_unify_ctxt.try_unify(a, b));
-            }
-        }
+use rustc_span::Span;
+use std::ops::ControlFlow;
 
-        Ok(false)
-    })()
-    .unwrap_or_else(|_: ErrorGuaranteed| true)
-    // FIXME(generic_const_exprs): We should instead have this
-    // method return the resulting `ty::Const` and return `ConstKind::Error`
-    // on `ErrorGuaranteed`.
-}
+use crate::traits::ObligationCtxt;
 
 /// Check if a given constant can be evaluated.
 #[instrument(skip(infcx), level = "debug")]
@@ -166,6 +32,8 @@ pub fn is_const_evaluatable<'tcx>(
     let tcx = infcx.tcx;
     let uv = match ct.kind() {
         ty::ConstKind::Unevaluated(uv) => uv,
+        // FIXME(generic_const_exprs): this seems wrong but I couldn't find a way to get this to trigger
+        ty::ConstKind::Expr(_) => bug!("unexpected expr in `is_const_evaluatable: {ct:?}"),
         ty::ConstKind::Param(_)
         | ty::ConstKind::Bound(_, _)
         | ty::ConstKind::Placeholder(_)
@@ -175,21 +43,25 @@ pub fn is_const_evaluatable<'tcx>(
     };
 
     if tcx.features().generic_const_exprs {
-        if let Some(ct) = AbstractConst::new(tcx, uv)? {
-            if satisfied_from_param_env(tcx, ct, param_env)? {
+        let ct = tcx.expand_abstract_consts(ct);
+
+        let is_anon_ct = if let ty::ConstKind::Unevaluated(uv) = ct.kind() {
+            tcx.def_kind(uv.def.did) == DefKind::AnonConst
+        } else {
+            false
+        };
+
+        if !is_anon_ct {
+            if satisfied_from_param_env(tcx, infcx, ct, param_env) {
                 return Ok(());
             }
-            match ct.unify_failure_kind(tcx) {
-                FailureKind::MentionsInfer => {
-                    return Err(NotConstEvaluatable::MentionsInfer);
-                }
-                FailureKind::MentionsParam => {
-                    return Err(NotConstEvaluatable::MentionsParam);
-                }
-                // returned below
-                FailureKind::Concrete => {}
+            if ct.has_non_region_infer() {
+                return Err(NotConstEvaluatable::MentionsInfer);
+            } else if ct.has_non_region_param() {
+                return Err(NotConstEvaluatable::MentionsParam);
             }
         }
+
         let concrete = infcx.const_eval_resolve(param_env, uv, Some(span));
         match concrete {
             Err(ErrorHandled::TooGeneric) => Err(NotConstEvaluatable::Error(
@@ -211,28 +83,33 @@ pub fn is_const_evaluatable<'tcx>(
         //
         // See #74595 for more details about this.
         let concrete = infcx.const_eval_resolve(param_env, uv, Some(span));
-
         match concrete {
-          // If we're evaluating a foreign constant, under a nightly compiler without generic
-          // const exprs, AND it would've passed if that expression had been evaluated with
-          // generic const exprs, then suggest using generic const exprs.
-          Err(_) if tcx.sess.is_nightly_build()
-            && let Ok(Some(ct)) = AbstractConst::new(tcx, uv)
-            && satisfied_from_param_env(tcx, ct, param_env) == Ok(true) => {
-              tcx.sess
-                  .struct_span_fatal(
-                      // Slightly better span than just using `span` alone
-                      if span == rustc_span::DUMMY_SP { tcx.def_span(uv.def.did) } else { span },
-                      "failed to evaluate generic const expression",
-                  )
-                  .note("the crate this constant originates from uses `#![feature(generic_const_exprs)]`")
-                  .span_suggestion_verbose(
-                      rustc_span::DUMMY_SP,
-                      "consider enabling this feature",
-                      "#![feature(generic_const_exprs)]\n",
-                      rustc_errors::Applicability::MaybeIncorrect,
-                  )
-                  .emit()
+            // If we're evaluating a generic foreign constant, under a nightly compiler while
+            // the current crate does not enable `feature(generic_const_exprs)`, abort
+            // compilation with a useful error.
+            Err(_)
+                if tcx.sess.is_nightly_build()
+                    && satisfied_from_param_env(
+                        tcx,
+                        infcx,
+                        tcx.expand_abstract_consts(ct),
+                        param_env,
+                    ) =>
+            {
+                tcx.sess
+                    .struct_span_fatal(
+                        // Slightly better span than just using `span` alone
+                        if span == rustc_span::DUMMY_SP { tcx.def_span(uv.def.did) } else { span },
+                        "failed to evaluate generic const expression",
+                    )
+                    .note("the crate this constant originates from uses `#![feature(generic_const_exprs)]`")
+                    .span_suggestion_verbose(
+                        rustc_span::DUMMY_SP,
+                        "consider enabling this feature",
+                        "#![feature(generic_const_exprs)]\n",
+                        rustc_errors::Applicability::MaybeIncorrect,
+                    )
+                    .emit()
             }
 
             Err(ErrorHandled::TooGeneric) => {
@@ -241,49 +118,82 @@ pub fn is_const_evaluatable<'tcx>(
                 } else if uv.has_non_region_param() {
                     NotConstEvaluatable::MentionsParam
                 } else {
-                    let guar = infcx.tcx.sess.delay_span_bug(span, format!("Missing value for constant, but no error reported?"));
+                    let guar = infcx.tcx.sess.delay_span_bug(
+                        span,
+                        format!("Missing value for constant, but no error reported?"),
+                    );
                     NotConstEvaluatable::Error(guar)
                 };
 
                 Err(err)
-            },
+            }
             Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)),
             Ok(_) => Ok(()),
         }
     }
 }
 
-#[instrument(skip(tcx), level = "debug")]
+#[instrument(skip(infcx, tcx), level = "debug")]
 fn satisfied_from_param_env<'tcx>(
     tcx: TyCtxt<'tcx>,
-    ct: AbstractConst<'tcx>,
+    infcx: &InferCtxt<'tcx>,
+    ct: ty::Const<'tcx>,
     param_env: ty::ParamEnv<'tcx>,
-) -> Result<bool, NotConstEvaluatable> {
+) -> bool {
+    // Try to unify with each subtree in the AbstractConst to allow for
+    // `N + 1` being const evaluatable even if theres only a `ConstEvaluatable`
+    // predicate for `(N + 1) * 2`
+    struct Visitor<'a, 'tcx> {
+        ct: ty::Const<'tcx>,
+        param_env: ty::ParamEnv<'tcx>,
+
+        infcx: &'a InferCtxt<'tcx>,
+    }
+    impl<'a, 'tcx> TypeVisitor<'tcx> for Visitor<'a, 'tcx> {
+        type BreakTy = ();
+        fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
+            if let Ok(()) = self.infcx.commit_if_ok(|_| {
+                let ocx = ObligationCtxt::new_in_snapshot(self.infcx);
+                if let Ok(()) = ocx.eq(&ObligationCause::dummy(), self.param_env, c.ty(), self.ct.ty())
+                    && let Ok(()) = ocx.eq(&ObligationCause::dummy(), self.param_env, c, self.ct)
+                    && ocx.select_all_or_error().is_empty()
+                {
+                    Ok(())
+                } else {
+                    Err(())
+                }
+            }) {
+                ControlFlow::BREAK
+            } else if let ty::ConstKind::Expr(e) = c.kind() {
+                e.visit_with(self)
+            } else {
+                // FIXME(generic_const_exprs): This doesn't recurse into `<T as Trait<U>>::ASSOC`'s substs.
+                // This is currently unobservable as `<T as Trait<{ U + 1 }>>::ASSOC` creates an anon const
+                // with its own `ConstEvaluatable` bound in the param env which we will visit separately.
+                //
+                // If we start allowing directly writing `ConstKind::Expr` without an intermediate anon const
+                // this will be incorrect. It might be worth investigating making `predicates_of` elaborate
+                // all of the `ConstEvaluatable` bounds rather than having a visitor here.
+                ControlFlow::CONTINUE
+            }
+        }
+    }
+
     for pred in param_env.caller_bounds() {
         match pred.kind().skip_binder() {
-            ty::PredicateKind::ConstEvaluatable(uv) => {
-                if let Some(b_ct) = AbstractConst::from_const(tcx, uv)? {
-                    let const_unify_ctxt = ConstUnifyCtxt { tcx, param_env };
-
-                    // Try to unify with each subtree in the AbstractConst to allow for
-                    // `N + 1` being const evaluatable even if theres only a `ConstEvaluatable`
-                    // predicate for `(N + 1) * 2`
-                    let result = walk_abstract_const(tcx, b_ct, |b_ct| {
-                        match const_unify_ctxt.try_unify(ct, b_ct) {
-                            true => ControlFlow::BREAK,
-                            false => ControlFlow::CONTINUE,
-                        }
-                    });
-
-                    if let ControlFlow::Break(()) = result {
-                        debug!("is_const_evaluatable: abstract_const ~~> ok");
-                        return Ok(true);
-                    }
+            ty::PredicateKind::ConstEvaluatable(ce) => {
+                let b_ct = tcx.expand_abstract_consts(ce);
+                let mut v = Visitor { ct, infcx, param_env };
+                let result = b_ct.visit_with(&mut v);
+
+                if let ControlFlow::Break(()) = result {
+                    debug!("is_const_evaluatable: abstract_const ~~> ok");
+                    return true;
                 }
             }
             _ => {} // don't care
         }
     }
 
-    Ok(false)
+    false
 }
diff --git a/compiler/rustc_trait_selection/src/traits/fulfill.rs b/compiler/rustc_trait_selection/src/traits/fulfill.rs
index b0d0d1a7cba..80ee363d72f 100644
--- a/compiler/rustc_trait_selection/src/traits/fulfill.rs
+++ b/compiler/rustc_trait_selection/src/traits/fulfill.rs
@@ -455,20 +455,47 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> {
                 }
 
                 ty::PredicateKind::ConstEquate(c1, c2) => {
+                    let tcx = self.selcx.tcx();
                     assert!(
-                        self.selcx.tcx().features().generic_const_exprs,
+                        tcx.features().generic_const_exprs,
                         "`ConstEquate` without a feature gate: {c1:?} {c2:?}",
                     );
-                    debug!(?c1, ?c2, "equating consts");
                     // FIXME: we probably should only try to unify abstract constants
                     // if the constants depend on generic parameters.
                     //
                     // Let's just see where this breaks :shrug:
-                    if let (ty::ConstKind::Unevaluated(a), ty::ConstKind::Unevaluated(b)) =
-                        (c1.kind(), c2.kind())
                     {
-                        if infcx.try_unify_abstract_consts(a, b, obligation.param_env) {
-                            return ProcessResult::Changed(vec![]);
+                        let c1 = tcx.expand_abstract_consts(c1);
+                        let c2 = tcx.expand_abstract_consts(c2);
+                        debug!("equating consts:\nc1= {:?}\nc2= {:?}", c1, c2);
+
+                        use rustc_hir::def::DefKind;
+                        use ty::ConstKind::Unevaluated;
+                        match (c1.kind(), c2.kind()) {
+                            (Unevaluated(a), Unevaluated(b))
+                                if a.def.did == b.def.did
+                                    && tcx.def_kind(a.def.did) == DefKind::AssocConst =>
+                            {
+                                if let Ok(new_obligations) = infcx
+                                    .at(&obligation.cause, obligation.param_env)
+                                    .trace(c1, c2)
+                                    .eq(a.substs, b.substs)
+                                {
+                                    return ProcessResult::Changed(mk_pending(
+                                        new_obligations.into_obligations(),
+                                    ));
+                                }
+                            }
+                            (_, Unevaluated(_)) | (Unevaluated(_), _) => (),
+                            (_, _) => {
+                                if let Ok(new_obligations) =
+                                    infcx.at(&obligation.cause, obligation.param_env).eq(c1, c2)
+                                {
+                                    return ProcessResult::Changed(mk_pending(
+                                        new_obligations.into_obligations(),
+                                    ));
+                                }
+                            }
                         }
                     }
 
@@ -508,7 +535,9 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> {
                                 .at(&obligation.cause, obligation.param_env)
                                 .eq(c1, c2)
                             {
-                                Ok(_) => ProcessResult::Changed(vec![]),
+                                Ok(inf_ok) => {
+                                    ProcessResult::Changed(mk_pending(inf_ok.into_obligations()))
+                                }
                                 Err(err) => ProcessResult::Error(
                                     FulfillmentErrorCode::CodeConstEquateError(
                                         ExpectedFound::new(true, c1, c2),
diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs
index 3112756a0b1..5285cfa6746 100644
--- a/compiler/rustc_trait_selection/src/traits/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/mod.rs
@@ -932,10 +932,6 @@ pub fn provide(providers: &mut ty::query::Providers) {
         vtable_trait_upcasting_coercion_new_vptr_slot,
         subst_and_check_impossible_predicates,
         is_impossible_method,
-        try_unify_abstract_consts: |tcx, param_env_and| {
-            let (param_env, (a, b)) = param_env_and.into_parts();
-            const_evaluatable::try_unify_abstract_consts(tcx, (a, b), param_env)
-        },
         ..*providers
     };
 }
diff --git a/compiler/rustc_trait_selection/src/traits/object_safety.rs b/compiler/rustc_trait_selection/src/traits/object_safety.rs
index 9f531e03f06..a45749fe48c 100644
--- a/compiler/rustc_trait_selection/src/traits/object_safety.rs
+++ b/compiler/rustc_trait_selection/src/traits/object_safety.rs
@@ -17,11 +17,10 @@ use hir::def::DefKind;
 use rustc_errors::{DelayDm, FatalError, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
-use rustc_middle::ty::abstract_const::{walk_abstract_const, AbstractConst};
+use rustc_middle::ty::subst::{GenericArg, InternalSubsts};
 use rustc_middle::ty::{
     self, EarlyBinder, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor,
 };
-use rustc_middle::ty::{GenericArg, InternalSubsts};
 use rustc_middle::ty::{Predicate, ToPredicate};
 use rustc_session::lint::builtin::WHERE_CLAUSES_OBJECT_SAFETY;
 use rustc_span::symbol::Symbol;
@@ -837,23 +836,9 @@ fn contains_illegal_self_type_reference<'tcx, T: TypeVisitable<'tcx>>(
         }
 
         fn visit_const(&mut self, ct: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
-            // Constants can only influence object safety if they reference `Self`.
+            // Constants can only influence object safety if they are generic and reference `Self`.
             // This is only possible for unevaluated constants, so we walk these here.
-            //
-            // If `AbstractConst::from_const` returned an error we already failed compilation
-            // so we don't have to emit an additional error here.
-            use rustc_middle::ty::abstract_const::Node;
-            if let Ok(Some(ct)) = AbstractConst::from_const(self.tcx, ct) {
-                walk_abstract_const(self.tcx, ct, |node| match node.root(self.tcx) {
-                    Node::Leaf(leaf) => self.visit_const(leaf),
-                    Node::Cast(_, _, ty) => self.visit_ty(ty),
-                    Node::Binop(..) | Node::UnaryOp(..) | Node::FunctionCall(_, _) => {
-                        ControlFlow::CONTINUE
-                    }
-                })
-            } else {
-                ct.super_visit_with(self)
-            }
+            self.tcx.expand_abstract_consts(ct).super_visit_with(self)
         }
     }
 
diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs
index 3c8f8b5642c..6e8706897bf 100644
--- a/compiler/rustc_trait_selection/src/traits/select/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs
@@ -657,21 +657,62 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                 }
 
                 ty::PredicateKind::ConstEquate(c1, c2) => {
+                    let tcx = self.tcx();
                     assert!(
-                        self.tcx().features().generic_const_exprs,
+                        tcx.features().generic_const_exprs,
                         "`ConstEquate` without a feature gate: {c1:?} {c2:?}",
                     );
-                    debug!(?c1, ?c2, "evaluate_predicate_recursively: equating consts");
 
-                    // FIXME: we probably should only try to unify abstract constants
-                    // if the constants depend on generic parameters.
-                    //
-                    // Let's just see where this breaks :shrug:
-                    if let (ty::ConstKind::Unevaluated(a), ty::ConstKind::Unevaluated(b)) =
-                        (c1.kind(), c2.kind())
                     {
-                        if self.infcx.try_unify_abstract_consts(a, b, obligation.param_env) {
-                            return Ok(EvaluatedToOk);
+                        let c1 = tcx.expand_abstract_consts(c1);
+                        let c2 = tcx.expand_abstract_consts(c2);
+                        debug!(
+                            "evalaute_predicate_recursively: equating consts:\nc1= {:?}\nc2= {:?}",
+                            c1, c2
+                        );
+
+                        use rustc_hir::def::DefKind;
+                        use ty::ConstKind::Unevaluated;
+                        match (c1.kind(), c2.kind()) {
+                            (Unevaluated(a), Unevaluated(b))
+                                if a.def.did == b.def.did
+                                    && tcx.def_kind(a.def.did) == DefKind::AssocConst =>
+                            {
+                                if let Ok(new_obligations) = self
+                                    .infcx
+                                    .at(&obligation.cause, obligation.param_env)
+                                    .trace(c1, c2)
+                                    .eq(a.substs, b.substs)
+                                {
+                                    let mut obligations = new_obligations.obligations;
+                                    self.add_depth(
+                                        obligations.iter_mut(),
+                                        obligation.recursion_depth,
+                                    );
+                                    return self.evaluate_predicates_recursively(
+                                        previous_stack,
+                                        obligations.into_iter(),
+                                    );
+                                }
+                            }
+                            (_, Unevaluated(_)) | (Unevaluated(_), _) => (),
+                            (_, _) => {
+                                if let Ok(new_obligations) = self
+                                    .infcx
+                                    .at(&obligation.cause, obligation.param_env)
+                                    .eq(c1, c2)
+                                {
+                                    let mut obligations = new_obligations.obligations;
+                                    self.add_depth(
+                                        obligations.iter_mut(),
+                                        obligation.recursion_depth,
+                                    );
+                                    return self.evaluate_predicates_recursively(
+                                        previous_stack,
+                                        obligations.into_iter(),
+                                    );
+                                }
+                            }
                         }
                     }
 
@@ -698,7 +739,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                                 .at(&obligation.cause, obligation.param_env)
                                 .eq(c1, c2)
                             {
-                                Ok(_) => Ok(EvaluatedToOk),
+                                Ok(inf_ok) => self.evaluate_predicates_recursively(
+                                    previous_stack,
+                                    inf_ok.into_obligations(),
+                                ),
                                 Err(_) => Ok(EvaluatedToErr),
                             }
                         }
diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs
index fe68315ceb6..0855d6d1973 100644
--- a/compiler/rustc_trait_selection/src/traits/wf.rs
+++ b/compiler/rustc_trait_selection/src/traits/wf.rs
@@ -476,6 +476,11 @@ impl<'tcx> WfPredicates<'tcx> {
                                 ty::Binder::dummy(ty::PredicateKind::WellFormed(ct.into())),
                             ));
                         }
+                        // FIXME(generic_const_exprs): This seems wrong but I could not find a way to get this to trigger
+                        ty::ConstKind::Expr(_) => {
+                            bug!("checking wfness of `ConstKind::Expr` is unsupported")
+                        }
+
                         ty::ConstKind::Error(_)
                         | ty::ConstKind::Param(_)
                         | ty::ConstKind::Bound(..)
diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs
index cb41c4f94e2..2b7018bc9c3 100644
--- a/compiler/rustc_ty_utils/src/consts.rs
+++ b/compiler/rustc_ty_utils/src/consts.rs
@@ -1,10 +1,11 @@
 use rustc_errors::ErrorGuaranteed;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::LocalDefId;
-use rustc_index::vec::IndexVec;
 use rustc_middle::mir::interpret::{LitToConstError, LitToConstInput};
-use rustc_middle::ty::abstract_const::{CastKind, Node, NodeId};
-use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
+use rustc_middle::thir::visit;
+use rustc_middle::thir::visit::Visitor;
+use rustc_middle::ty::abstract_const::CastKind;
+use rustc_middle::ty::{self, ConstKind, Expr, TyCtxt, TypeVisitable};
 use rustc_middle::{mir, thir};
 use rustc_span::Span;
 use rustc_target::abi::VariantIdx;
@@ -76,326 +77,278 @@ pub(crate) fn destructure_const<'tcx>(
     ty::DestructuredConst { variant, fields }
 }
 
-pub struct AbstractConstBuilder<'a, 'tcx> {
-    tcx: TyCtxt<'tcx>,
-    body_id: thir::ExprId,
-    body: &'a thir::Thir<'tcx>,
-    /// The current WIP node tree.
-    nodes: IndexVec<NodeId, Node<'tcx>>,
-}
-
-impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
-    fn root_span(&self) -> Span {
-        self.body.exprs[self.body_id].span
-    }
-
-    fn error(&mut self, sub: GenericConstantTooComplexSub) -> Result<!, ErrorGuaranteed> {
-        let reported = self.tcx.sess.emit_err(GenericConstantTooComplex {
-            span: self.root_span(),
-            maybe_supported: None,
-            sub,
-        });
-
-        Err(reported)
+/// We do not allow all binary operations in abstract consts, so filter disallowed ones.
+fn check_binop(op: mir::BinOp) -> bool {
+    use mir::BinOp::*;
+    match op {
+        Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Shl | Shr | Eq | Lt | Le | Ne
+        | Ge | Gt => true,
+        Offset => false,
     }
+}
 
-    fn maybe_supported_error(
-        &mut self,
-        sub: GenericConstantTooComplexSub,
-    ) -> Result<!, ErrorGuaranteed> {
-        let reported = self.tcx.sess.emit_err(GenericConstantTooComplex {
-            span: self.root_span(),
-            maybe_supported: Some(()),
-            sub,
-        });
-
-        Err(reported)
+/// While we currently allow all unary operations, we still want to explicitly guard against
+/// future changes here.
+fn check_unop(op: mir::UnOp) -> bool {
+    use mir::UnOp::*;
+    match op {
+        Not | Neg => true,
     }
+}
 
-    #[instrument(skip(tcx, body, body_id), level = "debug")]
-    pub fn new(
-        tcx: TyCtxt<'tcx>,
-        (body, body_id): (&'a thir::Thir<'tcx>, thir::ExprId),
-    ) -> Result<Option<AbstractConstBuilder<'a, 'tcx>>, ErrorGuaranteed> {
-        let builder = AbstractConstBuilder { tcx, body_id, body, nodes: IndexVec::new() };
-
-        struct IsThirPolymorphic<'a, 'tcx> {
-            is_poly: bool,
-            thir: &'a thir::Thir<'tcx>,
+fn recurse_build<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    body: &thir::Thir<'tcx>,
+    node: thir::ExprId,
+    root_span: Span,
+) -> Result<ty::Const<'tcx>, ErrorGuaranteed> {
+    use thir::ExprKind;
+    let node = &body.exprs[node];
+
+    let maybe_supported_error = |a| maybe_supported_error(tcx, a, root_span);
+    let error = |a| error(tcx, a, root_span);
+
+    Ok(match &node.kind {
+        // I dont know if handling of these 3 is correct
+        &ExprKind::Scope { value, .. } => recurse_build(tcx, body, value, root_span)?,
+        &ExprKind::PlaceTypeAscription { source, .. }
+        | &ExprKind::ValueTypeAscription { source, .. } => {
+            recurse_build(tcx, body, source, root_span)?
         }
-
-        use crate::rustc_middle::thir::visit::Visitor;
-        use thir::visit;
-
-        impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> {
-            fn expr_is_poly(&mut self, expr: &thir::Expr<'tcx>) -> bool {
-                if expr.ty.has_non_region_param() {
-                    return true;
+        &ExprKind::Literal { lit, neg } => {
+            let sp = node.span;
+            match tcx.at(sp).lit_to_const(LitToConstInput { lit: &lit.node, ty: node.ty, neg }) {
+                Ok(c) => c,
+                Err(LitToConstError::Reported(guar)) => {
+                    tcx.const_error_with_guaranteed(node.ty, guar)
                 }
-
-                match expr.kind {
-                    thir::ExprKind::NamedConst { substs, .. } => substs.has_non_region_param(),
-                    thir::ExprKind::ConstParam { .. } => true,
-                    thir::ExprKind::Repeat { value, count } => {
-                        self.visit_expr(&self.thir()[value]);
-                        count.has_non_region_param()
-                    }
-                    _ => false,
+                Err(LitToConstError::TypeError) => {
+                    bug!("encountered type error in lit_to_const")
                 }
             }
+        }
+        &ExprKind::NonHirLiteral { lit, user_ty: _ } => {
+            let val = ty::ValTree::from_scalar_int(lit);
+            ty::Const::from_value(tcx, val, node.ty)
+        }
+        &ExprKind::ZstLiteral { user_ty: _ } => {
+            let val = ty::ValTree::zst();
+            ty::Const::from_value(tcx, val, node.ty)
+        }
+        &ExprKind::NamedConst { def_id, substs, user_ty: _ } => {
+            let uneval = ty::UnevaluatedConst::new(ty::WithOptConstParam::unknown(def_id), substs);
+            tcx.mk_const(ty::ConstKind::Unevaluated(uneval), node.ty)
+        }
+        ExprKind::ConstParam { param, .. } => tcx.mk_const(ty::ConstKind::Param(*param), node.ty),
 
-            fn pat_is_poly(&mut self, pat: &thir::Pat<'tcx>) -> bool {
-                if pat.ty.has_non_region_param() {
-                    return true;
-                }
+        ExprKind::Call { fun, args, .. } => {
+            let fun = recurse_build(tcx, body, *fun, root_span)?;
 
-                match pat.kind {
-                    thir::PatKind::Constant { value } => value.has_non_region_param(),
-                    thir::PatKind::Range(box thir::PatRange { lo, hi, .. }) => {
-                        lo.has_non_region_param() || hi.has_non_region_param()
-                    }
-                    _ => false,
-                }
+            let mut new_args = Vec::<ty::Const<'tcx>>::with_capacity(args.len());
+            for &id in args.iter() {
+                new_args.push(recurse_build(tcx, body, id, root_span)?);
             }
+            let new_args = tcx.mk_const_list(new_args.iter());
+            tcx.mk_const(ConstKind::Expr(Expr::FunctionCall(fun, new_args)), node.ty)
         }
-
-        impl<'a, 'tcx> visit::Visitor<'a, 'tcx> for IsThirPolymorphic<'a, 'tcx> {
-            fn thir(&self) -> &'a thir::Thir<'tcx> {
-                &self.thir
-            }
-
-            #[instrument(skip(self), level = "debug")]
-            fn visit_expr(&mut self, expr: &thir::Expr<'tcx>) {
-                self.is_poly |= self.expr_is_poly(expr);
-                if !self.is_poly {
-                    visit::walk_expr(self, expr)
-                }
+        &ExprKind::Binary { op, lhs, rhs } if check_binop(op) => {
+            let lhs = recurse_build(tcx, body, lhs, root_span)?;
+            let rhs = recurse_build(tcx, body, rhs, root_span)?;
+            tcx.mk_const(ConstKind::Expr(Expr::Binop(op, lhs, rhs)), node.ty)
+        }
+        &ExprKind::Unary { op, arg } if check_unop(op) => {
+            let arg = recurse_build(tcx, body, arg, root_span)?;
+            tcx.mk_const(ConstKind::Expr(Expr::UnOp(op, arg)), node.ty)
+        }
+        // This is necessary so that the following compiles:
+        //
+        // ```
+        // fn foo<const N: usize>(a: [(); N + 1]) {
+        //     bar::<{ N + 1 }>();
+        // }
+        // ```
+        ExprKind::Block { block } => {
+            if let thir::Block { stmts: box [], expr: Some(e), .. } = &body.blocks[*block] {
+                recurse_build(tcx, body, *e, root_span)?
+            } else {
+                maybe_supported_error(GenericConstantTooComplexSub::BlockNotSupported(node.span))?
             }
-
-            #[instrument(skip(self), level = "debug")]
-            fn visit_pat(&mut self, pat: &thir::Pat<'tcx>) {
-                self.is_poly |= self.pat_is_poly(pat);
-                if !self.is_poly {
-                    visit::walk_pat(self, pat);
-                }
+        }
+        // `ExprKind::Use` happens when a `hir::ExprKind::Cast` is a
+        // "coercion cast" i.e. using a coercion or is a no-op.
+        // This is important so that `N as usize as usize` doesnt unify with `N as usize`. (untested)
+        &ExprKind::Use { source } => {
+            let arg = recurse_build(tcx, body, source, root_span)?;
+            tcx.mk_const(ConstKind::Expr(Expr::Cast(CastKind::Use, arg, node.ty)), node.ty)
+        }
+        &ExprKind::Cast { source } => {
+            let arg = recurse_build(tcx, body, source, root_span)?;
+            tcx.mk_const(ConstKind::Expr(Expr::Cast(CastKind::As, arg, node.ty)), node.ty)
+        }
+        ExprKind::Borrow { arg, .. } => {
+            let arg_node = &body.exprs[*arg];
+
+            // Skip reborrows for now until we allow Deref/Borrow/AddressOf
+            // expressions.
+            // FIXME(generic_const_exprs): Verify/explain why this is sound
+            if let ExprKind::Deref { arg } = arg_node.kind {
+                recurse_build(tcx, body, arg, root_span)?
+            } else {
+                maybe_supported_error(GenericConstantTooComplexSub::BorrowNotSupported(node.span))?
             }
         }
-
-        let mut is_poly_vis = IsThirPolymorphic { is_poly: false, thir: body };
-        visit::walk_expr(&mut is_poly_vis, &body[body_id]);
-        debug!("AbstractConstBuilder: is_poly={}", is_poly_vis.is_poly);
-        if !is_poly_vis.is_poly {
-            return Ok(None);
+        // FIXME(generic_const_exprs): We may want to support these.
+        ExprKind::AddressOf { .. } | ExprKind::Deref { .. } => maybe_supported_error(
+            GenericConstantTooComplexSub::AddressAndDerefNotSupported(node.span),
+        )?,
+        ExprKind::Repeat { .. } | ExprKind::Array { .. } => {
+            maybe_supported_error(GenericConstantTooComplexSub::ArrayNotSupported(node.span))?
         }
+        ExprKind::NeverToAny { .. } => {
+            maybe_supported_error(GenericConstantTooComplexSub::NeverToAnyNotSupported(node.span))?
+        }
+        ExprKind::Tuple { .. } => {
+            maybe_supported_error(GenericConstantTooComplexSub::TupleNotSupported(node.span))?
+        }
+        ExprKind::Index { .. } => {
+            maybe_supported_error(GenericConstantTooComplexSub::IndexNotSupported(node.span))?
+        }
+        ExprKind::Field { .. } => {
+            maybe_supported_error(GenericConstantTooComplexSub::FieldNotSupported(node.span))?
+        }
+        ExprKind::ConstBlock { .. } => {
+            maybe_supported_error(GenericConstantTooComplexSub::ConstBlockNotSupported(node.span))?
+        }
+        ExprKind::Adt(_) => {
+            maybe_supported_error(GenericConstantTooComplexSub::AdtNotSupported(node.span))?
+        }
+        // dont know if this is correct
+        ExprKind::Pointer { .. } => {
+            error(GenericConstantTooComplexSub::PointerNotSupported(node.span))?
+        }
+        ExprKind::Yield { .. } => {
+            error(GenericConstantTooComplexSub::YieldNotSupported(node.span))?
+        }
+        ExprKind::Continue { .. } | ExprKind::Break { .. } | ExprKind::Loop { .. } => {
+            error(GenericConstantTooComplexSub::LoopNotSupported(node.span))?
+        }
+        ExprKind::Box { .. } => error(GenericConstantTooComplexSub::BoxNotSupported(node.span))?,
 
-        Ok(Some(builder))
-    }
-
-    /// We do not allow all binary operations in abstract consts, so filter disallowed ones.
-    fn check_binop(op: mir::BinOp) -> bool {
-        use mir::BinOp::*;
-        match op {
-            Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Shl | Shr | Eq | Lt | Le
-            | Ne | Ge | Gt => true,
-            Offset => false,
+        ExprKind::Unary { .. } => unreachable!(),
+        // we handle valid unary/binary ops above
+        ExprKind::Binary { .. } => {
+            error(GenericConstantTooComplexSub::BinaryNotSupported(node.span))?
+        }
+        ExprKind::LogicalOp { .. } => {
+            error(GenericConstantTooComplexSub::LogicalOpNotSupported(node.span))?
+        }
+        ExprKind::Assign { .. } | ExprKind::AssignOp { .. } => {
+            error(GenericConstantTooComplexSub::AssignNotSupported(node.span))?
+        }
+        ExprKind::Closure { .. } | ExprKind::Return { .. } => {
+            error(GenericConstantTooComplexSub::ClosureAndReturnNotSupported(node.span))?
+        }
+        // let expressions imply control flow
+        ExprKind::Match { .. } | ExprKind::If { .. } | ExprKind::Let { .. } => {
+            error(GenericConstantTooComplexSub::ControlFlowNotSupported(node.span))?
+        }
+        ExprKind::InlineAsm { .. } => {
+            error(GenericConstantTooComplexSub::InlineAsmNotSupported(node.span))?
         }
-    }
 
-    /// While we currently allow all unary operations, we still want to explicitly guard against
-    /// future changes here.
-    fn check_unop(op: mir::UnOp) -> bool {
-        use mir::UnOp::*;
-        match op {
-            Not | Neg => true,
+        // we dont permit let stmts so `VarRef` and `UpvarRef` cant happen
+        ExprKind::VarRef { .. }
+        | ExprKind::UpvarRef { .. }
+        | ExprKind::StaticRef { .. }
+        | ExprKind::ThreadLocalRef(_) => {
+            error(GenericConstantTooComplexSub::OperationNotSupported(node.span))?
         }
-    }
+    })
+}
 
-    /// Builds the abstract const by walking the thir and bailing out when
-    /// encountering an unsupported operation.
-    pub fn build(mut self) -> Result<&'tcx [Node<'tcx>], ErrorGuaranteed> {
-        debug!("AbstractConstBuilder::build: body={:?}", &*self.body);
-        self.recurse_build(self.body_id)?;
+struct IsThirPolymorphic<'a, 'tcx> {
+    is_poly: bool,
+    thir: &'a thir::Thir<'tcx>,
+}
 
-        Ok(self.tcx.arena.alloc_from_iter(self.nodes.into_iter()))
-    }
+fn error<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    sub: GenericConstantTooComplexSub,
+    root_span: Span,
+) -> Result<!, ErrorGuaranteed> {
+    let reported = tcx.sess.emit_err(GenericConstantTooComplex {
+        span: root_span,
+        maybe_supported: None,
+        sub,
+    });
+
+    Err(reported)
+}
 
-    fn recurse_build(&mut self, node: thir::ExprId) -> Result<NodeId, ErrorGuaranteed> {
-        use thir::ExprKind;
-        let node = &self.body.exprs[node];
-        Ok(match &node.kind {
-            // I dont know if handling of these 3 is correct
-            &ExprKind::Scope { value, .. } => self.recurse_build(value)?,
-            &ExprKind::PlaceTypeAscription { source, .. }
-            | &ExprKind::ValueTypeAscription { source, .. } => self.recurse_build(source)?,
-            &ExprKind::Literal { lit, neg } => {
-                let sp = node.span;
-                let constant = match self.tcx.at(sp).lit_to_const(LitToConstInput {
-                    lit: &lit.node,
-                    ty: node.ty,
-                    neg,
-                }) {
-                    Ok(c) => c,
-                    Err(LitToConstError::Reported(guar)) => {
-                        self.tcx.const_error_with_guaranteed(node.ty, guar)
-                    }
-                    Err(LitToConstError::TypeError) => {
-                        bug!("encountered type error in lit_to_const")
-                    }
-                };
-
-                self.nodes.push(Node::Leaf(constant))
-            }
-            &ExprKind::NonHirLiteral { lit, user_ty: _ } => {
-                let val = ty::ValTree::from_scalar_int(lit);
-                self.nodes.push(Node::Leaf(ty::Const::from_value(self.tcx, val, node.ty)))
-            }
-            &ExprKind::ZstLiteral { user_ty: _ } => {
-                let val = ty::ValTree::zst();
-                self.nodes.push(Node::Leaf(ty::Const::from_value(self.tcx, val, node.ty)))
-            }
-            &ExprKind::NamedConst { def_id, substs, user_ty: _ } => {
-                let uneval =
-                    ty::UnevaluatedConst::new(ty::WithOptConstParam::unknown(def_id), substs);
+fn maybe_supported_error<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    sub: GenericConstantTooComplexSub,
+    root_span: Span,
+) -> Result<!, ErrorGuaranteed> {
+    let reported = tcx.sess.emit_err(GenericConstantTooComplex {
+        span: root_span,
+        maybe_supported: Some(()),
+        sub,
+    });
+
+    Err(reported)
+}
 
-                let constant = self.tcx.mk_const(ty::ConstKind::Unevaluated(uneval), node.ty);
+impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> {
+    fn expr_is_poly(&mut self, expr: &thir::Expr<'tcx>) -> bool {
+        if expr.ty.has_non_region_param() {
+            return true;
+        }
 
-                self.nodes.push(Node::Leaf(constant))
+        match expr.kind {
+            thir::ExprKind::NamedConst { substs, .. } => substs.has_non_region_param(),
+            thir::ExprKind::ConstParam { .. } => true,
+            thir::ExprKind::Repeat { value, count } => {
+                self.visit_expr(&self.thir()[value]);
+                count.has_non_region_param()
             }
+            _ => false,
+        }
+    }
+    fn pat_is_poly(&mut self, pat: &thir::Pat<'tcx>) -> bool {
+        if pat.ty.has_non_region_param() {
+            return true;
+        }
 
-            ExprKind::ConstParam { param, .. } => {
-                let const_param = self.tcx.mk_const(ty::ConstKind::Param(*param), node.ty);
-                self.nodes.push(Node::Leaf(const_param))
+        match pat.kind {
+            thir::PatKind::Constant { value } => value.has_non_region_param(),
+            thir::PatKind::Range(box thir::PatRange { lo, hi, .. }) => {
+                lo.has_non_region_param() || hi.has_non_region_param()
             }
+            _ => false,
+        }
+    }
+}
 
-            ExprKind::Call { fun, args, .. } => {
-                let fun = self.recurse_build(*fun)?;
-
-                let mut new_args = Vec::<NodeId>::with_capacity(args.len());
-                for &id in args.iter() {
-                    new_args.push(self.recurse_build(id)?);
-                }
-                let new_args = self.tcx.arena.alloc_slice(&new_args);
-                self.nodes.push(Node::FunctionCall(fun, new_args))
-            }
-            &ExprKind::Binary { op, lhs, rhs } if Self::check_binop(op) => {
-                let lhs = self.recurse_build(lhs)?;
-                let rhs = self.recurse_build(rhs)?;
-                self.nodes.push(Node::Binop(op, lhs, rhs))
-            }
-            &ExprKind::Unary { op, arg } if Self::check_unop(op) => {
-                let arg = self.recurse_build(arg)?;
-                self.nodes.push(Node::UnaryOp(op, arg))
-            }
-            // This is necessary so that the following compiles:
-            //
-            // ```
-            // fn foo<const N: usize>(a: [(); N + 1]) {
-            //     bar::<{ N + 1 }>();
-            // }
-            // ```
-            ExprKind::Block { block } => {
-                if let thir::Block { stmts: box [], expr: Some(e), .. } = &self.body.blocks[*block]
-                {
-                    self.recurse_build(*e)?
-                } else {
-                    self.maybe_supported_error(GenericConstantTooComplexSub::BlockNotSupported(
-                        node.span,
-                    ))?
-                }
-            }
-            // `ExprKind::Use` happens when a `hir::ExprKind::Cast` is a
-            // "coercion cast" i.e. using a coercion or is a no-op.
-            // This is important so that `N as usize as usize` doesnt unify with `N as usize`. (untested)
-            &ExprKind::Use { source } => {
-                let arg = self.recurse_build(source)?;
-                self.nodes.push(Node::Cast(CastKind::Use, arg, node.ty))
-            }
-            &ExprKind::Cast { source } => {
-                let arg = self.recurse_build(source)?;
-                self.nodes.push(Node::Cast(CastKind::As, arg, node.ty))
-            }
-            ExprKind::Borrow { arg, .. } => {
-                let arg_node = &self.body.exprs[*arg];
-
-                // Skip reborrows for now until we allow Deref/Borrow/AddressOf
-                // expressions.
-                // FIXME(generic_const_exprs): Verify/explain why this is sound
-                if let ExprKind::Deref { arg } = arg_node.kind {
-                    self.recurse_build(arg)?
-                } else {
-                    self.maybe_supported_error(GenericConstantTooComplexSub::BorrowNotSupported(
-                        node.span,
-                    ))?
-                }
-            }
-            // FIXME(generic_const_exprs): We may want to support these.
-            ExprKind::AddressOf { .. } | ExprKind::Deref { .. } => self.maybe_supported_error(
-                GenericConstantTooComplexSub::AddressAndDerefNotSupported(node.span),
-            )?,
-            ExprKind::Repeat { .. } | ExprKind::Array { .. } => self.maybe_supported_error(
-                GenericConstantTooComplexSub::ArrayNotSupported(node.span),
-            )?,
-            ExprKind::NeverToAny { .. } => self.maybe_supported_error(
-                GenericConstantTooComplexSub::NeverToAnyNotSupported(node.span),
-            )?,
-            ExprKind::Tuple { .. } => self.maybe_supported_error(
-                GenericConstantTooComplexSub::TupleNotSupported(node.span),
-            )?,
-            ExprKind::Index { .. } => self.maybe_supported_error(
-                GenericConstantTooComplexSub::IndexNotSupported(node.span),
-            )?,
-            ExprKind::Field { .. } => self.maybe_supported_error(
-                GenericConstantTooComplexSub::FieldNotSupported(node.span),
-            )?,
-            ExprKind::ConstBlock { .. } => self.maybe_supported_error(
-                GenericConstantTooComplexSub::ConstBlockNotSupported(node.span),
-            )?,
-            ExprKind::Adt(_) => self
-                .maybe_supported_error(GenericConstantTooComplexSub::AdtNotSupported(node.span))?,
-            // dont know if this is correct
-            ExprKind::Pointer { .. } => {
-                self.error(GenericConstantTooComplexSub::PointerNotSupported(node.span))?
-            }
-            ExprKind::Yield { .. } => {
-                self.error(GenericConstantTooComplexSub::YieldNotSupported(node.span))?
-            }
-            ExprKind::Continue { .. } | ExprKind::Break { .. } | ExprKind::Loop { .. } => {
-                self.error(GenericConstantTooComplexSub::LoopNotSupported(node.span))?
-            }
-            ExprKind::Box { .. } => {
-                self.error(GenericConstantTooComplexSub::BoxNotSupported(node.span))?
-            }
+impl<'a, 'tcx> visit::Visitor<'a, 'tcx> for IsThirPolymorphic<'a, 'tcx> {
+    fn thir(&self) -> &'a thir::Thir<'tcx> {
+        &self.thir
+    }
 
-            ExprKind::Unary { .. } => unreachable!(),
-            // we handle valid unary/binary ops above
-            ExprKind::Binary { .. } => {
-                self.error(GenericConstantTooComplexSub::BinaryNotSupported(node.span))?
-            }
-            ExprKind::LogicalOp { .. } => {
-                self.error(GenericConstantTooComplexSub::LogicalOpNotSupported(node.span))?
-            }
-            ExprKind::Assign { .. } | ExprKind::AssignOp { .. } => {
-                self.error(GenericConstantTooComplexSub::AssignNotSupported(node.span))?
-            }
-            ExprKind::Closure { .. } | ExprKind::Return { .. } => {
-                self.error(GenericConstantTooComplexSub::ClosureAndReturnNotSupported(node.span))?
-            }
-            // let expressions imply control flow
-            ExprKind::Match { .. } | ExprKind::If { .. } | ExprKind::Let { .. } => {
-                self.error(GenericConstantTooComplexSub::ControlFlowNotSupported(node.span))?
-            }
-            ExprKind::InlineAsm { .. } => {
-                self.error(GenericConstantTooComplexSub::InlineAsmNotSupported(node.span))?
-            }
+    #[instrument(skip(self), level = "debug")]
+    fn visit_expr(&mut self, expr: &thir::Expr<'tcx>) {
+        self.is_poly |= self.expr_is_poly(expr);
+        if !self.is_poly {
+            visit::walk_expr(self, expr)
+        }
+    }
 
-            // we dont permit let stmts so `VarRef` and `UpvarRef` cant happen
-            ExprKind::VarRef { .. }
-            | ExprKind::UpvarRef { .. }
-            | ExprKind::StaticRef { .. }
-            | ExprKind::ThreadLocalRef(_) => {
-                self.error(GenericConstantTooComplexSub::OperationNotSupported(node.span))?
-            }
-        })
+    #[instrument(skip(self), level = "debug")]
+    fn visit_pat(&mut self, pat: &thir::Pat<'tcx>) {
+        self.is_poly |= self.pat_is_poly(pat);
+        if !self.is_poly {
+            visit::walk_pat(self, pat);
+        }
     }
 }
 
@@ -403,7 +356,7 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
 pub fn thir_abstract_const<'tcx>(
     tcx: TyCtxt<'tcx>,
     def: ty::WithOptConstParam<LocalDefId>,
-) -> Result<Option<&'tcx [Node<'tcx>]>, ErrorGuaranteed> {
+) -> Result<Option<ty::Const<'tcx>>, ErrorGuaranteed> {
     if tcx.features().generic_const_exprs {
         match tcx.def_kind(def.did) {
             // FIXME(generic_const_exprs): We currently only do this for anonymous constants,
@@ -416,10 +369,17 @@ pub fn thir_abstract_const<'tcx>(
         }
 
         let body = tcx.thir_body(def)?;
+        let (body, body_id) = (&*body.0.borrow(), body.1);
+
+        let mut is_poly_vis = IsThirPolymorphic { is_poly: false, thir: body };
+        visit::walk_expr(&mut is_poly_vis, &body[body_id]);
+        if !is_poly_vis.is_poly {
+            return Ok(None);
+        }
+
+        let root_span = body.exprs[body_id].span;
 
-        AbstractConstBuilder::new(tcx, (&*body.0.borrow(), body.1))?
-            .map(AbstractConstBuilder::build)
-            .transpose()
+        Some(recurse_build(tcx, body, body_id, root_span)).transpose()
     } else {
         Ok(None)
     }
diff --git a/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/const_equate_assoc_consts.rs b/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/const_equate_assoc_consts.rs
new file mode 100644
index 00000000000..e8f89cb1aa2
--- /dev/null
+++ b/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/const_equate_assoc_consts.rs
@@ -0,0 +1,27 @@
+// check-pass
+#![feature(generic_const_exprs)]
+#![allow(incomplete_features)]
+
+trait Trait {
+    const ASSOC: usize;
+}
+impl<T> Trait for T {
+    const ASSOC: usize = std::mem::size_of::<T>();
+}
+
+struct Foo<T: Trait>([u8; T::ASSOC])
+where
+    [(); T::ASSOC]:;
+
+fn bar<T: Trait>()
+where
+    [(); T::ASSOC]:,
+{
+    let _: Foo<T> = Foo::<_>(make());
+}
+
+fn make() -> ! {
+    todo!()
+}
+
+fn main() {}
diff --git a/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.rs b/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.rs
new file mode 100644
index 00000000000..c8f7553da79
--- /dev/null
+++ b/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.rs
@@ -0,0 +1,15 @@
+#![feature(generic_const_exprs)]
+#![allow(incomplete_features)]
+
+trait Trait {
+    const ASSOC: usize;
+}
+
+fn foo<T: Trait, U: Trait>() where [(); U::ASSOC]:, {
+    bar::<{ T::ASSOC }>();
+    //~^ ERROR: unconstrained generic constant
+}
+
+fn bar<const N: usize>() {}
+
+fn main() {}
diff --git a/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.stderr b/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.stderr
new file mode 100644
index 00000000000..e4a0cabe572
--- /dev/null
+++ b/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.stderr
@@ -0,0 +1,10 @@
+error: unconstrained generic constant
+  --> $DIR/doesnt_unify_evaluatable.rs:9:11
+   |
+LL |     bar::<{ T::ASSOC }>();
+   |           ^^^^^^^^^^^^
+   |
+   = help: try adding a `where` bound using this expression: `where [(); { T::ASSOC }]:`
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/dropck_unifies_assoc_consts.rs b/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/dropck_unifies_assoc_consts.rs
new file mode 100644
index 00000000000..274caa1e993
--- /dev/null
+++ b/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/dropck_unifies_assoc_consts.rs
@@ -0,0 +1,20 @@
+// check-pass
+#![feature(generic_const_exprs)]
+#![allow(incomplete_features)]
+
+trait Trait {
+    const ASSOC: usize;
+}
+
+struct Foo<T: Trait>(T)
+where
+    [(); T::ASSOC]:;
+
+impl<T: Trait> Drop for Foo<T>
+where
+    [(); T::ASSOC]:,
+{
+    fn drop(&mut self) {}
+}
+
+fn main() {}
diff --git a/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/unifies_evaluatable.rs b/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/unifies_evaluatable.rs
new file mode 100644
index 00000000000..6597b9f2b3f
--- /dev/null
+++ b/src/test/ui/const-generics/generic_const_exprs/assoc_const_unification/unifies_evaluatable.rs
@@ -0,0 +1,18 @@
+// check-pass
+
+#![feature(generic_const_exprs)]
+#![allow(incomplete_features)]
+
+trait Trait {
+    const ASSOC: usize;
+}
+
+fn foo<T: Trait, U: Trait>() where [(); T::ASSOC]:, {
+    bar::<{ T::ASSOC }>();
+}
+
+fn bar<const N: usize>() -> [(); N] {
+    [(); N]
+}
+
+fn main() {}
diff --git a/src/test/ui/const-generics/invariant.rs b/src/test/ui/const-generics/invariant.rs
index ee191b65c2c..39d658be67d 100644
--- a/src/test/ui/const-generics/invariant.rs
+++ b/src/test/ui/const-generics/invariant.rs
@@ -24,7 +24,8 @@ where
 fn covariant(
     v: &'static Foo<for<'a> fn(&'a ())>
 ) -> &'static Foo<fn(&'static ())> {
-    v //~ ERROR mismatched types
+    v
+    //~^ ERROR mismatched types
 }
 
 fn main() {
diff --git a/src/test/ui/const-generics/issues/issue-83765.stderr b/src/test/ui/const-generics/issues/issue-83765.stderr
index 4becf3a364c..d7b2b006c2a 100644
--- a/src/test/ui/const-generics/issues/issue-83765.stderr
+++ b/src/test/ui/const-generics/issues/issue-83765.stderr
@@ -1,15 +1,15 @@
-error[E0391]: cycle detected when resolving instance `<LazyUpdim<'_, T, { T::DIM }, DIM> as TensorDimension>::DIM`
+error[E0391]: cycle detected when resolving instance `<LazyUpdim<'_, T, <T as TensorDimension>::DIM, DIM> as TensorDimension>::DIM`
   --> $DIR/issue-83765.rs:5:5
    |
 LL |     const DIM: usize;
    |     ^^^^^^^^^^^^^^^^
    |
-note: ...which requires computing candidate for `<LazyUpdim<'_, T, { T::DIM }, DIM> as TensorDimension>`...
+note: ...which requires computing candidate for `<LazyUpdim<'_, T, <T as TensorDimension>::DIM, DIM> as TensorDimension>`...
   --> $DIR/issue-83765.rs:4:1
    |
 LL | trait TensorDimension {
    | ^^^^^^^^^^^^^^^^^^^^^
-   = note: ...which again requires resolving instance `<LazyUpdim<'_, T, { T::DIM }, DIM> as TensorDimension>::DIM`, completing the cycle
+   = note: ...which again requires resolving instance `<LazyUpdim<'_, T, <T as TensorDimension>::DIM, DIM> as TensorDimension>::DIM`, completing the cycle
 note: cycle used when computing candidate for `<LazyUpdim<'_, T, { T::DIM }, DIM> as TensorDimension>`
   --> $DIR/issue-83765.rs:4:1
    |
diff --git a/src/test/incremental/const-generics/try_unify_abstract_const_regression_tests/issue-85031-2.rs b/src/test/ui/const-generics/issues/issue-85031-2.rs
index db1e2fc2af4..4908fb29692 100644
--- a/src/test/incremental/const-generics/try_unify_abstract_const_regression_tests/issue-85031-2.rs
+++ b/src/test/ui/const-generics/issues/issue-85031-2.rs
@@ -1,4 +1,8 @@
-// revisions: cfail
+// check-pass
+// known-bug
+
+// This should not compile, as the compiler should not know
+// `A - 0` is satisfied `?x - 0` if `?x` is inferred to `A`.
 #![allow(incomplete_features)]
 #![feature(generic_const_exprs)]
 
@@ -6,8 +10,8 @@ pub struct Ref<'a>(&'a i32);
 
 impl<'a> Ref<'a> {
     pub fn foo<const A: usize>() -> [(); A - 0] {
+        //~^ WARN function cannot
         Self::foo()
-        //~^ error: type annotations needed
     }
 }
 
diff --git a/src/test/ui/const-generics/issues/issue-85031-2.stderr b/src/test/ui/const-generics/issues/issue-85031-2.stderr
new file mode 100644
index 00000000000..fc690576875
--- /dev/null
+++ b/src/test/ui/const-generics/issues/issue-85031-2.stderr
@@ -0,0 +1,14 @@
+warning: function cannot return without recursing
+  --> $DIR/issue-85031-2.rs:12:5
+   |
+LL |     pub fn foo<const A: usize>() -> [(); A - 0] {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing
+LL |
+LL |         Self::foo()
+   |         ----------- recursive call site
+   |
+   = help: a `loop` may express intention better if this is on purpose
+   = note: `#[warn(unconditional_recursion)]` on by default
+
+warning: 1 warning emitted
+