diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs
index a0d31d3a22c3b..72a04a02bf4fb 100644
--- a/compiler/rustc_hir_typeck/src/method/suggest.rs
+++ b/compiler/rustc_hir_typeck/src/method/suggest.rs
@@ -1681,7 +1681,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             || found_assoc(tcx.types.u64)
             || found_assoc(tcx.types.u128)
             || found_assoc(tcx.types.f32)
-            || found_assoc(tcx.types.f32);
+            || found_assoc(tcx.types.f64);
         if found_candidate
             && actual.is_numeric()
             && !actual.has_concrete_skeleton()
diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs
index 4e13ec90228d6..07f04ec1e4449 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs
@@ -29,25 +29,15 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
         // version new_ty of its type where the anonymous region is replaced
         // with the named one.
         let (named, anon, anon_param_info, region_info) = if sub.has_name()
-            && self.tcx().is_suitable_region(sup).is_some()
-            && self.find_param_with_region(sup, sub).is_some()
+            && let Some(region_info) = self.tcx().is_suitable_region(sup)
+            && let Some(anon_param_info) = self.find_param_with_region(sup, sub)
         {
-            (
-                sub,
-                sup,
-                self.find_param_with_region(sup, sub).unwrap(),
-                self.tcx().is_suitable_region(sup).unwrap(),
-            )
+            (sub, sup, anon_param_info, region_info)
         } else if sup.has_name()
-            && self.tcx().is_suitable_region(sub).is_some()
-            && self.find_param_with_region(sub, sup).is_some()
+            && let Some(region_info) = self.tcx().is_suitable_region(sub)
+            && let Some(anon_param_info) = self.find_param_with_region(sub, sup)
         {
-            (
-                sup,
-                sub,
-                self.find_param_with_region(sub, sup).unwrap(),
-                self.tcx().is_suitable_region(sub).unwrap(),
-            )
+            (sup, sub, anon_param_info, region_info)
         } else {
             return None; // inapplicable
         };
diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs
index 2775b5ded038e..be6d1a3750cdb 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs
@@ -64,7 +64,7 @@ pub fn find_param_with_region<'tcx>(
     let body_id = hir.maybe_body_owned_by(def_id)?;
 
     let owner_id = hir.body_owner(body_id);
-    let fn_decl = hir.fn_decl_by_hir_id(owner_id).unwrap();
+    let fn_decl = hir.fn_decl_by_hir_id(owner_id)?;
     let poly_fn_sig = tcx.fn_sig(id).instantiate_identity();
 
     let fn_sig = tcx.liberate_late_bound_regions(id, poly_fn_sig);
diff --git a/compiler/rustc_lint/src/reference_casting.rs b/compiler/rustc_lint/src/reference_casting.rs
index ed3d47210494f..43f50a04aaddc 100644
--- a/compiler/rustc_lint/src/reference_casting.rs
+++ b/compiler/rustc_lint/src/reference_casting.rs
@@ -98,32 +98,56 @@ impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting {
 fn is_cast_from_const_to_mut<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
     let e = e.peel_blocks();
 
-    // <expr> as *mut ...
-    let e = if let ExprKind::Cast(e, t) = e.kind
-        && let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(t.hir_id).kind() {
-        e
-    // <expr>.cast_mut()
-    } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
-        && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
-        && cx.tcx.is_diagnostic_item(sym::ptr_cast_mut, def_id) {
-        expr
-    } else {
-        return false;
-    };
+    fn from_casts<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+        // <expr> as *mut ...
+        let e = if let ExprKind::Cast(e, t) = e.kind
+            && let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(t.hir_id).kind() {
+            e
+        // <expr>.cast_mut()
+        } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
+            && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
+            && cx.tcx.is_diagnostic_item(sym::ptr_cast_mut, def_id) {
+            expr
+        } else {
+            return None;
+        };
 
-    let e = e.peel_blocks();
+        let e = e.peel_blocks();
+
+        // <expr> as *const ...
+        let e = if let ExprKind::Cast(e, t) = e.kind
+            && let ty::RawPtr(TypeAndMut { mutbl: Mutability::Not, .. }) = cx.typeck_results().node_type(t.hir_id).kind() {
+            e
+        // ptr::from_ref(<expr>)
+        } else if let ExprKind::Call(path, [arg]) = e.kind
+            && let ExprKind::Path(ref qpath) = path.kind
+            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+            && cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) {
+            arg
+        } else {
+            return None;
+        };
+
+        Some(e)
+    }
+
+    fn from_transmute<'tcx>(
+        cx: &LateContext<'tcx>,
+        e: &'tcx Expr<'tcx>,
+    ) -> Option<&'tcx Expr<'tcx>> {
+        // mem::transmute::<_, *mut _>(<expr>)
+        if let ExprKind::Call(path, [arg]) = e.kind
+            && let ExprKind::Path(ref qpath) = path.kind
+            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+            && cx.tcx.is_diagnostic_item(sym::transmute, def_id)
+            && let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(e.hir_id).kind() {
+            Some(arg)
+        } else {
+            None
+        }
+    }
 
