about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_middle/src/traits/solve.rs23
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs44
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt/commit_if_ok.rs2
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs86
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs1
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt/select.rs13
-rw-r--r--compiler/rustc_trait_selection/src/solve/inspect/analyse.rs14
-rw-r--r--compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs9
-rw-r--r--compiler/rustc_trait_selection/src/solve/search_graph.rs10
-rw-r--r--tests/ui/traits/next-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr4
-rw-r--r--tests/ui/traits/next-solver/normalize/indirectly-constrained-term.rs45
-rw-r--r--tests/ui/traits/next-solver/overflow/recursive-self-normalization-2.stderr8
-rw-r--r--tests/ui/traits/next-solver/overflow/recursive-self-normalization.stderr8
13 files changed, 173 insertions, 94 deletions
diff --git a/compiler/rustc_middle/src/traits/solve.rs b/compiler/rustc_middle/src/traits/solve.rs
index dc4cd203415..d027b19dccf 100644
--- a/compiler/rustc_middle/src/traits/solve.rs
+++ b/compiler/rustc_middle/src/traits/solve.rs
@@ -164,6 +164,19 @@ pub struct ExternalConstraintsData<'tcx> {
     // FIXME: implement this.
     pub region_constraints: QueryRegionConstraints<'tcx>,
     pub opaque_types: Vec<(ty::OpaqueTypeKey<'tcx>, Ty<'tcx>)>,
+    pub normalization_nested_goals: NestedNormalizationGoals<'tcx>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash, HashStable, Default, TypeVisitable, TypeFoldable)]
+pub struct NestedNormalizationGoals<'tcx>(pub Vec<(GoalSource, Goal<'tcx, ty::Predicate<'tcx>>)>);
+impl<'tcx> NestedNormalizationGoals<'tcx> {
+    pub fn empty() -> Self {
+        NestedNormalizationGoals(vec![])
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
 }
 
 // FIXME: Having to clone `region_constraints` for folding feels bad and
@@ -183,6 +196,10 @@ impl<'tcx> TypeFoldable<TyCtxt<'tcx>> for ExternalConstraints<'tcx> {
                 .iter()
                 .map(|opaque| opaque.try_fold_with(folder))
                 .collect::<Result<_, F::Error>>()?,
+            normalization_nested_goals: self
+                .normalization_nested_goals
+                .clone()
+                .try_fold_with(folder)?,
         }))
     }
 
@@ -190,6 +207,7 @@ impl<'tcx> TypeFoldable<TyCtxt<'tcx>> for ExternalConstraints<'tcx> {
         TypeFolder::interner(folder).mk_external_constraints(ExternalConstraintsData {
             region_constraints: self.region_constraints.clone().fold_with(folder),
             opaque_types: self.opaque_types.iter().map(|opaque| opaque.fold_with(folder)).collect(),
+            normalization_nested_goals: self.normalization_nested_goals.clone().fold_with(folder),
         })
     }
 }
@@ -197,7 +215,8 @@ impl<'tcx> TypeFoldable<TyCtxt<'tcx>> for ExternalConstraints<'tcx> {
 impl<'tcx> TypeVisitable<TyCtxt<'tcx>> for ExternalConstraints<'tcx> {
     fn visit_with<V: TypeVisitor<TyCtxt<'tcx>>>(&self, visitor: &mut V) -> V::Result {
         try_visit!(self.region_constraints.visit_with(visitor));
-        self.opaque_types.visit_with(visitor)
+        try_visit!(self.opaque_types.visit_with(visitor));
+        self.normalization_nested_goals.visit_with(visitor)
     }
 }
 
