diff --git a/compiler/rustc_middle/src/traits/solve/inspect.rs b/compiler/rustc_middle/src/traits/solve/inspect.rs
index 16842c8208f..054772daab8 100644
--- a/compiler/rustc_middle/src/traits/solve/inspect.rs
+++ b/compiler/rustc_middle/src/traits/solve/inspect.rs
@@ -121,8 +121,6 @@ pub enum ProbeStep<'tcx> {
     /// used whenever there are multiple candidates to prove the
     /// current goalby .
     NestedProbe(Probe<'tcx>),
-    CommitIfOkStart,
-    CommitIfOkSuccess,
 }
 
 /// What kind of probe we're in. In case the probe represents a candidate, or
@@ -132,6 +130,8 @@ pub enum ProbeStep<'tcx> {
 pub enum ProbeKind<'tcx> {
     /// The root inference context while proving a goal.
     Root { result: QueryResult<'tcx> },
+    /// Trying to normalize an alias by at least one stpe in `NormalizesTo`.
+    TryNormalizeNonRigid { result: QueryResult<'tcx> },
     /// Probe entered when normalizing the self ty during candidate assembly
     NormalizedSelfTyAssembly,
     /// Some candidate to prove the current goal.
@@ -143,9 +143,6 @@ pub enum ProbeKind<'tcx> {
     /// Used in the probe that wraps normalizing the non-self type for the unsize
     /// trait, which is also structurally matched on.
     UnsizeAssembly,
-    /// A call to `EvalCtxt::commit_if_ok` which failed, causing the work
-    /// to be discarded.
-    CommitIfOk,
     /// During upcasting from some source object to target object type, used to
     /// do a probe to find out what projection type(s) may be used to prove that
     /// the source type upholds all of the target type's object bounds.
diff --git a/compiler/rustc_middle/src/traits/solve/inspect/format.rs b/compiler/rustc_middle/src/traits/solve/inspect/format.rs
index 43931205017..98f01fe8772 100644
--- a/compiler/rustc_middle/src/traits/solve/inspect/format.rs
+++ b/compiler/rustc_middle/src/traits/solve/inspect/format.rs
@@ -100,6 +100,9 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
             ProbeKind::Root { result } => {
                 write!(self.f, "ROOT RESULT: {result:?}")
             }
+            ProbeKind::TryNormalizeNonRigid { result } => {
+                write!(self.f, "TRY NORMALIZE NON-RIGID: {result:?}")
+            }
             ProbeKind::NormalizedSelfTyAssembly => {
                 write!(self.f, "NORMALIZING SELF TY FOR ASSEMBLY:")
             }
@@ -109,9 +112,6 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
             ProbeKind::UpcastProjectionCompatibility => {
                 write!(self.f, "PROBING FOR PROJECTION COMPATIBILITY FOR UPCASTING:")
             }
-            ProbeKind::CommitIfOk => {
-                write!(self.f, "COMMIT_IF_OK:")
-            }
             ProbeKind::MiscCandidate { name, result } => {
                 write!(self.f, "CANDIDATE {name}: {result:?}")
             }
@@ -132,8 +132,6 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
                     }
                     ProbeStep::EvaluateGoals(eval) => this.format_added_goals_evaluation(eval)?,
                     ProbeStep::NestedProbe(probe) => this.format_probe(probe)?,
-                    ProbeStep::CommitIfOkStart => writeln!(this.f, "COMMIT_IF_OK START")?,
-                    ProbeStep::CommitIfOkSuccess => writeln!(this.f, "COMMIT_IF_OK SUCCESS")?,
                 }
             }
             Ok(())
diff --git a/compiler/rustc_trait_selection/src/solve/alias_relate.rs b/compiler/rustc_trait_selection/src/solve/alias_relate.rs
index e081a9100e2..f2c441dcbed 100644
--- a/compiler/rustc_trait_selection/src/solve/alias_relate.rs
+++ b/compiler/rustc_trait_selection/src/solve/alias_relate.rs
@@ -2,8 +2,8 @@
 //! Doing this via a separate goal is called "deferred alias relation" and part
 //! of our more general approach to "lazy normalization".
 //!
-//! This is done by first normalizing both sides of the goal, ending up in
-//! either a concrete type, rigid alias, or an infer variable.
+//! This is done by first structurally normalizing both sides of the goal, ending
+//! up in either a concrete type, rigid alias, or an infer variable.
 //! These are related further according to the rules below:
 //!
 //! (1.) If we end up with two rigid aliases, then we relate them structurally.
@@ -14,18 +14,10 @@
 //!
 //! (3.) Otherwise, if we end with two rigid (non-projection) or infer types,
 //! relate them structurally.
