diff --git a/compiler/rustc_trait_selection/src/solve/assembly.rs b/compiler/rustc_trait_selection/src/solve/assembly.rs
index 841169ac78d7d..d55aebf1a2696 100644
--- a/compiler/rustc_trait_selection/src/solve/assembly.rs
+++ b/compiler/rustc_trait_selection/src/solve/assembly.rs
@@ -1,6 +1,5 @@
 //! Code shared by trait and projection goals for candidate assembly.
 
-use super::infcx_ext::InferCtxtExt;
 #[cfg(doc)]
 use super::trait_goals::structural_traits::*;
 use super::{CanonicalResponse, Certainty, EvalCtxt, Goal, MaybeCause, QueryResult};
@@ -206,7 +205,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         &mut self,
         goal: Goal<'tcx, G>,
     ) -> Vec<Candidate<'tcx>> {
-        debug_assert_eq!(goal, self.infcx.resolve_vars_if_possible(goal));
+        debug_assert_eq!(goal, self.resolve_vars_if_possible(goal));
 
         // HACK: `_: Trait` is ambiguous, because it may be satisfied via a builtin rule,
         // object bound, alias bound, etc. We are unable to determine this until we can at
@@ -250,8 +249,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         let &ty::Alias(ty::Projection, projection_ty) = goal.predicate.self_ty().kind() else {
             return
         };