-    // <expr> as *const ...
-    let e = if let ExprKind::Cast(e, t) = e.kind
-        && let ty::RawPtr(TypeAndMut { mutbl: Mutability::Not, .. }) = cx.typeck_results().node_type(t.hir_id).kind() {
-        e
-    // ptr::from_ref(<expr>)
-    } else if let ExprKind::Call(path, [arg]) = e.kind
-        && let ExprKind::Path(ref qpath) = path.kind
-        && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
-        && cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) {
-        arg
-    } else {
+    let Some(e) = from_casts(cx, e).or_else(|| from_transmute(cx, e)) else {
         return false;
     };
 
diff --git a/compiler/rustc_trait_selection/src/solve/assembly/mod.rs b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
index 1391b51e67f31..3750b3750bff1 100644
--- a/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
@@ -316,6 +316,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
 
         self.assemble_param_env_candidates(goal, &mut candidates);
 
+        self.assemble_coherence_unknowable_candidates(goal, &mut candidates);
+
         candidates
     }
 
@@ -363,10 +365,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
 
         self.assemble_object_bound_candidates(goal, &mut candidates);
 
-        self.assemble_coherence_unknowable_candidates(goal, &mut candidates);
-
         self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates, num_steps);
-
         candidates
     }
 
@@ -877,26 +876,43 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         goal: Goal<'tcx, G>,
         candidates: &mut Vec<Candidate<'tcx>>,
     ) {
+        let tcx = self.tcx();
         match self.solver_mode() {
             SolverMode::Normal => return,
-            SolverMode::Coherence => {
-                let trait_ref = goal.predicate.trait_ref(self.tcx());
-                match coherence::trait_ref_is_knowable(self.tcx(), trait_ref) {
-                    Ok(()) => {}
-                    Err(_) => match self
-                        .evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
-                    {
-                        Ok(result) => candidates.push(Candidate {
-                            source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
-                            result,
-                        }),
-                        // FIXME: This will be reachable at some point if we're in
-                        // `assemble_candidates_after_normalizing_self_ty` and we get a
-                        // universe error. We'll deal with it at this point.
-                        Err(NoSolution) => bug!("coherence candidate resulted in NoSolution"),
-                    },
+            SolverMode::Coherence => {}
+        };
+
+        let result = self.probe_candidate("coherence unknowable").enter(|ecx| {
+            let trait_ref = goal.predicate.trait_ref(tcx);
+
+            #[derive(Debug)]
+            enum FailureKind {
+                Overflow,
+                NoSolution(NoSolution),
+            }
+            let lazily_normalize_ty = |ty| match ecx.try_normalize_ty(goal.param_env, ty) {
+                Ok(Some(ty)) => Ok(ty),
+                Ok(None) => Err(FailureKind::Overflow),
+                Err(e) => Err(FailureKind::NoSolution(e)),
+            };
+
+            match coherence::trait_ref_is_knowable(tcx, trait_ref, lazily_normalize_ty) {
+                Err(FailureKind::Overflow) => {
+                    ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW)
+                }
+                Err(FailureKind::NoSolution(NoSolution)) | Ok(Ok(())) => Err(NoSolution),
+                Ok(Err(_)) => {
+                    ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
                 }
             }
+        });
+
+        match result {
+            Ok(result) => candidates.push(Candidate {
+                source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
+                result,
+            }),
+            Err(NoSolution) => {}
         }
     }
 
diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs
index 60c49f665a6a9..5c2cbe399536a 100644
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs
@@ -388,44 +388,60 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
             && is_normalizes_to_hack == IsNormalizesToHack::No
             && !self.search_graph.in_cycle()
         {
-            debug!("rerunning goal to check result is stable");
-            self.search_graph.reset_encountered_overflow(encountered_overflow);
-            let (_orig_values, canonical_goal) = self.canonicalize_goal(goal);
-            let Ok(new_canonical_response) = EvalCtxt::evaluate_canonical_goal(
-                self.tcx(),
-                self.search_graph,
-                canonical_goal,
-                // FIXME(-Ztrait-solver=next): we do not track what happens in `evaluate_canonical_goal`
-                &mut ProofTreeBuilder::new_noop(),
-            ) else {
-                bug!(
-                    "goal went from {certainty:?} to error: re-canonicalized goal={canonical_goal:#?} \
-                    first_response={canonical_response:#?},
-                    second response was error"
-                );
-            };
-            // We only check for modulo regions as we convert all regions in
-            // the input to new existentials, even if they're expected to be
-            // `'static` or a placeholder region.
-            if !new_canonical_response.value.var_values.is_identity_modulo_regions() {
-                bug!(
-                    "unstable result: re-canonicalized goal={canonical_goal:#?} \
-                    first_response={canonical_response:#?} \
-                    second_response={new_canonical_response:#?}"
-                );
-            }
-            if certainty != new_canonical_response.value.certainty {
-                bug!(
-                    "unstable certainty: {certainty:#?} re-canonicalized goal={canonical_goal:#?} \
-                     first_response={canonical_response:#?} \
-                     second_response={new_canonical_response:#?}"
-                );
-            }
+            // The nested evaluation has to happen with the original state
+            // of `encountered_overflow`.
+            let from_original_evaluation =
+                self.search_graph.reset_encountered_overflow(encountered_overflow);
+            self.check_evaluate_goal_stable_result(goal, canonical_goal, canonical_response);
+            // In case the evaluation was unstable, we manually make sure that this
+            // debug check does not influence the result of the parent goal.
+            self.search_graph.reset_encountered_overflow(from_original_evaluation);
         }
 
         Ok((has_changed, certainty, nested_goals))
     }
 