@@ -239,7 +258,7 @@ impl<'tcx> TypeVisitable<TyCtxt<'tcx>> for PredefinedOpaques<'tcx> {
 ///
 /// This is necessary as we treat nested goals different depending on
 /// their source.
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, HashStable, TypeVisitable, TypeFoldable)]
 pub enum GoalSource {
     Misc,
     /// We're proving a where-bound of an impl.
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 251b0a193f1..5badbe031d3 100644
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs
@@ -9,6 +9,7 @@
 //!
 //! [c]: https://rustc-dev-guide.rust-lang.org/solve/canonicalization.html
 use super::{CanonicalInput, Certainty, EvalCtxt, Goal};
+use crate::solve::eval_ctxt::NestedGoals;
 use crate::solve::{
     inspect, response_no_constraints_raw, CanonicalResponse, QueryResult, Response,
 };
@@ -19,6 +20,7 @@ use rustc_infer::infer::canonical::CanonicalVarValues;
 use rustc_infer::infer::canonical::{CanonicalExt, QueryRegionConstraints};
 use rustc_infer::infer::resolve::EagerResolver;
 use rustc_infer::infer::{InferCtxt, InferOk};
+use rustc_infer::traits::solve::NestedNormalizationGoals;
 use rustc_middle::infer::canonical::Canonical;
 use rustc_middle::traits::query::NoSolution;
 use rustc_middle::traits::solve::{
@@ -93,13 +95,26 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
             previous call to `try_evaluate_added_goals!`"
         );
 
-        let certainty = certainty.unify_with(goals_certainty);
-
-        let var_values = self.var_values;
-        let external_constraints = self.compute_external_query_constraints()?;
-
+        // When normalizing, we've replaced the expected term with an unconstrained
+        // inference variable. This means that we dropped information which could
+        // have been important. We handle this by instead returning the nested goals
+        // to the caller, where they are then handled.
+        //
+        // As we return all ambiguous nested goals, we can ignore the certainty returned
+        // by `try_evaluate_added_goals()`.
+        let (certainty, normalization_nested_goals) = if self.is_normalizes_to_goal {
+            let NestedGoals { normalizes_to_goals, goals } = std::mem::take(&mut self.nested_goals);
+            assert!(normalizes_to_goals.is_empty());
+            (certainty, NestedNormalizationGoals(goals))
+        } else {
+            let certainty = certainty.unify_with(goals_certainty);
+            (certainty, NestedNormalizationGoals::empty())
+        };
+
+        let external_constraints =
+            self.compute_external_query_constraints(normalization_nested_goals)?;
         let (var_values, mut external_constraints) =
-            (var_values, external_constraints).fold_with(&mut EagerResolver::new(self.infcx));
+            (self.var_values, external_constraints).fold_with(&mut EagerResolver::new(self.infcx));
         // Remove any trivial region constraints once we've resolved regions
         external_constraints
             .region_constraints
@@ -146,6 +161,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
     #[instrument(level = "debug", skip(self), ret)]
     fn compute_external_query_constraints(
         &self,
+        normalization_nested_goals: NestedNormalizationGoals<'tcx>,
     ) -> Result<ExternalConstraintsData<'tcx>, NoSolution> {
         // We only check for leaks from universes which were entered inside
         // of the query.
@@ -176,7 +192,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
             self.predefined_opaques_in_body.opaque_types.iter().all(|(pa, _)| pa != a)
         });
 
-        Ok(ExternalConstraintsData { region_constraints, opaque_types })
+        Ok(ExternalConstraintsData { region_constraints, opaque_types, normalization_nested_goals })
     }
 
     /// After calling a canonical query, we apply the constraints returned
@@ -185,13 +201,14 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
     /// This happens in three steps:
     /// - we instantiate the bound variables of the query response
     /// - we unify the `var_values` of the response with the `original_values`