-//!
-//! Subtle: when relating an opaque to another type, we emit a
-//! `NormalizesTo(opaque, ?fresh_var)` goal when trying to normalize the opaque.
-//! This nested goal starts out as ambiguous and does not actually define the opaque.
-//! However, if `?fresh_var` ends up geteting equated to another type, we retry the
-//! `NormalizesTo` goal, at which point the opaque is actually defined.
 
 use super::EvalCtxt;
-use rustc_infer::traits::query::NoSolution;
-use rustc_infer::traits::solve::GoalSource;
 use rustc_middle::traits::solve::{Certainty, Goal, QueryResult};
-use rustc_middle::ty::{self, Ty};
+use rustc_middle::ty;
 
 impl<'tcx> EvalCtxt<'_, 'tcx> {
     #[instrument(level = "debug", skip(self), ret)]
@@ -36,21 +28,34 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         let tcx = self.tcx();
         let Goal { param_env, predicate: (lhs, rhs, direction) } = goal;
 
-        let Some(lhs) = self.try_normalize_term(param_env, lhs)? else {
-            return self
-                .evaluate_added_goals_and_make_canonical_response(Certainty::overflow(true));
+        // Structurally normalize the lhs.
+        let lhs = if let Some(alias) = lhs.to_alias_ty(self.tcx()) {
+            let term = self.next_term_infer_of_kind(lhs);
+            self.add_normalizes_to_goal(goal.with(tcx, ty::NormalizesTo { alias, term }));
+            term
+        } else {
+            lhs
         };
 
-        let Some(rhs) = self.try_normalize_term(param_env, rhs)? else {
-            return self
-                .evaluate_added_goals_and_make_canonical_response(Certainty::overflow(true));
+        // Structurally normalize the rhs.
+        let rhs = if let Some(alias) = rhs.to_alias_ty(self.tcx()) {
+            let term = self.next_term_infer_of_kind(rhs);
+            self.add_normalizes_to_goal(goal.with(tcx, ty::NormalizesTo { alias, term }));
+            term
+        } else {
+            rhs
         };
 
+        // Apply the constraints.
+        self.try_evaluate_added_goals()?;
+        let lhs = self.resolve_vars_if_possible(lhs);
+        let rhs = self.resolve_vars_if_possible(rhs);
+        debug!(?lhs, ?rhs);
+
         let variance = match direction {
             ty::AliasRelationDirection::Equate => ty::Variance::Invariant,
             ty::AliasRelationDirection::Subtype => ty::Variance::Covariant,
         };
-
         match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) {
             (None, None) => {
                 self.relate(param_env, lhs, variance, rhs)?;
@@ -58,14 +63,18 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
             }
 
             (Some(alias), None) => {
-                self.relate_rigid_alias_non_alias(param_env, alias, variance, rhs)
+                self.relate_rigid_alias_non_alias(param_env, alias, variance, rhs)?;
+                self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
+            }
+            (None, Some(alias)) => {
+                self.relate_rigid_alias_non_alias(
+                    param_env,
+                    alias,
+                    variance.xform(ty::Variance::Contravariant),
+                    lhs,
+                )?;
+                self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
             }
-            (None, Some(alias)) => self.relate_rigid_alias_non_alias(
-                param_env,
-                alias,
-                variance.xform(ty::Variance::Contravariant),
-                lhs,
-            ),
 
             (Some(alias_lhs), Some(alias_rhs)) => {
                 self.relate(param_env, alias_lhs, variance, alias_rhs)?;
@@ -73,104 +82,4 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
             }
         }
     }