+    fn check_evaluate_goal_stable_result(
+        &mut self,
+        goal: Goal<'tcx, ty::Predicate<'tcx>>,
+        original_input: CanonicalInput<'tcx>,
+        original_result: CanonicalResponse<'tcx>,
+    ) {
+        let (_orig_values, canonical_goal) = self.canonicalize_goal(goal);
+        let result = EvalCtxt::evaluate_canonical_goal(
+            self.tcx(),
+            self.search_graph,
+            canonical_goal,
+            // FIXME(-Ztrait-solver=next): we do not track what happens in `evaluate_canonical_goal`
+            &mut ProofTreeBuilder::new_noop(),
+        );
+
+        macro_rules! fail {
+            ($msg:expr) => {{
+                let msg = $msg;
+                warn!(
+                    "unstable result: {msg}\n\
+                    original goal: {original_input:?},\n\
+                    original result: {original_result:?}\n\
+                    re-canonicalized goal: {canonical_goal:?}\n\
+                    second response: {result:?}"
+                );
+                return;
+            }};
+        }
+
+        let Ok(new_canonical_response) = result else { fail!("second response was error") };
+        // We only check for modulo regions as we convert all regions in
+        // the input to new existentials, even if they're expected to be
+        // `'static` or a placeholder region.
+        if !new_canonical_response.value.var_values.is_identity_modulo_regions() {
+            fail!("additional constraints from second response")
+        }
+        if original_result.value.certainty != new_canonical_response.value.certainty {
+            fail!("unstable certainty")
+        }
+    }
+
     fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> {
         let Goal { param_env, predicate } = goal;
         let kind = predicate.kind();
diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs
index 63d4a38119f93..75a99f799a24c 100644
--- a/compiler/rustc_trait_selection/src/solve/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/mod.rs
@@ -283,6 +283,37 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
 
         Ok(self.make_ambiguous_response_no_constraints(maybe_cause))
     }
+
+    /// Normalize a type when it is structually matched on.
+    ///
+    /// For self types this is generally already handled through
+    /// `assemble_candidates_after_normalizing_self_ty`, so anything happening
+    /// in [`EvalCtxt::assemble_candidates_via_self_ty`] does not have to normalize
+    /// the self type. It is required when structurally matching on any other
+    /// arguments of a trait goal, e.g. when assembling builtin unsize candidates.
+    fn try_normalize_ty(
+        &mut self,
+        param_env: ty::ParamEnv<'tcx>,
+        mut ty: Ty<'tcx>,
+    ) -> Result<Option<Ty<'tcx>>, NoSolution> {
+        for _ in 0..self.local_overflow_limit() {
+            let ty::Alias(_, projection_ty) = *ty.kind() else {
+                return Ok(Some(ty));
+            };
+
+            let normalized_ty = self.next_ty_infer();
+            let normalizes_to_goal = Goal::new(
+                self.tcx(),
+                param_env,
+                ty::ProjectionPredicate { projection_ty, term: normalized_ty.into() },
+            );
+            self.add_goal(normalizes_to_goal);
+            self.try_evaluate_added_goals()?;
+            ty = self.resolve_vars_if_possible(normalized_ty);
+        }
+
+        Ok(None)
+    }
 }
 
 fn response_no_constraints_raw<'tcx>(
diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs
index b860f374c0ab3..49ebfa4e6cbce 100644
--- a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs
@@ -134,9 +134,13 @@ impl<'tcx> SearchGraph<'tcx> {
     /// Resets `encountered_overflow` of the current goal.
     ///
     /// This should only be used for the check in `evaluate_goal`.
-    pub(super) fn reset_encountered_overflow(&mut self, encountered_overflow: bool) {
-        if encountered_overflow {
-            self.stack.raw.last_mut().unwrap().encountered_overflow = true;
+    pub(super) fn reset_encountered_overflow(&mut self, encountered_overflow: bool) -> bool {
+        if let Some(last) = self.stack.raw.last_mut() {
+            let prev = last.encountered_overflow;
+            last.encountered_overflow = encountered_overflow;
+            prev
+        } else {
+            false
         }
     }
 
diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs
index db80b62d8a28f..ee6f1686b8238 100644
--- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs
+++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs
@@ -448,7 +448,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
             // We need to normalize the b_ty since it's matched structurally
             // in the other functions below.
             let b_ty = match ecx
-                .normalize_non_self_ty(goal.predicate.trait_ref.args.type_at(1), goal.param_env)
+                .try_normalize_ty(goal.param_env, goal.predicate.trait_ref.args.type_at(1))
             {
                 Ok(Some(b_ty)) => b_ty,
                 Ok(None) => return vec![misc_candidate(ecx, Certainty::OVERFLOW)],
@@ -927,41 +927,4 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         let candidates = self.assemble_and_evaluate_candidates(goal);
         self.merge_candidates(candidates)
     }
-
-    /// Normalize a non-self type when it is structually matched on when solving
-    /// a built-in goal.
-    ///
-    /// This is handled already through `assemble_candidates_after_normalizing_self_ty`
-    /// for the self type, but for other goals, additional normalization of other
-    /// arguments may be needed to completely implement the semantics of the trait.
-    ///
-    /// This is required when structurally matching on any trait argument that is
-    /// not the self type.
-    fn normalize_non_self_ty(
-        &mut self,
-        mut ty: Ty<'tcx>,
-        param_env: ty::ParamEnv<'tcx>,
-    ) -> Result<Option<Ty<'tcx>>, NoSolution> {
-        if !matches!(ty.kind(), ty::Alias(..)) {
-            return Ok(Some(ty));
-        }
-
-        for _ in 0..self.local_overflow_limit() {
-            let ty::Alias(_, projection_ty) = *ty.kind() else {
-                return Ok(Some(ty));
-            };
-
-            let normalized_ty = self.next_ty_infer();
-            let normalizes_to_goal = Goal::new(
-                self.tcx(),
-                param_env,
-                ty::ProjectionPredicate { projection_ty, term: normalized_ty.into() },
-            );
-            self.add_goal(normalizes_to_goal);
-            self.try_evaluate_added_goals()?;
-            ty = self.resolve_vars_if_possible(normalized_ty);
-        }
-
-        Ok(None)
-    }
 }
diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs
index 19d5baa30ec6b..e56af586ed875 100644
--- a/compiler/rustc_trait_selection/src/traits/coherence.rs
+++ b/compiler/rustc_trait_selection/src/traits/coherence.rs
@@ -452,22 +452,23 @@ fn prove_negated_obligation<'tcx>(
 /// This both checks whether any downstream or sibling crates could
 /// implement it and whether an upstream crate can add this impl
 /// without breaking backwards compatibility.
-#[instrument(level = "debug", skip(tcx), ret)]
-pub fn trait_ref_is_knowable<'tcx>(
+#[instrument(level = "debug", skip(tcx, lazily_normalize_ty), ret)]
+pub fn trait_ref_is_knowable<'tcx, E: Debug>(
     tcx: TyCtxt<'tcx>,
     trait_ref: ty::TraitRef<'tcx>,
-) -> Result<(), Conflict> {
+    mut lazily_normalize_ty: impl FnMut(Ty<'tcx>) -> Result<Ty<'tcx>, E>,
+) -> Result<Result<(), Conflict>, E> {
     if Some(trait_ref.def_id) == tcx.lang_items().fn_ptr_trait() {
         // The only types implementing `FnPtr` are function pointers,
         // so if there's no impl of `FnPtr` in the current crate,
         // then such an impl will never be added in the future.
-        return Ok(());
+        return Ok(Ok(()));
     }
 
-    if orphan_check_trait_ref(trait_ref, InCrate::Remote).is_ok() {
+    if orphan_check_trait_ref(trait_ref, InCrate::Remote, &mut lazily_normalize_ty)?.is_ok() {
         // A downstream or cousin crate is allowed to implement some
         // substitution of this trait-ref.
-        return Err(Conflict::Downstream);
+        return Ok(Err(Conflict::Downstream));
     }
 
     if trait_ref_is_local_or_fundamental(tcx, trait_ref) {
@@ -476,7 +477,7 @@ pub fn trait_ref_is_knowable<'tcx>(
         // allowed to implement a substitution of this trait ref, which
         // means impls could only come from dependencies of this crate,
         // which we already know about.
-        return Ok(());
+        return Ok(Ok(()));
     }
 
     // This is a remote non-fundamental trait, so if another crate
@@ -487,10 +488,10 @@ pub fn trait_ref_is_knowable<'tcx>(
     // and if we are an intermediate owner, then we don't care
     // about future-compatibility, which means that we're OK if
     // we are an owner.
-    if orphan_check_trait_ref(trait_ref, InCrate::Local).is_ok() {
-        Ok(())
+    if orphan_check_trait_ref(trait_ref, InCrate::Local, &mut lazily_normalize_ty)?.is_ok() {
+        Ok(Ok(()))
     } else {
-        Err(Conflict::Upstream)
+        Ok(Err(Conflict::Upstream))
     }
 }
 
@@ -526,7 +527,7 @@ pub fn orphan_check(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Result<(), OrphanChe
         return Ok(());
     }
 
-    orphan_check_trait_ref(trait_ref, InCrate::Local)
+    orphan_check_trait_ref::<!>(trait_ref, InCrate::Local, |ty| Ok(ty)).unwrap()
 }
 
 /// Checks whether a trait-ref is potentially implementable by a crate.
@@ -615,11 +616,12 @@ pub fn orphan_check(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Result<(), OrphanChe
 ///
 /// Note that this function is never called for types that have both type
 /// parameters and inference variables.
-#[instrument(level = "trace", ret)]
-fn orphan_check_trait_ref<'tcx>(
+#[instrument(level = "trace", skip(lazily_normalize_ty), ret)]
+fn orphan_check_trait_ref<'tcx, E: Debug>(
     trait_ref: ty::TraitRef<'tcx>,
     in_crate: InCrate,
-) -> Result<(), OrphanCheckErr<'tcx>> {
+    lazily_normalize_ty: impl FnMut(Ty<'tcx>) -> Result<Ty<'tcx>, E>,
+) -> Result<Result<(), OrphanCheckErr<'tcx>>, E> {
     if trait_ref.has_infer() && trait_ref.has_param() {
         bug!(
             "can't orphan check a trait ref with both params and inference variables {:?}",
@@ -627,9 +629,10 @@ fn orphan_check_trait_ref<'tcx>(
         );
     }
 
-    let mut checker = OrphanChecker::new(in_crate);
-    match trait_ref.visit_with(&mut checker) {
+    let mut checker = OrphanChecker::new(in_crate, lazily_normalize_ty);
+    Ok(match trait_ref.visit_with(&mut checker) {
         ControlFlow::Continue(()) => Err(OrphanCheckErr::NonLocalInputType(checker.non_local_tys)),
+        ControlFlow::Break(OrphanCheckEarlyExit::NormalizationFailure(err)) => return Err(err),
         ControlFlow::Break(OrphanCheckEarlyExit::ParamTy(ty)) => {
             // Does there exist some local type after the `ParamTy`.
             checker.search_first_local_ty = true;
@@ -642,34 +645,39 @@ fn orphan_check_trait_ref<'tcx>(
             }
         }
         ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(_)) => Ok(()),
-    }
+    })
 }
 