-    /// - we apply the `external_constraints` returned by the query
+    /// - we apply the `external_constraints` returned by the query, returning
+    ///   the `normalization_nested_goals`
     pub(super) fn instantiate_and_apply_query_response(
         &mut self,
         param_env: ty::ParamEnv<'tcx>,
         original_values: Vec<ty::GenericArg<'tcx>>,
         response: CanonicalResponse<'tcx>,
-    ) -> Certainty {
+    ) -> (NestedNormalizationGoals<'tcx>, Certainty) {
         let instantiation = Self::compute_query_response_instantiation_values(
             self.infcx,
             &original_values,
@@ -203,11 +220,14 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
 
         Self::unify_query_var_values(self.infcx, param_env, &original_values, var_values);
 
-        let ExternalConstraintsData { region_constraints, opaque_types } =
-            external_constraints.deref();
+        let ExternalConstraintsData {
+            region_constraints,
+            opaque_types,
+            normalization_nested_goals,
+        } = external_constraints.deref();
         self.register_region_constraints(region_constraints);
         self.register_new_opaque_types(param_env, opaque_types);
-        certainty
+        (normalization_nested_goals.clone(), certainty)
     }
 
     /// This returns the canoncial variable values to instantiate the bound variables of
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
index 67b6801059a..c8f9a461adf 100644
--- 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
@@ -11,6 +11,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
             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,
@@ -25,6 +26,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
                 infcx: _,
                 variables: _,
                 var_values: _,
+                is_normalizes_to_goal: _,
                 predefined_opaques_in_body: _,
                 max_input_universe: _,
                 search_graph: _,
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 6444f12493e..b7977fb6d0d 100644
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs
@@ -7,7 +7,7 @@ use rustc_infer::infer::{
     BoundRegionConversionTime, DefineOpaqueTypes, InferCtxt, InferOk, TyCtxtInferExt,
 };
 use rustc_infer::traits::query::NoSolution;
-use rustc_infer::traits::solve::MaybeCause;
+use rustc_infer::traits::solve::{MaybeCause, NestedNormalizationGoals};
 use rustc_infer::traits::ObligationCause;
 use rustc_middle::infer::canonical::CanonicalVarInfos;
 use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
@@ -61,6 +61,14 @@ pub struct EvalCtxt<'a, 'tcx> {
     /// The variable info for the `var_values`, only used to make an ambiguous response
     /// with no constraints.
     variables: CanonicalVarInfos<'tcx>,
+    /// Whether we're currently computing a `NormalizesTo` goal. Unlike other goals,
+    /// `NormalizesTo` goals act like functions with the expected term always being
+    /// fully unconstrained. This would weaken inference however, as the nested goals
+    /// never get the inference constraints from the actual normalized-to type. Because
+    /// of this we return any ambiguous nested goals from `NormalizesTo` to the caller
+    /// when then adds these to its own context. The caller is always an `AliasRelate`
+    /// goal so this never leaks out of the solver.
+    is_normalizes_to_goal: bool,
     pub(super) var_values: CanonicalVarValues<'tcx>,
 
     predefined_opaques_in_body: PredefinedOpaques<'tcx>,
@@ -91,7 +99,7 @@ pub struct EvalCtxt<'a, 'tcx> {
     pub(super) inspect: ProofTreeBuilder<'tcx>,
 }
 
-#[derive(Debug, Clone)]
+#[derive(Default, Debug, Clone)]
 pub(super) struct NestedGoals<'tcx> {
     /// These normalizes-to goals are treated specially during the evaluation
     /// loop. In each iteration we take the RHS of the projection, replace it with
@@ -153,6 +161,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
         self.search_graph.solver_mode()
     }
 
+    pub(super) fn set_is_normalizes_to_goal(&mut self) {
+        self.is_normalizes_to_goal = true;
+    }
+
     /// Creates a root evaluation context and search graph. This should only be
     /// used from outside of any evaluation, and other methods should be preferred
     /// over using this manually (such as [`InferCtxtEvalExt::evaluate_root_goal`]).
@@ -165,8 +177,8 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
         let mut search_graph = search_graph::SearchGraph::new(mode);
 
         let mut ecx = EvalCtxt {
-            search_graph: &mut search_graph,
             infcx,
+            search_graph: &mut search_graph,
             nested_goals: NestedGoals::new(),
             inspect: ProofTreeBuilder::new_maybe_root(infcx.tcx, generate_proof_tree),
 
@@ -178,6 +190,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
             max_input_universe: ty::UniverseIndex::ROOT,
             variables: ty::List::empty(),
             var_values: CanonicalVarValues::dummy(),
+            is_normalizes_to_goal: false,
             tainted: Ok(()),
         };
         let result = f(&mut ecx);
@@ -231,6 +244,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
             infcx,
             variables: canonical_input.variables,
             var_values,
+            is_normalizes_to_goal: false,
             predefined_opaques_in_body: input.predefined_opaques_in_body,
             max_input_universe: canonical_input.max_universe,
             search_graph,
@@ -317,6 +331,20 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
         source: GoalSource,
         goal: Goal<'tcx, ty::Predicate<'tcx>>,
     ) -> Result<(bool, Certainty), NoSolution> {
+        let (normalization_nested_goals, has_changed, certainty) =
+            self.evaluate_goal_raw(goal_evaluation_kind, source, goal)?;
+        assert!(normalization_nested_goals.is_empty());
+        Ok((has_changed, certainty))
+    }
+
+    /// FIXME(-Znext-solver=coinduction): `_source` is currently unused but will
+    /// be necessary once we implement the new coinduction approach.
+    fn evaluate_goal_raw(
+        &mut self,
+        goal_evaluation_kind: GoalEvaluationKind,
+        _source: GoalSource,
+        goal: Goal<'tcx, ty::Predicate<'tcx>>,
+    ) -> Result<(NestedNormalizationGoals<'tcx>, bool, Certainty), NoSolution> {
         let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
         let mut goal_evaluation =
             self.inspect.new_goal_evaluation(goal, &orig_values, goal_evaluation_kind);
@@ -334,12 +362,12 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
             Ok(response) => response,
         };
 