-
-    /// Relate a rigid alias with another type. This is the same as
-    /// an ordinary relate except that we treat the outer most alias
-    /// constructor as rigid.
-    #[instrument(level = "debug", skip(self, param_env), ret)]
-    fn relate_rigid_alias_non_alias(
-        &mut self,
-        param_env: ty::ParamEnv<'tcx>,
-        alias: ty::AliasTy<'tcx>,
-        variance: ty::Variance,
-        term: ty::Term<'tcx>,
-    ) -> QueryResult<'tcx> {
-        // NOTE: this check is purely an optimization, the structural eq would
-        // always fail if the term is not an inference variable.
-        if term.is_infer() {
-            let tcx = self.tcx();
-            // We need to relate `alias` to `term` treating only the outermost
-            // constructor as rigid, relating any contained generic arguments as
-            // normal. We do this by first structurally equating the `term`
-            // with the alias constructor instantiated with unconstrained infer vars,
-            // and then relate this with the whole `alias`.
-            //
-            // Alternatively we could modify `Equate` for this case by adding another
-            // variant to `StructurallyRelateAliases`.
-            let identity_args = self.fresh_args_for_item(alias.def_id);
-            let rigid_ctor = ty::AliasTy::new(tcx, alias.def_id, identity_args);
-            self.eq_structurally_relating_aliases(param_env, term, rigid_ctor.to_ty(tcx).into())?;
-            self.eq(param_env, alias, rigid_ctor)?;
-            self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
-        } else {
-            Err(NoSolution)
-        }
-    }
-
-    // FIXME: This needs a name that reflects that it's okay to bottom-out with an inference var.
-    /// Normalize the `term` to equate it later.
-    #[instrument(level = "debug", skip(self, param_env), ret)]
-    fn try_normalize_term(
-        &mut self,
-        param_env: ty::ParamEnv<'tcx>,
-        term: ty::Term<'tcx>,
-    ) -> Result<Option<ty::Term<'tcx>>, NoSolution> {
-        match term.unpack() {
-            ty::TermKind::Ty(ty) => {
-                Ok(self.try_normalize_ty_recur(param_env, 0, ty).map(Into::into))
-            }
-            ty::TermKind::Const(_) => {
-                if let Some(alias) = term.to_alias_ty(self.tcx()) {
-                    let term = self.next_term_infer_of_kind(term);
-                    self.add_normalizes_to_goal(Goal::new(
-                        self.tcx(),
-                        param_env,
-                        ty::NormalizesTo { alias, term },
-                    ));
-                    self.try_evaluate_added_goals()?;
-                    Ok(Some(self.resolve_vars_if_possible(term)))
-                } else {
-                    Ok(Some(term))
-                }
-            }
-        }
-    }
-
-    #[instrument(level = "debug", skip(self, param_env), ret)]
-    fn try_normalize_ty_recur(
-        &mut self,
-        param_env: ty::ParamEnv<'tcx>,
-        depth: usize,
-        ty: Ty<'tcx>,
-    ) -> Option<Ty<'tcx>> {
-        if !self.tcx().recursion_limit().value_within_limit(depth) {
-            return None;
-        }
-
-        let ty::Alias(kind, alias) = *ty.kind() else {
-            return Some(ty);
-        };
-
-        match self.commit_if_ok(|this| {
-            let tcx = this.tcx();
-            let normalized_ty = this.next_ty_infer();
-            let normalizes_to = ty::NormalizesTo { alias, term: normalized_ty.into() };
-            match kind {
-                ty::AliasKind::Opaque => {
-                    // HACK: Unlike for associated types, `normalizes-to` for opaques
-                    // is currently not treated as a function. We do not erase the
-                    // expected term.
-                    this.add_goal(GoalSource::Misc, Goal::new(tcx, param_env, normalizes_to));
-                }
-                ty::AliasKind::Projection | ty::AliasKind::Inherent | ty::AliasKind::Weak => {
-                    this.add_normalizes_to_goal(Goal::new(tcx, param_env, normalizes_to))
-                }
-            }
-            this.try_evaluate_added_goals()?;
-            Ok(this.resolve_vars_if_possible(normalized_ty))
-        }) {
-            Ok(ty) => self.try_normalize_ty_recur(param_env, depth + 1, ty),
-            Err(NoSolution) => Some(ty),
-        }
-    }
 }
diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs
index 619435d2e8d..4a4efb6884f 100644
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs
@@ -332,7 +332,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
     /// whether an alias is rigid by using the trait solver. When instantiating a response
     /// from the solver we assume that the solver correctly handled aliases and therefore
     /// always relate them structurally here.
