about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_ast_lowering/src/index.rs11
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs2
-rw-r--r--compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp48
-rw-r--r--compiler/rustc_middle/src/hir/mod.rs2
-rw-r--r--compiler/rustc_middle/src/traits/solve/inspect.rs7
-rw-r--r--compiler/rustc_middle/src/traits/solve/inspect/format.rs8
-rw-r--r--compiler/rustc_middle/src/ty/normalize_erasing_regions.rs4
-rw-r--r--compiler/rustc_mir_transform/src/inline.rs2
-rw-r--r--compiler/rustc_trait_selection/src/solve/alias_relate.rs159
-rw-r--r--compiler/rustc_trait_selection/src/solve/assembly/mod.rs13
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs2
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt/commit_if_ok.rs47
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs79
-rw-r--r--compiler/rustc_trait_selection/src/solve/inspect/analyse.rs10
-rw-r--r--compiler/rustc_trait_selection/src/solve/inspect/build.rs27
-rw-r--r--compiler/rustc_trait_selection/src/solve/normalize.rs4
-rw-r--r--compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs65
-rw-r--r--compiler/rustc_trait_selection/src/solve/normalizes_to/opaque_types.rs6
-rw-r--r--compiler/rustc_trait_selection/src/traits/fulfill.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/normalize.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/project.rs4
-rw-r--r--compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/mod.rs2
-rw-r--r--compiler/rustc_ty_utils/src/layout.rs4
-rw-r--r--compiler/rustc_type_ir/src/flags.rs6
-rw-r--r--compiler/rustc_type_ir/src/visit.rs4
-rw-r--r--library/core/src/ptr/metadata.rs3
-rw-r--r--library/core/tests/lib.rs1
-rw-r--r--library/core/tests/ptr.rs3
-rw-r--r--src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version2
-rw-r--r--src/doc/rustdoc/src/what-is-rustdoc.md11
-rw-r--r--src/librustdoc/html/static/css/noscript.css4
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css8
-rw-r--r--src/tools/tidy/src/rustdoc_css_themes.rs7
-rw-r--r--tests/rustdoc-gui/javascript-disabled.goml14
-rw-r--r--tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.next.stderr34
-rw-r--r--tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.old.stderr2
-rw-r--r--tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.rs4
-rw-r--r--tests/ui/impl-trait/recursive-coroutine-boxed.next.stderr28
-rw-r--r--tests/ui/impl-trait/recursive-coroutine-boxed.rs6
-rw-r--r--tests/ui/sanitizer/cfi-closures.rs11
41 files changed, 286 insertions, 374 deletions
diff --git a/compiler/rustc_ast_lowering/src/index.rs b/compiler/rustc_ast_lowering/src/index.rs
index 402044c7af9..1c34fd0afbb 100644
--- a/compiler/rustc_ast_lowering/src/index.rs
+++ b/compiler/rustc_ast_lowering/src/index.rs
@@ -19,7 +19,7 @@ struct NodeCollector<'a, 'hir> {
     parenting: LocalDefIdMap<ItemLocalId>,
 
     /// The parent of this node
-    parent_node: hir::ItemLocalId,
+    parent_node: ItemLocalId,
 
     owner: OwnerId,
 }