-        let (certainty, has_changed) = self.instantiate_response_discarding_overflow(
-            goal.param_env,
-            source,
-            orig_values,
-            canonical_response,
-        );
+        let (normalization_nested_goals, certainty, has_changed) = self
+            .instantiate_response_discarding_overflow(
+                goal.param_env,
+                orig_values,
+                canonical_response,
+            );
         self.inspect.goal_evaluation(goal_evaluation);
         // FIXME: We previously had an assert here that checked that recomputing
         // a goal after applying its constraints did not change its response.
@@ -351,47 +379,25 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
         // Once we have decided on how to handle trait-system-refactor-initiative#75,
         // we should re-add an assert here.
 
-        Ok((has_changed, certainty))
+        Ok((normalization_nested_goals, has_changed, certainty))
     }
 
     fn instantiate_response_discarding_overflow(
         &mut self,
         param_env: ty::ParamEnv<'tcx>,
-        source: GoalSource,
         original_values: Vec<ty::GenericArg<'tcx>>,
         response: CanonicalResponse<'tcx>,
-    ) -> (Certainty, bool) {
-        // The old solver did not evaluate nested goals when normalizing.
-        // It returned the selection constraints allowing a `Projection`
-        // obligation to not hold in coherence while avoiding the fatal error
-        // from overflow.
-        //
-        // We match this behavior here by considering all constraints
-        // from nested goals which are not from where-bounds. We will already
-        // need to track which nested goals are required by impl where-bounds
-        // for coinductive cycles, so we simply reuse that here.
-        //
-        // While we could consider overflow constraints in more cases, this should
-        // not be necessary for backcompat and results in better perf. It also
-        // avoids a potential inconsistency which would otherwise require some
-        // tracking for root goals as well. See #119071 for an example.
-        let keep_overflow_constraints = || {
-            self.search_graph.current_goal_is_normalizes_to()
-                && source != GoalSource::ImplWhereBound
-        };
-
-        if let Certainty::Maybe(MaybeCause::Overflow { .. }) = response.value.certainty
-            && !keep_overflow_constraints()
-        {
-            return (response.value.certainty, false);
+    ) -> (NestedNormalizationGoals<'tcx>, Certainty, bool) {
+        if let Certainty::Maybe(MaybeCause::Overflow { .. }) = response.value.certainty {
+            return (NestedNormalizationGoals::empty(), response.value.certainty, false);
         }
 
         let has_changed = !response.value.var_values.is_identity_modulo_regions()
             || !response.value.external_constraints.opaque_types.is_empty();
 
-        let certainty =
+        let (normalization_nested_goals, certainty) =
             self.instantiate_and_apply_query_response(param_env, original_values, response);
-        (certainty, has_changed)
+        (normalization_nested_goals, certainty, has_changed)
     }
 
     fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> {
@@ -494,7 +500,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
     /// Goals for the next step get directly added to the nested goals of the `EvalCtxt`.
     fn evaluate_added_goals_step(&mut self) -> Result<Option<Certainty>, NoSolution> {
         let tcx = self.tcx();
-        let mut goals = core::mem::replace(&mut self.nested_goals, NestedGoals::new());
+        let mut goals = core::mem::take(&mut self.nested_goals);
 
         self.inspect.evaluate_added_goals_loop_start();
 
@@ -515,11 +521,13 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
                 ty::NormalizesTo { alias: goal.predicate.alias, term: unconstrained_rhs },
             );
 
-            let (_, certainty) = self.evaluate_goal(
+            let (NestedNormalizationGoals(nested_goals), _, certainty) = self.evaluate_goal_raw(
                 GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::Yes },
                 GoalSource::Misc,
                 unconstrained_goal,
             )?;
+            // Add the nested goals from normalization to our own 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
diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs
index 91fd48807a4..5b1124e8b9f 100644
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs
@@ -24,6 +24,7 @@ where
             infcx: outer_ecx.infcx,
             variables: outer_ecx.variables,
             var_values: outer_ecx.var_values,
+            is_normalizes_to_goal: outer_ecx.is_normalizes_to_goal,
             predefined_opaques_in_body: outer_ecx.predefined_opaques_in_body,
             max_input_universe: outer_ecx.max_input_universe,
             search_graph: outer_ecx.search_graph,
diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/select.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/select.rs
index 3262d64cb7d..e0c7804b6db 100644
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/select.rs
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/select.rs
@@ -58,12 +58,13 @@ impl<'tcx> InferCtxt<'tcx> {
                 }
 
                 let candidate = candidates.pop().unwrap();
-                let certainty = ecx.instantiate_and_apply_query_response(
-                    trait_goal.param_env,
-                    orig_values,
-                    candidate.result,
-                );
-
+                let (normalization_nested_goals, certainty) = ecx
+                    .instantiate_and_apply_query_response(
+                        trait_goal.param_env,
+                        orig_values,
+                        candidate.result,
+                    );
+                assert!(normalization_nested_goals.is_empty());
                 Ok(Some((candidate, certainty)))
             });
 
diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
index 9e3e6a4676e..cfec2e9bbf3 100644
--- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
+++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
@@ -70,7 +70,19 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> {
                     instantiated_goals.push(goal);
                 }
 
-                for &goal in &instantiated_goals {
+                for goal in instantiated_goals.iter().copied() {
+                    // We need to be careful with `NormalizesTo` goals as the
+                    // expected term has to be replaced with an unconstrained
+                    // inference variable.
+                    if let Some(kind) = goal.predicate.kind().no_bound_vars()
+                        && let ty::PredicateKind::NormalizesTo(predicate) = kind
+                        && !predicate.alias.is_opaque(infcx.tcx)
+                    {
+                        // FIXME: We currently skip these goals as
+                        // `fn evaluate_root_goal` ICEs if there are any
+                        // `NestedNormalizationGoals`.
+                        continue;
+                    };
                     let (_, proof_tree) = infcx.evaluate_root_goal(goal, GenerateProofTree::Yes);
                     let proof_tree = proof_tree.unwrap();
                     try_visit!(visitor.visit_goal(&InspectGoal::new(
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 4ef54dcf21a..d24bec5a766 100644
--- a/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs
@@ -32,10 +32,12 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
     ) -> QueryResult<'tcx> {
         let def_id = goal.predicate.def_id();
         let def_kind = self.tcx().def_kind(def_id);
-        if cfg!(debug_assertions) && !matches!(def_kind, DefKind::OpaqueTy) {
-            assert!(self.term_is_fully_unconstrained(goal));
+        match def_kind {
+            DefKind::OpaqueTy => return self.normalize_opaque_type(goal),
+            _ => self.set_is_normalizes_to_goal(),
         }
 
+        debug_assert!(self.term_is_fully_unconstrained(goal));
         match self.tcx().def_kind(def_id) {
             DefKind::AssocTy | DefKind::AssocConst => {
                 match self.tcx().associated_item(def_id).container {
@@ -49,9 +51,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
                 }
             }
             DefKind::AnonConst => self.normalize_anon_const(goal),
-            DefKind::OpaqueTy => self.normalize_opaque_type(goal),
             DefKind::TyAlias => self.normalize_weak_type(goal),
-            kind => bug!("unknown DefKind {} in projection goal: {goal:#?}", kind.descr(def_id)),
+            kind => bug!("unknown DefKind {} in normalizes-to goal: {goal:#?}", kind.descr(def_id)),
         }
     }
 
diff --git a/compiler/rustc_trait_selection/src/solve/search_graph.rs b/compiler/rustc_trait_selection/src/solve/search_graph.rs
index 07a8aca85a0..a48b2f2478b 100644
--- a/compiler/rustc_trait_selection/src/solve/search_graph.rs
+++ b/compiler/rustc_trait_selection/src/solve/search_graph.rs
@@ -10,7 +10,6 @@ use rustc_index::IndexVec;
 use rustc_middle::dep_graph::dep_kinds;
 use rustc_middle::traits::solve::CacheData;
 use rustc_middle::traits::solve::{CanonicalInput, Certainty, EvaluationCache, QueryResult};
-use rustc_middle::ty;
 use rustc_middle::ty::TyCtxt;
 use rustc_session::Limit;
 use std::mem;
@@ -175,15 +174,6 @@ impl<'tcx> SearchGraph<'tcx> {
         }
     }
 
-    pub(super) fn current_goal_is_normalizes_to(&self) -> bool {
-        self.stack.raw.last().map_or(false, |e| {
-            matches!(
-                e.input.value.goal.predicate.kind().skip_binder(),
-                ty::PredicateKind::NormalizesTo(..)
-            )
-        })
-    }
-
     /// Returns the remaining depth allowed for nested goals.
     ///
     /// This is generally simply one less than the current depth.
diff --git a/tests/ui/traits/next-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr b/tests/ui/traits/next-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr
index 39d453e8035..1d42dbdfe00 100644
--- a/tests/ui/traits/next-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr
+++ b/tests/ui/traits/next-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr
@@ -4,7 +4,6 @@ error[E0275]: overflow evaluating the requirement `<T as Overflow>::Assoc: Sized
 LL |     type Assoc = <T as Overflow>::Assoc;
    |                  ^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`trait_ref_is_knowable_norm_overflow`)
 note: required by a bound in `Overflow::Assoc`
   --> $DIR/trait_ref_is_knowable-norm-overflow.rs:7:5
    |
@@ -23,9 +22,6 @@ LL | impl<T: Copy> Trait for T {}
 LL | struct LocalTy;
 LL | impl Trait for <LocalTy as Overflow>::Assoc {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation
-   |
-   = note: overflow evaluating the requirement `_ == <LocalTy as Overflow>::Assoc`
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`trait_ref_is_knowable_norm_overflow`)
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/traits/next-solver/normalize/indirectly-constrained-term.rs b/tests/ui/traits/next-solver/normalize/indirectly-constrained-term.rs
new file mode 100644
index 00000000000..380477c2c3c
--- /dev/null
+++ b/tests/ui/traits/next-solver/normalize/indirectly-constrained-term.rs
@@ -0,0 +1,45 @@
+//@ revisions: current next
+//@[next] compile-flags: -Znext-solver=coherence
+//@ ignore-compare-mode-next-solver (explicit revisions)
+//@ check-pass
+
+// A regression test for `paperclip-core`. This previously failed to compile
+// in the new solver.
+//
+// Behavior in old solver:
+// We prove `Projection(<W<?0> as Unconstrained>::Assoc, ())`. This
+// normalizes `<W<?0> as Unconstrained>::Assoc` to `?1` with nested goals
+// `[Projection(<?0 as Unconstrained>::Assoc, ?1), Trait(?1: NoImpl)]`.
+// We then unify `?1` with `()`. At this point `?1: NoImpl` does not hold,
+// and we get an error.
+//
+// Previous behavior of the new solver:
+// We prove `Projection(<W<?0> as Unconstrained>::Assoc, ())`. This normalizes
+// `<W<?0> as Unconstrained>::Assoc` to `?1` and eagerly computes the nested
+// goals `[Projection(<?0 as Unconstrained>::Assoc, ?1), Trait(?1: NoImpl)]`.
+// These goals are both ambiguous. `NormalizesTo`` then returns `?1` as the
+// normalized-to type. It discards the nested goals, forcing the certainty of
+// the normalization to `Maybe`. Unifying `?1` with `()` succeeds¹. However,
+// this is never propagated to the `?1: NoImpl` goal, as it only exists inside
+// of the `NormalizesTo` goal. The normalized-to term always starts out as
+// unconstrained.
+//
+// We fix this regression by returning the nested goals of `NormalizesTo` goals
+// to the `AliasRelate`. This results in us checking `(): NoImpl`, same as the
+// old solver.
+
+struct W<T: ?Sized>(T);
+trait NoImpl {}
+trait Unconstrained {
+    type Assoc;
+}
+impl<T: Unconstrained<Assoc = U>, U: NoImpl> Unconstrained for W<T> {
+    type Assoc = U;
+}
+
+
+trait Overlap {}
+impl<T: Unconstrained<Assoc = ()>> Overlap for T {}
+impl<U> Overlap for W<U> {}
+
+fn main() {}
diff --git a/tests/ui/traits/next-solver/overflow/recursive-self-normalization-2.stderr b/tests/ui/traits/next-solver/overflow/recursive-self-normalization-2.stderr
index 09622bb9b6c..2b0e57966fe 100644
--- a/tests/ui/traits/next-solver/overflow/recursive-self-normalization-2.stderr
+++ b/tests/ui/traits/next-solver/overflow/recursive-self-normalization-2.stderr
@@ -3,8 +3,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo1>::Assoc1 == _`
    |
 LL |     needs_bar::<T::Assoc1>();
    |                 ^^^^^^^^^
-   |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization_2`)
 
 error[E0275]: overflow evaluating the requirement `<T as Foo1>::Assoc1: Bar`
   --> $DIR/recursive-self-normalization-2.rs:15:17
@@ -12,7 +10,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo1>::Assoc1: Bar`
 LL |     needs_bar::<T::Assoc1>();
    |                 ^^^^^^^^^
    |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization_2`)
 note: required by a bound in `needs_bar`
   --> $DIR/recursive-self-normalization-2.rs:12:17
    |
@@ -25,7 +22,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo1>::Assoc1: Sized`
 LL |     needs_bar::<T::Assoc1>();
    |                 ^^^^^^^^^
    |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization_2`)
 note: required by an implicit `Sized` bound in `needs_bar`
   --> $DIR/recursive-self-normalization-2.rs:12:14
    |