-    #[instrument(level = "debug", skip(infcx), ret)]
+    #[instrument(level = "debug", skip(infcx))]
     fn unify_query_var_values(
         infcx: &InferCtxt<'tcx>,
         param_env: ty::ParamEnv<'tcx>,
diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/commit_if_ok.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/commit_if_ok.rs
deleted file mode 100644
index c8f9a461adf..00000000000
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/commit_if_ok.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-use super::{EvalCtxt, NestedGoals};
-use crate::solve::inspect;
-use rustc_middle::traits::query::NoSolution;
-
-impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
-    pub(in crate::solve) fn commit_if_ok<T>(
-        &mut self,
-        f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> Result<T, NoSolution>,
-    ) -> Result<T, NoSolution> {
-        let mut nested_ecx = EvalCtxt {
-            infcx: self.infcx,
-            variables: self.variables,
-            var_values: self.var_values,
-            is_normalizes_to_goal: self.is_normalizes_to_goal,
-            predefined_opaques_in_body: self.predefined_opaques_in_body,
-            max_input_universe: self.max_input_universe,
-            search_graph: self.search_graph,
-            nested_goals: NestedGoals::new(),
-            tainted: self.tainted,
-            inspect: self.inspect.new_probe(),
-        };
-
-        let result = nested_ecx.infcx.commit_if_ok(|_| f(&mut nested_ecx));
-        if result.is_ok() {
-            let EvalCtxt {
-                infcx: _,
-                variables: _,
-                var_values: _,
-                is_normalizes_to_goal: _,
-                predefined_opaques_in_body: _,
-                max_input_universe: _,
-                search_graph: _,
-                nested_goals,
-                tainted,
-                inspect,
-            } = nested_ecx;
-            self.nested_goals.extend(nested_goals);
-            self.tainted = tainted;
-            self.inspect.integrate_snapshot(inspect);
-        } else {
-            nested_ecx.inspect.probe_kind(inspect::ProbeKind::CommitIfOk);
-            self.inspect.finish_probe(nested_ecx.inspect);
-        }
-
-        result
-    }
-}
diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs
index 5d9b588b55b..1739bd70e7b 100644
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs
@@ -24,7 +24,6 @@ use rustc_middle::ty::{
 use rustc_session::config::DumpSolverProofTree;
 use rustc_span::DUMMY_SP;
 use std::io::Write;
-use std::iter;
 use std::ops::ControlFlow;
 
 use crate::traits::vtable::{count_own_vtable_entries, prepare_vtable_segments, VtblSegment};
@@ -36,7 +35,6 @@ use super::{GoalSource, SolverMode};
 pub use select::InferCtxtSelectExt;
 
 mod canonical;
-mod commit_if_ok;
 mod probe;
 mod select;
 
@@ -124,11 +122,6 @@ impl<'tcx> NestedGoals<'tcx> {
     pub(super) fn is_empty(&self) -> bool {
         self.normalizes_to_goals.is_empty() && self.goals.is_empty()
     }
-
-    pub(super) fn extend(&mut self, other: NestedGoals<'tcx>) {
-        self.normalizes_to_goals.extend(other.normalizes_to_goals);
-        self.goals.extend(other.goals)
-    }
 }
 
 #[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)]
@@ -511,12 +504,6 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
 
         self.inspect.evaluate_added_goals_loop_start();
 
-        fn with_misc_source<'tcx>(
-            it: impl IntoIterator<Item = Goal<'tcx, ty::Predicate<'tcx>>>,
-        ) -> impl Iterator<Item = (GoalSource, Goal<'tcx, ty::Predicate<'tcx>>)> {
-            iter::zip(iter::repeat(GoalSource::Misc), it)
-        }
-
         // If this loop did not result in any progress, what's our final certainty.
         let mut unchanged_certainty = Some(Certainty::Yes);
         for goal in goals.normalizes_to_goals {
@@ -534,16 +521,28 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
                 unconstrained_goal,
             )?;
             // Add the nested goals from normalization to our own nested goals.
+            debug!(?nested_goals);
             goals.goals.extend(nested_goals);
 
             // Finally, equate the goal's RHS with the unconstrained var.
-            // We put the nested goals from this into goals instead of
-            // next_goals to avoid needing to process the loop one extra
-            // time if this goal returns something -- I don't think this
-            // matters in practice, though.
-            let eq_goals =
-                self.eq_and_get_goals(goal.param_env, goal.predicate.term, unconstrained_rhs)?;
-            goals.goals.extend(with_misc_source(eq_goals));
+            //
+            // SUBTLE:
+            // We structurally relate aliases here. This is necessary
+            // as we otherwise emit a nested `AliasRelate` goal in case the
+            // returned term is a rigid alias, resulting in overflow.
+            //
+            // It is correct as both `goal.predicate.term` and `unconstrained_rhs`
+            // start out as an unconstrained inference variable so any aliases get
+            // fully normalized when instantiating it.
+            //
+            // FIXME: Strictly speaking this may be incomplete if the normalized-to
+            // type contains an ambiguous alias referencing bound regions. We should
+            // consider changing this to only use "shallow structural equality".
+            self.eq_structurally_relating_aliases(
+                goal.param_env,
+                goal.predicate.term,
+                unconstrained_rhs,
+            )?;
 
             // We only look at the `projection_ty` part here rather than
             // looking at the "has changed" return from evaluate_goal,
@@ -731,6 +730,46 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
             })
     }
 