@@ -31,17 +31,16 @@ pub(super) fn index_hir<'hir>(
     bodies: &SortedMap<ItemLocalId, &'hir Body<'hir>>,
     num_nodes: usize,
 ) -> (IndexVec<ItemLocalId, ParentedNode<'hir>>, LocalDefIdMap<ItemLocalId>) {
-    let zero_id = ItemLocalId::ZERO;
-    let err_node = ParentedNode { parent: zero_id, node: Node::Err(item.span()) };
+    let err_node = ParentedNode { parent: ItemLocalId::ZERO, node: Node::Err(item.span()) };
     let mut nodes = IndexVec::from_elem_n(err_node, num_nodes);
     // This node's parent should never be accessed: the owner's parent is computed by the
     // hir_owner_parent query. Make it invalid (= ItemLocalId::MAX) to force an ICE whenever it is
     // used.
-    nodes[zero_id] = ParentedNode { parent: ItemLocalId::INVALID, node: item.into() };
+    nodes[ItemLocalId::ZERO] = ParentedNode { parent: ItemLocalId::INVALID, node: item.into() };
     let mut collector = NodeCollector {
         tcx,
         owner: item.def_id(),
-        parent_node: zero_id,
+        parent_node: ItemLocalId::ZERO,
         nodes,
         bodies,
         parenting: Default::default(),
@@ -112,7 +111,7 @@ impl<'a, 'hir> NodeCollector<'a, 'hir> {
     }
 
     fn insert_nested(&mut self, item: LocalDefId) {
-        if self.parent_node.as_u32() != 0 {
+        if self.parent_node != ItemLocalId::ZERO {
             self.parenting.insert(item, self.parent_node);
         }
     }
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
index 8d4d0833eef..85d04f7d1c4 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
@@ -461,7 +461,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     where
         T: TypeVisitable<TyCtxt<'tcx>>,
     {
-        t.has_free_regions() || t.has_projections() || t.has_infer_types()
+        t.has_free_regions() || t.has_aliases() || t.has_infer_types()
     }
 
     pub fn node_ty(&self, id: hir::HirId) -> Ty<'tcx> {
diff --git a/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp
index 8e0b1b38366..0998b463a88 100644
--- a/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp
+++ b/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp
@@ -1,8 +1,8 @@
 #include "LLVMWrapper.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ProfileData/Coverage/CoverageMapping.h"
 #include "llvm/ProfileData/Coverage/CoverageMappingWriter.h"
 #include "llvm/ProfileData/InstrProf.h"
-#include "llvm/ADT/ArrayRef.h"
 
 #include <iostream>
 
@@ -103,35 +103,30 @@ fromRust(LLVMRustCounterExprKind Kind) {
 }
 
 extern "C" void LLVMRustCoverageWriteFilenamesSectionToBuffer(
-    const char *const Filenames[],
-    size_t FilenamesLen,
-    const size_t *const Lengths,
-    size_t LengthsLen,
+    const char *const Filenames[], size_t FilenamesLen, // String start pointers
+    const size_t *const Lengths, size_t LengthsLen,     // Corresponding lengths
     RustStringRef BufferOut) {
   if (FilenamesLen != LengthsLen) {
     report_fatal_error(
         "Mismatched lengths in LLVMRustCoverageWriteFilenamesSectionToBuffer");
   }
 
-  SmallVector<std::string,32> FilenameRefs;
+  SmallVector<std::string, 32> FilenameRefs;
   FilenameRefs.reserve(FilenamesLen);
   for (size_t i = 0; i < FilenamesLen; i++) {
     FilenameRefs.emplace_back(Filenames[i], Lengths[i]);
   }
-  auto FilenamesWriter =
-      coverage::CoverageFilenamesSectionWriter(ArrayRef<std::string>(FilenameRefs));
+  auto FilenamesWriter = coverage::CoverageFilenamesSectionWriter(
+      ArrayRef<std::string>(FilenameRefs));
   auto OS = RawRustStringOstream(BufferOut);
   FilenamesWriter.write(OS);
 }
 
 extern "C" void LLVMRustCoverageWriteMappingToBuffer(
-    const unsigned *VirtualFileMappingIDs,
-    unsigned NumVirtualFileMappingIDs,
-    const LLVMRustCounterExpression *RustExpressions,
-    unsigned NumExpressions,
+    const unsigned *VirtualFileMappingIDs, unsigned NumVirtualFileMappingIDs,
+    const LLVMRustCounterExpression *RustExpressions, unsigned NumExpressions,
     const LLVMRustCounterMappingRegion *RustMappingRegions,
-    unsigned NumMappingRegions,
-    RustStringRef BufferOut) {
+    unsigned NumMappingRegions, RustStringRef BufferOut) {
   // Convert from FFI representation to LLVM representation.
   SmallVector<coverage::CounterMappingRegion, 0> MappingRegions;
   MappingRegions.reserve(NumMappingRegions);
@@ -142,7 +137,7 @@ extern "C" void LLVMRustCoverageWriteMappingToBuffer(
 #if LLVM_VERSION_GE(18, 0) && LLVM_VERSION_LT(19, 0)
         coverage::CounterMappingRegion::MCDCParameters{},
 #endif
-        Region.FileID, Region.ExpandedFileID,
+        Region.FileID, Region.ExpandedFileID, // File IDs, then region info.
         Region.LineStart, Region.ColumnStart, Region.LineEnd, Region.ColumnEnd,
         fromRust(Region.Kind));
   }
@@ -158,29 +153,25 @@ extern "C" void LLVMRustCoverageWriteMappingToBuffer(
 
   auto CoverageMappingWriter = coverage::CoverageMappingWriter(
       ArrayRef<unsigned>(VirtualFileMappingIDs, NumVirtualFileMappingIDs),
-      Expressions,
-      MappingRegions);
+      Expressions, MappingRegions);
   auto OS = RawRustStringOstream(BufferOut);
   CoverageMappingWriter.write(OS);
 }
 
-extern "C" LLVMValueRef LLVMRustCoverageCreatePGOFuncNameVar(
-    LLVMValueRef F,
-    const char *FuncName,
-    size_t FuncNameLen) {
+extern "C" LLVMValueRef
+LLVMRustCoverageCreatePGOFuncNameVar(LLVMValueRef F, const char *FuncName,
+                                     size_t FuncNameLen) {
   auto FuncNameRef = StringRef(FuncName, FuncNameLen);
   return wrap(createPGOFuncNameVar(*cast<Function>(unwrap(F)), FuncNameRef));
 }
 
-extern "C" uint64_t LLVMRustCoverageHashByteArray(
-    const char *Bytes,
-    size_t NumBytes) {
+extern "C" uint64_t LLVMRustCoverageHashByteArray(const char *Bytes,
+                                                  size_t NumBytes) {
   auto StrRef = StringRef(Bytes, NumBytes);
   return IndexedInstrProf::ComputeHash(StrRef);
 }
 
-static void WriteSectionNameToString(LLVMModuleRef M,
-                                     InstrProfSectKind SK,
+static void WriteSectionNameToString(LLVMModuleRef M, InstrProfSectKind SK,
                                      RustStringRef Str) {
   auto TargetTriple = Triple(unwrap(M)->getTargetTriple());
   auto name = getInstrProfSectionName(SK, TargetTriple.getObjectFormat());
@@ -193,8 +184,9 @@ extern "C" void LLVMRustCoverageWriteMapSectionNameToString(LLVMModuleRef M,
   WriteSectionNameToString(M, IPSK_covmap, Str);
 }
 
-extern "C" void LLVMRustCoverageWriteFuncSectionNameToString(LLVMModuleRef M,
-                                                             RustStringRef Str) {
+extern "C" void
+LLVMRustCoverageWriteFuncSectionNameToString(LLVMModuleRef M,
+                                             RustStringRef Str) {
   WriteSectionNameToString(M, IPSK_covfun, Str);
 }
 
diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs
index 42a06c968c7..94d1039c763 100644
--- a/compiler/rustc_middle/src/hir/mod.rs
+++ b/compiler/rustc_middle/src/hir/mod.rs
@@ -179,7 +179,7 @@ pub fn provide(providers: &mut Providers) {
                     .parenting
                     .get(&owner_id.def_id)
                     .copied()
-                    .unwrap_or(ItemLocalId::from_u32(0)),
+                    .unwrap_or(ItemLocalId::ZERO),
             }
         })
     };
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_middle/src/ty/normalize_erasing_regions.rs b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs
index d4c8f5900f9..58543674460 100644
--- a/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs
+++ b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs
@@ -49,7 +49,7 @@ impl<'tcx> TyCtxt<'tcx> {
         let value = self.erase_regions(value);
         debug!(?value);
 
-        if !value.has_projections() {
+        if !value.has_aliases() {
             value
         } else {
             value.fold_with(&mut NormalizeAfterErasingRegionsFolder { tcx: self, param_env })
@@ -81,7 +81,7 @@ impl<'tcx> TyCtxt<'tcx> {
         let value = self.erase_regions(value);
         debug!(?value);
 
-        if !value.has_projections() {
+        if !value.has_aliases() {
             Ok(value)
         } else {
             let mut folder = TryNormalizeAfterErasingRegionsFolder::new(self, param_env);
diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs
index ab9fa165a20..60513a674af 100644
--- a/compiler/rustc_mir_transform/src/inline.rs
+++ b/compiler/rustc_mir_transform/src/inline.rs
@@ -1077,7 +1077,7 @@ fn try_instance_mir<'tcx>(
         let fields = def.all_fields();
         for field in fields {
             let field_ty = field.ty(tcx, args);
-            if field_ty.has_param() && field_ty.has_projections() {
+            if field_ty.has_param() && field_ty.has_aliases() {
                 return Err("cannot build drop shim for polymorphic type");
             }
         }
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/assembly/mod.rs b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
index 5e580df01cb..35f7d1d7151 100644
--- a/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
@@ -312,11 +312,18 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
     fn forced_ambiguity(&mut self, cause: MaybeCause) -> Vec<Candidate<'tcx>> {
         let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc);
         let certainty = Certainty::Maybe(cause);
-        let result = self.evaluate_added_goals_and_make_canonical_response(certainty).unwrap();
+        // This may fail if `try_evaluate_added_goals` overflows because it
+        // fails to reach a fixpoint but ends up getting an error after
+        // running for some additional step.
+        //
+        // FIXME: Add a test for this. It seems to be necessary for typenum but
+        // is incredibly hard to minimize as it may rely on being inside of a
+        // trait solver cycle.
+        let result = self.evaluate_added_goals_and_make_canonical_response(certainty);
         let mut dummy_probe = self.inspect.new_probe();
-        dummy_probe.probe_kind(ProbeKind::TraitCandidate { source, result: Ok(result) });
+        dummy_probe.probe_kind(ProbeKind::TraitCandidate { source, result });
         self.inspect.finish_probe(dummy_probe);
-        vec![Candidate { source, result }]
+        if let Ok(result) = result { vec![Candidate { source, result }] } else { vec![] }
     }
 
     #[instrument(level = "debug", skip_all)]
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/normalize.rs b/compiler/rustc_trait_selection/src/solve/normalize.rs
index b1c03e82cab..5b45e1a34e4 100644
--- a/compiler/rustc_trait_selection/src/solve/normalize.rs
+++ b/compiler/rustc_trait_selection/src/solve/normalize.rs
@@ -177,7 +177,7 @@ impl<'tcx> FallibleTypeFolder<TyCtxt<'tcx>> for NormalizationFolder<'_, 'tcx> {
     fn try_fold_ty(&mut self, ty: Ty<'tcx>) -> Result<Ty<'tcx>, Self::Error> {
         let infcx = self.at.infcx;
         debug_assert_eq!(ty, infcx.shallow_resolve(ty));
-        if !ty.has_projections() {
+        if !ty.has_aliases() {
             return Ok(ty);
         }
 
@@ -204,7 +204,7 @@ impl<'tcx> FallibleTypeFolder<TyCtxt<'tcx>> for NormalizationFolder<'_, 'tcx> {
     fn try_fold_const(&mut self, ct: ty::Const<'tcx>) -> Result<ty::Const<'tcx>, Self::Error> {
         let infcx = self.at.infcx;
         debug_assert_eq!(ct, infcx.shallow_resolve(ct));
-        if !ct.has_projections() {
+        if !ct.has_aliases() {
             return Ok(ct);
         }
 
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)
+                }
+            }
         }
+    }
 
-        debug_assert!(self.term_is_fully_unconstrained(goal));
+    /// 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`.
-    ///
-    /// 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.
+    /// When normalizing an associated item, constrain the expected term to `term`.
     ///
-    /// 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/compiler/rustc_trait_selection/src/traits/fulfill.rs b/compiler/rustc_trait_selection/src/traits/fulfill.rs
index 88ac9d4dbe5..a04a3bc6ebe 100644
--- a/compiler/rustc_trait_selection/src/traits/fulfill.rs
+++ b/compiler/rustc_trait_selection/src/traits/fulfill.rs
@@ -311,7 +311,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> {
 
         let infcx = self.selcx.infcx;
 
-        if obligation.predicate.has_projections() {
+        if obligation.predicate.has_aliases() {
             let mut obligations = Vec::new();
             let predicate = normalize_with_depth_to(
                 &mut self.selcx,
diff --git a/compiler/rustc_trait_selection/src/traits/normalize.rs b/compiler/rustc_trait_selection/src/traits/normalize.rs
index 15bfffef3ce..b4969926f64 100644
--- a/compiler/rustc_trait_selection/src/traits/normalize.rs
+++ b/compiler/rustc_trait_selection/src/traits/normalize.rs
@@ -101,6 +101,8 @@ pub(super) fn needs_normalization<'tcx, T: TypeVisitable<TyCtxt<'tcx>>>(
     value: &T,
     reveal: Reveal,
 ) -> bool {
+    // This mirrors `ty::TypeFlags::HAS_ALIASES` except that we take `Reveal` into account.
+
     let mut flags = ty::TypeFlags::HAS_TY_PROJECTION
         | ty::TypeFlags::HAS_TY_WEAK
         | ty::TypeFlags::HAS_TY_INHERENT
diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs
index 9d744d9a032..9246a41a2bc 100644
--- a/compiler/rustc_trait_selection/src/traits/project.rs
+++ b/compiler/rustc_trait_selection/src/traits/project.rs
@@ -431,7 +431,7 @@ pub(super) fn opt_normalize_projection_type<'a, 'b, 'tcx>(
 
             let projected_term = selcx.infcx.resolve_vars_if_possible(projected_term);
 
-            let mut result = if projected_term.has_projections() {
+            let mut result = if projected_term.has_aliases() {
                 let normalized_ty = normalize_with_depth_to(
                     selcx,
                     param_env,
@@ -595,7 +595,7 @@ pub fn normalize_inherent_projection<'a, 'b, 'tcx>(
     let ty = tcx.type_of(alias_ty.def_id).instantiate(tcx, args);
 
     let mut ty = selcx.infcx.resolve_vars_if_possible(ty);
-    if ty.has_projections() {
+    if ty.has_aliases() {
         ty = normalize_with_depth_to(selcx, param_env, cause.clone(), depth + 1, ty, obligations);
     }
 
diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs
index 3b33f6e6144..279d96dec72 100644
--- a/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs
+++ b/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs
@@ -15,7 +15,7 @@ where
     type QueryResponse = T;
 
     fn try_fast_path(_tcx: TyCtxt<'tcx>, key: &ParamEnvAnd<'tcx, Self>) -> Option<T> {
-        if !key.value.value.has_projections() { Some(key.value.value) } else { None }
+        if !key.value.value.has_aliases() { Some(key.value.value) } else { None }
     }
 
     fn perform_query(
diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs
index 8fb8d21ac90..aa4ab9c7ee9 100644
--- a/compiler/rustc_trait_selection/src/traits/select/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs
@@ -1060,7 +1060,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
             // so we will try to normalize the obligation and evaluate again.
             // we will replace it with new solver in the future.
             if EvaluationResult::EvaluatedToErr == result
-                && fresh_trait_pred.has_projections()
+                && fresh_trait_pred.has_aliases()
                 && fresh_trait_pred.is_global()
             {
                 let mut nested_obligations = Vec::new();
diff --git a/compiler/rustc_ty_utils/src/layout.rs b/compiler/rustc_ty_utils/src/layout.rs
index 4ecb0ea7cd9..509727cdeab 100644
--- a/compiler/rustc_ty_utils/src/layout.rs
+++ b/compiler/rustc_ty_utils/src/layout.rs
@@ -241,9 +241,9 @@ fn layout_of_uncached<'tcx>(
 
         // Arrays and slices.
         ty::Array(element, mut count) => {
-            if count.has_projections() {
+            if count.has_aliases() {
                 count = tcx.normalize_erasing_regions(param_env, count);
-                if count.has_projections() {
+                if count.has_aliases() {
                     return Err(error(cx, LayoutError::Unknown(ty)));
                 }
             }
diff --git a/compiler/rustc_type_ir/src/flags.rs b/compiler/rustc_type_ir/src/flags.rs
index cd199222d90..997b410f819 100644
--- a/compiler/rustc_type_ir/src/flags.rs
+++ b/compiler/rustc_type_ir/src/flags.rs
@@ -78,8 +78,10 @@ bitflags! {
         /// Does this have `ConstKind::Unevaluated`?
         const HAS_CT_PROJECTION           = 1 << 14;
 
-        /// Could this type be normalized further?
-        const HAS_PROJECTION              = TypeFlags::HAS_TY_PROJECTION.bits()
+        /// Does this have `Alias` or `ConstKind::Unevaluated`?
+        ///
+        /// Rephrased, could this term be normalized further?
+        const HAS_ALIASES              = TypeFlags::HAS_TY_PROJECTION.bits()
                                           | TypeFlags::HAS_TY_WEAK.bits()
                                           | TypeFlags::HAS_TY_OPAQUE.bits()
                                           | TypeFlags::HAS_TY_INHERENT.bits()
diff --git a/compiler/rustc_type_ir/src/visit.rs b/compiler/rustc_type_ir/src/visit.rs
index 839e75dba4c..d6a3f9f0749 100644
--- a/compiler/rustc_type_ir/src/visit.rs
+++ b/compiler/rustc_type_ir/src/visit.rs
@@ -223,8 +223,8 @@ pub trait TypeVisitableExt<I: Interner>: TypeVisitable<I> {
         self.has_vars_bound_at_or_above(ty::INNERMOST)
     }
 
-    fn has_projections(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_PROJECTION)
+    fn has_aliases(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_ALIASES)
     }
 
     fn has_inherent_projections(&self) -> bool {
diff --git a/library/core/src/ptr/metadata.rs b/library/core/src/ptr/metadata.rs
index 9b5da935e07..25a06f121cd 100644
--- a/library/core/src/ptr/metadata.rs
+++ b/library/core/src/ptr/metadata.rs
@@ -2,6 +2,7 @@
 
 use crate::fmt;
 use crate::hash::{Hash, Hasher};
+use crate::marker::Freeze;
 
 /// Provides the pointer metadata type of any pointed-to type.
 ///
@@ -57,7 +58,7 @@ pub trait Pointee {
     // NOTE: Keep trait bounds in `static_assert_expected_bounds_for_metadata`
     // in `library/core/src/ptr/metadata.rs`
     // in sync with those here:
-    type Metadata: fmt::Debug + Copy + Send + Sync + Ord + Hash + Unpin;
+    type Metadata: fmt::Debug + Copy + Send + Sync + Ord + Hash + Unpin + Freeze;
 }
 
 /// Pointers to types implementing this trait alias are “thin”.
diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs
index 175c27662d0..8c5e5ecf5fe 100644
--- a/library/core/tests/lib.rs
+++ b/library/core/tests/lib.rs
@@ -38,6 +38,7 @@
 #![feature(duration_constructors)]
 #![feature(exact_size_is_empty)]
 #![feature(extern_types)]
+#![feature(freeze)]
 #![feature(flt2dec)]
 #![feature(fmt_internals)]
 #![feature(float_minimum_maximum)]
diff --git a/library/core/tests/ptr.rs b/library/core/tests/ptr.rs
index f0656f997fd..2c82eda9a58 100644
--- a/library/core/tests/ptr.rs
+++ b/library/core/tests/ptr.rs
@@ -1,4 +1,5 @@
 use core::cell::RefCell;
+use core::marker::Freeze;
 use core::mem::{self, MaybeUninit};
 use core::num::NonZero;
 use core::ptr;
@@ -841,7 +842,7 @@ fn ptr_metadata_bounds() {
     fn static_assert_expected_bounds_for_metadata<Meta>()
     where
         // Keep this in sync with the associated type in `library/core/src/ptr/metadata.rs`
-        Meta: Debug + Copy + Send + Sync + Ord + std::hash::Hash + Unpin,
+        Meta: Debug + Copy + Send + Sync + Ord + std::hash::Hash + Unpin + Freeze,
     {
     }
 }
diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version
index 07feb823492..14a8c245756 100644
--- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version
+++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version
@@ -1 +1 @@
-0.17.0
\ No newline at end of file
+0.17.1
\ No newline at end of file
diff --git a/src/doc/rustdoc/src/what-is-rustdoc.md b/src/doc/rustdoc/src/what-is-rustdoc.md
index 7179ee0cf03..cb4ec51caf2 100644
--- a/src/doc/rustdoc/src/what-is-rustdoc.md
+++ b/src/doc/rustdoc/src/what-is-rustdoc.md
@@ -34,6 +34,9 @@ the main page is located in `doc/lib/index.html`. If you open that up in
 a web browser, you will see a page with a search bar, and "Crate lib" at the
 top, with no contents.
 
+You can also use `cargo doc` to generate documentation for the whole project.
+See [Using rustdoc with Cargo](#using-rustdoc-with-cargo).
+
 ## Configuring rustdoc
 
 There are two problems with this: first, why does it
@@ -79,7 +82,13 @@ docs. Instead of the `rustdoc` command, we could have done this:
 $ cargo doc
 ```
 
-Internally, this calls out to `rustdoc` like this:
+If you want `cargo` to automatically open the generated documentation, you can use:
+
+```bash
+$ cargo doc --open
+```
+
+Internally, `cargo doc` calls out to `rustdoc` like this:
 
 ```bash
 $ rustdoc --crate-name docs src/lib.rs -o <path>/docs/target/doc -L
diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css
index f425f3ec95c..ccb97d7df4c 100644
--- a/src/librustdoc/html/static/css/noscript.css
+++ b/src/librustdoc/html/static/css/noscript.css
@@ -34,7 +34,7 @@ nav.sub {
 	in rustdoc.css */
 
 /* Begin theme: light */
-:root {
+:root, :root:not([data-theme]) {
 	--main-background-color: white;
 	--main-color: black;
 	--settings-input-color: #2196f3;
@@ -140,7 +140,7 @@ nav.sub {
 
 @media (prefers-color-scheme: dark) {
 	/* Begin theme: dark */
-	:root {
+	:root, :root:not([data-theme]) {
 		--main-background-color: #353535;
 		--main-color: #ddd;
 		--settings-input-color: #2196f3;
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 9993dfb1d8c..0bb073b1cea 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -2315,8 +2315,14 @@ in src-script.js and main.js
 	tooling to ensure different themes all define all the variables. Do not
 	alter their formatting. */
 
+/*
+About `:root:not([data-theme])`: if for any reason the JS is enabled but cannot be loaded,
+`noscript` won't be enabled and the doc will have no color applied. To do around this, we
+add a selector check that if `data-theme` is not defined, then we apply the light theme
+by default.
+*/
 /* Begin theme: light */
-:root[data-theme="light"] {
+:root[data-theme="light"], :root:not([data-theme]) {
 	--main-background-color: white;
 	--main-color: black;
 	--settings-input-color: #2196f3;
diff --git a/src/tools/tidy/src/rustdoc_css_themes.rs b/src/tools/tidy/src/rustdoc_css_themes.rs
index 852d6e14e91..af36f9ba58e 100644
--- a/src/tools/tidy/src/rustdoc_css_themes.rs
+++ b/src/tools/tidy/src/rustdoc_css_themes.rs
@@ -74,8 +74,11 @@ fn compare_themes<'a>(
         (noscript_css_line_number, noscript_css_line),
     ) in rustdoc_css_lines.zip(noscript_css_lines)
     {
-        if noscript_css_line.starts_with(":root {")
-            && rustdoc_css_line.starts_with(&format!(r#":root[data-theme="{name}"] {{"#))
+        if noscript_css_line.starts_with(":root, :root:not([data-theme]) {")
+            && (rustdoc_css_line.starts_with(&format!(r#":root[data-theme="{name}"] {{"#))
+                || rustdoc_css_line.starts_with(&format!(
+                    r#":root[data-theme="{name}"], :root:not([data-theme]) {{"#
+                )))
         {
             // selectors are different between rustdoc.css and noscript.css
             // that's why they both exist: one uses JS, the other uses media queries
diff --git a/tests/rustdoc-gui/javascript-disabled.goml b/tests/rustdoc-gui/javascript-disabled.goml
index a0872d553af..a7579ef7ec1 100644
--- a/tests/rustdoc-gui/javascript-disabled.goml
+++ b/tests/rustdoc-gui/javascript-disabled.goml
@@ -3,4 +3,18 @@
 javascript: false
 
 go-to: "file://" + |DOC_PATH| + "/test_docs/struct.Foo.html"
+show-text: true
 assert-css: (".sub", {"display": "none"})
+
+// Even though JS is disabled, we should still have themes applied. Links are never black-colored
+// if styles are applied so we check that they are not.
+assert-css-false: ("a.src", {"color": "#000"})
+
+javascript: true
+fail-on-request-error: false
+block-network-request: "*.js"
+reload:
+
+// JS is enabled but wasn't loaded, we should still have the light theme applied. Links are never
+// black-colored if styles are applied so we check that they are not.
+assert-css-false: ("a.src", {"color": "#000"})
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[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
+error: aborting due to 1 previous error
 
-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 = ()> {
+   |               ---------------------------------------
+   |               |
+   |               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
    |
-LL | fn foo() -> impl Coroutine<Yield = (), Return = ()> {
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for opaque type `impl Coroutine<Yield = (), Return = ()>`
+   = 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(());
diff --git a/tests/ui/sanitizer/cfi-closures.rs b/tests/ui/sanitizer/cfi-closures.rs
index f3d9be35716..9f9002da674 100644
--- a/tests/ui/sanitizer/cfi-closures.rs
+++ b/tests/ui/sanitizer/cfi-closures.rs
@@ -77,3 +77,14 @@ fn closure_addr_taken() {
     let call = Fn::<()>::call;
     use_closure(call, &f);
 }
+
+fn use_closure_once<C>(call: extern "rust-call" fn(C, ()) -> i32, f: C) -> i32 {
+    call(f, ())
+}
+
+#[test]
+fn closure_once_addr_taken() {
+    let g = || 3;
+    let call2 = FnOnce::<()>::call_once;
+    use_closure_once(call2, g);
+}