@@ -41,8 +37,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo1>::Assoc1 == _`
    |
 LL |     needs_bar::<T::Assoc1>();
    |     ^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization_2`)
 
 error[E0275]: overflow evaluating the requirement `<T as Foo1>::Assoc1 == _`
   --> $DIR/recursive-self-normalization-2.rs:15:5
@@ -50,7 +44,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo1>::Assoc1 == _`
 LL |     needs_bar::<T::Assoc1>();
    |     ^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization_2`)
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 error[E0275]: overflow evaluating the requirement `<T as Foo1>::Assoc1 == _`
@@ -59,7 +52,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo1>::Assoc1 == _`
 LL |     needs_bar::<T::Assoc1>();
    |                 ^^^^^^^^^
    |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization_2`)
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 error: aborting due to 6 previous errors
diff --git a/tests/ui/traits/next-solver/overflow/recursive-self-normalization.stderr b/tests/ui/traits/next-solver/overflow/recursive-self-normalization.stderr
index 7c058909df7..af8504dcaee 100644
--- a/tests/ui/traits/next-solver/overflow/recursive-self-normalization.stderr
+++ b/tests/ui/traits/next-solver/overflow/recursive-self-normalization.stderr
@@ -3,8 +3,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo>::Assoc == _`
    |
 LL |     needs_bar::<T::Assoc>();
    |                 ^^^^^^^^
-   |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization`)
 
 error[E0275]: overflow evaluating the requirement `<T as Foo>::Assoc: Bar`
   --> $DIR/recursive-self-normalization.rs:11:17
