diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index 57b8df52f4b..4f033e3fefa 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -2086,7 +2086,7 @@ impl HumanEmitter {
                 }
                 if !self.short_message {
                     for child in children {
-                        assert!(child.level.can_be_top_or_sub().1);
+                        assert!(child.level.can_be_subdiag());
                         let span = &child.span;
                         if let Err(err) = self.emit_messages_default_inner(
                             span,
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 286d4621850..723f13dbe8d 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -526,12 +526,15 @@ pub enum StashKey {
     UndeterminedMacroResolution,
 }
 
-fn default_track_diagnostic(diag: DiagInner, f: &mut dyn FnMut(DiagInner)) {
+fn default_track_diagnostic<R>(diag: DiagInner, f: &mut dyn FnMut(DiagInner) -> R) -> R {
     (*f)(diag)
 }
 
-pub static TRACK_DIAGNOSTIC: AtomicRef<fn(DiagInner, &mut dyn FnMut(DiagInner))> =
-    AtomicRef::new(&(default_track_diagnostic as _));
+/// Diagnostics emitted by `DiagCtxtInner::emit_diagnostic` are passed through this function. Used
+/// for tracking by incremental, to replay diagnostics as necessary.
+pub static TRACK_DIAGNOSTIC: AtomicRef<
+    fn(DiagInner, &mut dyn FnMut(DiagInner) -> Option<ErrorGuaranteed>) -> Option<ErrorGuaranteed>,
+> = AtomicRef::new(&(default_track_diagnostic as _));
 
 #[derive(Copy, Clone, Default)]
 pub struct DiagCtxtFlags {
@@ -1422,74 +1425,103 @@ impl DiagCtxtInner {
 
     // Return value is only `Some` if the level is `Error` or `DelayedBug`.
     fn emit_diagnostic(&mut self, mut diagnostic: DiagInner) -> Option<ErrorGuaranteed> {
-        assert!(diagnostic.level.can_be_top_or_sub().0);
-
-        if let Some(expectation_id) = diagnostic.level.get_expectation_id() {
-            // The `LintExpectationId` can be stable or unstable depending on when it was created.
-            // Diagnostics created before the definition of `HirId`s are unstable and can not yet
-            // be stored. Instead, they are buffered until the `LintExpectationId` is replaced by
-            // a stable one by the `LintLevelsBuilder`.
-            if let LintExpectationId::Unstable { .. } = expectation_id {
-                self.unstable_expect_diagnostics.push(diagnostic);
-                return None;
-            }
-            self.suppressed_expected_diag = true;
-            self.fulfilled_expectations.insert(expectation_id.normalize());
-        }
-
         if diagnostic.has_future_breakage() {
             // Future breakages aren't emitted if they're `Level::Allow`,
             // but they still need to be constructed and stashed below,
             // so they'll trigger the must_produce_diag check.
-            self.suppressed_expected_diag = true;
+            assert!(matches!(diagnostic.level, Error | Warning | Allow));
             self.future_breakage_diagnostics.push(diagnostic.clone());
         }
 
-        // Note that because this comes before the `match` below,
-        // `-Zeagerly-emit-delayed-bugs` continues to work even after we've
-        // issued an error and stopped recording new delayed bugs.
-        if diagnostic.level == DelayedBug && self.flags.eagerly_emit_delayed_bugs {
-            diagnostic.level = Error;
-        }
-
+        // We call TRACK_DIAGNOSTIC with an empty closure for the cases that
+        // return early *and* have some kind of side-effect, except where
+        // noted.
         match diagnostic.level {
-            // This must come after the possible promotion of `DelayedBug` to
-            // `Error` above.
-            Fatal | Error if self.treat_next_err_as_bug() => {
-                diagnostic.level = Bug;
+            Bug => {}
+            Fatal | Error => {
+                if self.treat_next_err_as_bug() {
+                    // `Fatal` and `Error` can be promoted to `Bug`.
+                    diagnostic.level = Bug;
+                }
             }
             DelayedBug => {
-                // If we have already emitted at least one error, we don't need
-                // to record the delayed bug, because it'll never be used.
-                return if let Some(guar) = self.has_errors() {
-                    Some(guar)
+                // Note that because we check these conditions first,
+                // `-Zeagerly-emit-delayed-bugs` and `-Ztreat-err-as-bug`
+                // continue to work even after we've issued an error and
+                // stopped recording new delayed bugs.
+                if self.flags.eagerly_emit_delayed_bugs {
+                    // `DelayedBug` can be promoted to `Error` or `Bug`.
+                    if self.treat_next_err_as_bug() {
+                        diagnostic.level = Bug;
+                    } else {
+                        diagnostic.level = Error;
+                    }
                 } else {
-                    let backtrace = std::backtrace::Backtrace::capture();
-                    // This `unchecked_error_guaranteed` is valid. It is where the
-                    // `ErrorGuaranteed` for delayed bugs originates. See
-                    // `DiagCtxtInner::drop`.
-                    #[allow(deprecated)]
-                    let guar = ErrorGuaranteed::unchecked_error_guaranteed();
-                    self.delayed_bugs
-                        .push((DelayedDiagInner::with_backtrace(diagnostic, backtrace), guar));
-                    Some(guar)
-                };
+                    // If we have already emitted at least one error, we don't need
+                    // to record the delayed bug, because it'll never be used.
+                    return if let Some(guar) = self.has_errors() {
+                        Some(guar)
+                    } else {
+                        // No `TRACK_DIAGNOSTIC` call is needed, because the
+                        // incremental session is deleted if there is a delayed
+                        // bug. This also saves us from cloning the diagnostic.
+                        let backtrace = std::backtrace::Backtrace::capture();
+                        // This `unchecked_error_guaranteed` is valid. It is where the
+                        // `ErrorGuaranteed` for delayed bugs originates. See
+                        // `DiagCtxtInner::drop`.
+                        #[allow(deprecated)]
+                        let guar = ErrorGuaranteed::unchecked_error_guaranteed();
+                        self.delayed_bugs
+                            .push((DelayedDiagInner::with_backtrace(diagnostic, backtrace), guar));
+                        Some(guar)
+                    };
+                }
             }
-            Warning if !self.flags.can_emit_warnings => {
+            ForceWarning(None) => {} // `ForceWarning(Some(...))` is below, with `Expect`
+            Warning => {
+                if !self.flags.can_emit_warnings {
+                    // We are not emitting warnings.
+                    if diagnostic.has_future_breakage() {
+                        // The side-effect is at the top of this method.
+                        TRACK_DIAGNOSTIC(diagnostic, &mut |_| None);
+                    }
+                    return None;
+                }
+            }
+            Note | Help | FailureNote => {}
+            OnceNote | OnceHelp => panic!("bad level: {:?}", diagnostic.level),
+            Allow => {
+                // Nothing emitted for allowed lints.
                 if diagnostic.has_future_breakage() {
-                    (*TRACK_DIAGNOSTIC)(diagnostic, &mut |_| {});
+                    // The side-effect is at the top of this method.
+                    TRACK_DIAGNOSTIC(diagnostic, &mut |_| None);
+                    self.suppressed_expected_diag = true;
                 }
                 return None;
             }
-            Allow | Expect(_) => {
-                (*TRACK_DIAGNOSTIC)(diagnostic, &mut |_| {});
-                return None;
+            Expect(expect_id) | ForceWarning(Some(expect_id)) => {
+                // Diagnostics created before the definition of `HirId`s are
+                // unstable and can not yet be stored. Instead, they are
+                // buffered until the `LintExpectationId` is replaced by a
+                // stable one by the `LintLevelsBuilder`.
+                if let LintExpectationId::Unstable { .. } = expect_id {
+                    // We don't call TRACK_DIAGNOSTIC because we wait for the
+                    // unstable ID to be updated, whereupon the diagnostic will
+                    // be passed into this method again.
+                    self.unstable_expect_diagnostics.push(diagnostic);
+                    return None;
+                }
+                self.fulfilled_expectations.insert(expect_id.normalize());
+                if let Expect(_) = diagnostic.level {
+                    // Nothing emitted here for expected lints.
+                    TRACK_DIAGNOSTIC(diagnostic, &mut |_| None);
+                    self.suppressed_expected_diag = true;
+                    return None;
+                }
             }
-            _ => {}
         }
 
-        let mut guaranteed = None;
-        (*TRACK_DIAGNOSTIC)(diagnostic, &mut |mut diagnostic| {
+        TRACK_DIAGNOSTIC(diagnostic, &mut |mut diagnostic| {
             if let Some(code) = diagnostic.code {
                 self.emitted_diagnostic_codes.insert(code);
             }
@@ -1552,17 +1584,17 @@ impl DiagCtxtInner {
                 // `ErrorGuaranteed` for errors and lint errors originates.
                 #[allow(deprecated)]
                 let guar = ErrorGuaranteed::unchecked_error_guaranteed();
-                guaranteed = Some(guar);
                 if is_lint {
                     self.lint_err_guars.push(guar);
                 } else {
                     self.err_guars.push(guar);
                 }
                 self.panic_if_treat_err_as_bug();
+                Some(guar)
+            } else {
+                None
             }
-        });
-
-        guaranteed
+        })
     }
 
     fn treat_err_as_bug(&self) -> bool {
@@ -1863,23 +1895,13 @@ impl Level {
         matches!(*self, FailureNote)
     }
 
-    pub fn get_expectation_id(&self) -> Option<LintExpectationId> {
-        match self {
-            Expect(id) | ForceWarning(Some(id)) => Some(*id),
-            _ => None,
-        }
-    }
-
-    // Can this level be used in a top-level diagnostic message and/or a
-    // subdiagnostic message?
-    fn can_be_top_or_sub(&self) -> (bool, bool) {
+    // Can this level be used in a subdiagnostic message?
+    fn can_be_subdiag(&self) -> bool {
         match self {
             Bug | DelayedBug | Fatal | Error | ForceWarning(_) | FailureNote | Allow
-            | Expect(_) => (true, false),
+            | Expect(_) => false,
 
-            Warning | Note | Help => (true, true),
-
-            OnceNote | OnceHelp => (false, true),
+            Warning | Note | Help | OnceNote | OnceHelp => true,
         }
     }
 }
diff --git a/compiler/rustc_interface/src/callbacks.rs b/compiler/rustc_interface/src/callbacks.rs
index f44ae705a3c..a27f73789cd 100644
--- a/compiler/rustc_interface/src/callbacks.rs
+++ b/compiler/rustc_interface/src/callbacks.rs
@@ -29,7 +29,7 @@ fn track_span_parent(def_id: rustc_span::def_id::LocalDefId) {
 /// This is a callback from `rustc_errors` as it cannot access the implicit state
 /// in `rustc_middle` otherwise. It is used when diagnostic messages are
 /// emitted and stores them in the current query, if there is one.
-fn track_diagnostic(diagnostic: DiagInner, f: &mut dyn FnMut(DiagInner)) {
+fn track_diagnostic<R>(diagnostic: DiagInner, f: &mut dyn FnMut(DiagInner) -> R) -> R {
     tls::with_context_opt(|icx| {
         if let Some(icx) = icx {
             if let Some(diagnostics) = icx.diagnostics {
@@ -38,11 +38,11 @@ fn track_diagnostic(diagnostic: DiagInner, f: &mut dyn FnMut(DiagInner)) {
 
             // Diagnostics are tracked, we can ignore the dependency.
             let icx = tls::ImplicitCtxt { task_deps: TaskDepsRef::Ignore, ..icx.clone() };
-            return tls::enter_context(&icx, move || (*f)(diagnostic));
+            tls::enter_context(&icx, move || (*f)(diagnostic))
+        } else {
+            // In any other case, invoke diagnostics anyway.
+            (*f)(diagnostic)
         }
-
-        // In any other case, invoke diagnostics anyway.
-        (*f)(diagnostic);
     })
 }
 
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index e89df1c9840..74b6c92dbfe 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -103,11 +103,12 @@ impl LintLevelSets {
         mut idx: LintStackIndex,
         aux: Option<&FxIndexMap<LintId, LevelAndSource>>,
     ) -> (Option<Level>, LintLevelSource) {
-        if let Some(specs) = aux {
-            if let Some(&(level, src)) = specs.get(&id) {
-                return (Some(level), src);
-            }
+        if let Some(specs) = aux
+            && let Some(&(level, src)) = specs.get(&id)
+        {
+            return (Some(level), src);
         }
+
         loop {
             let LintSet { ref specs, parent } = self.list[idx];
             if let Some(&(level, src)) = specs.get(&id) {
@@ -177,7 +178,7 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe
         // There is only something to do if there are attributes at all.
         [] => {}
         // Most of the time, there is only one attribute. Avoid fetching HIR in that case.
-        [(local_id, _)] => levels.add_id(HirId { owner, local_id: *local_id }),
+        &[(local_id, _)] => levels.add_id(HirId { owner, local_id }),
         // Otherwise, we need to visit the attributes in source code order, so we fetch HIR and do
         // a standard visit.
         // FIXME(#102522) Just iterate on attrs once that iteration order matches HIR's.
@@ -643,63 +644,61 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
         //
         // This means that this only errors if we're truly lowering the lint
         // level from forbid.
-        if self.lint_added_lints && level != Level::Forbid {
-            if let Level::Forbid = old_level {
-                // Backwards compatibility check:
-                //
-                // We used to not consider `forbid(lint_group)`
-                // as preventing `allow(lint)` for some lint `lint` in
-                // `lint_group`. For now, issue a future-compatibility
-                // warning for this case.
-                let id_name = id.lint.name_lower();
-                let fcw_warning = match old_src {
-                    LintLevelSource::Default => false,
-                    LintLevelSource::Node { name, .. } => self.store.is_lint_group(name),
-                    LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
-                };
-                debug!(
-                    "fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}",
-                    fcw_warning,
-                    self.current_specs(),
-                    old_src,
-                    id_name
-                );
-                let sub = match old_src {
-                    LintLevelSource::Default => {
-                        OverruledAttributeSub::DefaultSource { id: id.to_string() }
-                    }
-                    LintLevelSource::Node { span, reason, .. } => {
-                        OverruledAttributeSub::NodeSource { span, reason }
-                    }
-                    LintLevelSource::CommandLine(_, _) => OverruledAttributeSub::CommandLineSource,
-                };
-                if !fcw_warning {
-                    self.sess.dcx().emit_err(OverruledAttribute {
-                        span: src.span(),
+        if self.lint_added_lints && level != Level::Forbid && old_level == Level::Forbid {
+            // Backwards compatibility check:
+            //
+            // We used to not consider `forbid(lint_group)`
+            // as preventing `allow(lint)` for some lint `lint` in
+            // `lint_group`. For now, issue a future-compatibility
+            // warning for this case.
+            let id_name = id.lint.name_lower();
+            let fcw_warning = match old_src {
+                LintLevelSource::Default => false,
+                LintLevelSource::Node { name, .. } => self.store.is_lint_group(name),
+                LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
+            };
+            debug!(
+                "fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}",
+                fcw_warning,
+                self.current_specs(),
+                old_src,
+                id_name
+            );
+            let sub = match old_src {
+                LintLevelSource::Default => {
+                    OverruledAttributeSub::DefaultSource { id: id.to_string() }
+                }
+                LintLevelSource::Node { span, reason, .. } => {
+                    OverruledAttributeSub::NodeSource { span, reason }
+                }
+                LintLevelSource::CommandLine(_, _) => OverruledAttributeSub::CommandLineSource,
+            };
+            if !fcw_warning {
+                self.sess.dcx().emit_err(OverruledAttribute {
+                    span: src.span(),
+                    overruled: src.span(),
+                    lint_level: level.as_str(),
+                    lint_source: src.name(),
+                    sub,
+                });
+            } else {
+                self.emit_span_lint(
+                    FORBIDDEN_LINT_GROUPS,
+                    src.span().into(),
+                    OverruledAttributeLint {
                         overruled: src.span(),
                         lint_level: level.as_str(),
                         lint_source: src.name(),
                         sub,
-                    });
-                } else {
-                    self.emit_span_lint(
-                        FORBIDDEN_LINT_GROUPS,
-                        src.span().into(),
-                        OverruledAttributeLint {
-                            overruled: src.span(),
-                            lint_level: level.as_str(),
-                            lint_source: src.name(),
-                            sub,
-                        },
-                    );
-                }
+                    },
+                );
+            }
 
-                // Retain the forbid lint level, unless we are
-                // issuing a FCW. In the FCW case, we want to
-                // respect the new setting.
-                if !fcw_warning {
-                    return;
-                }
+            // Retain the forbid lint level, unless we are
+            // issuing a FCW. In the FCW case, we want to
+            // respect the new setting.
+            if !fcw_warning {
+                return;
             }
         }
 
@@ -770,15 +769,15 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
 
             let Some(mut metas) = attr.meta_item_list() else { continue };
 
-            if metas.is_empty() {
+            // Check whether `metas` is empty, and get its last element.
+            let Some(tail_li) = metas.last() else {
                 // This emits the unused_attributes lint for `#[level()]`
                 continue;
-            }
+            };
 
             // Before processing the lint names, look for a reason (RFC 2383)
             // at the end.
             let mut reason = None;
-            let tail_li = &metas[metas.len() - 1];
             if let Some(item) = tail_li.meta_item() {
                 match item.kind {
                     ast::MetaItemKind::Word => {} // actual lint names handled later
@@ -834,21 +833,16 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                 let meta_item = match li {
                     ast::NestedMetaItem::MetaItem(meta_item) if meta_item.is_word() => meta_item,
                     _ => {
-                        if let Some(item) = li.meta_item() {
-                            if let ast::MetaItemKind::NameValue(_) = item.kind {
-                                if item.path == sym::reason {
-                                    sess.dcx().emit_err(MalformedAttribute {
-                                        span: sp,
-                                        sub: MalformedAttributeSub::ReasonMustComeLast(sp),
-                                    });
-                                    continue;
-                                }
-                            }
-                        }
-                        sess.dcx().emit_err(MalformedAttribute {
-                            span: sp,
-                            sub: MalformedAttributeSub::BadAttributeArgument(sp),
-                        });
+                        let sub = if let Some(item) = li.meta_item()
+                            && let ast::MetaItemKind::NameValue(_) = item.kind
+                            && item.path == sym::reason
+                        {
+                            MalformedAttributeSub::ReasonMustComeLast(sp)
+                        } else {
+                            MalformedAttributeSub::BadAttributeArgument(sp)
+                        };
+
+                        sess.dcx().emit_err(MalformedAttribute { span: sp, sub });
                         continue;
                     }
                 };
@@ -987,11 +981,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                     }
 
                     CheckLintNameResult::NoLint(suggestion) => {
-                        let name = if let Some(tool_ident) = tool_ident {
-                            format!("{}::{}", tool_ident.name, name)
-                        } else {
-                            name.to_string()
-                        };
+                        let name = tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
                         let suggestion = suggestion.map(|(replace, from_rustc)| {
                             UnknownLintSuggestion::WithSpan { suggestion: sp, replace, from_rustc }
                         });
@@ -1005,27 +995,24 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                 if let CheckLintNameResult::Renamed(new_name) = lint_result {
                     // Ignore any errors or warnings that happen because the new name is inaccurate
                     // NOTE: `new_name` already includes the tool name, so we don't have to add it again.
-                    if let CheckLintNameResult::Ok(ids) =
+                    let CheckLintNameResult::Ok(ids) =
                         self.store.check_lint_name(&new_name, None, self.registered_tools)
-                    {
-                        let src = LintLevelSource::Node {
-                            name: Symbol::intern(&new_name),
-                            span: sp,
-                            reason,
-                        };
-                        for &id in ids {
-                            if self.check_gated_lint(id, attr.span, false) {
-                                self.insert_spec(id, (level, src));
-                            }
-                        }
-                        if let Level::Expect(expect_id) = level {
-                            self.provider.push_expectation(
-                                expect_id,
-                                LintExpectation::new(reason, sp, false, tool_name),
-                            );
-                        }
-                    } else {
+                    else {
                         panic!("renamed lint does not exist: {new_name}");
+                    };
+
+                    let src =
+                        LintLevelSource::Node { name: Symbol::intern(&new_name), span: sp, reason };
+                    for &id in ids {
+                        if self.check_gated_lint(id, attr.span, false) {
+                            self.insert_spec(id, (level, src));
+                        }
+                    }
+                    if let Level::Expect(expect_id) = level {
+                        self.provider.push_expectation(
+                            expect_id,
+                            LintExpectation::new(reason, sp, false, tool_name),
+                        );
                     }
                 }
             }
@@ -1058,38 +1045,44 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
     /// Returns `true` if the lint's feature is enabled.
     #[track_caller]
     fn check_gated_lint(&self, lint_id: LintId, span: Span, lint_from_cli: bool) -> bool {
-        if let Some(feature) = lint_id.lint.feature_gate {
-            if !self.features.active(feature) {
-                if self.lint_added_lints {
-                    let lint = builtin::UNKNOWN_LINTS;
-                    let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS);
-                    // FIXME: make this translatable
-                    #[allow(rustc::diagnostic_outside_of_impl)]
-                    #[allow(rustc::untranslatable_diagnostic)]
-                    lint_level(
-                        self.sess,
+        let feature = if let Some(feature) = lint_id.lint.feature_gate
+            && !self.features.active(feature)
+        {
+            // Lint is behind a feature that is not enabled; eventually return false.
+            feature
+        } else {
+            // Lint is ungated or its feature is enabled; exit early.
+            return true;
+        };
+
+        if self.lint_added_lints {
+            let lint = builtin::UNKNOWN_LINTS;
+            let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS);
+            // FIXME: make this translatable
+            #[allow(rustc::diagnostic_outside_of_impl)]
+            #[allow(rustc::untranslatable_diagnostic)]
+            lint_level(
+                self.sess,
+                lint,
+                level,
+                src,
+                Some(span.into()),
+                fluent::lint_unknown_gated_lint,
+                |lint| {
+                    lint.arg("name", lint_id.lint.name_lower());
+                    lint.note(fluent::lint_note);
+                    rustc_session::parse::add_feature_diagnostics_for_issue(
                         lint,
-                        level,
-                        src,
-                        Some(span.into()),
-                        fluent::lint_unknown_gated_lint,
-                        |lint| {
-                            lint.arg("name", lint_id.lint.name_lower());
-                            lint.note(fluent::lint_note);
-                            rustc_session::parse::add_feature_diagnostics_for_issue(
-                                lint,
-                                &self.sess,
-                                feature,
-                                GateIssue::Language,
-                                lint_from_cli,
-                            );
-                        },
+                        &self.sess,
+                        feature,
+                        GateIssue::Language,
+                        lint_from_cli,
                     );
-                }
-                return false;
-            }
+                },
+            );
         }
-        true
+
+        false
     }
 
     /// Find the lint level for a lint.
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 20e492dbd8a..f384f66a2c3 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -704,6 +704,20 @@ declare_lint! {
     /// `PhantomData`.
     ///
     /// Otherwise consider removing the unused code.
+    ///
+    /// ### Limitations
+    ///
+    /// Removing fields that are only used for side-effects and never
+    /// read will result in behavioral changes. Examples of this
+    /// include:
+    ///
+    /// - If a field's value performs an action when it is dropped.
+    /// - If a field's type does not implement an auto trait
+    ///   (e.g. `Send`, `Sync`, `Unpin`).
+    ///
+    /// For side-effects from dropping field values, this lint should
+    /// be allowed on those fields. For side-effects from containing
+    /// field types, `PhantomData` should be used.
     pub DEAD_CODE,
     Warn,
     "detect unused, unexported items"
diff --git a/compiler/rustc_smir/src/rustc_smir/context.rs b/compiler/rustc_smir/src/rustc_smir/context.rs
index 8488777eda9..509f0def256 100644
--- a/compiler/rustc_smir/src/rustc_smir/context.rs
+++ b/compiler/rustc_smir/src/rustc_smir/context.rs
@@ -23,7 +23,8 @@ use stable_mir::mir::Body;
 use stable_mir::target::{MachineInfo, MachineSize};
 use stable_mir::ty::{
     AdtDef, AdtKind, Allocation, ClosureDef, ClosureKind, Const, FieldDef, FnDef, ForeignDef,
-    ForeignItemKind, GenericArgs, LineInfo, PolyFnSig, RigidTy, Span, Ty, TyKind, VariantDef,
+    ForeignItemKind, GenericArgs, LineInfo, PolyFnSig, RigidTy, Span, Ty, TyKind, UintTy,
+    VariantDef,
 };
 use stable_mir::{Crate, CrateDef, CrateItem, CrateNum, DefId, Error, Filename, ItemKind, Symbol};
 use std::cell::RefCell;
@@ -341,15 +342,56 @@ impl<'tcx> Context for TablesWrapper<'tcx> {
             .ok_or_else(|| Error::new(format!("Const `{cnst:?}` cannot be encoded as u64")))
     }
 
-    fn usize_to_const(&self, val: u64) -> Result<Const, Error> {
+    fn try_new_const_zst(&self, ty: Ty) -> Result<Const, Error> {
         let mut tables = self.0.borrow_mut();
-        let ty = tables.tcx.types.usize;
+        let tcx = tables.tcx;
+        let ty_internal = ty.internal(&mut *tables, tcx);
+        let size = tables
+            .tcx
+            .layout_of(ParamEnv::empty().and(ty_internal))
+            .map_err(|err| {
+                Error::new(format!(
+                    "Cannot create a zero-sized constant for type `{ty_internal}`: {err}"
+                ))
+            })?
+            .size;
+        if size.bytes() != 0 {
+            return Err(Error::new(format!(
+                "Cannot create a zero-sized constant for type `{ty_internal}`: \
+                 Type `{ty_internal}` has {} bytes",
+                size.bytes()
+            )));
+        }
+
+        Ok(ty::Const::zero_sized(tables.tcx, ty_internal).stable(&mut *tables))
+    }
+
+    fn new_const_str(&self, value: &str) -> Const {
+        let mut tables = self.0.borrow_mut();
+        let tcx = tables.tcx;
+        let ty = ty::Ty::new_static_str(tcx);
+        let bytes = value.as_bytes();
+        let val_tree = ty::ValTree::from_raw_bytes(tcx, bytes);
+
+        ty::Const::new_value(tcx, val_tree, ty).stable(&mut *tables)
+    }
+
+    fn new_const_bool(&self, value: bool) -> Const {
+        let mut tables = self.0.borrow_mut();
+        ty::Const::from_bool(tables.tcx, value).stable(&mut *tables)
+    }
+
+    fn try_new_const_uint(&self, value: u128, uint_ty: UintTy) -> Result<Const, Error> {
+        let mut tables = self.0.borrow_mut();
+        let tcx = tables.tcx;
+        let ty = ty::Ty::new_uint(tcx, uint_ty.internal(&mut *tables, tcx));
         let size = tables.tcx.layout_of(ParamEnv::empty().and(ty)).unwrap().size;
 
-        let scalar = ScalarInt::try_from_uint(val, size).ok_or_else(|| {
-            Error::new(format!("Value overflow: cannot convert `{val}` to usize."))
+        // We don't use Const::from_bits since it doesn't have any error checking.
+        let scalar = ScalarInt::try_from_uint(value, size).ok_or_else(|| {
+            Error::new(format!("Value overflow: cannot convert `{value}` to `{ty}`."))
         })?;
-        Ok(rustc_middle::ty::Const::new_value(tables.tcx, ValTree::from_scalar_int(scalar), ty)
+        Ok(ty::Const::new_value(tables.tcx, ValTree::from_scalar_int(scalar), ty)
             .stable(&mut *tables))
     }
 
@@ -556,7 +598,9 @@ impl<'tcx> Context for TablesWrapper<'tcx> {
         global_alloc: &GlobalAlloc,
     ) -> Option<stable_mir::mir::alloc::AllocId> {
         let mut tables = self.0.borrow_mut();
-        let GlobalAlloc::VTable(ty, trait_ref) = global_alloc else { return None };
+        let GlobalAlloc::VTable(ty, trait_ref) = global_alloc else {
+            return None;
+        };
         let tcx = tables.tcx;
         let alloc_id = tables.tcx.vtable_allocation((
             ty.internal(&mut *tables, tcx),
diff --git a/compiler/stable_mir/src/compiler_interface.rs b/compiler/stable_mir/src/compiler_interface.rs
index 1c51c175d81..f53dbcfbd96 100644
--- a/compiler/stable_mir/src/compiler_interface.rs
+++ b/compiler/stable_mir/src/compiler_interface.rs
@@ -14,7 +14,7 @@ use crate::ty::{
     AdtDef, AdtKind, Allocation, ClosureDef, ClosureKind, Const, FieldDef, FnDef, ForeignDef,
     ForeignItemKind, ForeignModule, ForeignModuleDef, GenericArgs, GenericPredicates, Generics,
     ImplDef, ImplTrait, LineInfo, PolyFnSig, RigidTy, Span, TraitDecl, TraitDef, Ty, TyKind,
-    VariantDef,
+    UintTy, VariantDef,
 };
 use crate::{
     mir, Crate, CrateItem, CrateItems, CrateNum, DefId, Error, Filename, ImplTraitDecls, ItemKind,
@@ -101,8 +101,17 @@ pub trait Context {
     /// Evaluate constant as a target usize.
     fn eval_target_usize(&self, cnst: &Const) -> Result<u64, Error>;
 
-    /// Create a target usize constant for the given value.
-    fn usize_to_const(&self, val: u64) -> Result<Const, Error>;
+    /// Create a new zero-sized constant.
+    fn try_new_const_zst(&self, ty: Ty) -> Result<Const, Error>;
+
+    /// Create a new constant that represents the given string value.
+    fn new_const_str(&self, value: &str) -> Const;
+
+    /// Create a new constant that represents the given boolean value.
+    fn new_const_bool(&self, value: bool) -> Const;
+
+    /// Create a new constant that represents the given value.
+    fn try_new_const_uint(&self, value: u128, uint_ty: UintTy) -> Result<Const, Error>;
 
     /// Create a new type from the given kind.
     fn new_rigid_ty(&self, kind: RigidTy) -> Ty;
@@ -200,7 +209,7 @@ pub trait Context {
 
 // A thread local variable that stores a pointer to the tables mapping between TyCtxt
 // datastructures and stable MIR datastructures
-scoped_thread_local! (static TLV: Cell<*const ()>);
+scoped_thread_local!(static TLV: Cell<*const ()>);
 
 pub fn run<F, T>(context: &dyn Context, f: F) -> Result<T, Error>
 where
diff --git a/compiler/stable_mir/src/ty.rs b/compiler/stable_mir/src/ty.rs
index 86cc748eaec..a3376752028 100644
--- a/compiler/stable_mir/src/ty.rs
+++ b/compiler/stable_mir/src/ty.rs
@@ -128,13 +128,38 @@ impl Const {
 
     /// Creates an interned usize constant.
     fn try_from_target_usize(val: u64) -> Result<Self, Error> {
-        with(|cx| cx.usize_to_const(val))
+        with(|cx| cx.try_new_const_uint(val.into(), UintTy::Usize))
     }
 
     /// Try to evaluate to a target `usize`.
     pub fn eval_target_usize(&self) -> Result<u64, Error> {
         with(|cx| cx.eval_target_usize(self))
     }
+
+    /// Create a constant that represents a new zero-sized constant of type T.
+    /// Fails if the type is not a ZST or if it doesn't have a known size.
+    pub fn try_new_zero_sized(ty: Ty) -> Result<Const, Error> {
+        with(|cx| cx.try_new_const_zst(ty))
+    }
+
+    /// Build a new constant that represents the given string.
+    ///
+    /// Note that there is no guarantee today about duplication of the same constant.
+    /// I.e.: Calling this function multiple times with the same argument may or may not return
+    /// the same allocation.
+    pub fn from_str(value: &str) -> Const {
+        with(|cx| cx.new_const_str(value))
+    }
+
+    /// Build a new constant that represents the given boolean value.
+    pub fn from_bool(value: bool) -> Const {
+        with(|cx| cx.new_const_bool(value))
+    }
+
+    /// Build a new constant that represents the given unsigned integer.
+    pub fn try_from_uint(value: u128, uint_ty: UintTy) -> Result<Const, Error> {
+        with(|cx| cx.try_new_const_uint(value, uint_ty))
+    }
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs
index 111fb83088b..30debbffec1 100644
--- a/library/core/src/ffi/c_str.rs
+++ b/library/core/src/ffi/c_str.rs
@@ -5,8 +5,11 @@ use crate::error::Error;
 use crate::ffi::c_char;
 use crate::fmt;
 use crate::intrinsics;
+use crate::iter::FusedIterator;
+use crate::marker::PhantomData;
 use crate::ops;
 use crate::ptr::addr_of;
+use crate::ptr::NonNull;
 use crate::slice;
 use crate::slice::memchr;
 use crate::str;
@@ -504,6 +507,13 @@ impl CStr {
         self.inner.as_ptr()
     }
 
+    /// We could eventually expose this publicly, if we wanted.
+    #[inline]
+    #[must_use]
+    const fn as_non_null_ptr(&self) -> NonNull<c_char> {
+        NonNull::from(&self.inner).as_non_null_ptr()
+    }
+
     /// Returns the length of `self`. Like C's `strlen`, this does not include the nul terminator.
     ///
     /// > **Note**: This method is currently implemented as a constant-time
@@ -617,6 +627,26 @@ impl CStr {
         unsafe { &*(addr_of!(self.inner) as *const [u8]) }
     }
 
+    /// Iterates over the bytes in this C string.
+    ///
+    /// The returned iterator will **not** contain the trailing nul terminator
+    /// that this C string has.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(cstr_bytes)]
+    /// use std::ffi::CStr;
+    ///
+    /// let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed");
+    /// assert!(cstr.bytes().eq(*b"foo"));
+    /// ```
+    #[inline]
+    #[unstable(feature = "cstr_bytes", issue = "112115")]
+    pub fn bytes(&self) -> Bytes<'_> {
+        Bytes::new(self)
+    }
+
     /// Yields a <code>&[str]</code> slice if the `CStr` contains valid UTF-8.
     ///
     /// If the contents of the `CStr` are valid UTF-8 data, this
@@ -735,3 +765,64 @@ const unsafe fn const_strlen(ptr: *const c_char) -> usize {
         intrinsics::const_eval_select((ptr,), strlen_ct, strlen_rt)
     }
 }
+
+/// An iterator over the bytes of a [`CStr`], without the nul terminator.
+///
+/// This struct is created by the [`bytes`] method on [`CStr`].
+/// See its documentation for more.
+///
+/// [`bytes`]: CStr::bytes
+#[must_use = "iterators are lazy and do nothing unless consumed"]
+#[unstable(feature = "cstr_bytes", issue = "112115")]
+#[derive(Clone, Debug)]
+pub struct Bytes<'a> {
+    // since we know the string is nul-terminated, we only need one pointer
+    ptr: NonNull<u8>,
+    phantom: PhantomData<&'a u8>,
+}
+impl<'a> Bytes<'a> {
+    #[inline]
+    fn new(s: &'a CStr) -> Self {
+        Self { ptr: s.as_non_null_ptr().cast(), phantom: PhantomData }
+    }
+
+    #[inline]
+    fn is_empty(&self) -> bool {
+        // SAFETY: We uphold that the pointer is always valid to dereference
+        // by starting with a valid C string and then never incrementing beyond
+        // the nul terminator.
+        unsafe { self.ptr.read() == 0 }
+    }
+}
+
+#[unstable(feature = "cstr_bytes", issue = "112115")]
+impl Iterator for Bytes<'_> {
+    type Item = u8;
+
+    #[inline]
+    fn next(&mut self) -> Option<u8> {
+        // SAFETY: We only choose a pointer from a valid C string, which must
+        // be non-null and contain at least one value. Since we always stop at
+        // the nul terminator, which is guaranteed to exist, we can assume that
+        // the pointer is non-null and valid. This lets us safely dereference
+        // it and assume that adding 1 will create a new, non-null, valid
+        // pointer.
+        unsafe {
+            let ret = self.ptr.read();
+            if ret == 0 {
+                None
+            } else {
+                self.ptr = self.ptr.offset(1);
+                Some(ret)
+            }
+        }
+    }
+
+    #[inline]
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        if self.is_empty() { (0, Some(0)) } else { (1, None) }
+    }
+}
+
+#[unstable(feature = "cstr_bytes", issue = "112115")]
+impl FusedIterator for Bytes<'_> {}
diff --git a/library/core/src/iter/range.rs b/library/core/src/iter/range.rs
index 68937161e04..055ead117ea 100644
--- a/library/core/src/iter/range.rs
+++ b/library/core/src/iter/range.rs
@@ -22,7 +22,7 @@ unsafe_impl_trusted_step![AsciiChar char i8 i16 i32 i64 i128 isize u8 u16 u32 u6
 ///
 /// The *successor* operation moves towards values that compare greater.
 /// The *predecessor* operation moves towards values that compare lesser.
-#[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
+#[unstable(feature = "step_trait", issue = "42168")]
 pub trait Step: Clone + PartialOrd + Sized {
     /// Returns the number of *successor* steps required to get from `start` to `end`.
     ///
@@ -52,15 +52,12 @@ pub trait Step: Clone + PartialOrd + Sized {
     /// For any `a`, `n`, and `m`:
     ///
     /// * `Step::forward_checked(a, n).and_then(|x| Step::forward_checked(x, m)) == Step::forward_checked(a, m).and_then(|x| Step::forward_checked(x, n))`
-    ///
-    /// For any `a`, `n`, and `m` where `n + m` does not overflow:
-    ///
-    /// * `Step::forward_checked(a, n).and_then(|x| Step::forward_checked(x, m)) == Step::forward_checked(a, n + m)`
+    /// * `Step::forward_checked(a, n).and_then(|x| Step::forward_checked(x, m)) == try { Step::forward_checked(a, n.checked_add(m)) }`
     ///
     /// For any `a` and `n`:
     ///
     /// * `Step::forward_checked(a, n) == (0..n).try_fold(a, |x, _| Step::forward_checked(&x, 1))`
-    ///   * Corollary: `Step::forward_checked(&a, 0) == Some(a)`
+    ///   * Corollary: `Step::forward_checked(a, 0) == Some(a)`
     fn forward_checked(start: Self, count: usize) -> Option<Self>;
 
     /// Returns the value that would be obtained by taking the *successor*
@@ -106,6 +103,7 @@ pub trait Step: Clone + PartialOrd + Sized {
     /// * if there exists `b` such that `b > a`, it is safe to call `Step::forward_unchecked(a, 1)`
     /// * if there exists `b`, `n` such that `steps_between(&a, &b) == Some(n)`,
     ///   it is safe to call `Step::forward_unchecked(a, m)` for any `m <= n`.
+    ///   * Corollary: `Step::forward_unchecked(a, 0)` is always safe.
     ///
     /// For any `a` and `n`, where no overflow occurs:
     ///
@@ -128,8 +126,8 @@ pub trait Step: Clone + PartialOrd + Sized {
     ///
     /// For any `a` and `n`:
     ///
-    /// * `Step::backward_checked(a, n) == (0..n).try_fold(a, |x, _| Step::backward_checked(&x, 1))`
-    ///   * Corollary: `Step::backward_checked(&a, 0) == Some(a)`
+    /// * `Step::backward_checked(a, n) == (0..n).try_fold(a, |x, _| Step::backward_checked(x, 1))`
+    ///   * Corollary: `Step::backward_checked(a, 0) == Some(a)`
     fn backward_checked(start: Self, count: usize) -> Option<Self>;
 
     /// Returns the value that would be obtained by taking the *predecessor*
@@ -175,6 +173,7 @@ pub trait Step: Clone + PartialOrd + Sized {
     /// * if there exists `b` such that `b < a`, it is safe to call `Step::backward_unchecked(a, 1)`
     /// * if there exists `b`, `n` such that `steps_between(&b, &a) == Some(n)`,
     ///   it is safe to call `Step::backward_unchecked(a, m)` for any `m <= n`.
+    ///   * Corollary: `Step::backward_unchecked(a, 0)` is always safe.
     ///
     /// For any `a` and `n`, where no overflow occurs:
     ///
@@ -184,8 +183,25 @@ pub trait Step: Clone + PartialOrd + Sized {
     }
 }
 
-// These are still macro-generated because the integer literals resolve to different types.
-macro_rules! step_identical_methods {
+// Separate impls for signed ranges because the distance within a signed range can be larger
+// than the signed::MAX value. Therefore `as` casting to the signed type would be incorrect.
+macro_rules! step_signed_methods {
+    ($unsigned: ty) => {
+        #[inline]
+        unsafe fn forward_unchecked(start: Self, n: usize) -> Self {
+            // SAFETY: the caller has to guarantee that `start + n` doesn't overflow.
+            unsafe { start.checked_add_unsigned(n as $unsigned).unwrap_unchecked() }
+        }
+
+        #[inline]
+        unsafe fn backward_unchecked(start: Self, n: usize) -> Self {
+            // SAFETY: the caller has to guarantee that `start - n` doesn't overflow.
+            unsafe { start.checked_sub_unsigned(n as $unsigned).unwrap_unchecked() }
+        }
+    };
+}
+
+macro_rules! step_unsigned_methods {
     () => {
         #[inline]
         unsafe fn forward_unchecked(start: Self, n: usize) -> Self {
@@ -198,7 +214,12 @@ macro_rules! step_identical_methods {
             // SAFETY: the caller has to guarantee that `start - n` doesn't overflow.
             unsafe { start.unchecked_sub(n as Self) }
         }
+    };
+}
 
+// These are still macro-generated because the integer literals resolve to different types.
+macro_rules! step_identical_methods {
+    () => {
         #[inline]
         #[allow(arithmetic_overflow)]
         #[rustc_inherit_overflow_checks]
@@ -239,6 +260,7 @@ macro_rules! step_integer_impls {
             #[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
             impl Step for $u_narrower {
                 step_identical_methods!();
+                step_unsigned_methods!();
 
                 #[inline]
                 fn steps_between(start: &Self, end: &Self) -> Option<usize> {
@@ -271,6 +293,7 @@ macro_rules! step_integer_impls {
             #[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
             impl Step for $i_narrower {
                 step_identical_methods!();
+                step_signed_methods!($u_narrower);
 
                 #[inline]
                 fn steps_between(start: &Self, end: &Self) -> Option<usize> {
@@ -335,6 +358,7 @@ macro_rules! step_integer_impls {
             #[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
             impl Step for $u_wider {
                 step_identical_methods!();
+                step_unsigned_methods!();
 
                 #[inline]
                 fn steps_between(start: &Self, end: &Self) -> Option<usize> {
@@ -360,6 +384,7 @@ macro_rules! step_integer_impls {
             #[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
             impl Step for $i_wider {
                 step_identical_methods!();
+                step_signed_methods!($u_wider);
 
                 #[inline]
                 fn steps_between(start: &Self, end: &Self) -> Option<usize> {
diff --git a/library/core/tests/iter/range.rs b/library/core/tests/iter/range.rs
index 9af07119a89..e31db0732e0 100644
--- a/library/core/tests/iter/range.rs
+++ b/library/core/tests/iter/range.rs
@@ -325,6 +325,11 @@ fn test_range_advance_by() {
     assert_eq!(Ok(()), r.advance_back_by(usize::MAX));
 
     assert_eq!((r.start, r.end), (0u128 + usize::MAX as u128, u128::MAX - usize::MAX as u128));
+
+    // issue 122420, Step::forward_unchecked was unsound for signed integers
+    let mut r = -128i8..127;
+    assert_eq!(Ok(()), r.advance_by(200));
+    assert_eq!(r.next(), Some(72));
 }
 
 #[test]
diff --git a/src/doc/rustdoc/src/read-documentation/search.md b/src/doc/rustdoc/src/read-documentation/search.md
index b5f4060f059..e2def14b357 100644
--- a/src/doc/rustdoc/src/read-documentation/search.md
+++ b/src/doc/rustdoc/src/read-documentation/search.md
@@ -63,11 +63,12 @@ Before describing the syntax in more detail, here's a few sample searches of
 the standard library and functions that are included in the results list:
 
 | Query | Results |
-|-------|--------|
+|-------|---------|
 | [`usize -> vec`][] | `slice::repeat` and `Vec::with_capacity` |
 | [`vec, vec -> bool`][] | `Vec::eq` |
 | [`option<T>, fnonce -> option<U>`][] | `Option::map` and `Option::and_then` |
-| [`option<T>, fnonce -> option<T>`][] | `Option::filter` and `Option::inspect` |
+| [`option<T>, (fnonce (T) -> bool) -> option<T>`][optionfilter] | `Option::filter` |
+| [`option<T>, (T -> bool) -> option<T>`][optionfilter2] | `Option::filter` |
 | [`option -> default`][] | `Option::unwrap_or_default` |
 | [`stdout, [u8]`][stdoutu8] | `Stdout::write` |
 | [`any -> !`][] | `panic::panic_any` |
@@ -77,7 +78,8 @@ the standard library and functions that are included in the results list:
 [`usize -> vec`]: ../../std/vec/struct.Vec.html?search=usize%20-%3E%20vec&filter-crate=std
 [`vec, vec -> bool`]: ../../std/vec/struct.Vec.html?search=vec,%20vec%20-%3E%20bool&filter-crate=std
 [`option<T>, fnonce -> option<U>`]: ../../std/vec/struct.Vec.html?search=option<T>%2C%20fnonce%20->%20option<U>&filter-crate=std
-[`option<T>, fnonce -> option<T>`]: ../../std/vec/struct.Vec.html?search=option<T>%2C%20fnonce%20->%20option<T>&filter-crate=std
+[optionfilter]: ../../std/vec/struct.Vec.html?search=option<T>%2C+(fnonce+(T)+->+bool)+->+option<T>&filter-crate=std
+[optionfilter2]: ../../std/vec/struct.Vec.html?search=option<T>%2C+(T+->+bool)+->+option<T>&filter-crate=std
 [`option -> default`]: ../../std/vec/struct.Vec.html?search=option%20-%3E%20default&filter-crate=std
 [`any -> !`]: ../../std/vec/struct.Vec.html?search=any%20-%3E%20!&filter-crate=std
 [stdoutu8]: ../../std/vec/struct.Vec.html?search=stdout%2C%20[u8]&filter-crate=std
@@ -151,16 +153,26 @@ will match these queries:
 
 But it *does not* match `Result<Vec, u8>` or `Result<u8<Vec>>`.
 
+To search for a function that accepts a function as a parameter,
+like `Iterator::all`, wrap the nested signature in parenthesis,
+as in [`Iterator<T>, (T -> bool) -> bool`][iterator-all].
+You can also search for a specific closure trait,
+such as `Iterator<T>, (FnMut(T) -> bool) -> bool`,
+but you need to know which one you want.
+
+[iterator-all]: ../../std/vec/struct.Vec.html?search=Iterator<T>%2C+(T+->+bool)+->+bool&filter-crate=std
+
 ### Primitives with Special Syntax
 
-| Shorthand | Explicit names                                   |
-| --------- | ------------------------------------------------ |
-| `[]`      | `primitive:slice` and/or `primitive:array`       |
-| `[T]`     | `primitive:slice<T>` and/or `primitive:array<T>` |
-| `()`      | `primitive:unit` and/or `primitive:tuple`        |
-| `(T)`     | `T`                                              |
-| `(T,)`    | `primitive:tuple<T>`                             |
-| `!`       | `primitive:never`                                |
+| Shorthand        | Explicit names                                    |
+| ---------------- | ------------------------------------------------- |
+| `[]`             | `primitive:slice` and/or `primitive:array`        |
+| `[T]`            | `primitive:slice<T>` and/or `primitive:array<T>`  |
+| `()`             | `primitive:unit` and/or `primitive:tuple`         |
+| `(T)`            | `T`                                               |
+| `(T,)`           | `primitive:tuple<T>`                              |
+| `!`              | `primitive:never`                                 |
+| `(T, U -> V, W)` | `fn(T, U) -> (V, W)`, `Fn`, `FnMut`, and `FnOnce` |
 
 When searching for `[]`, Rustdoc will return search results with either slices
 or arrays. If you know which one you want, you can force it to return results
@@ -180,6 +192,10 @@ results for types that match tuples, even though it also matches the type on
 its own. That is, `(u32)` matches `(u32,)` for the exact same reason that it
 also matches `Result<u32, Error>`.
 
+The `->` operator has lower precedence than comma. If it's not wrapped
+in brackets, it delimits the return value for the function being searched for.
+To search for functions that take functions as parameters, use parenthesis.
+
 ### Limitations and quirks of type-based search
 
 Type-based search is still a buggy, experimental, work-in-progress feature.
@@ -218,9 +234,6 @@ Most of these limitations should be addressed in future version of Rustdoc.
 
   * Searching for lifetimes is not supported.
 
-  * It's impossible to search for closures based on their parameters or
-    return values.
-
   * It's impossible to search based on the length of an array.
 
 ## Item filtering
@@ -237,19 +250,21 @@ Item filters can be used in both name-based and type signature-based searches.
 
 ```text
 ident = *(ALPHA / DIGIT / "_")
-path = ident *(DOUBLE-COLON ident) [!]
+path = ident *(DOUBLE-COLON ident) [BANG]
 slice-like = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
 tuple-like = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN
-arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like / [!])
+arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like)
 type-sep = COMMA/WS *(COMMA/WS)
-nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
+nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep) [ return-args ]
 generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep)
-generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
+normal-generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
             CLOSE-ANGLE-BRACKET
+fn-like-generics = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN [ RETURN-ARROW arg ]
+generics = normal-generics / fn-like-generics
 return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
 
 exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
-type-search = [ nonempty-arg-list ] [ return-args ]
+type-search = [ nonempty-arg-list ]
 
 query = *WS (exact-search / type-search) *WS
 
@@ -294,6 +309,7 @@ QUOTE = %x22
 COMMA = ","
 RETURN-ARROW = "->"
 EQUAL = "="
+BANG = "!"
 
 ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
 DIGIT = %x30-39
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index cb059082f85..f153a908329 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -4,6 +4,7 @@ use std::collections::{BTreeMap, VecDeque};
 use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
 use rustc_middle::ty::TyCtxt;
 use rustc_span::def_id::DefId;
+use rustc_span::sym;
 use rustc_span::symbol::Symbol;
 use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
 use thin_vec::ThinVec;
@@ -566,6 +567,7 @@ fn get_index_type_id(
         // The type parameters are converted to generics in `simplify_fn_type`
         clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)),
         clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)),
+        clean::BareFunction(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Fn)),
         clean::Tuple(ref n) if n.is_empty() => {
             Some(RenderTypeId::Primitive(clean::PrimitiveType::Unit))
         }
@@ -584,7 +586,7 @@ fn get_index_type_id(
             }
         }
         // Not supported yet
-        clean::BareFunction(_) | clean::Generic(_) | clean::ImplTrait(_) | clean::Infer => None,
+        clean::Generic(_) | clean::ImplTrait(_) | clean::Infer => None,
     }
 }
 
@@ -785,6 +787,42 @@ fn simplify_fn_type<'tcx, 'a>(
             );
         }
         res.push(get_index_type(arg, ty_generics, rgen));
+    } else if let Type::BareFunction(ref bf) = *arg {
+        let mut ty_generics = Vec::new();
+        for ty in bf.decl.inputs.values.iter().map(|arg| &arg.type_) {
+            simplify_fn_type(
+                self_,
+                generics,
+                ty,
+                tcx,
+                recurse + 1,
+                &mut ty_generics,
+                rgen,
+                is_return,
+                cache,
+            );
+        }
+        // The search index, for simplicity's sake, represents fn pointers and closures
+        // the same way: as a tuple for the parameters, and an associated type for the
+        // return type.
+        let mut ty_output = Vec::new();
+        simplify_fn_type(
+            self_,
+            generics,
+            &bf.decl.output,
+            tcx,
+            recurse + 1,
+            &mut ty_output,
+            rgen,
+            is_return,
+            cache,
+        );
+        let ty_bindings = vec![(RenderTypeId::AssociatedType(sym::Output), ty_output)];
+        res.push(RenderType {
+            id: get_index_type_id(&arg, rgen),
+            bindings: Some(ty_bindings),
+            generics: Some(ty_generics),
+        });
     } else {
         // This is not a type parameter. So for example if we have `T, U: Option<T>`, and we're
         // looking at `Option`, we enter this "else" condition, otherwise if it's `T`, we don't.
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 7995a33f09f..73ac5608479 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -1,3 +1,4 @@
+// ignore-tidy-filelength
 /* global addClass, getNakedUrl, getSettingValue */
 /* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */
 
@@ -245,33 +246,49 @@ function initSearch(rawSearchIndex) {
      *
      * @type {Map<string, {id: integer, assocOnly: boolean}>}
      */
-    let typeNameIdMap;
+    const typeNameIdMap = new Map();
     const ALIASES = new Map();
 
     /**
      * Special type name IDs for searching by array.
      */
-    let typeNameIdOfArray;
+    const typeNameIdOfArray = buildTypeMapIndex("array");
     /**
      * Special type name IDs for searching by slice.
      */
-    let typeNameIdOfSlice;
+    const typeNameIdOfSlice = buildTypeMapIndex("slice");
     /**
      * Special type name IDs for searching by both array and slice (`[]` syntax).
      */
-    let typeNameIdOfArrayOrSlice;
+    const typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
     /**
      * Special type name IDs for searching by tuple.
      */
-    let typeNameIdOfTuple;
+    const typeNameIdOfTuple = buildTypeMapIndex("tuple");
     /**
      * Special type name IDs for searching by unit.
      */
-    let typeNameIdOfUnit;
+    const typeNameIdOfUnit = buildTypeMapIndex("unit");
     /**
      * Special type name IDs for searching by both tuple and unit (`()` syntax).
      */
-    let typeNameIdOfTupleOrUnit;
+    const typeNameIdOfTupleOrUnit = buildTypeMapIndex("()");
+    /**
+     * Special type name IDs for searching `fn`.
+     */
+    const typeNameIdOfFn = buildTypeMapIndex("fn");
+    /**
+     * Special type name IDs for searching `fnmut`.
+     */
+    const typeNameIdOfFnMut = buildTypeMapIndex("fnmut");
+    /**
+     * Special type name IDs for searching `fnonce`.
+     */
+    const typeNameIdOfFnOnce = buildTypeMapIndex("fnonce");
+    /**
+     * Special type name IDs for searching higher order functions (`->` syntax).
+     */
+    const typeNameIdOfHof = buildTypeMapIndex("->");
 
     /**
      * Add an item to the type Name->ID map, or, if one already exists, use it.
@@ -464,6 +481,21 @@ function initSearch(rawSearchIndex) {
         }
     }
 
+    function makePrimitiveElement(name, extra) {
+        return Object.assign({
+            name,
+            id: null,
+            fullPath: [name],
+            pathWithoutLast: [],
+            pathLast: name,
+            normalizedPathLast: name,
+            generics: [],
+            bindings: new Map(),
+            typeFilter: "primitive",
+            bindingName: null,
+        }, extra);
+    }
+
     /**
      * @param {ParsedQuery} query
      * @param {ParserState} parserState
@@ -501,18 +533,7 @@ function initSearch(rawSearchIndex) {
             }
             const bindingName = parserState.isInBinding;
             parserState.isInBinding = null;
-            return {
-                name: "never",
-                id: null,
-                fullPath: ["never"],
-                pathWithoutLast: [],
-                pathLast: "never",
-                normalizedPathLast: "never",
-                generics: [],
-                bindings: new Map(),
-                typeFilter: "primitive",
-                bindingName,
-            };
+            return makePrimitiveElement("never", { bindingName });
         }
         const quadcolon = /::\s*::/.exec(path);
         if (path.startsWith("::")) {
@@ -558,7 +579,10 @@ function initSearch(rawSearchIndex) {
                 // Syntactically, bindings are parsed as generics,
                 // but the query engine treats them differently.
                 if (gen.bindingName !== null) {
-                    bindings.set(gen.bindingName.name, [gen, ...gen.bindingName.generics]);
+                    if (gen.name !== null) {
+                        gen.bindingName.generics.unshift(gen);
+                    }
+                    bindings.set(gen.bindingName.name, gen.bindingName.generics);
                     return false;
                 }
                 return true;
@@ -658,6 +682,38 @@ function initSearch(rawSearchIndex) {
         return end;
     }
 
+    function getFilteredNextElem(query, parserState, elems, isInGenerics) {
+        const start = parserState.pos;
+        if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {
+            throw ["Expected type filter before ", ":"];
+        }
+        getNextElem(query, parserState, elems, isInGenerics);
+        if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {
+            if (parserState.typeFilter !== null) {
+                throw [
+                    "Unexpected ",
+                    ":",
+                    " (expected path after type filter ",
+                    parserState.typeFilter + ":",
+                    ")",
+                ];
+            }
+            if (elems.length === 0) {
+                throw ["Expected type filter before ", ":"];
+            } else if (query.literalSearch) {
+                throw ["Cannot use quotes on type filter"];
+            }
+            // The type filter doesn't count as an element since it's a modifier.
+            const typeFilterElem = elems.pop();
+            checkExtraTypeFilterCharacters(start, parserState);
+            parserState.typeFilter = typeFilterElem.name;
+            parserState.pos += 1;
+            parserState.totalElems -= 1;
+            query.literalSearch = false;
+            getNextElem(query, parserState, elems, isInGenerics);
+        }
+    }
+
     /**
      * @param {ParsedQuery} query
      * @param {ParserState} parserState
@@ -671,28 +727,19 @@ function initSearch(rawSearchIndex) {
         let start = parserState.pos;
         let end;
         if ("[(".indexOf(parserState.userQuery[parserState.pos]) !== -1) {
-let endChar = ")";
-let name = "()";
-let friendlyName = "tuple";
+            let endChar = ")";
+            let name = "()";
+            let friendlyName = "tuple";
 
-if (parserState.userQuery[parserState.pos] === "[") {
-    endChar = "]";
-    name = "[]";
-    friendlyName = "slice";
-}
+            if (parserState.userQuery[parserState.pos] === "[") {
+                endChar = "]";
+                name = "[]";
+                friendlyName = "slice";
+            }
             parserState.pos += 1;
             const { foundSeparator } = getItemsBefore(query, parserState, generics, endChar);
             const typeFilter = parserState.typeFilter;
-            const isInBinding = parserState.isInBinding;
-            if (typeFilter !== null && typeFilter !== "primitive") {
-                throw [
-                    "Invalid search type: primitive ",
-                    name,
-                    " and ",
-                    typeFilter,
-                    " both specified",
-                ];
-            }
+            const bindingName = parserState.isInBinding;
             parserState.typeFilter = null;
             parserState.isInBinding = null;
             for (const gen of generics) {
@@ -702,23 +749,26 @@ if (parserState.userQuery[parserState.pos] === "[") {
             }
             if (name === "()" && !foundSeparator && generics.length === 1 && typeFilter === null) {
                 elems.push(generics[0]);
+            } else if (name === "()" && generics.length === 1 && generics[0].name === "->") {
+                // `primitive:(a -> b)` parser to `primitive:"->"<output=b, (a,)>`
+                // not `primitive:"()"<"->"<output=b, (a,)>>`
+                generics[0].typeFilter = typeFilter;
+                elems.push(generics[0]);
             } else {
+                if (typeFilter !== null && typeFilter !== "primitive") {
+                    throw [
+                        "Invalid search type: primitive ",
+                        name,
+                        " and ",
+                        typeFilter,
+                        " both specified",
+                    ];
+                }
                 parserState.totalElems += 1;
                 if (isInGenerics) {
                     parserState.genericsElems += 1;
                 }
-                elems.push({
-                    name: name,
-                    id: null,
-                    fullPath: [name],
-                    pathWithoutLast: [],
-                    pathLast: name,
-                    normalizedPathLast: name,
-                    generics,
-                    bindings: new Map(),
-                    typeFilter: "primitive",
-                    bindingName: isInBinding,
-                });
+                elems.push(makePrimitiveElement(name, { bindingName, generics }));
             }
         } else {
             const isStringElem = parserState.userQuery[start] === "\"";
@@ -738,6 +788,32 @@ if (parserState.userQuery[parserState.pos] === "[") {
                 }
                 parserState.pos += 1;
                 getItemsBefore(query, parserState, generics, ">");
+            } else if (parserState.pos < parserState.length &&
+                parserState.userQuery[parserState.pos] === "("
+            ) {
+                if (start >= end) {
+                    throw ["Found generics without a path"];
+                }
+                if (parserState.isInBinding) {
+                    throw ["Unexpected ", "(", " after ", "="];
+                }
+                parserState.pos += 1;
+                const typeFilter = parserState.typeFilter;
+                parserState.typeFilter = null;
+                getItemsBefore(query, parserState, generics, ")");
+                skipWhitespace(parserState);
+                if (isReturnArrow(parserState)) {
+                    parserState.pos += 2;
+                    skipWhitespace(parserState);
+                    getFilteredNextElem(query, parserState, generics, isInGenerics);
+                    generics[generics.length - 1].bindingName = makePrimitiveElement("output");
+                } else {
+                    generics.push(makePrimitiveElement(null, {
+                        bindingName: makePrimitiveElement("output"),
+                        typeFilter: null,
+                    }));
+                }
+                parserState.typeFilter = typeFilter;
             }
             if (isStringElem) {
                 skipWhitespace(parserState);
@@ -797,7 +873,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
     function getItemsBefore(query, parserState, elems, endChar) {
         let foundStopChar = true;
         let foundSeparator = false;
-        let start = parserState.pos;
 
         // If this is a generic, keep the outer item's type filter around.
         const oldTypeFilter = parserState.typeFilter;
@@ -805,6 +880,19 @@ if (parserState.userQuery[parserState.pos] === "[") {
         const oldIsInBinding = parserState.isInBinding;
         parserState.isInBinding = null;
 
+        // ML-style Higher Order Function notation
+        //
+        // a way to search for any closure or fn pointer regardless of
+        // which closure trait is used
+        //
+        // Looks like this:
+        //
+        //     `option<t>, (t -> u) -> option<u>`
+        //                  ^^^^^^
+        //
+        // The Rust-style closure notation is implemented in getNextElem
+        let hofParameters = null;
+
         let extra = "";
         if (endChar === ">") {
             extra = "<";
@@ -825,6 +913,21 @@ if (parserState.userQuery[parserState.pos] === "[") {
                     throw ["Unexpected ", endChar, " after ", "="];
                 }
                 break;
+            } else if (endChar !== "" && isReturnArrow(parserState)) {
+                // ML-style HOF notation only works when delimited in something,
+                // otherwise a function arrow starts the return type of the top
+                if (parserState.isInBinding) {
+                    throw ["Unexpected ", "->", " after ", "="];
+                }
+                hofParameters = [...elems];
+                elems.length = 0;
+                parserState.pos += 2;
+                foundStopChar = true;
+                foundSeparator = false;
+                continue;
+            } else if (c === " ") {
+                parserState.pos += 1;
+                continue;
             } else if (isSeparatorCharacter(c)) {
                 parserState.pos += 1;
                 foundStopChar = true;
@@ -832,24 +935,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
                 continue;
             } else if (c === ":" && isPathStart(parserState)) {
                 throw ["Unexpected ", "::", ": paths cannot start with ", "::"];
-            }  else if (c === ":") {
-                if (parserState.typeFilter !== null) {
-                    throw ["Unexpected ", ":"];
-                }
-                if (elems.length === 0) {
-                    throw ["Expected type filter before ", ":"];
-                } else if (query.literalSearch) {
-                    throw ["Cannot use quotes on type filter"];
-                }
-                // The type filter doesn't count as an element since it's a modifier.
-                const typeFilterElem = elems.pop();
-                checkExtraTypeFilterCharacters(start, parserState);
-                parserState.typeFilter = typeFilterElem.name;
-                parserState.pos += 1;
-                parserState.totalElems -= 1;
-                query.literalSearch = false;
-                foundStopChar = true;
-                continue;
             } else if (isEndCharacter(c)) {
                 throw ["Unexpected ", c, " after ", extra];
             }
@@ -884,8 +969,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
                 ];
             }
             const posBefore = parserState.pos;
-            start = parserState.pos;
-            getNextElem(query, parserState, elems, endChar !== "");
+            getFilteredNextElem(query, parserState, elems, endChar !== "");
             if (endChar !== "" && parserState.pos >= parserState.length) {
                 throw ["Unclosed ", extra];
             }
@@ -904,6 +988,27 @@ if (parserState.userQuery[parserState.pos] === "[") {
         // in any case.
         parserState.pos += 1;
 
+        if (hofParameters) {
+            // Commas in a HOF don't cause wrapping parens to become a tuple.
+            // If you want a one-tuple with a HOF in it, write `((a -> b),)`.
+            foundSeparator = false;
+            // HOFs can't have directly nested bindings.
+            if ([...elems, ...hofParameters].some(x => x.bindingName) || parserState.isInBinding) {
+                throw ["Unexpected ", "=", " within ", "->"];
+            }
+            // HOFs are represented the same way closures are.
+            // The arguments are wrapped in a tuple, and the output
+            // is a binding, even though the compiler doesn't technically
+            // represent fn pointers that way.
+            const hofElem = makePrimitiveElement("->", {
+                generics: hofParameters,
+                bindings: new Map([["output", [...elems]]]),
+                typeFilter: null,
+            });
+            elems.length = 0;
+            elems[0] = hofElem;
+        }
+
         parserState.typeFilter = oldTypeFilter;
         parserState.isInBinding = oldIsInBinding;
 
@@ -941,7 +1046,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
      */
     function parseInput(query, parserState) {
         let foundStopChar = true;
-        let start = parserState.pos;
 
         while (parserState.pos < parserState.length) {
             const c = parserState.userQuery[parserState.pos];
@@ -959,29 +1063,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
                     throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1]];
                 }
                 throw ["Unexpected ", c];
-            } else if (c === ":" && !isPathStart(parserState)) {
-                if (parserState.typeFilter !== null) {
-                    throw [
-                        "Unexpected ",
-                        ":",
-                        " (expected path after type filter ",
-                        parserState.typeFilter + ":",
-                        ")",
-                    ];
-                } else if (query.elems.length === 0) {
-                    throw ["Expected type filter before ", ":"];
-                } else if (query.literalSearch) {
-                    throw ["Cannot use quotes on type filter"];
-                }
-                // The type filter doesn't count as an element since it's a modifier.
-                const typeFilterElem = query.elems.pop();
-                checkExtraTypeFilterCharacters(start, parserState);
-                parserState.typeFilter = typeFilterElem.name;
-                parserState.pos += 1;
-                parserState.totalElems -= 1;
-                query.literalSearch = false;
-                foundStopChar = true;
-                continue;
             } else if (c === " ") {
                 skipWhitespace(parserState);
                 continue;
@@ -1017,8 +1098,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
                 ];
             }
             const before = query.elems.length;
-            start = parserState.pos;
-            getNextElem(query, parserState, query.elems, false);
+            getFilteredNextElem(query, parserState, query.elems, false);
             if (query.elems.length === before) {
                 // Nothing was added, weird... Let's increase the position to not remain stuck.
                 parserState.pos += 1;
@@ -1258,11 +1338,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
          * @returns {[ResultObject]}
          */
         function sortResults(results, isType, preferredCrate) {
-            // if there are no results then return to default and fail
-            if (results.size === 0) {
-                return [];
-            }
-
             const userQuery = parsedQuery.userQuery;
             const result_list = [];
             for (const result of results.values()) {
@@ -1635,6 +1710,12 @@ if (parserState.userQuery[parserState.pos] === "[") {
                 ) {
                     // () matches primitive:tuple or primitive:unit
                     // if it matches, then we're fine, and this is an appropriate match candidate
+                } else if (queryElem.id === typeNameIdOfHof &&
+                    (fnType.id === typeNameIdOfFn || fnType.id === typeNameIdOfFnMut ||
+                        fnType.id === typeNameIdOfFnOnce)
+                ) {
+                    // -> matches fn, fnonce, and fnmut
+                    // if it matches, then we're fine, and this is an appropriate match candidate
                 } else if (fnType.id !== queryElem.id || queryElem.id === null) {
                     return false;
                 }
@@ -1829,6 +1910,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
                     typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
                     // special case
                     elem.id !== typeNameIdOfArrayOrSlice && elem.id !== typeNameIdOfTupleOrUnit
+                    && elem.id !== typeNameIdOfHof
                 ) {
                     return row.id === elem.id || checkIfInList(
                         row.generics,
@@ -2991,7 +3073,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
      */
     function buildFunctionTypeFingerprint(type, output, fps) {
         let input = type.id;
-        // All forms of `[]`/`()` get collapsed down to one thing in the bloom filter.
+        // All forms of `[]`/`()`/`->` get collapsed down to one thing in the bloom filter.
         // Differentiating between arrays and slices, if the user asks for it, is
         // still done in the matching algorithm.
         if (input === typeNameIdOfArray || input === typeNameIdOfSlice) {
@@ -3000,6 +3082,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
         if (input === typeNameIdOfTuple || input === typeNameIdOfUnit) {
             input = typeNameIdOfTupleOrUnit;
         }
+        if (input === typeNameIdOfFn || input === typeNameIdOfFnMut ||
+            input === typeNameIdOfFnOnce) {
+            input = typeNameIdOfHof;
+        }
         // http://burtleburtle.net/bob/hash/integer.html
         // ~~ is toInt32. It's used before adding, so
         // the number stays in safe integer range.
@@ -3090,20 +3176,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
      */
     function buildIndex(rawSearchIndex) {
         searchIndex = [];
-        typeNameIdMap = new Map();
         const charA = "A".charCodeAt(0);
         let currentIndex = 0;
         let id = 0;
 
-        // Initialize type map indexes for primitive list types
-        // that can be searched using `[]` syntax.
-        typeNameIdOfArray = buildTypeMapIndex("array");
-        typeNameIdOfSlice = buildTypeMapIndex("slice");
-        typeNameIdOfTuple = buildTypeMapIndex("tuple");
-        typeNameIdOfUnit = buildTypeMapIndex("unit");
-        typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
-        typeNameIdOfTupleOrUnit = buildTypeMapIndex("()");
-
         // Function type fingerprints are 128-bit bloom filters that are used to
         // estimate the distance between function and query.
         // This loop counts the number of items to allocate a fingerprint for.
diff --git a/src/tools/miri/tests/pass/shims/time-with-isolation2.stdout b/src/tools/miri/tests/pass/shims/time-with-isolation2.stdout
index c68b40b744b..dce51a7fdbe 100644
--- a/src/tools/miri/tests/pass/shims/time-with-isolation2.stdout
+++ b/src/tools/miri/tests/pass/shims/time-with-isolation2.stdout
@@ -1 +1 @@
-The loop took around 7s
+The loop took around 12s
diff --git a/tests/rustdoc-js-std/parser-errors.js b/tests/rustdoc-js-std/parser-errors.js
index 16d171260da..ffd169812b6 100644
--- a/tests/rustdoc-js-std/parser-errors.js
+++ b/tests/rustdoc-js-std/parser-errors.js
@@ -114,7 +114,7 @@ const PARSED = [
         original: "(p -> p",
         returned: [],
         userQuery: "(p -> p",
-        error: "Unexpected `-` after `(`",
+        error: "Unclosed `(`",
     },
     {
         query: "::a::b",
@@ -195,7 +195,7 @@ const PARSED = [
         original: "a (b:",
         returned: [],
         userQuery: "a (b:",
-        error: "Expected `,`, `:` or `->`, found `(`",
+        error: "Unclosed `(`",
     },
     {
         query: "_:",
@@ -330,7 +330,7 @@ const PARSED = [
         original: 'a<->',
         returned: [],
         userQuery: 'a<->',
-        error: 'Unexpected `-` after `<`',
+        error: 'Unclosed `<`',
     },
     {
         query: "a<a>:",
@@ -357,7 +357,16 @@ const PARSED = [
         original: "a,:",
         returned: [],
         userQuery: "a,:",
-        error: 'Unexpected `,` in type filter (before `:`)',
+        error: 'Expected type filter before `:`',
+    },
+    {
+        query: "a!:",
+        elems: [],
+        foundElems: 0,
+        original: "a!:",
+        returned: [],
+        userQuery: "a!:",
+        error: 'Unexpected `!` in type filter (before `:`)',
     },
     {
         query: "  a<>  :",
@@ -366,7 +375,7 @@ const PARSED = [
         original: "a<>  :",
         returned: [],
         userQuery: "a<>  :",
-        error: 'Unexpected `<` in type filter (before `:`)',
+        error: 'Expected `,`, `:` or `->` after `>`, found `:`',
     },
     {
         query: "mod : :",
diff --git a/tests/rustdoc-js-std/parser-hof.js b/tests/rustdoc-js-std/parser-hof.js
new file mode 100644
index 00000000000..0b99c45b7a9
--- /dev/null
+++ b/tests/rustdoc-js-std/parser-hof.js
@@ -0,0 +1,712 @@
+const PARSED = [
+    // ML-style HOF
+    {
+        query: "(-> F<P>)",
+        elems: [{
+            name: "->",
+            fullPath: ["->"],
+            pathWithoutLast: [],
+            pathLast: "->",
+            generics: [],
+            bindings: [
+                [
+                    "output",
+                    [{
+                        name: "f",
+                        fullPath: ["f"],
+                        pathWithoutLast: [],
+                        pathLast: "f",
+                        generics: [
+                            {
+                                name: "p",
+                                fullPath: ["p"],
+                                pathWithoutLast: [],
+                                pathLast: "p",
+                                generics: [],
+                            },
+                        ],
+                        typeFilter: -1,
+                    }],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "(-> F<P>)",
+        returned: [],
+        userQuery: "(-> f<p>)",
+        error: null,
+    },
+    {
+        query: "(-> P)",
+        elems: [{
+            name: "->",
+            fullPath: ["->"],
+            pathWithoutLast: [],
+            pathLast: "->",
+            generics: [],
+            bindings: [
+                [
+                    "output",
+                    [{
+                        name: "p",
+                        fullPath: ["p"],
+                        pathWithoutLast: [],
+                        pathLast: "p",
+                        generics: [],
+                        typeFilter: -1,
+                    }],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "(-> P)",
+        returned: [],
+        userQuery: "(-> p)",
+        error: null,
+    },
+    {
+        query: "(->,a)",
+        elems: [{
+            name: "->",
+            fullPath: ["->"],
+            pathWithoutLast: [],
+            pathLast: "->",
+            generics: [],
+            bindings: [
+                [
+                    "output",
+                    [{
+                        name: "a",
+                        fullPath: ["a"],
+                        pathWithoutLast: [],
+                        pathLast: "a",
+                        generics: [],
+                        typeFilter: -1,
+                    }],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "(->,a)",
+        returned: [],
+        userQuery: "(->,a)",
+        error: null,
+    },
+    {
+        query: "(F<P> ->)",
+        elems: [{
+            name: "->",
+            fullPath: ["->"],
+            pathWithoutLast: [],
+            pathLast: "->",
+            generics: [{
+                name: "f",
+                fullPath: ["f"],
+                pathWithoutLast: [],
+                pathLast: "f",
+                generics: [
+                    {
+                        name: "p",
+                        fullPath: ["p"],
+                        pathWithoutLast: [],
+                        pathLast: "p",
+                        generics: [],
+                    },
+                ],
+                typeFilter: -1,
+            }],
+            bindings: [
+                [
+                    "output",
+                    [],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "(F<P> ->)",
+        returned: [],
+        userQuery: "(f<p> ->)",
+        error: null,
+    },
+    {
+        query: "(P ->)",
+        elems: [{
+            name: "->",
+            fullPath: ["->"],
+            pathWithoutLast: [],
+            pathLast: "->",
+            generics: [{
+                name: "p",
+                fullPath: ["p"],
+                pathWithoutLast: [],
+                pathLast: "p",
+                generics: [],
+                typeFilter: -1,
+            }],
+            bindings: [
+                [
+                    "output",
+                    [],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "(P ->)",
+        returned: [],
+        userQuery: "(p ->)",
+        error: null,
+    },
+    {
+        query: "(,a->)",
+        elems: [{
+            name: "->",
+            fullPath: ["->"],
+            pathWithoutLast: [],
+            pathLast: "->",
+            generics: [{
+                name: "a",
+                fullPath: ["a"],
+                pathWithoutLast: [],
+                pathLast: "a",
+                generics: [],
+                typeFilter: -1,
+            }],
+            bindings: [
+                [
+                    "output",
+                    [],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "(,a->)",
+        returned: [],
+        userQuery: "(,a->)",
+        error: null,
+    },
+    {
+        query: "(aaaaa->a)",
+        elems: [{
+            name: "->",
+            fullPath: ["->"],
+            pathWithoutLast: [],
+            pathLast: "->",
+            generics: [{
+                name: "aaaaa",
+                fullPath: ["aaaaa"],
+                pathWithoutLast: [],
+                pathLast: "aaaaa",
+                generics: [],
+                typeFilter: -1,
+            }],
+            bindings: [
+                [
+                    "output",
+                    [{
+                        name: "a",
+                        fullPath: ["a"],
+                        pathWithoutLast: [],
+                        pathLast: "a",
+                        generics: [],
+                        typeFilter: -1,
+                    }],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "(aaaaa->a)",
+        returned: [],
+        userQuery: "(aaaaa->a)",
+        error: null,
+    },
+    {
+        query: "(aaaaa, b -> a)",
+        elems: [{
+            name: "->",
+            fullPath: ["->"],
+            pathWithoutLast: [],
+            pathLast: "->",
+            generics: [
+                {
+                    name: "aaaaa",
+                    fullPath: ["aaaaa"],
+                    pathWithoutLast: [],
+                    pathLast: "aaaaa",
+                    generics: [],
+                    typeFilter: -1,
+                },
+                {
+                    name: "b",
+                    fullPath: ["b"],
+                    pathWithoutLast: [],
+                    pathLast: "b",
+                    generics: [],
+                    typeFilter: -1,
+                },
+            ],
+            bindings: [
+                [
+                    "output",
+                    [{
+                        name: "a",
+                        fullPath: ["a"],
+                        pathWithoutLast: [],
+                        pathLast: "a",
+                        generics: [],
+                        typeFilter: -1,
+                    }],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "(aaaaa, b -> a)",
+        returned: [],
+        userQuery: "(aaaaa, b -> a)",
+        error: null,
+    },
+    {
+        query: "primitive:(aaaaa, b -> a)",
+        elems: [{
+            name: "->",
+            fullPath: ["->"],
+            pathWithoutLast: [],
+            pathLast: "->",
+            generics: [
+                {
+                    name: "aaaaa",
+                    fullPath: ["aaaaa"],
+                    pathWithoutLast: [],
+                    pathLast: "aaaaa",
+                    generics: [],
+                    typeFilter: -1,
+                },
+                {
+                    name: "b",
+                    fullPath: ["b"],
+                    pathWithoutLast: [],
+                    pathLast: "b",
+                    generics: [],
+                    typeFilter: -1,
+                },
+            ],
+            bindings: [
+                [
+                    "output",
+                    [{
+                        name: "a",
+                        fullPath: ["a"],
+                        pathWithoutLast: [],
+                        pathLast: "a",
+                        generics: [],
+                        typeFilter: -1,
+                    }],
+                ],
+            ],
+            typeFilter: 1,
+        }],
+        foundElems: 1,
+        original: "primitive:(aaaaa, b -> a)",
+        returned: [],
+        userQuery: "primitive:(aaaaa, b -> a)",
+        error: null,
+    },
+    {
+        query: "x, trait:(aaaaa, b -> a)",
+        elems: [
+            {
+                name: "x",
+                fullPath: ["x"],
+                pathWithoutLast: [],
+                pathLast: "x",
+                generics: [],
+                typeFilter: -1,
+            },
+            {
+                name: "->",
+                fullPath: ["->"],
+                pathWithoutLast: [],
+                pathLast: "->",
+                generics: [
+                    {
+                        name: "aaaaa",
+                        fullPath: ["aaaaa"],
+                        pathWithoutLast: [],
+                        pathLast: "aaaaa",
+                        generics: [],
+                        typeFilter: -1,
+                    },
+                    {
+                        name: "b",
+                        fullPath: ["b"],
+                        pathWithoutLast: [],
+                        pathLast: "b",
+                        generics: [],
+                        typeFilter: -1,
+                    },
+                ],
+                bindings: [
+                    [
+                        "output",
+                        [{
+                            name: "a",
+                            fullPath: ["a"],
+                            pathWithoutLast: [],
+                            pathLast: "a",
+                            generics: [],
+                            typeFilter: -1,
+                        }],
+                    ],
+                ],
+                typeFilter: 10,
+            }
+        ],
+        foundElems: 2,
+        original: "x, trait:(aaaaa, b -> a)",
+        returned: [],
+        userQuery: "x, trait:(aaaaa, b -> a)",
+        error: null,
+    },
+    // Rust-style HOF
+    {
+        query: "Fn () -> F<P>",
+        elems: [{
+            name: "fn",
+            fullPath: ["fn"],
+            pathWithoutLast: [],
+            pathLast: "fn",
+            generics: [],
+            bindings: [
+                [
+                    "output",
+                    [{
+                        name: "f",
+                        fullPath: ["f"],
+                        pathWithoutLast: [],
+                        pathLast: "f",
+                        generics: [
+                            {
+                                name: "p",
+                                fullPath: ["p"],
+                                pathWithoutLast: [],
+                                pathLast: "p",
+                                generics: [],
+                            },
+                        ],
+                        typeFilter: -1,
+                    }],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "Fn () -> F<P>",
+        returned: [],
+        userQuery: "fn () -> f<p>",
+        error: null,
+    },
+    {
+        query: "FnMut() -> P",
+        elems: [{
+            name: "fnmut",
+            fullPath: ["fnmut"],
+            pathWithoutLast: [],
+            pathLast: "fnmut",
+            generics: [],
+            bindings: [
+                [
+                    "output",
+                    [{
+                        name: "p",
+                        fullPath: ["p"],
+                        pathWithoutLast: [],
+                        pathLast: "p",
+                        generics: [],
+                        typeFilter: -1,
+                    }],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "FnMut() -> P",
+        returned: [],
+        userQuery: "fnmut() -> p",
+        error: null,
+    },
+    {
+        query: "(FnMut() -> P)",
+        elems: [{
+            name: "fnmut",
+            fullPath: ["fnmut"],
+            pathWithoutLast: [],
+            pathLast: "fnmut",
+            generics: [],
+            bindings: [
+                [
+                    "output",
+                    [{
+                        name: "p",
+                        fullPath: ["p"],
+                        pathWithoutLast: [],
+                        pathLast: "p",
+                        generics: [],
+                        typeFilter: -1,
+                    }],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "(FnMut() -> P)",
+        returned: [],
+        userQuery: "(fnmut() -> p)",
+        error: null,
+    },
+    {
+        query: "Fn(F<P>)",
+        elems: [{
+            name: "fn",
+            fullPath: ["fn"],
+            pathWithoutLast: [],
+            pathLast: "fn",
+            generics: [{
+                name: "f",
+                fullPath: ["f"],
+                pathWithoutLast: [],
+                pathLast: "f",
+                generics: [
+                    {
+                        name: "p",
+                        fullPath: ["p"],
+                        pathWithoutLast: [],
+                        pathLast: "p",
+                        generics: [],
+                    },
+                ],
+                typeFilter: -1,
+            }],
+            bindings: [
+                [
+                    "output",
+                    [],
+                ],
+            ],
+            typeFilter: -1,
+        }],
+        foundElems: 1,
+        original: "Fn(F<P>)",
+        returned: [],
+        userQuery: "fn(f<p>)",
+        error: null,
+    },
+    {
+        query: "primitive:fnonce(aaaaa, b) -> a",
+        elems: [{
+            name: "fnonce",
+            fullPath: ["fnonce"],
+            pathWithoutLast: [],
+            pathLast: "fnonce",
+            generics: [
+                {
+                    name: "aaaaa",
+                    fullPath: ["aaaaa"],
+                    pathWithoutLast: [],
+                    pathLast: "aaaaa",
+                    generics: [],
+                    typeFilter: -1,
+                },
+                {
+                    name: "b",
+                    fullPath: ["b"],
+                    pathWithoutLast: [],
+                    pathLast: "b",
+                    generics: [],
+                    typeFilter: -1,
+                },
+            ],
+            bindings: [
+                [
+                    "output",
+                    [{
+                        name: "a",
+                        fullPath: ["a"],
+                        pathWithoutLast: [],
+                        pathLast: "a",
+                        generics: [],
+                        typeFilter: -1,
+                    }],
+                ],
+            ],
+            typeFilter: 1,
+        }],
+        foundElems: 1,
+        original: "primitive:fnonce(aaaaa, b) -> a",
+        returned: [],
+        userQuery: "primitive:fnonce(aaaaa, b) -> a",
+        error: null,
+    },
+    {
+        query: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
+        elems: [{
+            name: "fnonce",
+            fullPath: ["fnonce"],
+            pathWithoutLast: [],
+            pathLast: "fnonce",
+            generics: [
+                {
+                    name: "aaaaa",
+                    fullPath: ["aaaaa"],
+                    pathWithoutLast: [],
+                    pathLast: "aaaaa",
+                    generics: [],
+                    typeFilter: -1,
+                },
+                {
+                    name: "b",
+                    fullPath: ["b"],
+                    pathWithoutLast: [],
+                    pathLast: "b",
+                    generics: [],
+                    typeFilter: 0,
+                },
+            ],
+            bindings: [
+                [
+                    "output",
+                    [{
+                        name: "a",
+                        fullPath: ["a"],
+                        pathWithoutLast: [],
+                        pathLast: "a",
+                        generics: [],
+                        typeFilter: 10,
+                    }],
+                ],
+            ],
+            typeFilter: 1,
+        }],
+        foundElems: 1,
+        original: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
+        returned: [],
+        userQuery: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
+        error: null,
+    },
+    {
+        query: "x, trait:fn(aaaaa, b -> a)",
+        elems: [
+            {
+                name: "x",
+                fullPath: ["x"],
+                pathWithoutLast: [],
+                pathLast: "x",
+                generics: [],
+                typeFilter: -1,
+            },
+            {
+                name: "fn",
+                fullPath: ["fn"],
+                pathWithoutLast: [],
+                pathLast: "fn",
+                generics: [
+                    {
+                        name: "->",
+                        fullPath: ["->"],
+                        pathWithoutLast: [],
+                        pathLast: "->",
+                        generics: [
+                            {
+                                name: "aaaaa",
+                                fullPath: ["aaaaa"],
+                                pathWithoutLast: [],
+                                pathLast: "aaaaa",
+                                generics: [],
+                                typeFilter: -1,
+                            },
+                            {
+                                name: "b",
+                                fullPath: ["b"],
+                                pathWithoutLast: [],
+                                pathLast: "b",
+                                generics: [],
+                                typeFilter: -1,
+                            },
+                        ],
+                        bindings: [
+                            [
+                                "output",
+                                [{
+                                    name: "a",
+                                    fullPath: ["a"],
+                                    pathWithoutLast: [],
+                                    pathLast: "a",
+                                    generics: [],
+                                    typeFilter: -1,
+                                }],
+                            ],
+                        ],
+                        typeFilter: -1,
+                    },
+                ],
+                bindings: [
+                    [
+                        "output",
+                        [],
+                    ]
+                ],
+                typeFilter: 10,
+            }
+        ],
+        foundElems: 2,
+        original: "x, trait:fn(aaaaa, b -> a)",
+        returned: [],
+        userQuery: "x, trait:fn(aaaaa, b -> a)",
+        error: null,
+    },
+    {
+        query: 'a,b(c)',
+        elems: [
+            {
+                name: "a",
+                fullPath: ["a"],
+                pathWithoutLast: [],
+                pathLast: "a",
+                generics: [],
+                typeFilter: -1,
+            },
+            {
+                name: "b",
+                fullPath: ["b"],
+                pathWithoutLast: [],
+                pathLast: "b",
+                generics: [{
+                    name: "c",
+                    fullPath: ["c"],
+                    pathWithoutLast: [],
+                    pathLast: "c",
+                    generics: [],
+                    typeFilter: -1,
+                }],
+                bindings: [
+                    [
+                        "output",
+                        [],
+                    ]
+                ],
+                typeFilter: -1,
+            }
+        ],
+        foundElems: 2,
+        original: "a,b(c)",
+        returned: [],
+        userQuery: "a,b(c)",
+        error: null,
+    },
+];
diff --git a/tests/rustdoc-js-std/parser-weird-queries.js b/tests/rustdoc-js-std/parser-weird-queries.js
index 26b8c32d680..499b82a3469 100644
--- a/tests/rustdoc-js-std/parser-weird-queries.js
+++ b/tests/rustdoc-js-std/parser-weird-queries.js
@@ -37,15 +37,6 @@ const PARSED = [
         userQuery: "a   b",
         error: null,
     },
-    {
-        query: 'a,b(c)',
-        elems: [],
-        foundElems: 0,
-        original: "a,b(c)",
-        returned: [],
-        userQuery: "a,b(c)",
-        error: "Expected `,`, `:` or `->`, found `(`",
-    },
     {
         query: 'aaa,a',
         elems: [
diff --git a/tests/rustdoc-js/hof.js b/tests/rustdoc-js/hof.js
new file mode 100644
index 00000000000..5e6c9d83c7c
--- /dev/null
+++ b/tests/rustdoc-js/hof.js
@@ -0,0 +1,176 @@
+// exact-check
+
+const EXPECTED = [
+    // not a HOF query
+    {
+        'query': 'u32 -> !',
+        'others': [],
+    },
+
+    // ML-style higher-order function notation
+    {
+        'query': 'bool, (u32 -> !) -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_ptr"},
+        ],
+    },
+    {
+        'query': 'u8, (u32 -> !) -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_once"},
+        ],
+    },
+    {
+        'query': 'i8, (u32 -> !) -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_mut"},
+        ],
+    },
+    {
+        'query': 'char, (u32 -> !) -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_"},
+        ],
+    },
+    {
+        'query': '(first<u32> -> !) -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_ptr"},
+        ],
+    },
+    {
+        'query': '(second<u32> -> !) -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_once"},
+        ],
+    },
+    {
+        'query': '(third<u32> -> !) -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_mut"},
+        ],
+    },
+    {
+        'query': '(u32 -> !) -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_"},
+            {"path": "hof", "name": "fn_ptr"},
+            {"path": "hof", "name": "fn_mut"},
+            {"path": "hof", "name": "fn_once"},
+        ],
+    },
+    {
+        'query': '(str, str -> i8) -> ()',
+        'others': [
+            {"path": "hof", "name": "multiple"},
+        ],
+    },
+    {
+        'query': '(str ->) -> ()',
+        'others': [
+            {"path": "hof", "name": "multiple"},
+        ],
+    },
+    {
+        'query': '(-> i8) -> ()',
+        'others': [
+            {"path": "hof", "name": "multiple"},
+        ],
+    },
+    {
+        'query': '(str -> str) -> ()',
+        // params and return are not the same
+        'others': [],
+    },
+    {
+        'query': '(i8 ->) -> ()',
+        // params and return are not the same
+        'others': [],
+    },
+    {
+        'query': '(-> str) -> ()',
+        // params and return are not the same
+        'others': [],
+    },
+
+    // Rust-style higher-order function notation
+    {
+        'query': 'bool, fn(u32) -> ! -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_ptr"},
+        ],
+    },
+    {
+        'query': 'u8, fnonce(u32) -> ! -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_once"},
+        ],
+    },
+    {
+        'query': 'u8, fn(u32) -> ! -> ()',
+        // fnonce != fn
+        'others': [],
+    },
+    {
+        'query': 'i8, fnmut(u32) -> ! -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_mut"},
+        ],
+    },
+    {
+        'query': 'i8, fn(u32) -> ! -> ()',
+        // fnmut != fn
+        'others': [],
+    },
+    {
+        'query': 'char, fn(u32) -> ! -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_"},
+        ],
+    },
+    {
+        'query': 'char, fnmut(u32) -> ! -> ()',
+        // fn != fnmut
+        'others': [],
+    },
+    {
+        'query': 'fn(first<u32>) -> ! -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_ptr"},
+        ],
+    },
+    {
+        'query': 'fnonce(second<u32>) -> ! -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_once"},
+        ],
+    },
+    {
+        'query': 'fnmut(third<u32>) -> ! -> ()',
+        'others': [
+            {"path": "hof", "name": "fn_mut"},
+        ],
+    },
+    {
+        'query': 'fn(u32) -> ! -> ()',
+        'others': [
+            // fn matches primitive:fn and trait:Fn
+            {"path": "hof", "name": "fn_"},
+            {"path": "hof", "name": "fn_ptr"},
+        ],
+    },
+    {
+        'query': 'trait:fn(u32) -> ! -> ()',
+        'others': [
+            // fn matches primitive:fn and trait:Fn
+            {"path": "hof", "name": "fn_"},
+        ],
+    },
+    {
+        'query': 'primitive:fn(u32) -> ! -> ()',
+        'others': [
+            // fn matches primitive:fn and trait:Fn
+            {"path": "hof", "name": "fn_ptr"},
+        ],
+    },
+];
diff --git a/tests/rustdoc-js/hof.rs b/tests/rustdoc-js/hof.rs
new file mode 100644
index 00000000000..4d2c6e331ca
--- /dev/null
+++ b/tests/rustdoc-js/hof.rs
@@ -0,0 +1,12 @@
+#![feature(never_type)]
+
+pub struct First<T>(T);
+pub struct Second<T>(T);
+pub struct Third<T>(T);
+
+pub fn fn_ptr(_: fn (First<u32>) -> !, _: bool) {}
+pub fn fn_once(_: impl FnOnce (Second<u32>) -> !, _: u8) {}
+pub fn fn_mut(_: impl FnMut (Third<u32>) -> !, _: i8) {}
+pub fn fn_(_: impl Fn (u32) -> !, _: char) {}
+
+pub fn multiple(_: impl Fn(&'static str, &'static str) -> i8) {}
diff --git a/tests/ui-fulldeps/stable-mir/check_transform.rs b/tests/ui-fulldeps/stable-mir/check_transform.rs
new file mode 100644
index 00000000000..e7d852a27df
--- /dev/null
+++ b/tests/ui-fulldeps/stable-mir/check_transform.rs
@@ -0,0 +1,147 @@
+//@ run-pass
+//! Test a few methods to transform StableMIR.
+
+//@ ignore-stage1
+//@ ignore-cross-compile
+//@ ignore-remote
+//@ ignore-windows-gnu mingw has troubles with linking https://github.com/rust-lang/rust/pull/116837
+
+#![feature(rustc_private)]
+#![feature(assert_matches)]
+#![feature(control_flow_enum)]
+#![feature(ascii_char, ascii_char_variants)]
+
+extern crate rustc_hir;
+#[macro_use]
+extern crate rustc_smir;
+extern crate rustc_driver;
+extern crate rustc_interface;
+extern crate stable_mir;
+
+use rustc_smir::rustc_internal;
+use stable_mir::mir::alloc::GlobalAlloc;
+use stable_mir::mir::mono::Instance;
+use stable_mir::mir::{Body, Constant, Operand, Rvalue, StatementKind, TerminatorKind};
+use stable_mir::ty::{Const, ConstantKind};
+use stable_mir::{CrateDef, CrateItems, ItemKind};
+use std::convert::TryFrom;
+use std::io::Write;
+use std::ops::ControlFlow;
+
+const CRATE_NAME: &str = "input";
+
+/// This function uses the Stable MIR APIs to transform the MIR.
+fn test_transform() -> ControlFlow<()> {
+    // Find items in the local crate.
+    let items = stable_mir::all_local_items();
+
+    // Test fn_abi
+    let target_fn = *get_item(&items, (ItemKind::Fn, "dummy")).unwrap();
+    let instance = Instance::try_from(target_fn).unwrap();
+    let body = instance.body().unwrap();
+    check_msg(&body, "oops");
+
+    let new_msg = "new panic message";
+    let new_body = change_panic_msg(body, new_msg);
+    check_msg(&new_body, new_msg);
+
+    ControlFlow::Continue(())
+}
+
+/// Check that the body panic message matches the given message.
+fn check_msg(body: &Body, expected: &str) {
+    let msg = body
+        .blocks
+        .iter()
+        .find_map(|bb| match &bb.terminator.kind {
+            TerminatorKind::Call { args, .. } => {
+                assert_eq!(args.len(), 1, "Expected panic message, but found {args:?}");
+                let msg_const = match &args[0] {
+                    Operand::Constant(msg_const) => msg_const,
+                    Operand::Copy(place) | Operand::Move(place) => {
+                        assert!(place.projection.is_empty());
+                        bb.statements
+                            .iter()
+                            .find_map(|stmt| match &stmt.kind {
+                                StatementKind::Assign(
+                                    destination,
+                                    Rvalue::Use(Operand::Constant(msg_const)),
+                                ) if destination == place => Some(msg_const),
+                                _ => None,
+                            })
+                            .unwrap()
+                    }
+                };
+                let ConstantKind::Allocated(alloc) = msg_const.literal.kind() else {
+                    unreachable!()
+                };
+                assert_eq!(alloc.provenance.ptrs.len(), 1);
+
+                let alloc_prov_id = alloc.provenance.ptrs[0].1 .0;
+                let GlobalAlloc::Memory(val) = GlobalAlloc::from(alloc_prov_id) else {
+                    unreachable!()
+                };
+                let bytes = val.raw_bytes().unwrap();
+                Some(std::str::from_utf8(&bytes).unwrap().to_string())
+            }
+            _ => None,
+        })
+        .expect("Failed to find panic message");
+    assert_eq!(&msg, expected);
+}
+
+/// Modify body to use a different panic message.
+fn change_panic_msg(mut body: Body, new_msg: &str) -> Body {
+    for bb in &mut body.blocks {
+        match &mut bb.terminator.kind {
+            TerminatorKind::Call { args, .. } => {
+                let new_const = Const::from_str(new_msg);
+                args[0] = Operand::Constant(Constant {
+                    literal: new_const,
+                    span: bb.terminator.span,
+                    user_ty: None,
+                });
+            }
+            _ => {}
+        }
+    }
+    body
+}
+
+fn get_item<'a>(
+    items: &'a CrateItems,
+    item: (ItemKind, &str),
+) -> Option<&'a stable_mir::CrateItem> {
+    items.iter().find(|crate_item| (item.0 == crate_item.kind()) && crate_item.name() == item.1)
+}
+
+/// This test will generate and analyze a dummy crate using the stable mir.
+/// For that, it will first write the dummy crate into a file.
+/// Then it will create a `StableMir` using custom arguments and then
+/// it will run the compiler.
+fn main() {
+    let path = "transform_input.rs";
+    generate_input(&path).unwrap();
+    let args = vec![
+        "rustc".to_string(),
+        "--crate-type=lib".to_string(),
+        "--crate-name".to_string(),
+        CRATE_NAME.to_string(),
+        path.to_string(),
+    ];
+    run!(args, test_transform).unwrap();
+}
+
+fn generate_input(path: &str) -> std::io::Result<()> {
+    let mut file = std::fs::File::create(path)?;
+    write!(
+        file,
+        r#"
+        #![feature(panic_internals)]
+        pub fn dummy() {{
+            core::panicking::panic_str("oops");
+        }}
+        "#
+    )?;
+    Ok(())
+}
diff --git a/tests/ui/consts/const-eval/erroneous-const.stderr b/tests/ui/consts/const-eval/erroneous-const.stderr
deleted file mode 100644
index bd25e96c2cf..00000000000
--- a/tests/ui/consts/const-eval/erroneous-const.stderr
+++ /dev/null
@@ -1,15 +0,0 @@
-error[E0080]: evaluation of `PrintName::<i32>::VOID` failed
-  --> $DIR/erroneous-const.rs:6:22
-   |
-LL |     const VOID: () = [()][2];
-   |                      ^^^^^^^ index out of bounds: the length is 1 but the index is 2
-
-note: erroneous constant encountered
-  --> $DIR/erroneous-const.rs:13:13
-   |
-LL |             PrintName::<T>::VOID;
-   |             ^^^^^^^^^^^^^^^^^^^^
-
-error: aborting due to 1 previous error
-
-For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/const-eval/erroneous-const2.rs b/tests/ui/consts/const-eval/erroneous-const2.rs
deleted file mode 100644
index 61f2955f2d8..00000000000
--- a/tests/ui/consts/const-eval/erroneous-const2.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-//! Make sure we error on erroneous consts even if they are unused.
-#![allow(unconditional_panic)]
-
-struct PrintName<T>(T);
-impl<T> PrintName<T> {
-    const VOID: () = [()][2]; //~ERROR evaluation of `PrintName::<i32>::VOID` failed
-}
-
-pub static FOO: () = {
-    if false {
-        // This bad constant is only used in dead code in a static initializer... and yet we still
-        // must make sure that the build fails.
-        PrintName::<i32>::VOID; //~ constant
-    }
-};
-
-fn main() {
-    FOO
-}
diff --git a/tests/ui/consts/const-eval/erroneous-const2.stderr b/tests/ui/consts/const-eval/erroneous-const2.stderr
deleted file mode 100644
index 6a5839e3dfb..00000000000
--- a/tests/ui/consts/const-eval/erroneous-const2.stderr
+++ /dev/null
@@ -1,15 +0,0 @@
-error[E0080]: evaluation of `PrintName::<i32>::VOID` failed
-  --> $DIR/erroneous-const2.rs:6:22
-   |
-LL |     const VOID: () = [()][2];
-   |                      ^^^^^^^ index out of bounds: the length is 1 but the index is 2
-
-note: erroneous constant encountered
-  --> $DIR/erroneous-const2.rs:13:9
-   |
-LL |         PrintName::<i32>::VOID;
-   |         ^^^^^^^^^^^^^^^^^^^^^^
-
-error: aborting due to 1 previous error
-
-For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/const-eval/unused-broken-const-late.stderr b/tests/ui/consts/const-eval/unused-broken-const-late.stderr
deleted file mode 100644
index c2cf2f3813c..00000000000
--- a/tests/ui/consts/const-eval/unused-broken-const-late.stderr
+++ /dev/null
@@ -1,11 +0,0 @@
-error[E0080]: evaluation of `PrintName::<i32>::VOID` failed
-  --> $DIR/unused-broken-const-late.rs:8:22
-   |
-LL |     const VOID: () = panic!();
-   |                      ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/unused-broken-const-late.rs:8:22
-   |
-   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
-
-error: aborting due to 1 previous error
-
-For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/required-consts/collect-in-called-fn.noopt.stderr b/tests/ui/consts/required-consts/collect-in-called-fn.noopt.stderr
new file mode 100644
index 00000000000..c7ff1328917
--- /dev/null
+++ b/tests/ui/consts/required-consts/collect-in-called-fn.noopt.stderr
@@ -0,0 +1,17 @@
+error[E0080]: evaluation of `Fail::<i32>::C` failed
+  --> $DIR/collect-in-called-fn.rs:9:19
+   |
+LL |     const C: () = panic!();
+   |                   ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-called-fn.rs:9:19
+   |
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+note: the above error was encountered while instantiating `fn called::<i32>`
+  --> $DIR/collect-in-called-fn.rs:23:5
+   |
+LL |     called::<i32>();
+   |     ^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/required-consts/collect-in-called-fn.opt.stderr b/tests/ui/consts/required-consts/collect-in-called-fn.opt.stderr
new file mode 100644
index 00000000000..c7ff1328917
--- /dev/null
+++ b/tests/ui/consts/required-consts/collect-in-called-fn.opt.stderr
@@ -0,0 +1,17 @@
+error[E0080]: evaluation of `Fail::<i32>::C` failed
+  --> $DIR/collect-in-called-fn.rs:9:19
+   |
+LL |     const C: () = panic!();
+   |                   ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-called-fn.rs:9:19
+   |
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+note: the above error was encountered while instantiating `fn called::<i32>`
+  --> $DIR/collect-in-called-fn.rs:23:5
+   |
+LL |     called::<i32>();
+   |     ^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/const-eval/unused-broken-const-late.rs b/tests/ui/consts/required-consts/collect-in-called-fn.rs
similarity index 55%
rename from tests/ui/consts/const-eval/unused-broken-const-late.rs
rename to tests/ui/consts/required-consts/collect-in-called-fn.rs
index c4916061f9e..55133a10cd9 100644
--- a/tests/ui/consts/const-eval/unused-broken-const-late.rs
+++ b/tests/ui/consts/required-consts/collect-in-called-fn.rs
@@ -1,20 +1,24 @@
+//@revisions: noopt opt
 //@ build-fail
-//@ compile-flags: -O
+//@[opt] compile-flags: -O
 //! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
 //! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
 
-struct PrintName<T>(T);
-impl<T> PrintName<T> {
-    const VOID: () = panic!(); //~ERROR evaluation of `PrintName::<i32>::VOID` failed
+struct Fail<T>(T);
+impl<T> Fail<T> {
+    const C: () = panic!(); //~ERROR evaluation of `Fail::<i32>::C` failed
 }
 
-fn no_codegen<T>() {
+#[inline(never)]
+fn called<T>() {
     // Any function that is called is guaranteed to have all consts that syntactically
     // appear in its body evaluated, even if they only appear in dead code.
+    // This relies on mono-item collection checking `required_consts` in collected functions.
     if false {
-        let _ = PrintName::<T>::VOID;
+        let _ = Fail::<T>::C;
     }
 }
+
 pub fn main() {
-    no_codegen::<i32>();
+    called::<i32>();
 }
diff --git a/tests/ui/consts/required-consts/collect-in-dead-drop.noopt.stderr b/tests/ui/consts/required-consts/collect-in-dead-drop.noopt.stderr
new file mode 100644
index 00000000000..b7010e78763
--- /dev/null
+++ b/tests/ui/consts/required-consts/collect-in-dead-drop.noopt.stderr
@@ -0,0 +1,14 @@
+error[E0080]: evaluation of `Fail::<i32>::C` failed
+  --> $DIR/collect-in-dead-drop.rs:12:19
+   |
+LL |     const C: () = panic!();
+   |                   ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-drop.rs:12:19
+   |
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+note: the above error was encountered while instantiating `fn <Fail<i32> as std::ops::Drop>::drop`
+  --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/required-consts/collect-in-dead-drop.rs b/tests/ui/consts/required-consts/collect-in-dead-drop.rs
new file mode 100644
index 00000000000..c9ffcec6903
--- /dev/null
+++ b/tests/ui/consts/required-consts/collect-in-dead-drop.rs
@@ -0,0 +1,33 @@
+//@revisions: noopt opt
+//@[noopt] build-fail
+//@[opt] compile-flags: -O
+//FIXME: `opt` revision currently does not stop with an error due to
+//<https://github.com/rust-lang/rust/issues/107503>.
+//@[opt] build-pass
+//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
+//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
+
+struct Fail<T>(T);
+impl<T> Fail<T> {
+    const C: () = panic!(); //[noopt]~ERROR evaluation of `Fail::<i32>::C` failed
+}
+
+// This function is not actually called, but is mentioned implicitly as destructor in dead code in a
+// function that is called. Make sure we still find this error.
+impl<T> Drop for Fail<T> {
+    fn drop(&mut self) {
+        let _ = Fail::<T>::C;
+    }
+}
+
+#[inline(never)]
+fn called<T>(x: T) {
+    if false {
+        let v = Fail(x);
+        // Now it gest dropped implicitly, at the end of this scope.
+    }
+}
+
+pub fn main() {
+    called::<i32>(0);
+}
diff --git a/tests/ui/consts/required-consts/collect-in-dead-fn.noopt.stderr b/tests/ui/consts/required-consts/collect-in-dead-fn.noopt.stderr
new file mode 100644
index 00000000000..2162c35c837
--- /dev/null
+++ b/tests/ui/consts/required-consts/collect-in-dead-fn.noopt.stderr
@@ -0,0 +1,17 @@
+error[E0080]: evaluation of `Fail::<i32>::C` failed
+  --> $DIR/collect-in-dead-fn.rs:12:19
+   |
+LL |     const C: () = panic!();
+   |                   ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-fn.rs:12:19
+   |
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+note: the above error was encountered while instantiating `fn not_called::<i32>`
+  --> $DIR/collect-in-dead-fn.rs:29:9
+   |
+LL |         not_called::<T>();
+   |         ^^^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/required-consts/collect-in-dead-fn.rs b/tests/ui/consts/required-consts/collect-in-dead-fn.rs
new file mode 100644
index 00000000000..9e6b1519153
--- /dev/null
+++ b/tests/ui/consts/required-consts/collect-in-dead-fn.rs
@@ -0,0 +1,35 @@
+//@revisions: noopt opt
+//@[noopt] build-fail
+//@[opt] compile-flags: -O
+//FIXME: `opt` revision currently does not stop with an error due to
+//<https://github.com/rust-lang/rust/issues/107503>.
+//@[opt] build-pass
+//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
+//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
+
+struct Fail<T>(T);
+impl<T> Fail<T> {
+    const C: () = panic!(); //[noopt]~ERROR evaluation of `Fail::<i32>::C` failed
+}
+
+// This function is not actually called, but it is mentioned in dead code in a function that is
+// called. Make sure we still find this error.
+// This relies on mono-item collection checking `required_consts` in functions that syntactically
+// are called in collected functions (even inside dead code).
+#[inline(never)]
+fn not_called<T>() {
+    if false {
+        let _ = Fail::<T>::C;
+    }
+}
+
+#[inline(never)]
+fn called<T>() {
+    if false {
+        not_called::<T>();
+    }
+}
+
+pub fn main() {
+    called::<i32>();
+}
diff --git a/tests/ui/consts/required-consts/collect-in-dead-forget.rs b/tests/ui/consts/required-consts/collect-in-dead-forget.rs
new file mode 100644
index 00000000000..720b7a499f7
--- /dev/null
+++ b/tests/ui/consts/required-consts/collect-in-dead-forget.rs
@@ -0,0 +1,32 @@
+//@revisions: noopt opt
+//@build-pass
+//@[opt] compile-flags: -O
+//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
+//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
+
+struct Fail<T>(T);
+impl<T> Fail<T> {
+    const C: () = panic!();
+}
+
+// This function is not actually called, but is mentioned implicitly as destructor in dead code in a
+// function that is called. Make sure we still find this error.
+impl<T> Drop for Fail<T> {
+    fn drop(&mut self) {
+        let _ = Fail::<T>::C;
+    }
+}
+
+#[inline(never)]
+fn called<T>(x: T) {
+    if false {
+        let v = Fail(x);
+        std::mem::forget(v);
+        // Now the destructor never gets "mentioned" so this build should *not* fail.
+        // IOW, this demonstrates that we are using a post-drop-elab notion of "mentioned".
+    }
+}
+
+pub fn main() {
+    called::<i32>(0);
+}
diff --git a/tests/ui/consts/required-consts/collect-in-dead-move.noopt.stderr b/tests/ui/consts/required-consts/collect-in-dead-move.noopt.stderr
new file mode 100644
index 00000000000..8c853127e04
--- /dev/null
+++ b/tests/ui/consts/required-consts/collect-in-dead-move.noopt.stderr
@@ -0,0 +1,14 @@
+error[E0080]: evaluation of `Fail::<i32>::C` failed
+  --> $DIR/collect-in-dead-move.rs:12:19
+   |
+LL |     const C: () = panic!();
+   |                   ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-move.rs:12:19
+   |
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+note: the above error was encountered while instantiating `fn <Fail<i32> as std::ops::Drop>::drop`
+  --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/required-consts/collect-in-dead-move.rs b/tests/ui/consts/required-consts/collect-in-dead-move.rs
new file mode 100644
index 00000000000..f3a6ba8a657
--- /dev/null
+++ b/tests/ui/consts/required-consts/collect-in-dead-move.rs
@@ -0,0 +1,33 @@
+//@revisions: noopt opt
+//@[noopt] build-fail
+//@[opt] compile-flags: -O
+//FIXME: `opt` revision currently does not stop with an error due to
+//<https://github.com/rust-lang/rust/issues/107503>.
+//@[opt] build-pass
+//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
+//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
+
+struct Fail<T>(T);
+impl<T> Fail<T> {
+    const C: () = panic!(); //[noopt]~ERROR evaluation of `Fail::<i32>::C` failed
+}
+
+// This function is not actually called, but is mentioned implicitly as destructor in dead code in a
+// function that is called. Make sure we still find this error.
+impl<T> Drop for Fail<T> {
+    fn drop(&mut self) {
+        let _ = Fail::<T>::C;
+    }
+}
+
+#[inline(never)]
+fn called<T>(x: T) {
+    if false {
+        let v = Fail(x);
+        drop(v); // move `v` away (and it then gets dropped there so build still fails)
+    }
+}
+
+pub fn main() {
+    called::<i32>(0);
+}
diff --git a/tests/ui/consts/required-consts/collect-in-dead-vtable.noopt.stderr b/tests/ui/consts/required-consts/collect-in-dead-vtable.noopt.stderr
new file mode 100644
index 00000000000..6fd82777bd3
--- /dev/null
+++ b/tests/ui/consts/required-consts/collect-in-dead-vtable.noopt.stderr
@@ -0,0 +1,17 @@
+error[E0080]: evaluation of `Fail::<i32>::C` failed
+  --> $DIR/collect-in-dead-vtable.rs:12:19
+   |
+LL |     const C: () = panic!();
+   |                   ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-vtable.rs:12:19
+   |
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+note: the above error was encountered while instantiating `fn <std::vec::Vec<i32> as MyTrait>::not_called`
+  --> $DIR/collect-in-dead-vtable.rs:35:40
+   |
+LL |         let gen_vtable: &dyn MyTrait = &v; // vtable "appears" here
+   |                                        ^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/required-consts/collect-in-dead-vtable.rs b/tests/ui/consts/required-consts/collect-in-dead-vtable.rs
new file mode 100644
index 00000000000..f21a1cc1fc2
--- /dev/null
+++ b/tests/ui/consts/required-consts/collect-in-dead-vtable.rs
@@ -0,0 +1,41 @@
+//@revisions: noopt opt
+//@[noopt] build-fail
+//@[opt] compile-flags: -O
+//FIXME: `opt` revision currently does not stop with an error due to
+//<https://github.com/rust-lang/rust/issues/107503>.
+//@[opt] build-pass
+//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
+//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
+
+struct Fail<T>(T);
+impl<T> Fail<T> {
+    const C: () = panic!(); //[noopt]~ERROR evaluation of `Fail::<i32>::C` failed
+}
+
+trait MyTrait {
+    fn not_called(&self);
+}
+
+// This function is not actually called, but it is mentioned in a vtable in a function that is
+// called. Make sure we still find this error.
+// This relies on mono-item collection checking `required_consts` in functions that are referenced
+// in vtables that syntactically appear in collected functions (even inside dead code).
+impl<T> MyTrait for Vec<T> {
+    fn not_called(&self) {
+        if false {
+            let _ = Fail::<T>::C;
+        }
+    }
+}
+
+#[inline(never)]
+fn called<T>() {
+    if false {
+        let v: Vec<T> = Vec::new();
+        let gen_vtable: &dyn MyTrait = &v; // vtable "appears" here
+    }
+}
+
+pub fn main() {
+    called::<i32>();
+}
diff --git a/tests/ui/consts/required-consts/interpret-in-const-called-fn.noopt.stderr b/tests/ui/consts/required-consts/interpret-in-const-called-fn.noopt.stderr
new file mode 100644
index 00000000000..75304591b9f
--- /dev/null
+++ b/tests/ui/consts/required-consts/interpret-in-const-called-fn.noopt.stderr
@@ -0,0 +1,17 @@
+error[E0080]: evaluation of `Fail::<i32>::C` failed
+  --> $DIR/interpret-in-const-called-fn.rs:7:19
+   |
+LL |     const C: () = panic!();
+   |                   ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/interpret-in-const-called-fn.rs:7:19
+   |
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+note: erroneous constant encountered
+  --> $DIR/interpret-in-const-called-fn.rs:16:9
+   |
+LL |         Fail::<T>::C;
+   |         ^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/required-consts/interpret-in-const-called-fn.opt.stderr b/tests/ui/consts/required-consts/interpret-in-const-called-fn.opt.stderr
new file mode 100644
index 00000000000..75304591b9f
--- /dev/null
+++ b/tests/ui/consts/required-consts/interpret-in-const-called-fn.opt.stderr
@@ -0,0 +1,17 @@
+error[E0080]: evaluation of `Fail::<i32>::C` failed
+  --> $DIR/interpret-in-const-called-fn.rs:7:19
+   |
+LL |     const C: () = panic!();
+   |                   ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/interpret-in-const-called-fn.rs:7:19
+   |
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+note: erroneous constant encountered
+  --> $DIR/interpret-in-const-called-fn.rs:16:9
+   |
+LL |         Fail::<T>::C;
+   |         ^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/const-eval/erroneous-const.rs b/tests/ui/consts/required-consts/interpret-in-const-called-fn.rs
similarity index 52%
rename from tests/ui/consts/const-eval/erroneous-const.rs
rename to tests/ui/consts/required-consts/interpret-in-const-called-fn.rs
index 74d44c5259a..c409fae0bb9 100644
--- a/tests/ui/consts/const-eval/erroneous-const.rs
+++ b/tests/ui/consts/required-consts/interpret-in-const-called-fn.rs
@@ -1,16 +1,19 @@
+//@revisions: noopt opt
+//@[opt] compile-flags: -O
 //! Make sure we error on erroneous consts even if they are unused.
-#![allow(unconditional_panic)]
 
-struct PrintName<T>(T);
-impl<T> PrintName<T> {
-    const VOID: () = [()][2]; //~ERROR evaluation of `PrintName::<i32>::VOID` failed
+struct Fail<T>(T);
+impl<T> Fail<T> {
+    const C: () = panic!(); //~ERROR evaluation of `Fail::<i32>::C` failed
 }
 
+#[inline(never)]
 const fn no_codegen<T>() {
     if false {
         // This bad constant is only used in dead code in a no-codegen function... and yet we still
         // must make sure that the build fails.
-            PrintName::<T>::VOID; //~ constant
+        // This relies on const-eval evaluating all `required_consts` of `const fn`.
+        Fail::<T>::C; //~ constant
     }
 }
 
diff --git a/tests/ui/consts/required-consts/interpret-in-promoted.noopt.stderr b/tests/ui/consts/required-consts/interpret-in-promoted.noopt.stderr
new file mode 100644
index 00000000000..491131daf8d
--- /dev/null
+++ b/tests/ui/consts/required-consts/interpret-in-promoted.noopt.stderr
@@ -0,0 +1,27 @@
+error[E0080]: evaluation of constant value failed
+  --> $SRC_DIR/core/src/hint.rs:LL:COL
+   |
+   = note: entering unreachable code
+   |
+note: inside `unreachable_unchecked`
+  --> $SRC_DIR/core/src/hint.rs:LL:COL
+note: inside `ub`
+  --> $DIR/interpret-in-promoted.rs:6:5
+   |
+LL |     std::hint::unreachable_unchecked();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+note: inside `FOO`
+  --> $DIR/interpret-in-promoted.rs:12:28
+   |
+LL |     let _x: &'static () = &ub();
+   |                            ^^^^
+
+note: erroneous constant encountered
+  --> $DIR/interpret-in-promoted.rs:12:27
+   |
+LL |     let _x: &'static () = &ub();
+   |                           ^^^^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/required-consts/interpret-in-promoted.opt.stderr b/tests/ui/consts/required-consts/interpret-in-promoted.opt.stderr
new file mode 100644
index 00000000000..491131daf8d
--- /dev/null
+++ b/tests/ui/consts/required-consts/interpret-in-promoted.opt.stderr
@@ -0,0 +1,27 @@
+error[E0080]: evaluation of constant value failed
+  --> $SRC_DIR/core/src/hint.rs:LL:COL
+   |
+   = note: entering unreachable code
+   |
+note: inside `unreachable_unchecked`
+  --> $SRC_DIR/core/src/hint.rs:LL:COL
+note: inside `ub`
+  --> $DIR/interpret-in-promoted.rs:6:5
+   |
+LL |     std::hint::unreachable_unchecked();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+note: inside `FOO`
+  --> $DIR/interpret-in-promoted.rs:12:28
+   |
+LL |     let _x: &'static () = &ub();
+   |                            ^^^^
+
+note: erroneous constant encountered
+  --> $DIR/interpret-in-promoted.rs:12:27
+   |
+LL |     let _x: &'static () = &ub();
+   |                           ^^^^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/required-consts/interpret-in-promoted.rs b/tests/ui/consts/required-consts/interpret-in-promoted.rs
new file mode 100644
index 00000000000..9c2cf4e70d3
--- /dev/null
+++ b/tests/ui/consts/required-consts/interpret-in-promoted.rs
@@ -0,0 +1,15 @@
+//@revisions: noopt opt
+//@[opt] compile-flags: -O
+//! Make sure we error on erroneous consts even if they are unused.
+
+const unsafe fn ub() {
+    std::hint::unreachable_unchecked();
+}
+
+pub const FOO: () = unsafe {
+    // Make sure that this gets promoted and then fails to evaluate, and we deal with that
+    // correctly.
+    let _x: &'static () = &ub(); //~ erroneous constant
+};
+
+fn main() {}
diff --git a/tests/ui/consts/required-consts/interpret-in-static.noopt.stderr b/tests/ui/consts/required-consts/interpret-in-static.noopt.stderr
new file mode 100644
index 00000000000..159c9449fc0
--- /dev/null
+++ b/tests/ui/consts/required-consts/interpret-in-static.noopt.stderr
@@ -0,0 +1,17 @@
+error[E0080]: evaluation of `Fail::<i32>::C` failed
+  --> $DIR/interpret-in-static.rs:7:19
+   |
+LL |     const C: () = panic!();
+   |                   ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/interpret-in-static.rs:7:19
+   |
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+note: erroneous constant encountered
+  --> $DIR/interpret-in-static.rs:15:9
+   |
+LL |         Fail::<i32>::C;
+   |         ^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/required-consts/interpret-in-static.opt.stderr b/tests/ui/consts/required-consts/interpret-in-static.opt.stderr
new file mode 100644
index 00000000000..159c9449fc0
--- /dev/null
+++ b/tests/ui/consts/required-consts/interpret-in-static.opt.stderr
@@ -0,0 +1,17 @@
+error[E0080]: evaluation of `Fail::<i32>::C` failed
+  --> $DIR/interpret-in-static.rs:7:19
+   |
+LL |     const C: () = panic!();
+   |                   ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/interpret-in-static.rs:7:19
+   |
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+note: erroneous constant encountered
+  --> $DIR/interpret-in-static.rs:15:9
+   |
+LL |         Fail::<i32>::C;
+   |         ^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/required-consts/interpret-in-static.rs b/tests/ui/consts/required-consts/interpret-in-static.rs
new file mode 100644
index 00000000000..559e281b2b0
--- /dev/null
+++ b/tests/ui/consts/required-consts/interpret-in-static.rs
@@ -0,0 +1,21 @@
+//@revisions: noopt opt
+//@[opt] compile-flags: -O
+//! Make sure we error on erroneous consts even if they are unused.
+
+struct Fail<T>(T);
+impl<T> Fail<T> {
+    const C: () = panic!(); //~ERROR evaluation of `Fail::<i32>::C` failed
+}
+
+pub static FOO: () = {
+    if false {
+        // This bad constant is only used in dead code in a static initializer... and yet we still
+        // must make sure that the build fails.
+        // This relies on const-eval evaluating all `required_consts` of the `static` MIR body.
+        Fail::<i32>::C; //~ constant
+    }
+};
+
+fn main() {
+    FOO
+}