-        self.infcx.probe(|_| {
-            let normalized_ty = self.infcx.next_ty_infer();
+        self.probe(|this| {
+            let normalized_ty = this.next_ty_infer();
             let normalizes_to_goal = goal.with(
                 tcx,
                 ty::Binder::dummy(ty::ProjectionPredicate {
@@ -259,16 +258,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
                     term: normalized_ty.into(),
                 }),
             );
-            let normalization_certainty = match self.evaluate_goal(normalizes_to_goal) {
+            let normalization_certainty = match this.evaluate_goal(normalizes_to_goal) {
                 Ok((_, certainty)) => certainty,
                 Err(NoSolution) => return,
             };
-            let normalized_ty = self.infcx.resolve_vars_if_possible(normalized_ty);
+            let normalized_ty = this.resolve_vars_if_possible(normalized_ty);
 
             // NOTE: Alternatively we could call `evaluate_goal` here and only have a `Normalized` candidate.
             // This doesn't work as long as we use `CandidateSource` in winnowing.
             let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty));
-            let normalized_candidates = self.assemble_and_evaluate_candidates(goal);
+            let normalized_candidates = this.assemble_and_evaluate_candidates(goal);
             for mut normalized_candidate in normalized_candidates {
                 normalized_candidate.result =
                     normalized_candidate.result.unchecked_map(|mut response| {
diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs
new file mode 100644
index 0000000000000..67addd732d0ad
--- /dev/null
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs
@@ -0,0 +1,172 @@
+use rustc_hir::def_id::DefId;
+use rustc_infer::infer::at::ToTrace;
+use rustc_infer::infer::canonical::CanonicalVarValues;
+use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
+use rustc_infer::infer::{InferCtxt, InferOk, LateBoundRegionConversionTime};
+use rustc_infer::traits::query::NoSolution;
+use rustc_infer::traits::ObligationCause;
+use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
+use rustc_middle::ty::{self, ir::TypeVisitor, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable};
+use rustc_span::DUMMY_SP;
+use std::ops::ControlFlow;
+
+use super::search_graph::SearchGraph;
+use super::Goal;
+
+pub struct EvalCtxt<'a, 'tcx> {
+    // FIXME: should be private.
+    pub(super) infcx: &'a InferCtxt<'tcx>,
+
+    pub(super) var_values: CanonicalVarValues<'tcx>,
+
+    pub(super) search_graph: &'a mut SearchGraph<'tcx>,
+
+    /// This field is used by a debug assertion in [`EvalCtxt::evaluate_goal`],
+    /// see the comment in that method for more details.
+    pub in_projection_eq_hack: bool,
+}
+
+impl<'tcx> EvalCtxt<'_, 'tcx> {
+    pub(super) fn probe<T>(&mut self, f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> T) -> T {
+        self.infcx.probe(|_| f(self))
+    }
+
+    pub(super) fn tcx(&self) -> TyCtxt<'tcx> {
+        self.infcx.tcx
+    }
+
+    pub(super) fn next_ty_infer(&self) -> Ty<'tcx> {
+        self.infcx.next_ty_var(TypeVariableOrigin {
+            kind: TypeVariableOriginKind::MiscVariable,
+            span: DUMMY_SP,
+        })
+    }
+
+    pub(super) fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx> {
+        self.infcx.next_const_var(
+            ty,
+            ConstVariableOrigin { kind: ConstVariableOriginKind::MiscVariable, span: DUMMY_SP },
+        )
+    }
+
+    /// Is the projection predicate is of the form `exists<T> <Ty as Trait>::Assoc = T`.
+    ///
+    /// This is the case if the `term` is an inference variable in the innermost universe
+    /// and does not occur in any other part of the predicate.
+    pub(super) fn term_is_fully_unconstrained(
+        &self,
+        goal: Goal<'tcx, ty::ProjectionPredicate<'tcx>>,
+    ) -> bool {
+        let term_is_infer = match goal.predicate.term.unpack() {
+            ty::TermKind::Ty(ty) => {
+                if let &ty::Infer(ty::TyVar(vid)) = ty.kind() {
+                    match self.infcx.probe_ty_var(vid) {
+                        Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"),
+                        Err(universe) => universe == self.universe(),
+                    }
+                } else {
+                    false
+                }
+            }
+            ty::TermKind::Const(ct) => {
+                if let ty::ConstKind::Infer(ty::InferConst::Var(vid)) = ct.kind() {
+                    match self.infcx.probe_const_var(vid) {
+                        Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"),
+                        Err(universe) => universe == self.universe(),
+                    }
+                } else {
+                    false
+                }
+            }
+        };
+
+        // Guard against `<T as Trait<?0>>::Assoc = ?0>`.
+        struct ContainsTerm<'tcx> {
+            term: ty::Term<'tcx>,
+        }
+        impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsTerm<'tcx> {
+            type BreakTy = ();
+            fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+                if t.needs_infer() {
+                    if ty::Term::from(t) == self.term {
+                        ControlFlow::Break(())
+                    } else {
+                        t.super_visit_with(self)
+                    }
+                } else {
+                    ControlFlow::Continue(())
+                }
+            }
+
+            fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
+                if c.needs_infer() {
+                    if ty::Term::from(c) == self.term {
+                        ControlFlow::Break(())
+                    } else {
+                        c.super_visit_with(self)
+                    }
+                } else {
+                    ControlFlow::Continue(())
+                }
+            }
+        }
+
+        let mut visitor = ContainsTerm { term: goal.predicate.term };
+
+        term_is_infer
+            && goal.predicate.projection_ty.visit_with(&mut visitor).is_continue()
+            && goal.param_env.visit_with(&mut visitor).is_continue()
+    }
+
+    #[instrument(level = "debug", skip(self, param_env), ret)]
+    pub(super) fn eq<T: ToTrace<'tcx>>(
+        &self,
+        param_env: ty::ParamEnv<'tcx>,
+        lhs: T,
+        rhs: T,
+    ) -> Result<Vec<Goal<'tcx, ty::Predicate<'tcx>>>, NoSolution> {
+        self.infcx
+            .at(&ObligationCause::dummy(), param_env)
+            .eq(lhs, rhs)
+            .map(|InferOk { value: (), obligations }| {
+                obligations.into_iter().map(|o| o.into()).collect()
+            })
+            .map_err(|e| {
+                debug!(?e, "failed to equate");
+                NoSolution
+            })
+    }
+
+    pub(super) fn instantiate_binder_with_infer<T: TypeFoldable<'tcx> + Copy>(
+        &self,
+        value: ty::Binder<'tcx, T>,
+    ) -> T {
+        self.infcx.instantiate_binder_with_fresh_vars(
+            DUMMY_SP,
+            LateBoundRegionConversionTime::HigherRankedType,
+            value,
+        )
+    }
+
+    pub(super) fn instantiate_binder_with_placeholders<T: TypeFoldable<'tcx> + Copy>(
+        &self,
+        value: ty::Binder<'tcx, T>,
+    ) -> T {
+        self.infcx.instantiate_binder_with_placeholders(value)
+    }
+
+    pub(super) fn resolve_vars_if_possible<T>(&self, value: T) -> T
+    where
+        T: TypeFoldable<'tcx>,
+    {
+        self.infcx.resolve_vars_if_possible(value)
+    }
+
+    pub(super) fn fresh_substs_for_item(&self, def_id: DefId) -> ty::SubstsRef<'tcx> {
+        self.infcx.fresh_substs_for_item(DUMMY_SP, def_id)
+    }
+
+    pub(super) fn universe(&self) -> ty::UniverseIndex {
+        self.infcx.universe()
+    }
+}
diff --git a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs
deleted file mode 100644
index 06570e1f4b41c..0000000000000
--- a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-use rustc_infer::infer::at::ToTrace;
-use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
-use rustc_infer::infer::{InferCtxt, InferOk, LateBoundRegionConversionTime};
-use rustc_infer::traits::query::NoSolution;
-use rustc_infer::traits::ObligationCause;
-use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
-use rustc_middle::ty::{self, Ty, TypeFoldable};
-use rustc_span::DUMMY_SP;
-
-use super::Goal;
-
-/// Methods used inside of the canonical queries of the solver.
-///
-/// Most notably these do not care about diagnostics information.
-/// If you find this while looking for methods to use outside of the
-/// solver, you may look at the implementation of these method for
-/// help.
-pub(super) trait InferCtxtExt<'tcx> {
-    fn next_ty_infer(&self) -> Ty<'tcx>;
-    fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx>;
-
-    fn eq<T: ToTrace<'tcx>>(
-        &self,
-        param_env: ty::ParamEnv<'tcx>,
-        lhs: T,
-        rhs: T,
-    ) -> Result<Vec<Goal<'tcx, ty::Predicate<'tcx>>>, NoSolution>;
-
-    fn instantiate_binder_with_infer<T: TypeFoldable<'tcx> + Copy>(
-        &self,
-        value: ty::Binder<'tcx, T>,
-    ) -> T;
-}
-
-impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
-    fn next_ty_infer(&self) -> Ty<'tcx> {
-        self.next_ty_var(TypeVariableOrigin {
-            kind: TypeVariableOriginKind::MiscVariable,
-            span: DUMMY_SP,
-        })
-    }
-    fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx> {
-        self.next_const_var(
-            ty,
-            ConstVariableOrigin { kind: ConstVariableOriginKind::MiscVariable, span: DUMMY_SP },
-        )
-    }
-
-    #[instrument(level = "debug", skip(self, param_env), ret)]
-    fn eq<T: ToTrace<'tcx>>(
-        &self,
-        param_env: ty::ParamEnv<'tcx>,
-        lhs: T,
-        rhs: T,
-    ) -> Result<Vec<Goal<'tcx, ty::Predicate<'tcx>>>, NoSolution> {
-        self.at(&ObligationCause::dummy(), param_env)
-            .eq(lhs, rhs)
-            .map(|InferOk { value: (), obligations }| {
-                obligations.into_iter().map(|o| o.into()).collect()
-            })
-            .map_err(|e| {
-                debug!(?e, "failed to equate");
-                NoSolution
-            })
-    }
-
-    fn instantiate_binder_with_infer<T: TypeFoldable<'tcx> + Copy>(
-        &self,
-        value: ty::Binder<'tcx, T>,
-    ) -> T {
-        self.instantiate_binder_with_fresh_vars(
-            DUMMY_SP,
-            LateBoundRegionConversionTime::HigherRankedType,
-            value,
-        )
-    }
-}
diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs
index 6890811fd046e..c080f7e59fa22 100644
--- a/compiler/rustc_trait_selection/src/solve/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/mod.rs
@@ -35,16 +35,15 @@ use crate::solve::search_graph::OverflowHandler;
 use crate::traits::ObligationCause;
 
 mod assembly;