@@ -12,7 +10,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo>::Assoc: Bar`
 LL |     needs_bar::<T::Assoc>();
    |                 ^^^^^^^^
    |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization`)
 note: required by a bound in `needs_bar`
   --> $DIR/recursive-self-normalization.rs:8:17
    |
@@ -25,7 +22,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo>::Assoc: Sized`
 LL |     needs_bar::<T::Assoc>();
    |                 ^^^^^^^^
    |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization`)
 note: required by an implicit `Sized` bound in `needs_bar`
   --> $DIR/recursive-self-normalization.rs:8:14
    |
@@ -41,8 +37,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo>::Assoc == _`
    |
 LL |     needs_bar::<T::Assoc>();
    |     ^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization`)
 
 error[E0275]: overflow evaluating the requirement `<T as Foo>::Assoc == _`
   --> $DIR/recursive-self-normalization.rs:11:5
@@ -50,7 +44,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo>::Assoc == _`
 LL |     needs_bar::<T::Assoc>();
    |     ^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization`)
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 error[E0275]: overflow evaluating the requirement `<T as Foo>::Assoc == _`
@@ -59,7 +52,6 @@ error[E0275]: overflow evaluating the requirement `<T as Foo>::Assoc == _`
 LL |     needs_bar::<T::Assoc>();
    |                 ^^^^^^^^
    |
-   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_self_normalization`)
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 error: aborting due to 6 previous errors