+    /// This should be used when relating a rigid alias with another type.
+    ///
+    /// Normally we emit a nested `AliasRelate` when equating an inference
+    /// variable and an alias. This causes us to instead constrain the inference
+    /// variable to the alias without emitting a nested alias relate goals.
+    #[instrument(level = "debug", skip(self, param_env), ret)]
+    pub(super) fn relate_rigid_alias_non_alias(
+        &mut self,
+        param_env: ty::ParamEnv<'tcx>,
+        alias: ty::AliasTy<'tcx>,
+        variance: ty::Variance,
+        term: ty::Term<'tcx>,
+    ) -> Result<(), NoSolution> {
+        // NOTE: this check is purely an optimization, the structural eq would
+        // always fail if the term is not an inference variable.
+        if term.is_infer() {
+            let tcx = self.tcx();
+            // We need to relate `alias` to `term` treating only the outermost
+            // constructor as rigid, relating any contained generic arguments as
+            // normal. We do this by first structurally equating the `term`
+            // with the alias constructor instantiated with unconstrained infer vars,
+            // and then relate this with the whole `alias`.
+            //
+            // Alternatively we could modify `Equate` for this case by adding another
+            // variant to `StructurallyRelateAliases`.
+            let identity_args = self.fresh_args_for_item(alias.def_id);
+            let rigid_ctor = ty::AliasTy::new(tcx, alias.def_id, identity_args);
+            let ctor_ty = rigid_ctor.to_ty(tcx);
+            let InferOk { value: (), obligations } = self
+                .infcx
+                .at(&ObligationCause::dummy(), param_env)
+                .trace(term, ctor_ty.into())
+                .eq_structurally_relating_aliases(term, ctor_ty.into())?;
+            debug_assert!(obligations.is_empty());
+            self.relate(param_env, alias, variance, rigid_ctor)
+        } else {
+            Err(NoSolution)
+        }
+    }
+
     /// This sohuld only be used when we're either instantiating a previously
     /// unconstrained "return value" or when we're sure that all aliases in
     /// the types are rigid.
diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
index cfec2e9bbf3..56c32d3d539 100644
--- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
+++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
@@ -130,17 +130,14 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
                     self.candidates_recur(candidates, nested_goals, probe);
                     nested_goals.truncate(num_goals);
                 }
-                inspect::ProbeStep::EvaluateGoals(_)
-                | inspect::ProbeStep::CommitIfOkStart
-                | inspect::ProbeStep::CommitIfOkSuccess => (),
+                inspect::ProbeStep::EvaluateGoals(_) => (),
             }
         }
 
         match probe.kind {
             inspect::ProbeKind::NormalizedSelfTyAssembly
             | inspect::ProbeKind::UnsizeAssembly
-            | inspect::ProbeKind::UpcastProjectionCompatibility
-            | inspect::ProbeKind::CommitIfOk => (),
+            | inspect::ProbeKind::UpcastProjectionCompatibility => (),
             // We add a candidate for the root evaluation if there
             // is only one way to prove a given goal, e.g. for `WellFormed`.
             //
@@ -157,7 +154,8 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
                     });
                 }
             }
-            inspect::ProbeKind::MiscCandidate { name: _, result }
+            inspect::ProbeKind::TryNormalizeNonRigid { result }
+            | inspect::ProbeKind::MiscCandidate { name: _, result }
             | inspect::ProbeKind::TraitCandidate { source: _, result } => {
                 candidates.push(InspectCandidate {
                     goal: self,
diff --git a/compiler/rustc_trait_selection/src/solve/inspect/build.rs b/compiler/rustc_trait_selection/src/solve/inspect/build.rs
index 4da999f2406..43c76cc5f4a 100644
--- a/compiler/rustc_trait_selection/src/solve/inspect/build.rs
+++ b/compiler/rustc_trait_selection/src/solve/inspect/build.rs
@@ -220,8 +220,6 @@ enum WipProbeStep<'tcx> {
     AddGoal(GoalSource, inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>),
     EvaluateGoals(WipAddedGoalsEvaluation<'tcx>),
     NestedProbe(WipProbe<'tcx>),
-    CommitIfOkStart,
-    CommitIfOkSuccess,
 }
 
 impl<'tcx> WipProbeStep<'tcx> {
@@ -230,8 +228,6 @@ impl<'tcx> WipProbeStep<'tcx> {
             WipProbeStep::AddGoal(source, goal) => inspect::ProbeStep::AddGoal(source, goal),
             WipProbeStep::EvaluateGoals(eval) => inspect::ProbeStep::EvaluateGoals(eval.finalize()),
             WipProbeStep::NestedProbe(probe) => inspect::ProbeStep::NestedProbe(probe.finalize()),
-            WipProbeStep::CommitIfOkStart => inspect::ProbeStep::CommitIfOkStart,
-            WipProbeStep::CommitIfOkSuccess => inspect::ProbeStep::CommitIfOkSuccess,
         }
     }
 }
@@ -467,29 +463,6 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
         }
     }
 
-    /// Used by `EvalCtxt::commit_if_ok` to flatten the work done inside
-    /// of the probe into the parent.
-    pub fn integrate_snapshot(&mut self, probe: ProofTreeBuilder<'tcx>) {
-        if let Some(this) = self.as_mut() {
-            match (this, *probe.state.unwrap()) {
-                (
-                    DebugSolver::Probe(WipProbe { steps, .. })
-                    | DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
-                        evaluation: WipProbe { steps, .. },
-                        ..
-                    }),
-                    DebugSolver::Probe(probe),
-                ) => {
-                    steps.push(WipProbeStep::CommitIfOkStart);
-                    assert_eq!(probe.kind, None);
-                    steps.extend(probe.steps);
-                    steps.push(WipProbeStep::CommitIfOkSuccess);
-                }
-                _ => unreachable!(),
-            }
-        }
-    }
-
     pub fn new_evaluate_added_goals(&mut self) -> ProofTreeBuilder<'tcx> {
         self.nested(|| WipAddedGoalsEvaluation { evaluations: vec![], result: None })
     }