+mod eval_ctxt;
 mod fulfill;
-mod infcx_ext;
 mod project_goals;
 mod search_graph;
 mod trait_goals;
 
+pub use eval_ctxt::EvalCtxt;
 pub use fulfill::FulfillmentCtxt;
 
-use self::infcx_ext::InferCtxtExt;
-
 /// A goal is a statement, i.e. `predicate`, we want to prove
 /// given some assumptions, i.e. `param_env`.
 ///
@@ -180,22 +179,7 @@ impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> {
     }
 }
 
-struct EvalCtxt<'a, 'tcx> {
-    infcx: &'a InferCtxt<'tcx>,
-    var_values: CanonicalVarValues<'tcx>,
-
-    search_graph: &'a mut search_graph::SearchGraph<'tcx>,
-
-    /// This field is used by a debug assertion in [`EvalCtxt::evaluate_goal`],
-    /// see the comment in that method for more details.
-    in_projection_eq_hack: bool,
-}
-
 impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
-    fn tcx(&self) -> TyCtxt<'tcx> {
-        self.infcx.tcx
-    }
-
     /// The entry point of the solver.
     ///
     /// This function deals with (coinductive) cycles, overflow, and caching
@@ -427,7 +411,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
 
         let evaluate_normalizes_to = |ecx: &mut EvalCtxt<'_, 'tcx>, alias, other| {
             debug!("evaluate_normalizes_to(alias={:?}, other={:?})", alias, other);
-            let r = ecx.infcx.probe(|_| {
+            let r = ecx.probe(|ecx| {
                 let (_, certainty) = ecx.evaluate_goal(goal.with(
                     tcx,
                     ty::Binder::dummy(ty::ProjectionPredicate {
@@ -462,10 +446,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
                 // Evaluate all 3 potential candidates for the alias' being equal
                 candidates.push(evaluate_normalizes_to(self, alias_lhs, goal.predicate.1));
                 candidates.push(evaluate_normalizes_to(self, alias_rhs, goal.predicate.0));
-                candidates.push(self.infcx.probe(|_| {
+                candidates.push(self.probe(|this| {
                     debug!("compute_alias_eq_goal: alias defids are equal, equating substs");
-                    let nested_goals = self.infcx.eq(goal.param_env, alias_lhs, alias_rhs)?;
-                    self.evaluate_all_and_make_canonical_response(nested_goals)
+                    let nested_goals = this.eq(goal.param_env, alias_lhs, alias_rhs)?;
+                    this.evaluate_all_and_make_canonical_response(nested_goals)
                 }));
 
                 debug!(?candidates);
@@ -481,7 +465,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
         goal: Goal<'tcx, (ty::Const<'tcx>, Ty<'tcx>)>,
     ) -> QueryResult<'tcx> {
         let (ct, ty) = goal.predicate;
-        let nested_goals = self.infcx.eq(goal.param_env, ct.ty(), ty)?;
+        let nested_goals = self.eq(goal.param_env, ct.ty(), ty)?;
         self.evaluate_all_and_make_canonical_response(nested_goals)
     }
 }
diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs
index 48153b465b7ec..9f83bfa1ccb9b 100644
--- a/compiler/rustc_trait_selection/src/solve/project_goals.rs
+++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs
@@ -1,7 +1,6 @@
 use crate::traits::{specialization_graph, translate_substs};
 
 use super::assembly;
-use super::infcx_ext::InferCtxtExt;
 use super::trait_goals::structural_traits;
 use super::{Certainty, EvalCtxt, Goal, QueryResult};
 use rustc_errors::ErrorGuaranteed;
@@ -13,12 +12,11 @@ use rustc_infer::traits::query::NoSolution;
 use rustc_infer::traits::specialization_graph::LeafDef;
 use rustc_infer::traits::Reveal;
 use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
+use rustc_middle::ty::ProjectionPredicate;
 use rustc_middle::ty::{self, Ty, TyCtxt};
-use rustc_middle::ty::{ir::TypeVisitor, ProjectionPredicate, TypeSuperVisitable};
 use rustc_middle::ty::{ToPredicate, TypeVisitable};
 use rustc_span::{sym, DUMMY_SP};
 use std::iter;
-use std::ops::ControlFlow;
 
 impl<'tcx> EvalCtxt<'_, 'tcx> {
     pub(super) fn compute_projection_goal(
@@ -38,8 +36,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         } else {
             let predicate = goal.predicate;
             let unconstrained_rhs = match predicate.term.unpack() {
-                ty::TermKind::Ty(_) => self.infcx.next_ty_infer().into(),
-                ty::TermKind::Const(ct) => self.infcx.next_const_infer(ct.ty()).into(),
+                ty::TermKind::Ty(_) => self.next_ty_infer().into(),
+                ty::TermKind::Const(ct) => self.next_const_infer(ct.ty()).into(),
             };
             let unconstrained_predicate = ty::Clause::Projection(ProjectionPredicate {
                 projection_ty: goal.predicate.projection_ty,
@@ -49,8 +47,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
                 this.evaluate_goal(goal.with(this.tcx(), unconstrained_predicate))
             })?;
 
-            let nested_eq_goals =
-                self.infcx.eq(goal.param_env, unconstrained_rhs, predicate.term)?;
+            let nested_eq_goals = self.eq(goal.param_env, unconstrained_rhs, predicate.term)?;
             let eval_certainty = self.evaluate_all(nested_eq_goals)?;
             self.make_canonical_response(normalize_certainty.unify_and(eval_certainty))
         }
@@ -65,73 +62,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         result
     }
 
-    /// Is the projection predicate is of the form `exists<T> <Ty as Trait>::Assoc = T`.
-    ///
-    /// This is the case if the `term` is an inference variable in the innermost universe
-    /// and does not occur in any other part of the predicate.
-    fn term_is_fully_unconstrained(&self, goal: Goal<'tcx, ProjectionPredicate<'tcx>>) -> bool {
-        let infcx = self.infcx;
-        let term_is_infer = match goal.predicate.term.unpack() {
-            ty::TermKind::Ty(ty) => {
-                if let &ty::Infer(ty::TyVar(vid)) = ty.kind() {
-                    match infcx.probe_ty_var(vid) {
-                        Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"),
-                        Err(universe) => universe == infcx.universe(),
-                    }
-                } else {
-                    false
-                }
-            }
-            ty::TermKind::Const(ct) => {
-                if let ty::ConstKind::Infer(ty::InferConst::Var(vid)) = ct.kind() {
-                    match self.infcx.probe_const_var(vid) {
-                        Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"),
-                        Err(universe) => universe == infcx.universe(),
-                    }
-                } else {
-                    false
-                }
-            }
-        };
-
-        // Guard against `<T as Trait<?0>>::Assoc = ?0>`.
-        struct ContainsTerm<'tcx> {
-            term: ty::Term<'tcx>,
-        }
-        impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsTerm<'tcx> {
-            type BreakTy = ();
-            fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-                if t.needs_infer() {
-                    if ty::Term::from(t) == self.term {
-                        ControlFlow::Break(())
-                    } else {
-                        t.super_visit_with(self)
-                    }
-                } else {
-                    ControlFlow::Continue(())
-                }
-            }
-
-            fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
-                if c.needs_infer() {
-                    if ty::Term::from(c) == self.term {
-                        ControlFlow::Break(())
-                    } else {
-                        c.super_visit_with(self)
-                    }
-                } else {
-                    ControlFlow::Continue(())
-                }
-            }
-        }
-
-        let mut visitor = ContainsTerm { term: goal.predicate.term };
-
-        term_is_infer
-            && goal.predicate.projection_ty.visit_with(&mut visitor).is_continue()
-            && goal.param_env.visit_with(&mut visitor).is_continue()
-    }
-
     /// After normalizing the projection to `normalized_alias` with the given
     /// `normalization_certainty`, constrain the inference variable `term` to it
     /// and return a query response.
@@ -145,7 +75,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         //
         // It can however be ambiguous when the `normalized_alias` contains a projection.
         let nested_goals = self
-            .infcx
             .eq(goal.param_env, goal.predicate.term, normalized_alias.into())
             .expect("failed to unify with unconstrained term");
         let rhs_certainty =
@@ -177,10 +106,10 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
         if let Some(poly_projection_pred) = assumption.to_opt_poly_projection_pred()
             && poly_projection_pred.projection_def_id() == goal.predicate.def_id()
         {
-            ecx.infcx.probe(|_| {
+            ecx.probe(|ecx| {
                 let assumption_projection_pred =
-                    ecx.infcx.instantiate_binder_with_infer(poly_projection_pred);
-                let mut nested_goals = ecx.infcx.eq(
+                    ecx.instantiate_binder_with_infer(poly_projection_pred);
+                let mut nested_goals = ecx.eq(
                     goal.param_env,
                     goal.predicate.projection_ty,
                     assumption_projection_pred.projection_ty,
@@ -215,11 +144,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
             return Err(NoSolution);
         }
 
-        ecx.infcx.probe(|_| {
-            let impl_substs = ecx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
+        ecx.probe(|ecx| {
+            let impl_substs = ecx.fresh_substs_for_item(impl_def_id);
             let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs);
 
-            let mut nested_goals = ecx.infcx.eq(goal.param_env, goal_trait_ref, impl_trait_ref)?;
+            let mut nested_goals = ecx.eq(goal.param_env, goal_trait_ref, impl_trait_ref)?;
             let where_clause_bounds = tcx
                 .predicates_of(impl_def_id)
                 .instantiate(tcx, impl_substs)
@@ -367,7 +296,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
         goal: Goal<'tcx, Self>,
     ) -> QueryResult<'tcx> {
         let tcx = ecx.tcx();
-        ecx.infcx.probe(|_| {
+        ecx.probe(|ecx| {
             let metadata_ty = match goal.predicate.self_ty().kind() {
                 ty::Bool
                 | ty::Char
@@ -546,8 +475,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
         goal: Goal<'tcx, Self>,
     ) -> QueryResult<'tcx> {
         let discriminant = goal.predicate.self_ty().discriminant_ty(ecx.tcx());
-        ecx.infcx
-            .probe(|_| ecx.eq_term_and_make_canonical_response(goal, Certainty::Yes, discriminant))
+        ecx.probe(|ecx| ecx.eq_term_and_make_canonical_response(goal, Certainty::Yes, discriminant))
     }
 }
 
diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs
index f2f25ef850a9a..6d1381bbf0c9f 100644
--- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs
+++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs
@@ -3,11 +3,9 @@
 use std::iter;
 
 use super::assembly;
-use super::infcx_ext::InferCtxtExt;
 use super::{CanonicalResponse, Certainty, EvalCtxt, Goal, QueryResult};
 use rustc_hir::def_id::DefId;
 use rustc_hir::LangItem;
-use rustc_infer::infer::InferCtxt;
 use rustc_infer::traits::query::NoSolution;
 use rustc_infer::traits::util::supertraits;
 use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
@@ -45,12 +43,12 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
             return Err(NoSolution);
         }
 
-        ecx.infcx.probe(|_| {
-            let impl_substs = ecx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
+        ecx.probe(|ecx| {
+            let impl_substs = ecx.fresh_substs_for_item(impl_def_id);
             let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs);
 
             let mut nested_goals =
-                ecx.infcx.eq(goal.param_env, goal.predicate.trait_ref, impl_trait_ref)?;
+                ecx.eq(goal.param_env, goal.predicate.trait_ref, impl_trait_ref)?;
             let where_clause_bounds = tcx
                 .predicates_of(impl_def_id)
                 .instantiate(tcx, impl_substs)
@@ -72,10 +70,10 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
             && poly_trait_pred.def_id() == goal.predicate.def_id()
         {
             // FIXME: Constness and polarity
-            ecx.infcx.probe(|_| {
+            ecx.probe(|ecx| {
                 let assumption_trait_pred =
-                    ecx.infcx.instantiate_binder_with_infer(poly_trait_pred);
-                let mut nested_goals = ecx.infcx.eq(
+                    ecx.instantiate_binder_with_infer(poly_trait_pred);
+                let mut nested_goals = ecx.eq(
                     goal.param_env,
                     goal.predicate.trait_ref,
                     assumption_trait_pred.trait_ref,
@@ -118,7 +116,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
     ) -> QueryResult<'tcx> {
         let tcx = ecx.tcx();
 
-        ecx.infcx.probe(|_| {
+        ecx.probe(|ecx| {
             let nested_obligations = tcx
                 .predicates_of(goal.predicate.def_id())
                 .instantiate(tcx, goal.predicate.trait_ref.substs);
@@ -275,7 +273,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
         if b_ty.is_ty_var() {
             return ecx.make_canonical_response(Certainty::AMBIGUOUS);
         }
-        ecx.infcx.probe(|_| {
+        ecx.probe(|ecx| {
             match (a_ty.kind(), b_ty.kind()) {
                 // Trait upcasting, or `dyn Trait + Auto + 'a` -> `dyn Trait + 'b`
                 (&ty::Dynamic(_, _, ty::Dyn), &ty::Dynamic(_, _, ty::Dyn)) => {
@@ -318,7 +316,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
                 // `[T; n]` -> `[T]` unsizing
                 (&ty::Array(a_elem_ty, ..), &ty::Slice(b_elem_ty)) => {
                     // We just require that the element type stays the same
-                    let nested_goals = ecx.infcx.eq(goal.param_env, a_elem_ty, b_elem_ty)?;
+                    let nested_goals = ecx.eq(goal.param_env, a_elem_ty, b_elem_ty)?;
                     ecx.evaluate_all_and_make_canonical_response(nested_goals)
                 }
                 // Struct unsizing `Struct<T>` -> `Struct<U>` where `T: Unsize<U>`
@@ -352,7 +350,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
 
                     // Finally, we require that `TailA: Unsize<TailB>` for the tail field
                     // types.
-                    let mut nested_goals = ecx.infcx.eq(goal.param_env, unsized_a_ty, b_ty)?;
+                    let mut nested_goals = ecx.eq(goal.param_env, unsized_a_ty, b_ty)?;
                     nested_goals.push(goal.with(
                         tcx,
                         ty::Binder::dummy(
@@ -371,7 +369,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
 
                     // Substitute just the tail field of B., and require that they're equal.
                     let unsized_a_ty = tcx.mk_tup(a_rest_tys.iter().chain([b_last_ty]).copied());
-                    let mut nested_goals = ecx.infcx.eq(goal.param_env, unsized_a_ty, b_ty)?;
+                    let mut nested_goals = ecx.eq(goal.param_env, unsized_a_ty, b_ty)?;
 
                     // Similar to ADTs, require that the rest of the fields are equal.
                     nested_goals.push(goal.with(
@@ -411,7 +409,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
         }
 
         let mut unsize_dyn_to_principal = |principal: Option<ty::PolyExistentialTraitRef<'tcx>>| {
-            ecx.infcx.probe(|_| -> Result<_, NoSolution> {
+            ecx.probe(|ecx| -> Result<_, NoSolution> {
                 // Require that all of the trait predicates from A match B, except for
                 // the auto traits. We do this by constructing a new A type with B's
                 // auto traits, and equating these types.
@@ -431,7 +429,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
                 let new_a_ty = tcx.mk_dynamic(new_a_data, b_region, ty::Dyn);
 
                 // We also require that A's lifetime outlives B's lifetime.
-                let mut nested_obligations = ecx.infcx.eq(goal.param_env, new_a_ty, b_ty)?;
+                let mut nested_obligations = ecx.eq(goal.param_env, new_a_ty, b_ty)?;
                 nested_obligations.push(
                     goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region))),
                 );
@@ -482,16 +480,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
     fn probe_and_evaluate_goal_for_constituent_tys(
         &mut self,
         goal: Goal<'tcx, TraitPredicate<'tcx>>,
-        constituent_tys: impl Fn(&InferCtxt<'tcx>, Ty<'tcx>) -> Result<Vec<Ty<'tcx>>, NoSolution>,
+        constituent_tys: impl Fn(&EvalCtxt<'_, 'tcx>, Ty<'tcx>) -> Result<Vec<Ty<'tcx>>, NoSolution>,
     ) -> QueryResult<'tcx> {
-        self.infcx.probe(|_| {
-            self.evaluate_all_and_make_canonical_response(
-                constituent_tys(self.infcx, goal.predicate.self_ty())?
+        self.probe(|this| {
+            this.evaluate_all_and_make_canonical_response(
+                constituent_tys(this, goal.predicate.self_ty())?
                     .into_iter()
                     .map(|ty| {
                         goal.with(
-                            self.tcx(),
-                            ty::Binder::dummy(goal.predicate.with_self_ty(self.tcx(), ty)),
+                            this.tcx(),
+                            ty::Binder::dummy(goal.predicate.with_self_ty(this.tcx(), ty)),
                         )
                     })
                     .collect(),
diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs b/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs
index 3662463178f85..2c13465d347c4 100644
--- a/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs
+++ b/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs
@@ -1,16 +1,18 @@
 use rustc_hir::{Movability, Mutability};
-use rustc_infer::{infer::InferCtxt, traits::query::NoSolution};
+use rustc_infer::traits::query::NoSolution;
 use rustc_middle::ty::{self, Ty, TyCtxt};
 
+use crate::solve::EvalCtxt;
+
 // Calculates the constituent types of a type for `auto trait` purposes.
 //
 // For types with an "existential" binder, i.e. generator witnesses, we also
 // instantiate the binder with placeholders eagerly.
 pub(super) fn instantiate_constituent_tys_for_auto_trait<'tcx>(
-    infcx: &InferCtxt<'tcx>,
+    ecx: &EvalCtxt<'_, 'tcx>,
     ty: Ty<'tcx>,
 ) -> Result<Vec<Ty<'tcx>>, NoSolution> {
-    let tcx = infcx.tcx;
+    let tcx = ecx.tcx();
     match *ty.kind() {
         ty::Uint(_)
         | ty::Int(_)
@@ -53,9 +55,7 @@ pub(super) fn instantiate_constituent_tys_for_auto_trait<'tcx>(
             Ok(vec![generator_substs.tupled_upvars_ty(), generator_substs.witness()])
         }
 
-        ty::GeneratorWitness(types) => {
-            Ok(infcx.instantiate_binder_with_placeholders(types).to_vec())
-        }
+        ty::GeneratorWitness(types) => Ok(ecx.instantiate_binder_with_placeholders(types).to_vec()),
 
         ty::GeneratorWitnessMIR(..) => todo!(),
 
@@ -74,7 +74,7 @@ pub(super) fn instantiate_constituent_tys_for_auto_trait<'tcx>(
 }
 
 pub(super) fn instantiate_constituent_tys_for_sized_trait<'tcx>(
-    infcx: &InferCtxt<'tcx>,
+    ecx: &EvalCtxt<'_, 'tcx>,
     ty: Ty<'tcx>,
 ) -> Result<Vec<Ty<'tcx>>, NoSolution> {
     match *ty.kind() {
@@ -113,18 +113,18 @@ pub(super) fn instantiate_constituent_tys_for_sized_trait<'tcx>(
         ty::Tuple(tys) => Ok(tys.to_vec()),
 
         ty::Adt(def, substs) => {
-            let sized_crit = def.sized_constraint(infcx.tcx);
+            let sized_crit = def.sized_constraint(ecx.tcx());
             Ok(sized_crit
                 .0
                 .iter()
-                .map(|ty| sized_crit.rebind(*ty).subst(infcx.tcx, substs))
+                .map(|ty| sized_crit.rebind(*ty).subst(ecx.tcx(), substs))
                 .collect())
         }
     }
 }
 
 pub(super) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>(
-    infcx: &InferCtxt<'tcx>,
+    ecx: &EvalCtxt<'_, 'tcx>,
     ty: Ty<'tcx>,
 ) -> Result<Vec<Ty<'tcx>>, NoSolution> {
     match *ty.kind() {
@@ -165,7 +165,7 @@ pub(super) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>(
         ty::Closure(_, substs) => Ok(vec![substs.as_closure().tupled_upvars_ty()]),
 
         ty::Generator(_, substs, Movability::Movable) => {
-            if infcx.tcx.features().generator_clone {
+            if ecx.tcx().features().generator_clone {
                 let generator = substs.as_generator();
                 Ok(vec![generator.tupled_upvars_ty(), generator.witness()])
             } else {
@@ -173,9 +173,7 @@ pub(super) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>(
             }
         }
 
-        ty::GeneratorWitness(types) => {
-            Ok(infcx.instantiate_binder_with_placeholders(types).to_vec())
-        }
+        ty::GeneratorWitness(types) => Ok(ecx.instantiate_binder_with_placeholders(types).to_vec()),
 
         ty::GeneratorWitnessMIR(..) => todo!(),
     }