-struct OrphanChecker<'tcx> {
+struct OrphanChecker<'tcx, F> {
     in_crate: InCrate,
     in_self_ty: bool,
+    lazily_normalize_ty: F,
     /// Ignore orphan check failures and exclusively search for the first
     /// local type.
     search_first_local_ty: bool,
     non_local_tys: Vec<(Ty<'tcx>, bool)>,
 }
 
-impl<'tcx> OrphanChecker<'tcx> {
-    fn new(in_crate: InCrate) -> Self {
+impl<'tcx, F, E> OrphanChecker<'tcx, F>
+where
+    F: FnOnce(Ty<'tcx>) -> Result<Ty<'tcx>, E>,
+{
+    fn new(in_crate: InCrate, lazily_normalize_ty: F) -> Self {
         OrphanChecker {
             in_crate,
             in_self_ty: true,
+            lazily_normalize_ty,
             search_first_local_ty: false,
             non_local_tys: Vec::new(),
         }
     }
 
-    fn found_non_local_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<OrphanCheckEarlyExit<'tcx>> {
+    fn found_non_local_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<OrphanCheckEarlyExit<'tcx, E>> {
         self.non_local_tys.push((t, self.in_self_ty));
         ControlFlow::Continue(())
     }
 
-    fn found_param_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<OrphanCheckEarlyExit<'tcx>> {
+    fn found_param_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<OrphanCheckEarlyExit<'tcx, E>> {
         if self.search_first_local_ty {
             ControlFlow::Continue(())
         } else {
@@ -685,18 +693,28 @@ impl<'tcx> OrphanChecker<'tcx> {
     }
 }
 
-enum OrphanCheckEarlyExit<'tcx> {
+enum OrphanCheckEarlyExit<'tcx, E> {
+    NormalizationFailure(E),
     ParamTy(Ty<'tcx>),
     LocalTy(Ty<'tcx>),
 }
 
-impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for OrphanChecker<'tcx> {
-    type BreakTy = OrphanCheckEarlyExit<'tcx>;
+impl<'tcx, F, E> TypeVisitor<TyCtxt<'tcx>> for OrphanChecker<'tcx, F>
+where
+    F: FnMut(Ty<'tcx>) -> Result<Ty<'tcx>, E>,
+{
+    type BreakTy = OrphanCheckEarlyExit<'tcx, E>;
     fn visit_region(&mut self, _r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
         ControlFlow::Continue(())
     }
 
     fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+        // Need to lazily normalize here in with `-Ztrait-solver=next-coherence`.
+        let ty = match (self.lazily_normalize_ty)(ty) {
+            Ok(ty) => ty,
+            Err(err) => return ControlFlow::Break(OrphanCheckEarlyExit::NormalizationFailure(err)),
+        };
+
         let result = match *ty.kind() {
             ty::Bool
             | ty::Char
diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs
index 4371b6c1239ee..840980da30de1 100644
--- a/compiler/rustc_trait_selection/src/traits/select/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs
@@ -1457,7 +1457,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
         // bound regions.
         let trait_ref = predicate.skip_binder().trait_ref;
 
-        coherence::trait_ref_is_knowable(self.tcx(), trait_ref)
+        coherence::trait_ref_is_knowable::<!>(self.tcx(), trait_ref, |ty| Ok(ty)).unwrap()
     }
 
     /// Returns `true` if the global caches can be used.
diff --git a/tests/rustdoc-gui/sidebar-links-color.goml b/tests/rustdoc-gui/sidebar-links-color.goml
index cec1a7999267c..079d582a56755 100644
--- a/tests/rustdoc-gui/sidebar-links-color.goml
+++ b/tests/rustdoc-gui/sidebar-links-color.goml
@@ -92,80 +92,80 @@ call-function: (
     "check-colors",
     {
         "theme": "ayu",
-        "struct": "rgb(83, 177, 219)",
-        "struct_hover": "rgb(255, 180, 76)",
-        "struct_hover_background": "rgba(0, 0, 0, 0)",
-        "enum": "rgb(83, 177, 219)",
-        "enum_hover": "rgb(255, 180, 76)",
-        "enum_hover_background": "rgba(0, 0, 0, 0)",
-        "union": "rgb(83, 177, 219)",
-        "union_hover": "rgb(255, 180, 76)",
-        "union_hover_background": "rgba(0, 0, 0, 0)",
-        "trait": "rgb(83, 177, 219)",
-        "trait_hover": "rgb(255, 180, 76)",
-        "trait_hover_background": "rgba(0, 0, 0, 0)",
-        "fn": "rgb(83, 177, 219)",
-        "fn_hover": "rgb(255, 180, 76)",
-        "fn_hover_background": "rgba(0, 0, 0, 0)",
-        "type": "rgb(83, 177, 219)",
-        "type_hover": "rgb(255, 180, 76)",
-        "type_hover_background": "rgba(0, 0, 0, 0)",
-        "keyword": "rgb(83, 177, 219)",
-        "keyword_hover": "rgb(255, 180, 76)",
-        "keyword_hover_background": "rgba(0, 0, 0, 0)",
+        "struct": "#53b1db",
+        "struct_hover": "#ffb44c",
+        "struct_hover_background": "transparent",
+        "enum": "#53b1db",
+        "enum_hover": "#ffb44c",
+        "enum_hover_background": "transparent",
+        "union": "#53b1db",
+        "union_hover": "#ffb44c",
+        "union_hover_background": "transparent",
+        "trait": "#53b1db",
+        "trait_hover": "#ffb44c",
+        "trait_hover_background": "transparent",
+        "fn": "#53b1db",
+        "fn_hover": "#ffb44c",
+        "fn_hover_background": "transparent",
+        "type": "#53b1db",
+        "type_hover": "#ffb44c",
+        "type_hover_background": "transparent",
+        "keyword": "#53b1db",
+        "keyword_hover": "#ffb44c",
+        "keyword_hover_background": "transparent",
     }
 )
 call-function: (
     "check-colors",
     {
         "theme": "dark",
-        "struct": "rgb(253, 191, 53)",
-        "struct_hover": "rgb(253, 191, 53)",
-        "struct_hover_background": "rgb(68, 68, 68)",
-        "enum": "rgb(253, 191, 53)",
-        "enum_hover": "rgb(253, 191, 53)",
-        "enum_hover_background": "rgb(68, 68, 68)",
-        "union": "rgb(253, 191, 53)",
-        "union_hover": "rgb(253, 191, 53)",
-        "union_hover_background": "rgb(68, 68, 68)",
-        "trait": "rgb(253, 191, 53)",
-        "trait_hover": "rgb(253, 191, 53)",
-        "trait_hover_background": "rgb(68, 68, 68)",
-        "fn": "rgb(253, 191, 53)",
-        "fn_hover": "rgb(253, 191, 53)",
-        "fn_hover_background": "rgb(68, 68, 68)",
-        "type": "rgb(253, 191, 53)",
-        "type_hover": "rgb(253, 191, 53)",
-        "type_hover_background": "rgb(68, 68, 68)",
-        "keyword": "rgb(253, 191, 53)",
-        "keyword_hover": "rgb(253, 191, 53)",
-        "keyword_hover_background": "rgb(68, 68, 68)",
+        "struct": "#fdbf35",
+        "struct_hover": "#fdbf35",
+        "struct_hover_background": "#444",
+        "enum": "#fdbf35",
+        "enum_hover": "#fdbf35",
+        "enum_hover_background": "#444",
+        "union": "#fdbf35",
+        "union_hover": "#fdbf35",
+        "union_hover_background": "#444",
+        "trait": "#fdbf35",
+        "trait_hover": "#fdbf35",
+        "trait_hover_background": "#444",
+        "fn": "#fdbf35",
+        "fn_hover": "#fdbf35",
+        "fn_hover_background": "#444",
+        "type": "#fdbf35",
+        "type_hover": "#fdbf35",
+        "type_hover_background": "#444",
+        "keyword": "#fdbf35",
+        "keyword_hover": "#fdbf35",
+        "keyword_hover_background": "#444",
     }
 )
 call-function: (
     "check-colors",
     {
         "theme": "light",
-        "struct": "rgb(53, 109, 164)",
-        "struct_hover": "rgb(53, 109, 164)",
-        "struct_hover_background": "rgb(255, 255, 255)",
-        "enum": "rgb(53, 109, 164)",
-        "enum_hover": "rgb(53, 109, 164)",
-        "enum_hover_background": "rgb(255, 255, 255)",
-        "union": "rgb(53, 109, 164)",
-        "union_hover": "rgb(53, 109, 164)",
-        "union_hover_background": "rgb(255, 255, 255)",
-        "trait": "rgb(53, 109, 164)",
-        "trait_hover": "rgb(53, 109, 164)",
-        "trait_hover_background": "rgb(255, 255, 255)",
-        "fn": "rgb(53, 109, 164)",
-        "fn_hover": "rgb(53, 109, 164)",
-        "fn_hover_background": "rgb(255, 255, 255)",
-        "type": "rgb(53, 109, 164)",
-        "type_hover": "rgb(53, 109, 164)",
-        "type_hover_background": "rgb(255, 255, 255)",
-        "keyword": "rgb(53, 109, 164)",
-        "keyword_hover": "rgb(53, 109, 164)",
-        "keyword_hover_background": "rgb(255, 255, 255)",
+        "struct": "#356da4",
+        "struct_hover": "#356da4",
+        "struct_hover_background": "#fff",
+        "enum": "#356da4",
+        "enum_hover": "#356da4",
+        "enum_hover_background": "#fff",
+        "union": "#356da4",
+        "union_hover": "#356da4",
+        "union_hover_background": "#fff",
+        "trait": "#356da4",
+        "trait_hover": "#356da4",
+        "trait_hover_background": "#fff",
+        "fn": "#356da4",
+        "fn_hover": "#356da4",
+        "fn_hover_background": "#fff",
+        "type": "#356da4",
+        "type_hover": "#356da4",
+        "type_hover_background": "#fff",
+        "keyword": "#356da4",
+        "keyword_hover": "#356da4",
+        "keyword_hover_background": "#fff",
     }
 )
diff --git a/tests/ui/generic-const-items/reference-outlives-referent.rs b/tests/ui/generic-const-items/reference-outlives-referent.rs
new file mode 100644
index 0000000000000..13e4eaac39f88
--- /dev/null
+++ b/tests/ui/generic-const-items/reference-outlives-referent.rs
@@ -0,0 +1,9 @@
+// Test that we catch that the reference outlives the referent and we
+// successfully emit a diagnostic. Regression test for issue #114714.
+
+#![feature(generic_const_items)]
+#![allow(incomplete_features)]
+
+const Q<'a, 'b>: &'a &'b () = &&(); //~ ERROR reference has a longer lifetime than the data it references
+
+fn main() {}
diff --git a/tests/ui/generic-const-items/reference-outlives-referent.stderr b/tests/ui/generic-const-items/reference-outlives-referent.stderr
new file mode 100644
index 0000000000000..2b57713b5c109
--- /dev/null
+++ b/tests/ui/generic-const-items/reference-outlives-referent.stderr
@@ -0,0 +1,20 @@
+error[E0491]: in type `&'a &'b ()`, reference has a longer lifetime than the data it references
+  --> $DIR/reference-outlives-referent.rs:7:18
+   |
+LL | const Q<'a, 'b>: &'a &'b () = &&();
+   |                  ^^^^^^^^^^
+   |
+note: the pointer is valid for the lifetime `'a` as defined here
+  --> $DIR/reference-outlives-referent.rs:7:9
+   |
+LL | const Q<'a, 'b>: &'a &'b () = &&();
+   |         ^^
+note: but the referenced data is only valid for the lifetime `'b` as defined here
+  --> $DIR/reference-outlives-referent.rs:7:13
+   |
+LL | const Q<'a, 'b>: &'a &'b () = &&();
+   |             ^^
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0491`.
diff --git a/tests/ui/lint/reference_casting.rs b/tests/ui/lint/reference_casting.rs
index 6e70626ef997f..f4e463b67c07b 100644
--- a/tests/ui/lint/reference_casting.rs
+++ b/tests/ui/lint/reference_casting.rs
@@ -24,6 +24,8 @@ unsafe fn ref_to_mut() {
     //~^ ERROR casting `&T` to `&mut T` is undefined behavior
     let _num = &mut *(std::ptr::from_ref({ num }) as *mut i32);
     //~^ ERROR casting `&T` to `&mut T` is undefined behavior
+    let _num = &mut *std::mem::transmute::<_, *mut i32>(num);
+    //~^ ERROR casting `&T` to `&mut T` is undefined behavior
 
     let deferred = num as *const i32 as *mut i32;
     let _num = &mut *deferred;
@@ -47,6 +49,9 @@ unsafe fn assign_to_ref() {
     //~^ ERROR assigning to `&T` is undefined behavior
     *(std::ptr::from_ref({ num }) as *mut i32) += 1;
     //~^ ERROR assigning to `&T` is undefined behavior
+    *std::mem::transmute::<_, *mut i32>(num) += 1;
+    //~^ ERROR assigning to `&T` is undefined behavior
+
     let value = num as *const i32 as *mut i32;
     *value = 1;
     //~^ ERROR assigning to `&T` is undefined behavior
diff --git a/tests/ui/lint/reference_casting.stderr b/tests/ui/lint/reference_casting.stderr
index 02b23600557d5..e8bb0557ca8a0 100644
--- a/tests/ui/lint/reference_casting.stderr
+++ b/tests/ui/lint/reference_casting.stderr
@@ -37,7 +37,13 @@ LL |     let _num = &mut *(std::ptr::from_ref({ num }) as *mut i32);
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
-  --> $DIR/reference_casting.rs:29:16
+  --> $DIR/reference_casting.rs:27:16
+   |
+LL |     let _num = &mut *std::mem::transmute::<_, *mut i32>(num);
+   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
+  --> $DIR/reference_casting.rs:31:16
    |
 LL |     let deferred = num as *const i32 as *mut i32;
    |                    ----------------------------- casting happend here
@@ -45,48 +51,54 @@ LL |     let _num = &mut *deferred;
    |                ^^^^^^^^^^^^^^
 
 error: assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
-  --> $DIR/reference_casting.rs:38:5
+  --> $DIR/reference_casting.rs:40:5
    |
 LL |     *(a as *const _ as *mut _) = String::from("Replaced");
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
-  --> $DIR/reference_casting.rs:40:5
+  --> $DIR/reference_casting.rs:42:5
    |
 LL |     *(a as *const _ as *mut String) += " world";
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
-  --> $DIR/reference_casting.rs:42:5
+  --> $DIR/reference_casting.rs:44:5
    |
 LL |     *std::ptr::from_ref(num).cast_mut() += 1;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
-  --> $DIR/reference_casting.rs:44:5
+  --> $DIR/reference_casting.rs:46:5
    |
 LL |     *std::ptr::from_ref({ num }).cast_mut() += 1;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
-  --> $DIR/reference_casting.rs:46:5
+  --> $DIR/reference_casting.rs:48:5
    |
 LL |     *{ std::ptr::from_ref(num) }.cast_mut() += 1;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
-  --> $DIR/reference_casting.rs:48:5
+  --> $DIR/reference_casting.rs:50:5
    |
 LL |     *(std::ptr::from_ref({ num }) as *mut i32) += 1;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
-  --> $DIR/reference_casting.rs:51:5
+  --> $DIR/reference_casting.rs:52:5
+   |
+LL |     *std::mem::transmute::<_, *mut i32>(num) += 1;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
+  --> $DIR/reference_casting.rs:56:5
    |
 LL |     let value = num as *const i32 as *mut i32;
    |                 ----------------------------- casting happend here
 LL |     *value = 1;
    |     ^^^^^^^^^^
 
-error: aborting due to 14 previous errors
+error: aborting due to 16 previous errors
 
diff --git a/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-norm-overflow.rs b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-norm-overflow.rs
new file mode 100644
index 0000000000000..b39ae0333ad31
--- /dev/null
+++ b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-norm-overflow.rs
@@ -0,0 +1,20 @@
+// compile-flags: -Ztrait-solver=next
+
+// Coherence should handle overflow while normalizing for
+// `trait_ref_is_knowable` correctly.
+
+trait Overflow {
+    type Assoc;
+}
+impl<T> Overflow for T {
+    type Assoc = <T as Overflow>::Assoc;
+}
+
+
+trait Trait {}
+impl<T: Copy> Trait for T {}
+struct LocalTy;
+impl Trait for <LocalTy as Overflow>::Assoc {}
+//~^ ERROR conflicting implementations of trait `Trait` for type `<LocalTy as Overflow>::Assoc`
+
+fn main() {}
diff --git a/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr
new file mode 100644
index 0000000000000..5d5f325e4b473
--- /dev/null
+++ b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr
@@ -0,0 +1,12 @@
+error[E0119]: conflicting implementations of trait `Trait` for type `<LocalTy as Overflow>::Assoc`
+  --> $DIR/trait_ref_is_knowable-norm-overflow.rs:17:1
+   |
+LL | impl<T: Copy> Trait for T {}
+   | ------------------------- first implementation here
+LL | struct LocalTy;
+LL | impl Trait for <LocalTy as Overflow>::Assoc {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `<LocalTy as Overflow>::Assoc`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0119`.
diff --git a/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-normalization-1.rs b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-normalization-1.rs
new file mode 100644
index 0000000000000..c38e3baf5b465
--- /dev/null
+++ b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-normalization-1.rs
@@ -0,0 +1,22 @@
+// compile-flags: -Ztrait-solver=next
+// check-pass
+
+trait Id {
+    type Assoc;
+}
+impl<T> Id for T {
+    type Assoc = T;
+}
+
+
+// Coherence should be able to reason that `<LocalTy as Id>::Assoc: Copy`
+// does not hold.
+//
+// See https://github.com/rust-lang/trait-system-refactor-initiative/issues/51
+// for more details.
+trait Trait {}
+impl<T: Copy> Trait for T {}
+struct LocalTy;
+impl Trait for <LocalTy as Id>::Assoc {}
+
+fn main() {}
diff --git a/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-normalization-2.rs b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-normalization-2.rs
new file mode 100644
index 0000000000000..2d53266db09d5
--- /dev/null
+++ b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-normalization-2.rs
@@ -0,0 +1,25 @@
+// compile-flags: -Ztrait-solver=next
+// check-pass
+
+use std::future::{Future, IntoFuture};
+use std::pin::Pin;
+
+// We check that this does not overlap with the following impl from std:
+//     impl<P> Future for Pin<P> where P: DerefMut, <P as Deref>::Target: Future { .. }
+// This should fail because we know ` <&mut Value as Deref>::Target: Future` not to hold.
+// For this to work we have to normalize in the `trait_ref_is_knowable` check as we
+// otherwise add an ambiguous candidate here.
+//
+// See https://github.com/rust-lang/trait-system-refactor-initiative/issues/51
+// for more details.
+struct Value;
+impl<'a> IntoFuture for Pin<&'a mut Value> {
+    type Output = ();
+    type IntoFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
+
+    fn into_future(self) -> Self::IntoFuture {
+        todo!()
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-normalization-3.rs b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-normalization-3.rs
new file mode 100644
index 0000000000000..2f27de4e4f4da
--- /dev/null
+++ b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-normalization-3.rs
@@ -0,0 +1,24 @@
+// compile-flags: -Ztrait-solver=next
+// check-pass
+
+trait Id {
+    type Assoc;
+}
+impl<T> Id for T {
+    type Assoc = T;
+}
+
+
+// Coherence should be able to reason that `(): PartialEq<<T as Id>::Assoc>>`
+// does not hold.
+//
+// See https://github.com/rust-lang/trait-system-refactor-initiative/issues/51
+// for more details.
+trait Trait {}
+impl<T> Trait for T
+where
+    (): PartialEq<T> {}
+struct LocalTy;
+impl Trait for <LocalTy as Id>::Assoc {}
+
+fn main() {}