diff --git a/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs b/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs
index d8aeadd07b3..fb296d55100 100644
--- a/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs
@@ -7,6 +7,7 @@ use rustc_hir::def::DefKind;
 use rustc_hir::def_id::DefId;
 use rustc_hir::LangItem;
 use rustc_infer::traits::query::NoSolution;
+use rustc_infer::traits::solve::inspect::ProbeKind;
 use rustc_infer::traits::specialization_graph::LeafDef;
 use rustc_infer::traits::Reveal;
 use rustc_middle::traits::solve::{
@@ -30,14 +31,41 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         &mut self,
         goal: Goal<'tcx, NormalizesTo<'tcx>>,
     ) -> QueryResult<'tcx> {
-        let def_id = goal.predicate.def_id();
-        let def_kind = self.tcx().def_kind(def_id);
-        match def_kind {
-            DefKind::OpaqueTy => return self.normalize_opaque_type(goal),
-            _ => self.set_is_normalizes_to_goal(),
-        }
-
+        self.set_is_normalizes_to_goal();
         debug_assert!(self.term_is_fully_unconstrained(goal));
+        let normalize_result = self
+            .probe(|&result| ProbeKind::TryNormalizeNonRigid { result })
+            .enter(|this| this.normalize_at_least_one_step(goal));
+
+        match normalize_result {
+            Ok(res) => Ok(res),
+            Err(NoSolution) => {
+                let Goal { param_env, predicate: NormalizesTo { alias, term } } = goal;
+                if alias.opt_kind(self.tcx()).is_some() {
+                    self.relate_rigid_alias_non_alias(
+                        param_env,
+                        alias,
+                        ty::Variance::Invariant,
+                        term,
+                    )?;
+                    self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
+                } else {
+                    // FIXME(generic_const_exprs): we currently do not support rigid
+                    // unevaluated constants.
+                    Err(NoSolution)
+                }
+            }
+        }
+    }
+
+    /// Normalize the given alias by at least one step. If the alias is rigid, this
+    /// returns `NoSolution`.
+    #[instrument(level = "debug", skip(self), ret)]
+    fn normalize_at_least_one_step(
+        &mut self,
+        goal: Goal<'tcx, NormalizesTo<'tcx>>,
+    ) -> QueryResult<'tcx> {
+        let def_id = goal.predicate.def_id();
         match self.tcx().def_kind(def_id) {
             DefKind::AssocTy | DefKind::AssocConst => {
                 match self.tcx().associated_item(def_id).container {
@@ -52,35 +80,22 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
             }
             DefKind::AnonConst => self.normalize_anon_const(goal),
             DefKind::TyAlias => self.normalize_weak_type(goal),
+            DefKind::OpaqueTy => self.normalize_opaque_type(goal),
             kind => bug!("unknown DefKind {} in normalizes-to goal: {goal:#?}", kind.descr(def_id)),
         }
     }
 
-    /// When normalizing an associated item, constrain the result to `term`.
+    /// When normalizing an associated item, constrain the expected term to `term`.
     ///
-    /// While `NormalizesTo` goals have the normalized-to term as an argument,
-    /// this argument is always fully unconstrained for associated items.
-    /// It is therefore appropriate to instead think of these `NormalizesTo` goals
-    /// as function returning a term after normalizing.
-    ///
-    /// When equating an inference variable and an alias, we tend to emit `alias-relate`
-    /// goals and only actually instantiate the inference variable with an alias if the
-    /// alias is rigid. However, this means that constraining the expected term of
-    /// such goals ends up fully structurally normalizing the resulting type instead of
-    /// only by one step. To avoid this we instead use structural equality here, resulting
-    /// in each `NormalizesTo` only projects by a single step.
-    ///
-    /// Not doing so, currently causes issues because trying to normalize an opaque type
-    /// during alias-relate doesn't actually constrain the opaque if the concrete type
-    /// is an inference variable. This means that `NormalizesTo` for associated types
-    /// normalizing to an opaque type always resulted in ambiguity, breaking tests e.g.
-    /// tests/ui/type-alias-impl-trait/issue-78450.rs.
+    /// We know `term` to always be a fully unconstrained inference variable, so
+    /// `eq` should never fail here. However, in case `term` contains aliases, we
+    /// emit nested `AliasRelate` goals to structurally normalize the alias.
     pub fn instantiate_normalizes_to_term(
         &mut self,
         goal: Goal<'tcx, NormalizesTo<'tcx>>,
         term: ty::Term<'tcx>,
     ) {
-        self.eq_structurally_relating_aliases(goal.param_env, goal.predicate.term, term)
+        self.eq(goal.param_env, goal.predicate.term, term)
             .expect("expected goal term to be fully unconstrained");
     }
 }
diff --git a/compiler/rustc_trait_selection/src/solve/normalizes_to/opaque_types.rs b/compiler/rustc_trait_selection/src/solve/normalizes_to/opaque_types.rs
index 356c3776c04..9fdb280cdc6 100644
--- a/compiler/rustc_trait_selection/src/solve/normalizes_to/opaque_types.rs
+++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/opaque_types.rs
@@ -58,12 +58,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
                     }
                 }
 
-                let expected = self.structurally_normalize_ty(goal.param_env, expected)?;
-                if expected.is_ty_var() {
-                    return self
-                        .evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
-                }
-
                 // Otherwise, define a new opaque type
                 self.insert_hidden_type(opaque_type_key, goal.param_env, expected)?;
                 self.add_item_bounds_for_hidden_type(
diff --git a/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.next.stderr b/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.next.stderr
index 06ffff057f9..d913b2e91ca 100644
--- a/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.next.stderr
+++ b/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.next.stderr
@@ -6,7 +6,7 @@ LL |     cmp_eq
    |
    = note: cannot satisfy `_: Scalar`
 note: required by a bound in `cmp_eq`
-  --> $DIR/ambig-hr-projection-issue-93340.rs:9:22
+  --> $DIR/ambig-hr-projection-issue-93340.rs:10:22
    |
 LL | fn cmp_eq<'a, 'b, A: Scalar, B: Scalar, O: Scalar>(a: A::RefType<'a>, b: B::RefType<'b>) -> O {
    |                      ^^^^^^ required by this bound in `cmp_eq`
@@ -15,34 +15,6 @@ help: consider specifying the generic arguments
 LL |     cmp_eq::<A, B, O>
    |           +++++++++++
 
-error[E0275]: overflow evaluating the requirement `impl for<'a, 'b> Fn(<A as Scalar>::RefType<'a>, <B as Scalar>::RefType<'b>) -> O == for<'a, 'b> fn(..., ...) -> ... {cmp_eq::<..., ..., ...>}`
-  --> $DIR/ambig-hr-projection-issue-93340.rs:16:5
-   |
-LL |     cmp_eq
-   |     ^^^^^^
+error: aborting due to 1 previous error
 
-error[E0275]: overflow evaluating the requirement `impl for<'a, 'b> Fn(<A as Scalar>::RefType<'a>, <B as Scalar>::RefType<'b>) -> O == for<'a, 'b> fn(..., ...) -> ... {cmp_eq::<..., ..., ...>}`
-  --> $DIR/ambig-hr-projection-issue-93340.rs:16:5
-   |
-LL |     cmp_eq
-   |     ^^^^^^
-   |
-   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
-
-error[E0275]: overflow evaluating the requirement `for<'a, 'b> fn(<O as Scalar>::RefType<'a>, <_ as Scalar>::RefType<'b>) -> _ {cmp_eq::<O, ..., ...>} <: ...`
-  --> $DIR/ambig-hr-projection-issue-93340.rs:14:51
-   |
-LL |   ) -> impl Fn(A::RefType<'_>, B::RefType<'_>) -> O {
-   |  ___________________________________________________^
-LL | |
-LL | |     cmp_eq
-LL | |
-LL | |
-LL | |
-LL | | }
-   | |_^
-
-error: aborting due to 4 previous errors
-
-Some errors have detailed explanations: E0275, E0283.
-For more information about an error, try `rustc --explain E0275`.
+For more information about this error, try `rustc --explain E0283`.
diff --git a/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.old.stderr b/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.old.stderr
index df2ec4ab182..d913b2e91ca 100644
--- a/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.old.stderr
+++ b/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.old.stderr
@@ -6,7 +6,7 @@ LL |     cmp_eq
    |
    = note: cannot satisfy `_: Scalar`
 note: required by a bound in `cmp_eq`
-  --> $DIR/ambig-hr-projection-issue-93340.rs:9:22
+  --> $DIR/ambig-hr-projection-issue-93340.rs:10:22
    |
 LL | fn cmp_eq<'a, 'b, A: Scalar, B: Scalar, O: Scalar>(a: A::RefType<'a>, b: B::RefType<'b>) -> O {
    |                      ^^^^^^ required by this bound in `cmp_eq`
diff --git a/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.rs b/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.rs
index 4d8ea9d8d48..acfebad38db 100644
--- a/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.rs
+++ b/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.rs
@@ -1,4 +1,5 @@
 //@ revisions: old next
+//@ ignore-compare-mode-next-solver (explicit revisions)
 //@[next] compile-flags: -Znext-solver
 pub trait Scalar: 'static {
     type RefType<'a>: ScalarRef<'a>;
@@ -12,11 +13,8 @@ fn cmp_eq<'a, 'b, A: Scalar, B: Scalar, O: Scalar>(a: A::RefType<'a>, b: B::RefT
 
 fn build_expression<A: Scalar, B: Scalar, O: Scalar>(
 ) -> impl Fn(A::RefType<'_>, B::RefType<'_>) -> O {
-    //[next]~^ ERROR overflow evaluating the requirement
     cmp_eq
     //~^ ERROR type annotations needed
-    //[next]~| ERROR overflow evaluating the requirement
-    //[next]~| ERROR overflow evaluating the requirement
 }
 
 fn main() {}
diff --git a/tests/ui/impl-trait/recursive-coroutine-boxed.next.stderr b/tests/ui/impl-trait/recursive-coroutine-boxed.next.stderr
index 755d12d7448..c0b399746ea 100644
--- a/tests/ui/impl-trait/recursive-coroutine-boxed.next.stderr
+++ b/tests/ui/impl-trait/recursive-coroutine-boxed.next.stderr
@@ -1,5 +1,5 @@
 error[E0282]: type annotations needed
-  --> $DIR/recursive-coroutine-boxed.rs:12:23
+  --> $DIR/recursive-coroutine-boxed.rs:14:23
    |
 LL |         let mut gen = Box::pin(foo());
    |                       ^^^^^^^^ cannot infer type of the type parameter `T` declared on the struct `Box`
@@ -12,12 +12,28 @@ help: consider specifying the generic argument
 LL |         let mut gen = Box::<T>::pin(foo());
    |                          +++++
 
-error[E0282]: type annotations needed
-  --> $DIR/recursive-coroutine-boxed.rs:9:13
+error[E0308]: mismatched types
+  --> $DIR/recursive-coroutine-boxed.rs:13:5
    |
-LL | fn foo() -> impl Coroutine<Yield = (), Return = ()> {
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for opaque type `impl Coroutine<Yield = (), Return = ()>`
+LL |   fn foo() -> impl Coroutine<Yield = (), Return = ()> {
+   |               ---------------------------------------
+   |               |
+   |               the expected opaque type
+   |               expected `impl Coroutine<Yield = (), Return = ()>` because of return type
+...
+LL | /     || {
+LL | |         let mut gen = Box::pin(foo());
+LL | |
+LL | |         let mut r = gen.as_mut().resume(());
+...  |
+LL | |         }
+LL | |     }
+   | |_____^ types differ
+   |
+   = note: expected opaque type `impl Coroutine<Yield = (), Return = ()>`
+                found coroutine `{coroutine@$DIR/recursive-coroutine-boxed.rs:13:5: 13:7}`
 
 error: aborting due to 2 previous errors
 
-For more information about this error, try `rustc --explain E0282`.
+Some errors have detailed explanations: E0282, E0308.
+For more information about an error, try `rustc --explain E0282`.
diff --git a/tests/ui/impl-trait/recursive-coroutine-boxed.rs b/tests/ui/impl-trait/recursive-coroutine-boxed.rs
index 3b8ffb92090..02c75be0f3a 100644
--- a/tests/ui/impl-trait/recursive-coroutine-boxed.rs
+++ b/tests/ui/impl-trait/recursive-coroutine-boxed.rs
@@ -7,8 +7,10 @@
 use std::ops::{Coroutine, CoroutineState};
 
 fn foo() -> impl Coroutine<Yield = (), Return = ()> {
-    //[next]~^ ERROR type annotations needed
-    || {
+    // FIXME(-Znext-solver): this fails with a mismatched types as the
+    // hidden type of the opaque ends up as {type error}. We should not
+    // emit errors for such goals.
+    || { //[next]~ ERROR mismatched types
         let mut gen = Box::pin(foo());
         //[next]~^ ERROR type annotations needed
         let mut r = gen.as_mut().resume(());