about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_expand/src/proc_macro.rs10
-rw-r--r--compiler/rustc_feature/src/active.rs2
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs4
-rw-r--r--compiler/rustc_hir_typeck/src/upvar.rs5
-rw-r--r--compiler/rustc_interface/src/interface.rs2
-rw-r--r--compiler/rustc_macros/src/diagnostics/fluent.rs23
-rw-r--r--compiler/rustc_passes/messages.ftl3
-rw-r--r--compiler/rustc_passes/src/check_attr.rs11
-rw-r--r--compiler/rustc_passes/src/errors.rs4
-rw-r--r--compiler/rustc_resolve/src/rustdoc.rs8
-rw-r--r--compiler/rustc_span/src/lib.rs23
-rw-r--r--compiler/rustc_span/src/profiling.rs16
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--library/core/src/primitive_docs.rs75
-rw-r--r--library/std/src/primitive_docs.rs75
-rw-r--r--src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version2
-rw-r--r--src/doc/rustdoc/src/unstable-features.md6
-rw-r--r--src/librustdoc/clean/types.rs44
-rw-r--r--src/librustdoc/json/conversions.rs4
-rw-r--r--src/librustdoc/json/mod.rs2
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs390
-rw-r--r--tests/rustdoc-gui/go-to-collapsed-elem.goml21
-rw-r--r--tests/rustdoc-json/impls/local_for_local_primitive.rs4
-rw-r--r--tests/rustdoc-json/primitives/local_primitive.rs2
-rw-r--r--tests/rustdoc-json/primitives/primitive_impls.rs2
-rw-r--r--tests/rustdoc-json/primitives/primitive_overloading.rs4
-rw-r--r--tests/rustdoc-json/primitives/use_primitive.rs4
-rw-r--r--tests/rustdoc-ui/coverage/exotic.rs3
-rw-r--r--tests/rustdoc-ui/intra-doc/ambiguity.rs2
-rw-r--r--tests/rustdoc-ui/intra-doc/ambiguity.stderr54
-rw-r--r--tests/rustdoc-ui/intra-doc/errors.rs4
-rw-r--r--tests/rustdoc-ui/intra-doc/errors.stderr4
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-10.rs22
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-2.rs17
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-2.stderr37
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.rs16
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.stderr37
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-4.rs21
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-4.stderr22
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-5.rs8
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-5.stderr22
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-6.rs8
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-6.stderr22
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-7.rs12
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-7.stderr22
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-8.rs12
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-8.stderr22
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items-9.rs11
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items.rs35
-rw-r--r--tests/rustdoc-ui/intra-doc/issue-108653-associated-items.stderr67
-rw-r--r--tests/rustdoc-ui/intra-doc/non-path-primitives.stderr8
-rw-r--r--tests/rustdoc-ui/intra-doc/prim-conflict.rs10
-rw-r--r--tests/rustdoc-ui/intra-doc/prim-conflict.stderr12
-rw-r--r--tests/rustdoc/auto-impl-primitive.rs4
-rw-r--r--tests/rustdoc/auxiliary/issue-15318.rs3
-rw-r--r--tests/rustdoc/auxiliary/primitive-doc.rs3
-rw-r--r--tests/rustdoc/check-source-code-urls-to-def.rs4
-rw-r--r--tests/rustdoc/intra-doc/auxiliary/my-core.rs2
-rw-r--r--tests/rustdoc/intra-doc/no-doc-primitive.rs2
-rw-r--r--tests/rustdoc/intra-doc/prim-methods-local.rs2
-rw-r--r--tests/rustdoc/intra-doc/prim-self.rs2
-rw-r--r--tests/rustdoc/issue-15318-3.rs4
-rw-r--r--tests/rustdoc/issue-23511.rs2
-rw-r--r--tests/rustdoc/notable-trait/doc-notable_trait-mut_t_is_not_an_iterator.rs4
-rw-r--r--tests/rustdoc/notable-trait/doc-notable_trait-mut_t_is_not_ref_t.rs4
-rw-r--r--tests/rustdoc/primitive-reference.rs4
-rw-r--r--tests/rustdoc/primitive-slice-auto-trait.rs4
-rw-r--r--tests/rustdoc/primitive-tuple-auto-trait.rs4
-rw-r--r--tests/rustdoc/primitive-unit-auto-trait.rs4
-rw-r--r--tests/rustdoc/primitive/primitive-generic-impl.rs4
-rw-r--r--tests/rustdoc/primitive/primitive.rs6
-rw-r--r--tests/rustdoc/sidebar-all-page.rs4
-rw-r--r--tests/rustdoc/tab_title.rs3
-rw-r--r--tests/rustdoc/titles.rs4
-rw-r--r--tests/ui-fulldeps/fluent-messages/test.rs2
-rw-r--r--tests/ui-fulldeps/fluent-messages/test.stderr14
-rw-r--r--tests/ui/closures/2229_closure_analysis/array_subslice.rs13
-rw-r--r--tests/ui/closures/2229_closure_analysis/array_subslice.stderr26
-rw-r--r--tests/ui/rustdoc/doc-primitive.rs8
-rw-r--r--tests/ui/rustdoc/doc-primitive.stderr16
-rw-r--r--tests/ui/rustdoc/feature-gate-doc_primitive.rs6
-rw-r--r--tests/ui/rustdoc/feature-gate-doc_primitive.stderr15
82 files changed, 1023 insertions, 407 deletions
diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs
index ddba1441719..26bc216f678 100644
--- a/compiler/rustc_expand/src/proc_macro.rs
+++ b/compiler/rustc_expand/src/proc_macro.rs
@@ -54,7 +54,7 @@ impl base::BangProcMacro for BangProcMacro {
     ) -> Result<TokenStream, ErrorGuaranteed> {
         let _timer =
             ecx.sess.prof.generic_activity_with_arg_recorder("expand_proc_macro", |recorder| {
-                recorder.record_arg_with_span(ecx.expansion_descr(), span);
+                recorder.record_arg_with_span(ecx.sess.source_map(), ecx.expansion_descr(), span);
             });
 
         let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
@@ -85,7 +85,7 @@ impl base::AttrProcMacro for AttrProcMacro {
     ) -> Result<TokenStream, ErrorGuaranteed> {
         let _timer =
             ecx.sess.prof.generic_activity_with_arg_recorder("expand_proc_macro", |recorder| {
-                recorder.record_arg_with_span(ecx.expansion_descr(), span);
+                recorder.record_arg_with_span(ecx.sess.source_map(), ecx.expansion_descr(), span);
             });
 
         let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
@@ -134,7 +134,11 @@ impl MultiItemModifier for DeriveProcMacro {
         let stream = {
             let _timer =
                 ecx.sess.prof.generic_activity_with_arg_recorder("expand_proc_macro", |recorder| {
-                    recorder.record_arg_with_span(ecx.expansion_descr(), span);
+                    recorder.record_arg_with_span(
+                        ecx.sess.source_map(),
+                        ecx.expansion_descr(),
+                        span,
+                    );
                 });
             let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
             let strategy = exec_strategy(ecx);
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index b7d280b8751..b62da63644d 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -225,7 +225,7 @@ declare_features! (
     (active, rustc_allow_const_fn_unstable, "1.49.0", Some(69399), None),
     /// Allows using compiler's own crates.
     (active, rustc_private, "1.0.0", Some(27812), None),
-    /// Allows using internal rustdoc features like `doc(primitive)` or `doc(keyword)`.
+    /// Allows using internal rustdoc features like `doc(keyword)`.
     (active, rustdoc_internals, "1.58.0", Some(90418), None),
     /// Allows using the `rustdoc::missing_doc_code_examples` lint
     (active, rustdoc_missing_doc_code_examples, "1.31.0", Some(101730), None),
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 493a9cd89e3..c77292fdd16 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -778,6 +778,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         definition of a trait, it's currently in experimental form and should be changed before \
         being exposed outside of the std"
     ),
+    rustc_attr!(
+        rustc_doc_primitive, Normal, template!(NameValueStr: "primitive name"), ErrorFollowing,
+        r#"`rustc_doc_primitive` is a rustc internal attribute"#,
+    ),
 
     // ==========================================================================
     // Internal attributes, Testing:
diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs
index 504da7dc090..41a6ad80b65 100644
--- a/compiler/rustc_hir_typeck/src/upvar.rs
+++ b/compiler/rustc_hir_typeck/src/upvar.rs
@@ -1893,14 +1893,13 @@ fn restrict_capture_precision(
 
     for (i, proj) in place.projections.iter().enumerate() {
         match proj.kind {
-            ProjectionKind::Index => {
-                // Arrays are completely captured, so we drop Index projections
+            ProjectionKind::Index | ProjectionKind::Subslice => {
+                // Arrays are completely captured, so we drop Index and Subslice projections
                 truncate_place_to_len_and_update_capture_kind(&mut place, &mut curr_mode, i);
                 return (place, curr_mode);
             }
             ProjectionKind::Deref => {}
             ProjectionKind::Field(..) => {} // ignore
-            ProjectionKind::Subslice => {}  // We never capture this
         }
     }
 
diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs
index 5e38ca034ac..be7fa9378ca 100644
--- a/compiler/rustc_interface/src/interface.rs
+++ b/compiler/rustc_interface/src/interface.rs
@@ -292,7 +292,7 @@ pub fn run_compiler<R: Send>(config: Config, f: impl FnOnce(&Compiler) -> R + Se
                 override_queries: config.override_queries,
             };
 
-            rustc_span::with_source_map(compiler.sess.parse_sess.clone_source_map(), move || {
+            rustc_span::set_source_map(compiler.sess.parse_sess.clone_source_map(), move || {
                 let r = {
                     let _sess_abort_error = OnDrop(|| {
                         compiler.sess.finish_diagnostics(registry);
diff --git a/compiler/rustc_macros/src/diagnostics/fluent.rs b/compiler/rustc_macros/src/diagnostics/fluent.rs
index 3b2f5cfdc73..9f96a041487 100644
--- a/compiler/rustc_macros/src/diagnostics/fluent.rs
+++ b/compiler/rustc_macros/src/diagnostics/fluent.rs
@@ -15,8 +15,7 @@ use proc_macro2::TokenStream;
 use quote::quote;
 use std::{
     collections::{HashMap, HashSet},
-    fs::File,
-    io::Read,
+    fs::read_to_string,
     path::{Path, PathBuf},
 };
 use syn::{parse_macro_input, Ident, LitStr};
@@ -95,22 +94,18 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
 
     // As this macro also outputs an `include_str!` for this file, the macro will always be
     // re-executed when the file changes.
-    let mut resource_file = match File::open(absolute_ftl_path) {
-        Ok(resource_file) => resource_file,
+    let resource_contents = match read_to_string(absolute_ftl_path) {
+        Ok(resource_contents) => resource_contents,
         Err(e) => {
-            Diagnostic::spanned(resource_span, Level::Error, "could not open Fluent resource")
-                .note(e.to_string())
-                .emit();
+            Diagnostic::spanned(
+                resource_span,
+                Level::Error,
+                format!("could not open Fluent resource: {}", e.to_string()),
+            )
+            .emit();
             return failed(&crate_name);
         }
     };
-    let mut resource_contents = String::new();
-    if let Err(e) = resource_file.read_to_string(&mut resource_contents) {
-        Diagnostic::spanned(resource_span, Level::Error, "could not read Fluent resource")
-            .note(e.to_string())
-            .emit();
-        return failed(&crate_name);
-    }
     let mut bad = false;
     for esc in ["\\n", "\\\"", "\\'"] {
         for _ in resource_contents.matches(esc) {
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index d063b51c8b8..b354dca7cc4 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -148,9 +148,6 @@ passes_doc_test_unknown =
 passes_doc_test_takes_list =
     `#[doc(test(...)]` takes a list of attributes
 
-passes_doc_primitive =
-    `doc(primitive)` should never have been stable
-
 passes_doc_cfg_hide_takes_list =
     `#[doc(cfg_hide(...)]` takes a list of attributes
 
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 1c459edabb8..80a93da2b45 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -1109,17 +1109,6 @@ impl CheckAttrVisitor<'_> {
                             }
                         }
 
-                        sym::primitive => {
-                            if !self.tcx.features().rustdoc_internals {
-                                self.tcx.emit_spanned_lint(
-                                    INVALID_DOC_ATTRIBUTES,
-                                    hir_id,
-                                    i_meta.span,
-                                    errors::DocPrimitive,
-                                );
-                            }
-                        }
-
                         _ => {
                             let path = rustc_ast_pretty::pprust::path_to_string(&i_meta.path);
                             if i_meta.has_name(sym::spotlight) {
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 1b0cd5d91ab..139ba8c9677 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -289,10 +289,6 @@ pub struct DocTestTakesList;
 pub struct DocCfgHideTakesList;
 
 #[derive(LintDiagnostic)]
-#[diag(passes_doc_primitive)]
-pub struct DocPrimitive;
-
-#[derive(LintDiagnostic)]
 #[diag(passes_doc_test_unknown_any)]
 pub struct DocTestUnknownAny {
     pub path: String,
diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs
index 44a27bbc175..9eae99be2e9 100644
--- a/compiler/rustc_resolve/src/rustdoc.rs
+++ b/compiler/rustc_resolve/src/rustdoc.rs
@@ -339,12 +339,14 @@ pub fn inner_docs(attrs: &[ast::Attribute]) -> bool {
     attrs.iter().find(|a| a.doc_str().is_some()).map_or(true, |a| a.style == ast::AttrStyle::Inner)
 }
 
-/// Has `#[doc(primitive)]` or `#[doc(keyword)]`.
+/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]`.
 pub fn has_primitive_or_keyword_docs(attrs: &[ast::Attribute]) -> bool {
     for attr in attrs {
-        if attr.has_name(sym::doc) && let Some(items) = attr.meta_item_list() {
+        if attr.has_name(sym::rustc_doc_primitive) {
+            return true;
+        } else if attr.has_name(sym::doc) && let Some(items) = attr.meta_item_list() {
             for item in items {
-                if item.has_name(sym::primitive) || item.has_name(sym::keyword) {
+                if item.has_name(sym::keyword) {
                     return true;
                 }
             }
diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs
index 02cffc762be..e14760aa018 100644
--- a/compiler/rustc_span/src/lib.rs
+++ b/compiler/rustc_span/src/lib.rs
@@ -87,6 +87,14 @@ pub struct SessionGlobals {
     symbol_interner: symbol::Interner,
     span_interner: Lock<span_encoding::SpanInterner>,
     hygiene_data: Lock<hygiene::HygieneData>,
+
+    /// A reference to the source map in the `Session`. It's an `Option`
+    /// because it can't be initialized until `Session` is created, which
+    /// happens after `SessionGlobals`. `set_source_map` does the
+    /// initialization.
+    ///
+    /// This field should only be used in places where the `Session` is truly
+    /// not available, such as `<Span as Debug>::fmt`.
     source_map: Lock<Option<Lrc<SourceMap>>>,
 }
 
@@ -1013,16 +1021,9 @@ impl<D: Decoder> Decodable<D> for Span {
     }
 }
 
-/// Calls the provided closure, using the provided `SourceMap` to format
-/// any spans that are debug-printed during the closure's execution.
-///
-/// Normally, the global `TyCtxt` is used to retrieve the `SourceMap`
-/// (see `rustc_interface::callbacks::span_debug1`). However, some parts
-/// of the compiler (e.g. `rustc_parse`) may debug-print `Span`s before
-/// a `TyCtxt` is available. In this case, we fall back to
-/// the `SourceMap` provided to this function. If that is not available,
-/// we fall back to printing the raw `Span` field values.
-pub fn with_source_map<T, F: FnOnce() -> T>(source_map: Lrc<SourceMap>, f: F) -> T {
+/// Insert `source_map` into the session globals for the duration of the
+/// closure's execution.
+pub fn set_source_map<T, F: FnOnce() -> T>(source_map: Lrc<SourceMap>, f: F) -> T {
     with_session_globals(|session_globals| {
         *session_globals.source_map.borrow_mut() = Some(source_map);
     });
@@ -1041,6 +1042,8 @@ pub fn with_source_map<T, F: FnOnce() -> T>(source_map: Lrc<SourceMap>, f: F) ->
 
 impl fmt::Debug for Span {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        // Use the global `SourceMap` to print the span. If that's not
+        // available, fall back to printing the raw values.
         with_session_globals(|session_globals| {
             if let Some(source_map) = &*session_globals.source_map.borrow() {
                 write!(f, "{} ({:?})", source_map.span_to_diagnostic_string(*self), self.ctxt())
diff --git a/compiler/rustc_span/src/profiling.rs b/compiler/rustc_span/src/profiling.rs
index 0ab890b9f01..66e5369da3a 100644
--- a/compiler/rustc_span/src/profiling.rs
+++ b/compiler/rustc_span/src/profiling.rs
@@ -1,3 +1,5 @@
+use crate::source_map::SourceMap;
+
 use std::borrow::Borrow;
 
 use rustc_data_structures::profiling::EventArgRecorder;
@@ -11,25 +13,17 @@ pub trait SpannedEventArgRecorder {
     ///
     /// Note: when self-profiling with costly event arguments, at least one argument
     /// needs to be recorded. A panic will be triggered if that doesn't happen.
-    fn record_arg_with_span<A>(&mut self, event_arg: A, span: crate::Span)
+    fn record_arg_with_span<A>(&mut self, source_map: &SourceMap, event_arg: A, span: crate::Span)
     where
         A: Borrow<str> + Into<String>;
 }
 
 impl SpannedEventArgRecorder for EventArgRecorder<'_> {
-    fn record_arg_with_span<A>(&mut self, event_arg: A, span: crate::Span)
+    fn record_arg_with_span<A>(&mut self, source_map: &SourceMap, event_arg: A, span: crate::Span)
     where
         A: Borrow<str> + Into<String>,
     {
         self.record_arg(event_arg);
-
-        let span_arg = crate::with_session_globals(|session_globals| {
-            if let Some(source_map) = &*session_globals.source_map.borrow() {
-                source_map.span_to_embeddable_string(span)
-            } else {
-                format!("{span:?}")
-            }
-        });
-        self.record_arg(span_arg);
+        self.record_arg(source_map.span_to_embeddable_string(span));
     }
 }
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index d5dc1d0b315..6de226bbb68 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1246,6 +1246,7 @@ symbols! {
         rustc_diagnostic_macros,
         rustc_dirty,
         rustc_do_not_const_check,
+        rustc_doc_primitive,
         rustc_dummy,
         rustc_dump_env_program_clauses,
         rustc_dump_program_clauses,
diff --git a/library/core/src/primitive_docs.rs b/library/core/src/primitive_docs.rs
index e12a3e378a6..bf8339335dd 100644
--- a/library/core/src/primitive_docs.rs
+++ b/library/core/src/primitive_docs.rs
@@ -1,7 +1,8 @@
 // `library/{std,core}/src/primitive_docs.rs` should have the same contents.
 // These are different files so that relative links work properly without
 // having to have `CARGO_PKG_NAME` set, but conceptually they should always be the same.
-#[doc(primitive = "bool")]
+#[cfg_attr(bootstrap, doc(primitive = "bool"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "bool")]
 #[doc(alias = "true")]
 #[doc(alias = "false")]
 /// The boolean type.
@@ -63,7 +64,8 @@
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_bool {}
 
-#[doc(primitive = "never")]
+#[cfg_attr(bootstrap, doc(primitive = "never"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "never")]
 #[doc(alias = "!")]
 //
 /// The `!` type, also called "never".
@@ -274,7 +276,8 @@ mod prim_bool {}
 #[unstable(feature = "never_type", issue = "35121")]
 mod prim_never {}
 
-#[doc(primitive = "char")]
+#[cfg_attr(bootstrap, doc(primitive = "char"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "char")]
 #[allow(rustdoc::invalid_rust_codeblocks)]
 /// A character type.
 ///
@@ -398,7 +401,8 @@ mod prim_never {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_char {}
 
-#[doc(primitive = "unit")]
+#[cfg_attr(bootstrap, doc(primitive = "unit"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "unit")]
 #[doc(alias = "(")]
 #[doc(alias = ")")]
 #[doc(alias = "()")]
@@ -460,7 +464,8 @@ impl Copy for () {
     // empty
 }
 
-#[doc(primitive = "pointer")]
+#[cfg_attr(bootstrap, doc(primitive = "pointer"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "pointer")]
 #[doc(alias = "ptr")]
 #[doc(alias = "*")]
 #[doc(alias = "*const")]
@@ -577,7 +582,8 @@ impl Copy for () {
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_pointer {}
 
-#[doc(primitive = "array")]
+#[cfg_attr(bootstrap, doc(primitive = "array"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "array")]
 #[doc(alias = "[]")]
 #[doc(alias = "[T;N]")] // unfortunately, rustdoc doesn't have fuzzy search for aliases
 #[doc(alias = "[T; N]")]
@@ -778,7 +784,8 @@ mod prim_pointer {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_array {}
 
-#[doc(primitive = "slice")]
+#[cfg_attr(bootstrap, doc(primitive = "slice"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "slice")]
 #[doc(alias = "[")]
 #[doc(alias = "]")]
 #[doc(alias = "[]")]
@@ -870,7 +877,8 @@ mod prim_array {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_slice {}
 
-#[doc(primitive = "str")]
+#[cfg_attr(bootstrap, doc(primitive = "str"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "str")]
 /// String slices.
 ///
 /// *[See also the `std::str` module](crate::str).*
@@ -937,7 +945,8 @@ mod prim_slice {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_str {}
 
-#[doc(primitive = "tuple")]
+#[cfg_attr(bootstrap, doc(primitive = "tuple"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "tuple")]
 #[doc(alias = "(")]
 #[doc(alias = ")")]
 #[doc(alias = "()")]
@@ -1081,7 +1090,8 @@ impl<T: Copy> Copy for (T,) {
     // empty
 }
 
-#[doc(primitive = "f32")]
+#[cfg_attr(bootstrap, doc(primitive = "f32"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "f32")]
 /// A 32-bit floating point type (specifically, the "binary32" type defined in IEEE 754-2008).
 ///
 /// This type can represent a wide range of decimal numbers, like `3.5`, `27`,
@@ -1147,7 +1157,8 @@ impl<T: Copy> Copy for (T,) {
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_f32 {}
 
-#[doc(primitive = "f64")]
+#[cfg_attr(bootstrap, doc(primitive = "f64"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "f64")]
 /// A 64-bit floating point type (specifically, the "binary64" type defined in IEEE 754-2008).
 ///
 /// This type is very similar to [`f32`], but has increased
@@ -1162,67 +1173,78 @@ mod prim_f32 {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_f64 {}
 
-#[doc(primitive = "i8")]
+#[cfg_attr(bootstrap, doc(primitive = "i8"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "i8")]
 //
 /// The 8-bit signed integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_i8 {}
 
-#[doc(primitive = "i16")]
+#[cfg_attr(bootstrap, doc(primitive = "i16"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "i16")]
 //
 /// The 16-bit signed integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_i16 {}
 
-#[doc(primitive = "i32")]
+#[cfg_attr(bootstrap, doc(primitive = "i32"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "i32")]
 //
 /// The 32-bit signed integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_i32 {}
 
-#[doc(primitive = "i64")]
+#[cfg_attr(bootstrap, doc(primitive = "i64"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "i64")]
 //
 /// The 64-bit signed integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_i64 {}
 
-#[doc(primitive = "i128")]
+#[cfg_attr(bootstrap, doc(primitive = "i128"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "i128")]
 //
 /// The 128-bit signed integer type.
 #[stable(feature = "i128", since = "1.26.0")]
 mod prim_i128 {}
 
-#[doc(primitive = "u8")]
+#[cfg_attr(bootstrap, doc(primitive = "u8"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "u8")]
 //
 /// The 8-bit unsigned integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_u8 {}
 
-#[doc(primitive = "u16")]
+#[cfg_attr(bootstrap, doc(primitive = "u16"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "u16")]
 //
 /// The 16-bit unsigned integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_u16 {}
 
-#[doc(primitive = "u32")]
+#[cfg_attr(bootstrap, doc(primitive = "u32"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "u32")]
 //
 /// The 32-bit unsigned integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_u32 {}
 
-#[doc(primitive = "u64")]
+#[cfg_attr(bootstrap, doc(primitive = "u64"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "u64")]
 //
 /// The 64-bit unsigned integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_u64 {}
 
-#[doc(primitive = "u128")]
+#[cfg_attr(bootstrap, doc(primitive = "u128"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "u128")]
 //
 /// The 128-bit unsigned integer type.
 #[stable(feature = "i128", since = "1.26.0")]
 mod prim_u128 {}
 
-#[doc(primitive = "isize")]
+#[cfg_attr(bootstrap, doc(primitive = "isize"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "isize")]
 //
 /// The pointer-sized signed integer type.
 ///
@@ -1232,7 +1254,8 @@ mod prim_u128 {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_isize {}
 
-#[doc(primitive = "usize")]
+#[cfg_attr(bootstrap, doc(primitive = "usize"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "usize")]
 //
 /// The pointer-sized unsigned integer type.
 ///
@@ -1242,7 +1265,8 @@ mod prim_isize {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_usize {}
 
-#[doc(primitive = "reference")]
+#[cfg_attr(bootstrap, doc(primitive = "reference"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "reference")]
 #[doc(alias = "&")]
 #[doc(alias = "&mut")]
 //
@@ -1373,7 +1397,8 @@ mod prim_usize {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_ref {}
 
-#[doc(primitive = "fn")]
+#[cfg_attr(bootstrap, doc(primitive = "fn"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "fn")]
 //
 /// Function pointers, like `fn(usize) -> bool`.
 ///
diff --git a/library/std/src/primitive_docs.rs b/library/std/src/primitive_docs.rs
index e12a3e378a6..bf8339335dd 100644
--- a/library/std/src/primitive_docs.rs
+++ b/library/std/src/primitive_docs.rs
@@ -1,7 +1,8 @@
 // `library/{std,core}/src/primitive_docs.rs` should have the same contents.
 // These are different files so that relative links work properly without
 // having to have `CARGO_PKG_NAME` set, but conceptually they should always be the same.
-#[doc(primitive = "bool")]
+#[cfg_attr(bootstrap, doc(primitive = "bool"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "bool")]
 #[doc(alias = "true")]
 #[doc(alias = "false")]
 /// The boolean type.
@@ -63,7 +64,8 @@
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_bool {}
 
-#[doc(primitive = "never")]
+#[cfg_attr(bootstrap, doc(primitive = "never"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "never")]
 #[doc(alias = "!")]
 //
 /// The `!` type, also called "never".
@@ -274,7 +276,8 @@ mod prim_bool {}
 #[unstable(feature = "never_type", issue = "35121")]
 mod prim_never {}
 
-#[doc(primitive = "char")]
+#[cfg_attr(bootstrap, doc(primitive = "char"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "char")]
 #[allow(rustdoc::invalid_rust_codeblocks)]
 /// A character type.
 ///
@@ -398,7 +401,8 @@ mod prim_never {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_char {}
 
-#[doc(primitive = "unit")]
+#[cfg_attr(bootstrap, doc(primitive = "unit"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "unit")]
 #[doc(alias = "(")]
 #[doc(alias = ")")]
 #[doc(alias = "()")]
@@ -460,7 +464,8 @@ impl Copy for () {
     // empty
 }
 
-#[doc(primitive = "pointer")]
+#[cfg_attr(bootstrap, doc(primitive = "pointer"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "pointer")]
 #[doc(alias = "ptr")]
 #[doc(alias = "*")]
 #[doc(alias = "*const")]
@@ -577,7 +582,8 @@ impl Copy for () {
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_pointer {}
 
-#[doc(primitive = "array")]
+#[cfg_attr(bootstrap, doc(primitive = "array"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "array")]
 #[doc(alias = "[]")]
 #[doc(alias = "[T;N]")] // unfortunately, rustdoc doesn't have fuzzy search for aliases
 #[doc(alias = "[T; N]")]
@@ -778,7 +784,8 @@ mod prim_pointer {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_array {}
 
-#[doc(primitive = "slice")]
+#[cfg_attr(bootstrap, doc(primitive = "slice"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "slice")]
 #[doc(alias = "[")]
 #[doc(alias = "]")]
 #[doc(alias = "[]")]
@@ -870,7 +877,8 @@ mod prim_array {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_slice {}
 
-#[doc(primitive = "str")]
+#[cfg_attr(bootstrap, doc(primitive = "str"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "str")]
 /// String slices.
 ///
 /// *[See also the `std::str` module](crate::str).*
@@ -937,7 +945,8 @@ mod prim_slice {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_str {}
 
-#[doc(primitive = "tuple")]
+#[cfg_attr(bootstrap, doc(primitive = "tuple"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "tuple")]
 #[doc(alias = "(")]
 #[doc(alias = ")")]
 #[doc(alias = "()")]
@@ -1081,7 +1090,8 @@ impl<T: Copy> Copy for (T,) {
     // empty
 }
 
-#[doc(primitive = "f32")]
+#[cfg_attr(bootstrap, doc(primitive = "f32"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "f32")]
 /// A 32-bit floating point type (specifically, the "binary32" type defined in IEEE 754-2008).
 ///
 /// This type can represent a wide range of decimal numbers, like `3.5`, `27`,
@@ -1147,7 +1157,8 @@ impl<T: Copy> Copy for (T,) {
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_f32 {}
 
-#[doc(primitive = "f64")]
+#[cfg_attr(bootstrap, doc(primitive = "f64"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "f64")]
 /// A 64-bit floating point type (specifically, the "binary64" type defined in IEEE 754-2008).
 ///
 /// This type is very similar to [`f32`], but has increased
@@ -1162,67 +1173,78 @@ mod prim_f32 {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_f64 {}
 
-#[doc(primitive = "i8")]
+#[cfg_attr(bootstrap, doc(primitive = "i8"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "i8")]
 //
 /// The 8-bit signed integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_i8 {}
 
-#[doc(primitive = "i16")]
+#[cfg_attr(bootstrap, doc(primitive = "i16"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "i16")]
 //
 /// The 16-bit signed integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_i16 {}
 
-#[doc(primitive = "i32")]
+#[cfg_attr(bootstrap, doc(primitive = "i32"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "i32")]
 //
 /// The 32-bit signed integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_i32 {}
 
-#[doc(primitive = "i64")]
+#[cfg_attr(bootstrap, doc(primitive = "i64"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "i64")]
 //
 /// The 64-bit signed integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_i64 {}
 
-#[doc(primitive = "i128")]
+#[cfg_attr(bootstrap, doc(primitive = "i128"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "i128")]
 //
 /// The 128-bit signed integer type.
 #[stable(feature = "i128", since = "1.26.0")]
 mod prim_i128 {}
 
-#[doc(primitive = "u8")]
+#[cfg_attr(bootstrap, doc(primitive = "u8"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "u8")]
 //
 /// The 8-bit unsigned integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_u8 {}
 
-#[doc(primitive = "u16")]
+#[cfg_attr(bootstrap, doc(primitive = "u16"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "u16")]
 //
 /// The 16-bit unsigned integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_u16 {}
 
-#[doc(primitive = "u32")]
+#[cfg_attr(bootstrap, doc(primitive = "u32"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "u32")]
 //
 /// The 32-bit unsigned integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_u32 {}
 
-#[doc(primitive = "u64")]
+#[cfg_attr(bootstrap, doc(primitive = "u64"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "u64")]
 //
 /// The 64-bit unsigned integer type.
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_u64 {}
 
-#[doc(primitive = "u128")]
+#[cfg_attr(bootstrap, doc(primitive = "u128"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "u128")]
 //
 /// The 128-bit unsigned integer type.
 #[stable(feature = "i128", since = "1.26.0")]
 mod prim_u128 {}
 
-#[doc(primitive = "isize")]
+#[cfg_attr(bootstrap, doc(primitive = "isize"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "isize")]
 //
 /// The pointer-sized signed integer type.
 ///
@@ -1232,7 +1254,8 @@ mod prim_u128 {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_isize {}
 
-#[doc(primitive = "usize")]
+#[cfg_attr(bootstrap, doc(primitive = "usize"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "usize")]
 //
 /// The pointer-sized unsigned integer type.
 ///
@@ -1242,7 +1265,8 @@ mod prim_isize {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_usize {}
 
-#[doc(primitive = "reference")]
+#[cfg_attr(bootstrap, doc(primitive = "reference"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "reference")]
 #[doc(alias = "&")]
 #[doc(alias = "&mut")]
 //
@@ -1373,7 +1397,8 @@ mod prim_usize {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_ref {}
 
-#[doc(primitive = "fn")]
+#[cfg_attr(bootstrap, doc(primitive = "fn"))]
+#[cfg_attr(not(bootstrap), rustc_doc_primitive = "fn")]
 //
 /// Function pointers, like `fn(usize) -> bool`.
 ///
diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version
index 6fd113fcfd8..66fb941ea37 100644
--- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version
+++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version
@@ -1 +1 @@
-0.14.5
\ No newline at end of file
+0.14.6
\ No newline at end of file
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index b8b5014ab42..960c1de1782 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -177,9 +177,9 @@ Book][unstable-masked] and [its tracking issue][issue-masked].
 This is for Rust compiler internal use only.
 
 Since primitive types are defined in the compiler, there's no place to attach documentation
-attributes. The `#[doc(primitive)]` attribute is used by the standard library to provide a way
-to generate documentation for primitive types, and requires `#![feature(rustdoc_internals)]` to
-enable.
+attributes. The `#[rustc_doc_primitive = "..."]` attribute is used by the standard library to
+provide a way to generate documentation for primitive types, and requires `#![feature(rustc_attrs)]`
+to enable.
 
 ### Document keywords
 
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 909e0a07e4c..ffa13ebb77c 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -249,38 +249,24 @@ impl ExternalCrate {
         //
         // Note that this loop only searches the top-level items of the crate,
         // and this is intentional. If we were to search the entire crate for an
-        // item tagged with `#[doc(primitive)]` then we would also have to
+        // item tagged with `#[rustc_doc_primitive]` then we would also have to
         // search the entirety of external modules for items tagged
-        // `#[doc(primitive)]`, which is a pretty inefficient process (decoding
+        // `#[rustc_doc_primitive]`, which is a pretty inefficient process (decoding
         // all that metadata unconditionally).
         //
         // In order to keep the metadata load under control, the
-        // `#[doc(primitive)]` feature is explicitly designed to only allow the
+        // `#[rustc_doc_primitive]` feature is explicitly designed to only allow the
         // primitive tags to show up as the top level items in a crate.
         //
         // Also note that this does not attempt to deal with modules tagged
         // duplicately for the same primitive. This is handled later on when
         // rendering by delegating everything to a hash map.
         let as_primitive = |res: Res<!>| {
-            if let Res::Def(DefKind::Mod, def_id) = res {
-                let mut prim = None;
-                let meta_items = tcx
-                    .get_attrs(def_id, sym::doc)
-                    .flat_map(|attr| attr.meta_item_list().unwrap_or_default());
-                for meta in meta_items {
-                    if let Some(v) = meta.value_str() {
-                        if meta.has_name(sym::primitive) {
-                            prim = PrimitiveType::from_symbol(v);
-                            if prim.is_some() {
-                                break;
-                            }
-                            // FIXME: should warn on unknown primitives?
-                        }
-                    }
-                }
-                return prim.map(|p| (def_id, p));
-            }
-            None
+            let Res::Def(DefKind::Mod, def_id) = res else { return None };
+            tcx.get_attrs(def_id, sym::rustc_doc_primitive).find_map(|attr| {
+                // FIXME: should warn on unknown primitives?
+                Some((def_id, PrimitiveType::from_symbol(attr.value_str()?)?))
+            })
         };
 
         if root.is_local() {
@@ -1829,13 +1815,17 @@ impl PrimitiveType {
         }
     }
 
-    /// Returns the DefId of the module with `doc(primitive)` for this primitive type.
+    /// Returns the DefId of the module with `rustc_doc_primitive` for this primitive type.
     /// Panics if there is no such module.
     ///
-    /// This gives precedence to primitives defined in the current crate, and deprioritizes primitives defined in `core`,
-    /// but otherwise, if multiple crates define the same primitive, there is no guarantee of which will be picked.
-    /// In particular, if a crate depends on both `std` and another crate that also defines `doc(primitive)`, then
-    /// it's entirely random whether `std` or the other crate is picked. (no_std crates are usually fine unless multiple dependencies define a primitive.)
+    /// This gives precedence to primitives defined in the current crate, and deprioritizes
+    /// primitives defined in `core`,
+    /// but otherwise, if multiple crates define the same primitive, there is no guarantee of which
+    /// will be picked.
+    ///
+    /// In particular, if a crate depends on both `std` and another crate that also defines
+    /// `rustc_doc_primitive`, then it's entirely random whether `std` or the other crate is picked.
+    /// (no_std crates are usually fine unless multiple dependencies define a primitive.)
     pub(crate) fn primitive_locations(tcx: TyCtxt<'_>) -> &FxHashMap<PrimitiveType, DefId> {
         static PRIMITIVE_LOCATIONS: OnceCell<FxHashMap<PrimitiveType, DefId>> = OnceCell::new();
         PRIMITIVE_LOCATIONS.get_or_init(|| {
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 59d67f27b30..c39caf73a93 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -249,9 +249,7 @@ pub(crate) fn id_from_item_inner(
                     // instead, we directly get the primitive symbol and convert it to u32 to
                     // generate the ID.
                     if matches!(tcx.def_kind(def_id), DefKind::Mod) &&
-                        let Some(prim) = tcx.get_attrs(*def_id, sym::doc)
-                            .flat_map(|attr| attr.meta_item_list().unwrap_or_default())
-                            .filter(|attr| attr.has_name(sym::primitive))
+                        let Some(prim) = tcx.get_attrs(*def_id, sym::rustc_doc_primitive)
                             .find_map(|attr| attr.value_str()) {
                         format!(":{}", prim.as_u32())
                     } else {
diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs
index 08bceb59cfd..d6da6e09938 100644
--- a/src/librustdoc/json/mod.rs
+++ b/src/librustdoc/json/mod.rs
@@ -78,7 +78,7 @@ impl<'tcx> JsonRenderer<'tcx> {
                         // HACK(hkmatsumoto): For impls of primitive types, we index them
                         // regardless of whether they're local. This is because users can
                         // document primitive items in an arbitrary crate by using
-                        // `doc(primitive)`.
+                        // `rustc_doc_primitive`.
                         let mut is_primitive_impl = false;
                         if let clean::types::ItemKind::ImplItem(ref impl_) = *item.kind &&
                             impl_.trait_.is_none() &&
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index d98cf251e97..4188aa1037f 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -47,7 +47,18 @@ fn collect_intra_doc_links(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
     krate
 }
 
-#[derive(Copy, Clone, Debug, Hash)]
+fn filter_assoc_items_by_name_and_namespace<'a>(
+    tcx: TyCtxt<'a>,
+    assoc_items_of: DefId,
+    ident: Ident,
+    ns: Namespace,
+) -> impl Iterator<Item = &ty::AssocItem> + 'a {
+    tcx.associated_items(assoc_items_of).filter_by_name_unhygienic(ident.name).filter(move |item| {
+        item.kind.namespace() == ns && tcx.hygienic_eq(ident, item.ident(tcx), assoc_items_of)
+    })
+}
+
+#[derive(Copy, Clone, Debug, Hash, PartialEq)]
 enum Res {
     Def(DefKind, DefId),
     Primitive(PrimitiveType),
@@ -59,7 +70,7 @@ impl Res {
     fn descr(self) -> &'static str {
         match self {
             Res::Def(kind, id) => ResolveRes::Def(kind, id).descr(),
-            Res::Primitive(_) => "builtin type",
+            Res::Primitive(_) => "primitive type",
         }
     }
 
@@ -317,14 +328,21 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         prim_ty: PrimitiveType,
         ns: Namespace,
         item_name: Symbol,
-    ) -> Option<(Res, DefId)> {
+    ) -> Vec<(Res, DefId)> {
         let tcx = self.cx.tcx;
 
-        prim_ty.impls(tcx).find_map(|impl_| {
-            tcx.associated_items(impl_)
-                .find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, impl_)
+        prim_ty
+            .impls(tcx)
+            .flat_map(|impl_| {
+                filter_assoc_items_by_name_and_namespace(
+                    tcx,
+                    impl_,
+                    Ident::with_dummy_span(item_name),
+                    ns,
+                )
                 .map(|item| (Res::Primitive(prim_ty), item.def_id))
-        })
+            })
+            .collect::<Vec<_>>()
     }
 
     fn resolve_self_ty(&self, path_str: &str, ns: Namespace, item_id: DefId) -> Option<Res> {
@@ -394,14 +412,16 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         ns: Namespace,
         item_id: DefId,
         module_id: DefId,
-    ) -> Result<(Res, Option<DefId>), UnresolvedPath<'path>> {
+    ) -> Result<Vec<(Res, Option<DefId>)>, UnresolvedPath<'path>> {
         if let Some(res) = self.resolve_path(path_str, ns, item_id, module_id) {
             return Ok(match res {
                 Res::Def(
                     DefKind::AssocFn | DefKind::AssocConst | DefKind::AssocTy | DefKind::Variant,
                     def_id,
-                ) => (Res::from_def_id(self.cx.tcx, self.cx.tcx.parent(def_id)), Some(def_id)),
-                _ => (res, None),
+                ) => {
+                    vec![(Res::from_def_id(self.cx.tcx, self.cx.tcx.parent(def_id)), Some(def_id))]
+                }
+                _ => vec![(res, None)],
             });
         } else if ns == MacroNS {
             return Err(UnresolvedPath {
@@ -433,17 +453,24 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
             })?;
 
         // FIXME(#83862): this arbitrarily gives precedence to primitives over modules to support
-        // links to primitives when `#[doc(primitive)]` is present. It should give an ambiguity
-        // error instead and special case *only* modules with `#[doc(primitive)]`, not all
+        // links to primitives when `#[rustc_doc_primitive]` is present. It should give an ambiguity
+        // error instead and special case *only* modules with `#[rustc_doc_primitive]`, not all
         // primitives.
-        resolve_primitive(&path_root, TypeNS)
+        match resolve_primitive(&path_root, TypeNS)
             .or_else(|| self.resolve_path(&path_root, TypeNS, item_id, module_id))
             .and_then(|ty_res| {
-                self.resolve_associated_item(ty_res, item_name, ns, module_id).map(Ok)
-            })
-            .unwrap_or_else(|| {
+                let candidates = self
+                    .resolve_associated_item(ty_res, item_name, ns, module_id)
+                    .into_iter()
+                    .map(|(res, def_id)| (res, Some(def_id)))
+                    .collect::<Vec<_>>();
+                if !candidates.is_empty() { Some(candidates) } else { None }
+            }) {
+            Some(r) => Ok(r),
+            None => {
                 if ns == Namespace::ValueNS {
                     self.variant_field(path_str, item_id, module_id)
+                        .map(|(res, def_id)| vec![(res, Some(def_id))])
                 } else {
                     Err(UnresolvedPath {
                         item_id,
@@ -452,8 +479,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         unresolved: path_root.into(),
                     })
                 }
-            })
-            .map(|(res, def_id)| (res, Some(def_id)))
+            }
+        }
     }
 
     /// Convert a DefId to a Res, where possible.
@@ -535,24 +562,31 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         item_name: Symbol,
         ns: Namespace,
         module_id: DefId,
-    ) -> Option<(Res, DefId)> {
+    ) -> Vec<(Res, DefId)> {
         let tcx = self.cx.tcx;
 
         match root_res {
             Res::Primitive(prim) => {
-                self.resolve_primitive_associated_item(prim, ns, item_name).or_else(|| {
+                let items = self.resolve_primitive_associated_item(prim, ns, item_name);
+                if !items.is_empty() {
+                    items
+                // Inherent associated items take precedence over items that come from trait impls.
+                } else {
                     self.primitive_type_to_ty(prim)
-                        .and_then(|ty| {
+                        .map(|ty| {
                             resolve_associated_trait_item(ty, module_id, item_name, ns, self.cx)
+                                .iter()
+                                .map(|item| (root_res, item.def_id))
+                                .collect::<Vec<_>>()
                         })
-                        .map(|item| (root_res, item.def_id))
-                })
+                        .unwrap_or(Vec::new())
+                }
             }
             Res::Def(DefKind::TyAlias, did) => {
                 // Resolve the link on the type the alias points to.
                 // FIXME: if the associated item is defined directly on the type alias,
                 // it will show up on its documentation page, we should link there instead.
-                let res = self.def_id_to_res(did)?;
+                let Some(res) = self.def_id_to_res(did) else { return Vec::new() };
                 self.resolve_associated_item(res, item_name, ns, module_id)
             }
             Res::Def(
@@ -566,7 +600,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         ty::Adt(adt_def, _) => {
                             for variant in adt_def.variants() {
                                 if variant.name == item_name {
-                                    return Some((root_res, variant.def_id));
+                                    return vec![(root_res, variant.def_id)];
                                 }
                             }
                         }
@@ -575,43 +609,46 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 }
 
                 // Checks if item_name belongs to `impl SomeItem`
-                let assoc_item = tcx
+                let mut assoc_items: Vec<_> = tcx
                     .inherent_impls(did)
                     .iter()
                     .flat_map(|&imp| {
-                        tcx.associated_items(imp).find_by_name_and_namespace(
+                        filter_assoc_items_by_name_and_namespace(
                             tcx,
+                            imp,
                             Ident::with_dummy_span(item_name),
                             ns,
-                            imp,
                         )
                     })
-                    .copied()
-                    // There should only ever be one associated item that matches from any inherent impl
-                    .next()
+                    .map(|item| (root_res, item.def_id))
+                    .collect();
+
+                if assoc_items.is_empty() {
                     // Check if item_name belongs to `impl SomeTrait for SomeItem`
                     // FIXME(#74563): This gives precedence to `impl SomeItem`:
                     // Although having both would be ambiguous, use impl version for compatibility's sake.
                     // To handle that properly resolve() would have to support
                     // something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
-                    .or_else(|| {
-                        resolve_associated_trait_item(
-                            tcx.type_of(did).subst_identity(),
-                            module_id,
-                            item_name,
-                            ns,
-                            self.cx,
-                        )
-                    });
+                    assoc_items = resolve_associated_trait_item(
+                        tcx.type_of(did).subst_identity(),
+                        module_id,
+                        item_name,
+                        ns,
+                        self.cx,
+                    )
+                    .into_iter()
+                    .map(|item| (root_res, item.def_id))
+                    .collect::<Vec<_>>();
+                }
 
-                debug!("got associated item {:?}", assoc_item);
+                debug!("got associated item {:?}", assoc_items);
 
-                if let Some(item) = assoc_item {
-                    return Some((root_res, item.def_id));
+                if !assoc_items.is_empty() {
+                    return assoc_items;
                 }
 
                 if ns != Namespace::ValueNS {
-                    return None;
+                    return Vec::new();
                 }
                 debug!("looking for fields named {} for {:?}", item_name, did);
                 // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?)
@@ -631,20 +668,27 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 // field syntax) and are handled by the compiler's resolver.
                 let def = match tcx.type_of(did).subst_identity().kind() {
                     ty::Adt(def, _) if !def.is_enum() => def,
-                    _ => return None,
+                    _ => return Vec::new(),
                 };
-                let field =
-                    def.non_enum_variant().fields.iter().find(|item| item.name == item_name)?;
-                Some((root_res, field.did))
+                def.non_enum_variant()
+                    .fields
+                    .iter()
+                    .filter(|field| field.name == item_name)
+                    .map(|field| (root_res, field.did))
+                    .collect::<Vec<_>>()
             }
-            Res::Def(DefKind::Trait, did) => tcx
-                .associated_items(did)
-                .find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, did)
-                .map(|item| {
-                    let res = Res::Def(item.kind.as_def_kind(), item.def_id);
-                    (res, item.def_id)
-                }),
-            _ => None,
+            Res::Def(DefKind::Trait, did) => filter_assoc_items_by_name_and_namespace(
+                tcx,
+                did,
+                Ident::with_dummy_span(item_name),
+                ns,
+            )
+            .map(|item| {
+                let res = Res::Def(item.kind.as_def_kind(), item.def_id);
+                (res, item.def_id)
+            })
+            .collect::<Vec<_>>(),
+            _ => Vec::new(),
         }
     }
 }
@@ -664,7 +708,7 @@ fn resolve_associated_trait_item<'a>(
     item_name: Symbol,
     ns: Namespace,
     cx: &mut DocContext<'a>,
-) -> Option<ty::AssocItem> {
+) -> Vec<ty::AssocItem> {
     // FIXME: this should also consider blanket impls (`impl<T> X for T`). Unfortunately
     // `get_auto_trait_and_blanket_impls` is broken because the caching behavior is wrong. In the
     // meantime, just don't look for these blanket impls.
@@ -672,19 +716,26 @@ fn resolve_associated_trait_item<'a>(
     // Next consider explicit impls: `impl MyTrait for MyType`
     // Give precedence to inherent impls.
     let traits = trait_impls_for(cx, ty, module);
+    let tcx = cx.tcx;
     debug!("considering traits {:?}", traits);
-    let mut candidates = traits.iter().filter_map(|&(impl_, trait_)| {
-        cx.tcx
-            .associated_items(trait_)
-            .find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, trait_)
-            .map(|trait_assoc| {
-                trait_assoc_to_impl_assoc_item(cx.tcx, impl_, trait_assoc.def_id)
+    let candidates = traits
+        .iter()
+        .flat_map(|&(impl_, trait_)| {
+            filter_assoc_items_by_name_and_namespace(
+                cx.tcx,
+                trait_,
+                Ident::with_dummy_span(item_name),
+                ns,
+            )
+            .map(move |trait_assoc| {
+                trait_assoc_to_impl_assoc_item(tcx, impl_, trait_assoc.def_id)
                     .unwrap_or(*trait_assoc)
             })
-    });
+        })
+        .collect::<Vec<_>>();
     // FIXME(#74563): warn about ambiguity
-    debug!("the candidates were {:?}", candidates.clone().collect::<Vec<_>>());
-    candidates.next()
+    debug!("the candidates were {:?}", candidates);
+    candidates
 }
 
 /// Find the associated item in the impl `impl_id` that corresponds to the
@@ -758,15 +809,15 @@ fn trait_impls_for<'a>(
 /// Check for resolve collisions between a trait and its derive.
 ///
 /// These are common and we should just resolve to the trait in that case.
-fn is_derive_trait_collision<T>(ns: &PerNS<Result<(Res, T), ResolutionFailure<'_>>>) -> bool {
-    matches!(
-        *ns,
-        PerNS {
-            type_ns: Ok((Res::Def(DefKind::Trait, _), _)),
-            macro_ns: Ok((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)),
-            ..
-        }
-    )
+fn is_derive_trait_collision<T>(ns: &PerNS<Result<Vec<(Res, T)>, ResolutionFailure<'_>>>) -> bool {
+    if let (&Ok(ref type_ns), &Ok(ref macro_ns)) = (&ns.type_ns, &ns.macro_ns) {
+        type_ns.iter().any(|(res, _)| matches!(res, Res::Def(DefKind::Trait, _)))
+            && macro_ns
+                .iter()
+                .any(|(res, _)| matches!(res, Res::Def(DefKind::Macro(MacroKind::Derive), _)))
+    } else {
+        false
+    }
 }
 
 impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
@@ -987,15 +1038,15 @@ impl LinkCollector<'_, '_> {
                     res = prim;
                 } else {
                     // `[char]` when a `char` module is in scope
-                    let candidates = vec![res, prim];
-                    ambiguity_error(self.cx, diag_info, path_str, candidates);
+                    let candidates = &[(res, res.def_id(self.cx.tcx)), (prim, None)];
+                    ambiguity_error(self.cx, &diag_info, path_str, candidates);
                     return None;
                 }
             }
         }
 
         match res {
-            Res::Primitive(prim) => {
+            Res::Primitive(_) => {
                 if let Some(UrlFragment::Item(id)) = fragment {
                     // We're actually resolving an associated item of a primitive, so we need to
                     // verify the disambiguator (if any) matches the type of the associated item.
@@ -1015,15 +1066,6 @@ impl LinkCollector<'_, '_> {
                         item,
                         &diag_info,
                     )?;
-
-                    // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
-                    // However I'm not sure how to check that across crates.
-                    if prim == PrimitiveType::RawPointer
-                        && item.item_id.is_local()
-                        && !self.cx.tcx.features().intra_doc_pointers
-                    {
-                        self.report_rawptr_assoc_feature_gate(dox, ori_link, item);
-                    }
                 } else {
                     match disambiguator {
                         Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {}
@@ -1102,7 +1144,7 @@ impl LinkCollector<'_, '_> {
                 }
             }
 
-        // item can be non-local e.g. when using #[doc(primitive = "pointer")]
+        // item can be non-local e.g. when using `#[rustc_doc_primitive = "pointer"]`
         if let Some((src_id, dst_id)) = id.as_local().and_then(|dst_id| {
             item.item_id.expect_def_id().as_local().map(|src_id| (src_id, dst_id))
         }) {
@@ -1144,10 +1186,9 @@ impl LinkCollector<'_, '_> {
         report_diagnostic(self.cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, diag_info, callback);
     }
 
-    fn report_rawptr_assoc_feature_gate(&self, dox: &str, ori_link: &MarkdownLink, item: &Item) {
-        let span =
-            super::source_span_for_markdown_range(self.cx.tcx, dox, &ori_link.range, &item.attrs)
-                .unwrap_or_else(|| item.attr_span(self.cx.tcx));
+    fn report_rawptr_assoc_feature_gate(&self, dox: &str, ori_link: &Range<usize>, item: &Item) {
+        let span = super::source_span_for_markdown_range(self.cx.tcx, dox, ori_link, &item.attrs)
+            .unwrap_or_else(|| item.attr_span(self.cx.tcx));
         rustc_session::parse::feature_err(
             &self.cx.tcx.sess.parse_sess,
             sym::intra_doc_pointers,
@@ -1172,7 +1213,31 @@ impl LinkCollector<'_, '_> {
             }
         }
 
-        let res = self.resolve_with_disambiguator(&key, diag.clone()).and_then(|(res, def_id)| {
+        let mut candidates = self.resolve_with_disambiguator(&key, diag.clone());
+
+        // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
+        // However I'm not sure how to check that across crates.
+        if let Some(candidate) = candidates.get(0) &&
+            candidate.0 == Res::Primitive(PrimitiveType::RawPointer) &&
+            key.path_str.contains("::") // We only want to check this if this is an associated item.
+        {
+            if key.item_id.is_local() && !self.cx.tcx.features().intra_doc_pointers {
+                self.report_rawptr_assoc_feature_gate(diag.dox, &diag.link_range, diag.item);
+                return None;
+            } else {
+                candidates = vec![candidates[0]];
+            }
+        }
+
+        // If there are multiple items with the same "kind" (for example, both "associated types")
+        // and after removing duplicated kinds, only one remains, the `ambiguity_error` function
+        // won't emit an error. So at this point, we can just take the first candidate as it was
+        // the first retrieved and use it to generate the link.
+        if candidates.len() > 1 && !ambiguity_error(self.cx, &diag, &key.path_str, &candidates) {
+            candidates = vec![candidates[0]];
+        }
+
+        if let &[(res, def_id)] = candidates.as_slice() {
             let fragment = match (&key.extra_fragment, def_id) {
                 (Some(_), Some(def_id)) => {
                     report_anchor_conflict(self.cx, diag, def_id);
@@ -1182,13 +1247,15 @@ impl LinkCollector<'_, '_> {
                 (None, Some(def_id)) => Some(UrlFragment::Item(def_id)),
                 (None, None) => None,
             };
-            Some((res, fragment))
-        });
+            let r = Some((res, fragment));
+            self.visited_links.insert(key, r.clone());
+            return r;
+        }
 
-        if res.is_some() || cache_errors {
-            self.visited_links.insert(key, res.clone());
+        if cache_errors {
+            self.visited_links.insert(key, None);
         }
-        res
+        None
     }
 
     /// After parsing the disambiguator, resolve the main part of the link.
@@ -1197,7 +1264,7 @@ impl LinkCollector<'_, '_> {
         &mut self,
         key: &ResolutionInfo,
         diag: DiagnosticInfo<'_>,
-    ) -> Option<(Res, Option<DefId>)> {
+    ) -> Vec<(Res, Option<DefId>)> {
         let disambiguator = key.dis;
         let path_str = &key.path_str;
         let item_id = key.item_id;
@@ -1206,7 +1273,7 @@ impl LinkCollector<'_, '_> {
         match disambiguator.map(Disambiguator::ns) {
             Some(expected_ns) => {
                 match self.resolve(path_str, expected_ns, item_id, module_id) {
-                    Ok(res) => Some(res),
+                    Ok(candidates) => candidates,
                     Err(err) => {
                         // We only looked in one namespace. Try to give a better error if possible.
                         // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`.
@@ -1215,10 +1282,11 @@ impl LinkCollector<'_, '_> {
                         for other_ns in [TypeNS, ValueNS, MacroNS] {
                             if other_ns != expected_ns {
                                 if let Ok(res) =
-                                    self.resolve(path_str, other_ns, item_id, module_id)
+                                    self.resolve(path_str, other_ns, item_id, module_id) &&
+                                    !res.is_empty()
                                 {
                                     err = ResolutionFailure::WrongNamespace {
-                                        res: full_res(self.cx.tcx, res),
+                                        res: full_res(self.cx.tcx, res[0]),
                                         expected_ns,
                                     };
                                     break;
@@ -1239,18 +1307,26 @@ impl LinkCollector<'_, '_> {
                 let candidates = PerNS {
                     macro_ns: candidate(MacroNS),
                     type_ns: candidate(TypeNS),
-                    value_ns: candidate(ValueNS).and_then(|(res, def_id)| {
-                        match res {
-                            // Constructors are picked up in the type namespace.
-                            Res::Def(DefKind::Ctor(..), _) => {
-                                Err(ResolutionFailure::WrongNamespace { res, expected_ns: TypeNS })
+                    value_ns: candidate(ValueNS).and_then(|v_res| {
+                        for (res, _) in v_res.iter() {
+                            match res {
+                                // Constructors are picked up in the type namespace.
+                                Res::Def(DefKind::Ctor(..), _) => {
+                                    return Err(ResolutionFailure::WrongNamespace {
+                                        res: *res,
+                                        expected_ns: TypeNS,
+                                    });
+                                }
+                                _ => {}
                             }
-                            _ => Ok((res, def_id)),
                         }
+                        Ok(v_res)
                     }),
                 };
 
-                let len = candidates.iter().filter(|res| res.is_ok()).count();
+                let len = candidates
+                    .iter()
+                    .fold(0, |acc, res| if let Ok(res) = res { acc + res.len() } else { acc });
 
                 if len == 0 {
                     return resolution_failure(
@@ -1260,22 +1336,21 @@ impl LinkCollector<'_, '_> {
                         disambiguator,
                         candidates.into_iter().filter_map(|res| res.err()).collect(),
                     );
-                }
-
-                if len == 1 {
-                    Some(candidates.into_iter().find_map(|res| res.ok()).unwrap())
-                } else if len == 2 && is_derive_trait_collision(&candidates) {
-                    Some(candidates.type_ns.unwrap())
+                } else if len == 1 {
+                    candidates.into_iter().filter_map(|res| res.ok()).flatten().collect::<Vec<_>>()
                 } else {
-                    let ignore_macro = is_derive_trait_collision(&candidates);
-                    // If we're reporting an ambiguity, don't mention the namespaces that failed
-                    let mut candidates =
-                        candidates.map(|candidate| candidate.ok().map(|(res, _)| res));
-                    if ignore_macro {
-                        candidates.macro_ns = None;
+                    let has_derive_trait_collision = is_derive_trait_collision(&candidates);
+                    if len == 2 && has_derive_trait_collision {
+                        candidates.type_ns.unwrap()
+                    } else {
+                        // If we're reporting an ambiguity, don't mention the namespaces that failed
+                        let mut candidates = candidates.map(|candidate| candidate.ok());
+                        // If there a collision between a trait and a derive, we ignore the derive.
+                        if has_derive_trait_collision {
+                            candidates.macro_ns = None;
+                        }
+                        candidates.into_iter().filter_map(|res| res).flatten().collect::<Vec<_>>()
                     }
-                    ambiguity_error(self.cx, diag, path_str, candidates.present_items().collect());
-                    None
                 }
             }
         }
@@ -1563,7 +1638,7 @@ fn resolution_failure(
     path_str: &str,
     disambiguator: Option<Disambiguator>,
     kinds: SmallVec<[ResolutionFailure<'_>; 3]>,
-) -> Option<(Res, Option<DefId>)> {
+) -> Vec<(Res, Option<DefId>)> {
     let tcx = collector.cx.tcx;
     let mut recovered_res = None;
     report_diagnostic(
@@ -1622,11 +1697,13 @@ fn resolution_failure(
                         };
                         name = start;
                         for ns in [TypeNS, ValueNS, MacroNS] {
-                            if let Ok(res) = collector.resolve(start, ns, item_id, module_id) {
-                                debug!("found partial_res={:?}", res);
-                                *partial_res = Some(full_res(collector.cx.tcx, res));
-                                *unresolved = end.into();
-                                break 'outer;
+                            if let Ok(v_res) = collector.resolve(start, ns, item_id, module_id) {
+                                debug!("found partial_res={:?}", v_res);
+                                if !v_res.is_empty() {
+                                    *partial_res = Some(full_res(collector.cx.tcx, v_res[0]));
+                                    *unresolved = end.into();
+                                    break 'outer;
+                                }
                             }
                         }
                         *unresolved = end.into();
@@ -1774,7 +1851,10 @@ fn resolution_failure(
         },
     );
 
-    recovered_res
+    match recovered_res {
+        Some(r) => vec![r],
+        None => Vec::new(),
+    }
 }
 
 fn report_multiple_anchors(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>) {
@@ -1859,28 +1939,47 @@ fn report_malformed_generics(
 }
 
 /// Report an ambiguity error, where there were multiple possible resolutions.
+///
+/// If all `candidates` have the same kind, it's not possible to disambiguate so in this case,
+/// the function won't emit an error and will return `false`. Otherwise, it'll emit the error and
+/// return `true`.
 fn ambiguity_error(
     cx: &DocContext<'_>,
-    diag_info: DiagnosticInfo<'_>,
+    diag_info: &DiagnosticInfo<'_>,
     path_str: &str,
-    candidates: Vec<Res>,
-) {
-    let mut msg = format!("`{}` is ", path_str);
+    candidates: &[(Res, Option<DefId>)],
+) -> bool {
+    let mut descrs = FxHashSet::default();
+    let kinds = candidates
+        .iter()
+        .map(
+            |(res, def_id)| {
+                if let Some(def_id) = def_id { Res::from_def_id(cx.tcx, *def_id) } else { *res }
+            },
+        )
+        .filter(|res| descrs.insert(res.descr()))
+        .collect::<Vec<_>>();
+    if descrs.len() == 1 {
+        // There is no way for users to disambiguate at this point, so better return the first
+        // candidate and not show a warning.
+        return false;
+    }
 
-    match candidates.as_slice() {
-        [first_def, second_def] => {
+    let mut msg = format!("`{}` is ", path_str);
+    match kinds.as_slice() {
+        [res1, res2] => {
             msg += &format!(
                 "both {} {} and {} {}",
-                first_def.article(),
-                first_def.descr(),
-                second_def.article(),
-                second_def.descr(),
+                res1.article(),
+                res1.descr(),
+                res2.article(),
+                res2.descr()
             );
         }
         _ => {
-            let mut candidates = candidates.iter().peekable();
-            while let Some(res) = candidates.next() {
-                if candidates.peek().is_some() {
+            let mut kinds = kinds.iter().peekable();
+            while let Some(res) = kinds.next() {
+                if kinds.peek().is_some() {
                     msg += &format!("{} {}, ", res.article(), res.descr());
                 } else {
                     msg += &format!("and {} {}", res.article(), res.descr());
@@ -1889,17 +1988,18 @@ fn ambiguity_error(
         }
     }
 
-    report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, &diag_info, |diag, sp| {
+    report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, diag_info, |diag, sp| {
         if let Some(sp) = sp {
             diag.span_label(sp, "ambiguous link");
         } else {
             diag.note("ambiguous link");
         }
 
-        for res in candidates {
+        for res in kinds {
             suggest_disambiguator(res, diag, path_str, diag_info.ori_link, sp);
         }
     });
+    true
 }
 
 /// In case of an ambiguity or mismatched disambiguator, suggest the correct
diff --git a/tests/rustdoc-gui/go-to-collapsed-elem.goml b/tests/rustdoc-gui/go-to-collapsed-elem.goml
new file mode 100644
index 00000000000..279048e37c1
--- /dev/null
+++ b/tests/rustdoc-gui/go-to-collapsed-elem.goml
@@ -0,0 +1,21 @@
+// This test ensures that when clicking on a link which leads to an item inside a collapsed element,
+// the collapsed element will be expanded.
+goto: "file://" + |DOC_PATH| + "/test_docs/struct.Foo.html"
+// We check that the implementors block is expanded.
+assert-property: ("#implementations-list .implementors-toggle", {"open": "true"})
+// We now collapse the implementors block.
+property: ("#implementations-list .implementors-toggle", {"open": "false"})
+// And now we click on the link to the method to ensure it'll expand the implementors block.
+click: "//*[@class='sidebar']//a[@href='#method.must_use']"
+assert-property: ("#implementations-list .implementors-toggle", {"open": "true"})
+
+// Now we do the same through search result.
+// First we reload the page without the anchor in the URL.
+goto: "file://" + |DOC_PATH| + "/test_docs/struct.Foo.html"
+// Then we collapse the section again...
+property: ("#implementations-list .implementors-toggle", {"open": "false"})
+// Then we run the search.
+write: (".search-input", "foo::must_use")
+wait-for: "//*[@id='search']//a[@href='../test_docs/struct.Foo.html#method.must_use']"
+click: "//*[@id='search']//a[@href='../test_docs/struct.Foo.html#method.must_use']"
+assert-property: ("#implementations-list .implementors-toggle", {"open": "true"})
diff --git a/tests/rustdoc-json/impls/local_for_local_primitive.rs b/tests/rustdoc-json/impls/local_for_local_primitive.rs
index 38e7e2658df..8383dcc0482 100644
--- a/tests/rustdoc-json/impls/local_for_local_primitive.rs
+++ b/tests/rustdoc-json/impls/local_for_local_primitive.rs
@@ -1,5 +1,5 @@
 #![feature(no_core)]
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 #![no_core]
 
 // @set Local = "$.index[*][?(@.name=='Local')].id"
@@ -16,6 +16,6 @@ impl Local for bool {}
 
 // FIXME(#101695): Test bool's `impls` include "Local for bool"
 // @has "$.index[*][?(@.name=='bool')]"
-#[doc(primitive = "bool")]
+#[rustc_doc_primitive = "bool"]
 /// Boolean docs
 mod prim_bool {}
diff --git a/tests/rustdoc-json/primitives/local_primitive.rs b/tests/rustdoc-json/primitives/local_primitive.rs
index f27e6a2adec..0cf479faf29 100644
--- a/tests/rustdoc-json/primitives/local_primitive.rs
+++ b/tests/rustdoc-json/primitives/local_primitive.rs
@@ -8,7 +8,7 @@
 
 //! Link to [i32][prim@i32] [i64][prim@i64]
 
-#[doc(primitive = "i32")]
+#[rustc_doc_primitive = "i32"]
 mod prim_i32 {}
 
 // @set local_i32 = "$.index[*][?(@.name=='i32')].id"
diff --git a/tests/rustdoc-json/primitives/primitive_impls.rs b/tests/rustdoc-json/primitives/primitive_impls.rs
index 1fc9374065f..85d179ee45f 100644
--- a/tests/rustdoc-json/primitives/primitive_impls.rs
+++ b/tests/rustdoc-json/primitives/primitive_impls.rs
@@ -25,7 +25,7 @@ pub trait Trait {}
 impl Trait for i32 {}
 
 /// i32
-#[doc(primitive = "i32")]
+#[rustc_doc_primitive = "i32"]
 mod prim_i32 {}
 
 // @set i32 = "$.index[*][?(@.docs=='i32')].id"
diff --git a/tests/rustdoc-json/primitives/primitive_overloading.rs b/tests/rustdoc-json/primitives/primitive_overloading.rs
index 56b35cd14c0..81e0acdc6e9 100644
--- a/tests/rustdoc-json/primitives/primitive_overloading.rs
+++ b/tests/rustdoc-json/primitives/primitive_overloading.rs
@@ -2,7 +2,7 @@
 
 // Regression test for <https://github.com/rust-lang/rust/issues/98006>.
 
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 #![feature(no_core)]
 
 #![no_core]
@@ -10,7 +10,7 @@
 // @has "$.index[*][?(@.name=='usize')]"
 // @has "$.index[*][?(@.name=='prim')]"
 
-#[doc(primitive = "usize")]
+#[rustc_doc_primitive = "usize"]
 /// This is the built-in type `usize`.
 mod prim {
 }
diff --git a/tests/rustdoc-json/primitives/use_primitive.rs b/tests/rustdoc-json/primitives/use_primitive.rs
index e2292737462..5180a804f07 100644
--- a/tests/rustdoc-json/primitives/use_primitive.rs
+++ b/tests/rustdoc-json/primitives/use_primitive.rs
@@ -1,8 +1,8 @@
 // edition:2018
 
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
-#[doc(primitive = "usize")]
+#[rustc_doc_primitive = "usize"]
 mod usize {}
 
 // @set local_crate_id = "$.index[*][?(@.name=='use_primitive')].crate_id"
diff --git a/tests/rustdoc-ui/coverage/exotic.rs b/tests/rustdoc-ui/coverage/exotic.rs
index 72b70d6980b..f45405fbf5d 100644
--- a/tests/rustdoc-ui/coverage/exotic.rs
+++ b/tests/rustdoc-ui/coverage/exotic.rs
@@ -2,12 +2,13 @@
 // check-pass
 
 #![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
 //! the features only used in std also have entries in the table, so make sure those get pulled out
 //! properly as well
 
 /// woo, check it out, we can write our own primitive docs lol
-#[doc(primitive="unit")]
+#[rustc_doc_primitive = "unit"]
 mod prim_unit {}
 
 /// keywords? sure, pile them on
diff --git a/tests/rustdoc-ui/intra-doc/ambiguity.rs b/tests/rustdoc-ui/intra-doc/ambiguity.rs
index 1f3dc722eff..0290b858204 100644
--- a/tests/rustdoc-ui/intra-doc/ambiguity.rs
+++ b/tests/rustdoc-ui/intra-doc/ambiguity.rs
@@ -35,6 +35,6 @@ pub mod foo {
 /// Ambiguous non-implied shortcut link [`foo::bar`]. //~ERROR `foo::bar`
 pub struct Docs {}
 
-/// [true] //~ ERROR `true` is both a module and a builtin type
+/// [true] //~ ERROR `true` is both a module and a primitive type
 /// [primitive@true]
 pub mod r#true {}
diff --git a/tests/rustdoc-ui/intra-doc/ambiguity.stderr b/tests/rustdoc-ui/intra-doc/ambiguity.stderr
index 7974796e47b..47853e0b589 100644
--- a/tests/rustdoc-ui/intra-doc/ambiguity.stderr
+++ b/tests/rustdoc-ui/intra-doc/ambiguity.stderr
@@ -1,4 +1,4 @@
-error: `true` is both a module and a builtin type
+error: `true` is both a module and a primitive type
   --> $DIR/ambiguity.rs:38:6
    |
 LL | /// [true]
@@ -13,89 +13,89 @@ help: to link to the module, prefix with `mod@`
    |
 LL | /// [mod@true]
    |      ++++
-help: to link to the builtin type, prefix with `prim@`
+help: to link to the primitive type, prefix with `prim@`
    |
 LL | /// [prim@true]
    |      +++++
 
-error: `ambiguous` is both a struct and a function
+error: `ambiguous` is both a function and a struct
   --> $DIR/ambiguity.rs:27:7
    |
 LL | /// [`ambiguous`] is ambiguous.
    |       ^^^^^^^^^ ambiguous link
    |
-help: to link to the struct, prefix with `struct@`
-   |
-LL | /// [`struct@ambiguous`] is ambiguous.
-   |       +++++++
 help: to link to the function, add parentheses
    |
 LL | /// [`ambiguous()`] is ambiguous.
    |                ++
+help: to link to the struct, prefix with `struct@`
+   |
+LL | /// [`struct@ambiguous`] is ambiguous.
+   |       +++++++
 
-error: `ambiguous` is both a struct and a function
+error: `ambiguous` is both a function and a struct
   --> $DIR/ambiguity.rs:29:6
    |
 LL | /// [ambiguous] is ambiguous.
    |      ^^^^^^^^^ ambiguous link
    |
-help: to link to the struct, prefix with `struct@`
-   |
-LL | /// [struct@ambiguous] is ambiguous.
-   |      +++++++
 help: to link to the function, add parentheses
    |
 LL | /// [ambiguous()] is ambiguous.
    |               ++
+help: to link to the struct, prefix with `struct@`
+   |
+LL | /// [struct@ambiguous] is ambiguous.
+   |      +++++++
 
-error: `multi_conflict` is a struct, a function, and a macro
+error: `multi_conflict` is a function, a struct, and a macro
   --> $DIR/ambiguity.rs:31:7
    |
 LL | /// [`multi_conflict`] is a three-way conflict.
    |       ^^^^^^^^^^^^^^ ambiguous link
    |
-help: to link to the struct, prefix with `struct@`
-   |
-LL | /// [`struct@multi_conflict`] is a three-way conflict.
-   |       +++++++
 help: to link to the function, add parentheses
    |
 LL | /// [`multi_conflict()`] is a three-way conflict.
    |                     ++
+help: to link to the struct, prefix with `struct@`
+   |
+LL | /// [`struct@multi_conflict`] is a three-way conflict.
+   |       +++++++
 help: to link to the macro, add an exclamation mark
    |
 LL | /// [`multi_conflict!`] is a three-way conflict.
    |                     +
 
-error: `type_and_value` is both a module and a constant
+error: `type_and_value` is both a constant and a module
   --> $DIR/ambiguity.rs:33:16
    |
 LL | /// Ambiguous [type_and_value].
    |                ^^^^^^^^^^^^^^ ambiguous link
    |
-help: to link to the module, prefix with `mod@`
-   |
-LL | /// Ambiguous [mod@type_and_value].
-   |                ++++
 help: to link to the constant, prefix with `const@`
    |
 LL | /// Ambiguous [const@type_and_value].
    |                ++++++
+help: to link to the module, prefix with `mod@`
+   |
+LL | /// Ambiguous [mod@type_and_value].
+   |                ++++
 
-error: `foo::bar` is both an enum and a function
+error: `foo::bar` is both a function and an enum
   --> $DIR/ambiguity.rs:35:43
    |
 LL | /// Ambiguous non-implied shortcut link [`foo::bar`].
    |                                           ^^^^^^^^ ambiguous link
    |
-help: to link to the enum, prefix with `enum@`
-   |
-LL | /// Ambiguous non-implied shortcut link [`enum@foo::bar`].
-   |                                           +++++
 help: to link to the function, add parentheses
    |
 LL | /// Ambiguous non-implied shortcut link [`foo::bar()`].
    |                                                   ++
+help: to link to the enum, prefix with `enum@`
+   |
+LL | /// Ambiguous non-implied shortcut link [`enum@foo::bar`].
+   |                                           +++++
 
 error: aborting due to 6 previous errors
 
diff --git a/tests/rustdoc-ui/intra-doc/errors.rs b/tests/rustdoc-ui/intra-doc/errors.rs
index 95dd2b98e03..f37f49c24cc 100644
--- a/tests/rustdoc-ui/intra-doc/errors.rs
+++ b/tests/rustdoc-ui/intra-doc/errors.rs
@@ -54,11 +54,11 @@
 
 /// [u8::not_found]
 //~^ ERROR unresolved link
-//~| NOTE the builtin type `u8` has no associated item named `not_found`
+//~| NOTE the primitive type `u8` has no associated item named `not_found`
 
 /// [std::primitive::u8::not_found]
 //~^ ERROR unresolved link
-//~| NOTE the builtin type `u8` has no associated item named `not_found`
+//~| NOTE the primitive type `u8` has no associated item named `not_found`
 
 /// [type@Vec::into_iter]
 //~^ ERROR unresolved link
diff --git a/tests/rustdoc-ui/intra-doc/errors.stderr b/tests/rustdoc-ui/intra-doc/errors.stderr
index 1b2416d7da7..a982bba0095 100644
--- a/tests/rustdoc-ui/intra-doc/errors.stderr
+++ b/tests/rustdoc-ui/intra-doc/errors.stderr
@@ -80,13 +80,13 @@ error: unresolved link to `u8::not_found`
   --> $DIR/errors.rs:55:6
    |
 LL | /// [u8::not_found]
-   |      ^^^^^^^^^^^^^ the builtin type `u8` has no associated item named `not_found`
+   |      ^^^^^^^^^^^^^ the primitive type `u8` has no associated item named `not_found`
 
 error: unresolved link to `std::primitive::u8::not_found`
   --> $DIR/errors.rs:59:6
    |
 LL | /// [std::primitive::u8::not_found]
-   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the builtin type `u8` has no associated item named `not_found`
+   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the primitive type `u8` has no associated item named `not_found`
 
 error: unresolved link to `Vec::into_iter`
   --> $DIR/errors.rs:63:6
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-10.rs b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-10.rs
new file mode 100644
index 00000000000..464c5f0d543
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-10.rs
@@ -0,0 +1,22 @@
+// This test ensures that this warning doesn't show up:
+// warning: `PartialEq` is both a trait and a derive macro
+//  --> tests/rustdoc-ui/intra-doc/issue-108653-associated-items-10.rs:1:7
+//   |
+// 1 | //! [`PartialEq`]
+//   |       ^^^^^^^^^ ambiguous link
+//   |
+//   = note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default
+// help: to link to the trait, prefix with `trait@`
+//   |
+// 1 | //! [`trait@PartialEq`]
+//   |       ++++++
+// help: to link to the derive macro, prefix with `derive@`
+//   |
+// 1 | //! [`derive@PartialEq`]
+//   |       +++++++
+
+// check-pass
+
+#![deny(rustdoc::broken_intra_doc_links)]
+
+//! [`PartialEq`]
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-2.rs b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-2.rs
new file mode 100644
index 00000000000..cbe60f746b6
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-2.rs
@@ -0,0 +1,17 @@
+// This is ensuring that the UI output for associated items is as expected.
+
+#![deny(rustdoc::broken_intra_doc_links)]
+
+/// [`Trait::IDENT`]
+//~^ ERROR both an associated constant and an associated type
+pub trait Trait {
+    type IDENT;
+    const IDENT: usize;
+}
+
+/// [`Trait2::IDENT`]
+//~^ ERROR both an associated function and an associated type
+pub trait Trait2 {
+    type IDENT;
+    fn IDENT() {}
+}
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-2.stderr b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-2.stderr
new file mode 100644
index 00000000000..952392548da
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-2.stderr
@@ -0,0 +1,37 @@
+error: `Trait::IDENT` is both an associated constant and an associated type
+  --> $DIR/issue-108653-associated-items-2.rs:5:7
+   |
+LL | /// [`Trait::IDENT`]
+   |       ^^^^^^^^^^^^ ambiguous link
+   |
+note: the lint level is defined here
+  --> $DIR/issue-108653-associated-items-2.rs:3:9
+   |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: to link to the associated constant, prefix with `const@`
+   |
+LL | /// [`const@Trait::IDENT`]
+   |       ++++++
+help: to link to the associated type, prefix with `type@`
+   |
+LL | /// [`type@Trait::IDENT`]
+   |       +++++
+
+error: `Trait2::IDENT` is both an associated function and an associated type
+  --> $DIR/issue-108653-associated-items-2.rs:12:7
+   |
+LL | /// [`Trait2::IDENT`]
+   |       ^^^^^^^^^^^^^ ambiguous link
+   |
+help: to link to the associated function, add parentheses
+   |
+LL | /// [`Trait2::IDENT()`]
+   |                    ++
+help: to link to the associated type, prefix with `type@`
+   |
+LL | /// [`type@Trait2::IDENT`]
+   |       +++++
+
+error: aborting due to 2 previous errors
+
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.rs b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.rs
new file mode 100644
index 00000000000..7ffd0a40e7c
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.rs
@@ -0,0 +1,16 @@
+// This is ensuring that the UI output for associated items works when it's being documented
+// from another item.
+
+#![deny(rustdoc::broken_intra_doc_links)]
+#![allow(nonstandard_style)]
+
+pub trait Trait {
+    type Trait;
+    const Trait: usize;
+}
+
+/// [`Trait`]
+//~^ ERROR both a constant and a trait
+/// [`Trait::Trait`]
+//~^ ERROR both an associated constant and an associated type
+pub const Trait: usize = 0;
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.stderr b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.stderr
new file mode 100644
index 00000000000..6401dacb57a
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.stderr
@@ -0,0 +1,37 @@
+error: `Trait` is both a constant and a trait
+  --> $DIR/issue-108653-associated-items-3.rs:12:7
+   |
+LL | /// [`Trait`]
+   |       ^^^^^ ambiguous link
+   |
+note: the lint level is defined here
+  --> $DIR/issue-108653-associated-items-3.rs:4:9
+   |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: to link to the constant, prefix with `const@`
+   |
+LL | /// [`const@Trait`]
+   |       ++++++
+help: to link to the trait, prefix with `trait@`
+   |
+LL | /// [`trait@Trait`]
+   |       ++++++
+
+error: `Trait::Trait` is both an associated constant and an associated type
+  --> $DIR/issue-108653-associated-items-3.rs:14:7
+   |
+LL | /// [`Trait::Trait`]
+   |       ^^^^^^^^^^^^ ambiguous link
+   |
+help: to link to the associated constant, prefix with `const@`
+   |
+LL | /// [`const@Trait::Trait`]
+   |       ++++++
+help: to link to the associated type, prefix with `type@`
+   |
+LL | /// [`type@Trait::Trait`]
+   |       +++++
+
+error: aborting due to 2 previous errors
+
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-4.rs b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-4.rs
new file mode 100644
index 00000000000..537d61364bb
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-4.rs
@@ -0,0 +1,21 @@
+// This is ensuring that the UI output for associated items works when it's being documented
+// from another item.
+
+#![deny(rustdoc::broken_intra_doc_links)]
+#![allow(nonstandard_style)]
+
+pub trait Trait {
+    type Trait;
+}
+
+/// [`Struct::Trait`]
+//~^ ERROR both an associated constant and an associated type
+pub struct Struct;
+
+impl Trait for Struct {
+    type Trait = Struct;
+}
+
+impl Struct {
+    pub const Trait: usize = 0;
+}
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-4.stderr b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-4.stderr
new file mode 100644
index 00000000000..a8dc91204c0
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-4.stderr
@@ -0,0 +1,22 @@
+error: `Struct::Trait` is both an associated constant and an associated type
+  --> $DIR/issue-108653-associated-items-4.rs:11:7
+   |
+LL | /// [`Struct::Trait`]
+   |       ^^^^^^^^^^^^^ ambiguous link
+   |
+note: the lint level is defined here
+  --> $DIR/issue-108653-associated-items-4.rs:4:9
+   |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: to link to the associated constant, prefix with `const@`
+   |
+LL | /// [`const@Struct::Trait`]
+   |       ++++++
+help: to link to the associated type, prefix with `type@`
+   |
+LL | /// [`type@Struct::Trait`]
+   |       +++++
+
+error: aborting due to previous error
+
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-5.rs b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-5.rs
new file mode 100644
index 00000000000..bc28bc54421
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-5.rs
@@ -0,0 +1,8 @@
+#![deny(rustdoc::broken_intra_doc_links)]
+#![allow(nonstandard_style)]
+
+/// [`u32::MAX`]
+//~^ ERROR both an associated constant and a trait
+pub mod u32 {
+    pub trait MAX {}
+}
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-5.stderr b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-5.stderr
new file mode 100644
index 00000000000..7430044ac3f
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-5.stderr
@@ -0,0 +1,22 @@
+error: `u32::MAX` is both an associated constant and a trait
+  --> $DIR/issue-108653-associated-items-5.rs:4:7
+   |
+LL | /// [`u32::MAX`]
+   |       ^^^^^^^^ ambiguous link
+   |
+note: the lint level is defined here
+  --> $DIR/issue-108653-associated-items-5.rs:1:9
+   |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: to link to the associated constant, prefix with `const@`
+   |
+LL | /// [`const@u32::MAX`]
+   |       ++++++
+help: to link to the trait, prefix with `trait@`
+   |
+LL | /// [`trait@u32::MAX`]
+   |       ++++++
+
+error: aborting due to previous error
+
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-6.rs b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-6.rs
new file mode 100644
index 00000000000..8fde74d0ddb
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-6.rs
@@ -0,0 +1,8 @@
+#![deny(rustdoc::broken_intra_doc_links)]
+#![allow(nonstandard_style)]
+
+/// [`u32::MAX`]
+//~^ ERROR both an associated constant and a primitive type
+pub mod u32 {
+    pub use std::primitive::u32 as MAX;
+}
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-6.stderr b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-6.stderr
new file mode 100644
index 00000000000..fe2d8cafa30
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-6.stderr
@@ -0,0 +1,22 @@
+error: `u32::MAX` is both an associated constant and a primitive type
+  --> $DIR/issue-108653-associated-items-6.rs:4:7
+   |
+LL | /// [`u32::MAX`]
+   |       ^^^^^^^^ ambiguous link
+   |
+note: the lint level is defined here
+  --> $DIR/issue-108653-associated-items-6.rs:1:9
+   |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: to link to the associated constant, prefix with `const@`
+   |
+LL | /// [`const@u32::MAX`]
+   |       ++++++
+help: to link to the primitive type, prefix with `prim@`
+   |
+LL | /// [`prim@u32::MAX`]
+   |       +++++
+
+error: aborting due to previous error
+
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-7.rs b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-7.rs
new file mode 100644
index 00000000000..6e99f4365a7
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-7.rs
@@ -0,0 +1,12 @@
+#![deny(rustdoc::broken_intra_doc_links)]
+#![allow(nonstandard_style)]
+
+pub trait Trait {
+    type MAX;
+}
+
+/// [`u32::MAX`]
+//~^ ERROR both an associated constant and an associated type
+impl Trait for u32 {
+    type MAX = u32;
+}
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-7.stderr b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-7.stderr
new file mode 100644
index 00000000000..1d302ff42e8
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-7.stderr
@@ -0,0 +1,22 @@
+error: `u32::MAX` is both an associated constant and an associated type
+  --> $DIR/issue-108653-associated-items-7.rs:8:7
+   |
+LL | /// [`u32::MAX`]
+   |       ^^^^^^^^ ambiguous link
+   |
+note: the lint level is defined here
+  --> $DIR/issue-108653-associated-items-7.rs:1:9
+   |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: to link to the associated constant, prefix with `const@`
+   |
+LL | /// [`const@u32::MAX`]
+   |       ++++++
+help: to link to the associated type, prefix with `type@`
+   |
+LL | /// [`type@u32::MAX`]
+   |       +++++
+
+error: aborting due to previous error
+
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-8.rs b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-8.rs
new file mode 100644
index 00000000000..2f8ee1566bd
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-8.rs
@@ -0,0 +1,12 @@
+#![deny(rustdoc::broken_intra_doc_links)]
+#![allow(nonstandard_style)]
+
+/// [`u32::MAX`]
+//~^ ERROR both an associated constant and an associated type
+pub trait T {
+    type MAX;
+}
+
+impl T for u32 {
+    type MAX = ();
+}
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-8.stderr b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-8.stderr
new file mode 100644
index 00000000000..efed0e2ce0f
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-8.stderr
@@ -0,0 +1,22 @@
+error: `u32::MAX` is both an associated constant and an associated type
+  --> $DIR/issue-108653-associated-items-8.rs:4:7
+   |
+LL | /// [`u32::MAX`]
+   |       ^^^^^^^^ ambiguous link
+   |
+note: the lint level is defined here
+  --> $DIR/issue-108653-associated-items-8.rs:1:9
+   |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: to link to the associated constant, prefix with `const@`
+   |
+LL | /// [`const@u32::MAX`]
+   |       ++++++
+help: to link to the associated type, prefix with `type@`
+   |
+LL | /// [`type@u32::MAX`]
+   |       +++++
+
+error: aborting due to previous error
+
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-9.rs b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-9.rs
new file mode 100644
index 00000000000..3357ccf2460
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-9.rs
@@ -0,0 +1,11 @@
+// check-pass
+
+#![deny(warnings)]
+
+//! [usize::Item]
+
+pub trait Foo { type Item; }
+pub trait Bar { type Item; }
+
+impl Foo for usize { type Item = u32; }
+impl Bar for usize { type Item = i32; }
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items.rs b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items.rs
new file mode 100644
index 00000000000..0a393e26d6a
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items.rs
@@ -0,0 +1,35 @@
+// This is ensuring that the UI output for associated items is as expected.
+
+#![deny(rustdoc::broken_intra_doc_links)]
+
+pub enum Enum {
+    IDENT,
+}
+
+/// [`Self::IDENT`]
+//~^ ERROR both an associated function and an associated type
+pub trait Trait {
+    type IDENT;
+    fn IDENT();
+}
+
+/// [`Self::IDENT`]
+//~^ ERROR both an associated function and a variant
+impl Trait for Enum {
+    type IDENT = usize;
+    fn IDENT() {}
+}
+
+/// [`Self::IDENT2`]
+//~^ ERROR both an associated constant and an associated type
+pub trait Trait2 {
+    type IDENT2;
+    const IDENT2: usize;
+}
+
+/// [`Self::IDENT2`]
+//~^ ERROR both an associated constant and an associated type
+impl Trait2 for Enum {
+    type IDENT2 = usize;
+    const IDENT2: usize = 0;
+}
diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items.stderr b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items.stderr
new file mode 100644
index 00000000000..084aefc97c8
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items.stderr
@@ -0,0 +1,67 @@
+error: `Self::IDENT` is both an associated function and an associated type
+  --> $DIR/issue-108653-associated-items.rs:9:7
+   |
+LL | /// [`Self::IDENT`]
+   |       ^^^^^^^^^^^ ambiguous link
+   |
+note: the lint level is defined here
+  --> $DIR/issue-108653-associated-items.rs:3:9
+   |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: to link to the associated function, add parentheses
+   |
+LL | /// [`Self::IDENT()`]
+   |                  ++
+help: to link to the associated type, prefix with `type@`
+   |
+LL | /// [`type@Self::IDENT`]
+   |       +++++
+
+error: `Self::IDENT2` is both an associated constant and an associated type
+  --> $DIR/issue-108653-associated-items.rs:23:7
+   |
+LL | /// [`Self::IDENT2`]
+   |       ^^^^^^^^^^^^ ambiguous link
+   |
+help: to link to the associated constant, prefix with `const@`
+   |
+LL | /// [`const@Self::IDENT2`]
+   |       ++++++
+help: to link to the associated type, prefix with `type@`
+   |
+LL | /// [`type@Self::IDENT2`]
+   |       +++++
+
+error: `Self::IDENT2` is both an associated constant and an associated type
+  --> $DIR/issue-108653-associated-items.rs:30:7
+   |
+LL | /// [`Self::IDENT2`]
+   |       ^^^^^^^^^^^^ ambiguous link
+   |
+help: to link to the associated constant, prefix with `const@`
+   |
+LL | /// [`const@Self::IDENT2`]
+   |       ++++++
+help: to link to the associated type, prefix with `type@`
+   |
+LL | /// [`type@Self::IDENT2`]
+   |       +++++
+
+error: `Self::IDENT` is both an associated function and a variant
+  --> $DIR/issue-108653-associated-items.rs:16:7
+   |
+LL | /// [`Self::IDENT`]
+   |       ^^^^^^^^^^^ ambiguous link
+   |
+help: to link to the associated function, add parentheses
+   |
+LL | /// [`Self::IDENT()`]
+   |                  ++
+help: to link to the variant, prefix with `type@`
+   |
+LL | /// [`type@Self::IDENT`]
+   |       +++++
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/rustdoc-ui/intra-doc/non-path-primitives.stderr b/tests/rustdoc-ui/intra-doc/non-path-primitives.stderr
index 8ec894d101b..6e08a923963 100644
--- a/tests/rustdoc-ui/intra-doc/non-path-primitives.stderr
+++ b/tests/rustdoc-ui/intra-doc/non-path-primitives.stderr
@@ -39,25 +39,25 @@ error: unresolved link to `unit::eq`
   --> $DIR/non-path-primitives.rs:28:6
    |
 LL | //! [unit::eq]
-   |      ^^^^^^^^ the builtin type `unit` has no associated item named `eq`
+   |      ^^^^^^^^ the primitive type `unit` has no associated item named `eq`
 
 error: unresolved link to `tuple::eq`
   --> $DIR/non-path-primitives.rs:29:6
    |
 LL | //! [tuple::eq]
-   |      ^^^^^^^^^ the builtin type `tuple` has no associated item named `eq`
+   |      ^^^^^^^^^ the primitive type `tuple` has no associated item named `eq`
 
 error: unresolved link to `fn::eq`
   --> $DIR/non-path-primitives.rs:30:6
    |
 LL | //! [fn::eq]
-   |      ^^^^^^ the builtin type `fn` has no associated item named `eq`
+   |      ^^^^^^ the primitive type `fn` has no associated item named `eq`
 
 error: unresolved link to `reference::deref`
   --> $DIR/non-path-primitives.rs:34:6
    |
 LL | //! [reference::deref]
-   |      ^^^^^^^^^^^^^^^^ the builtin type `reference` has no associated item named `deref`
+   |      ^^^^^^^^^^^^^^^^ the primitive type `reference` has no associated item named `deref`
 
 error: aborting due to 8 previous errors
 
diff --git a/tests/rustdoc-ui/intra-doc/prim-conflict.rs b/tests/rustdoc-ui/intra-doc/prim-conflict.rs
index 2c1a8b5357a..e87ce095cd4 100644
--- a/tests/rustdoc-ui/intra-doc/prim-conflict.rs
+++ b/tests/rustdoc-ui/intra-doc/prim-conflict.rs
@@ -2,16 +2,16 @@
 //~^ NOTE lint level is defined
 
 /// [char]
-//~^ ERROR both a module and a builtin type
+//~^ ERROR both a module and a primitive type
 //~| NOTE ambiguous link
 //~| HELP to link to the module
-//~| HELP to link to the builtin type
+//~| HELP to link to the primitive type
 
 /// [type@char]
-//~^ ERROR both a module and a builtin type
+//~^ ERROR both a module and a primitive type
 //~| NOTE ambiguous link
 //~| HELP to link to the module
-//~| HELP to link to the builtin type
+//~| HELP to link to the primitive type
 
 /// [mod@char] // ok
 /// [prim@char] // ok
@@ -26,5 +26,5 @@ pub mod inner {
     //! [struct@char]
     //~^ ERROR incompatible link
     //~| HELP prefix with `prim@`
-    //~| NOTE resolved to a builtin type
+    //~| NOTE resolved to a primitive type
 }
diff --git a/tests/rustdoc-ui/intra-doc/prim-conflict.stderr b/tests/rustdoc-ui/intra-doc/prim-conflict.stderr
index 6ef3b7eab3b..03ce8f15f0a 100644
--- a/tests/rustdoc-ui/intra-doc/prim-conflict.stderr
+++ b/tests/rustdoc-ui/intra-doc/prim-conflict.stderr
@@ -1,4 +1,4 @@
-error: `char` is both a module and a builtin type
+error: `char` is both a module and a primitive type
   --> $DIR/prim-conflict.rs:4:6
    |
 LL | /// [char]
@@ -13,12 +13,12 @@ help: to link to the module, prefix with `mod@`
    |
 LL | /// [mod@char]
    |      ++++
-help: to link to the builtin type, prefix with `prim@`
+help: to link to the primitive type, prefix with `prim@`
    |
 LL | /// [prim@char]
    |      +++++
 
-error: `char` is both a module and a builtin type
+error: `char` is both a module and a primitive type
   --> $DIR/prim-conflict.rs:10:6
    |
 LL | /// [type@char]
@@ -28,7 +28,7 @@ help: to link to the module, prefix with `mod@`
    |
 LL | /// [mod@char]
    |      ~~~~
-help: to link to the builtin type, prefix with `prim@`
+help: to link to the primitive type, prefix with `prim@`
    |
 LL | /// [prim@char]
    |      ~~~~~
@@ -48,9 +48,9 @@ error: incompatible link kind for `char`
   --> $DIR/prim-conflict.rs:26:10
    |
 LL |     //! [struct@char]
-   |          ^^^^^^^^^^^ this link resolved to a builtin type, which is not a struct
+   |          ^^^^^^^^^^^ this link resolved to a primitive type, which is not a struct
    |
-help: to link to the builtin type, prefix with `prim@`
+help: to link to the primitive type, prefix with `prim@`
    |
 LL |     //! [prim@char]
    |          ~~~~~
diff --git a/tests/rustdoc/auto-impl-primitive.rs b/tests/rustdoc/auto-impl-primitive.rs
index 172333d445d..a6db93dbc33 100644
--- a/tests/rustdoc/auto-impl-primitive.rs
+++ b/tests/rustdoc/auto-impl-primitive.rs
@@ -1,10 +1,10 @@
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
 #![crate_name = "foo"]
 
 pub use std::fs::File;
 
 // @has 'foo/primitive.i16.html' '//h2[@id="synthetic-implementations"]' 'Auto Trait Implementation'
-#[doc(primitive = "i16")]
+#[rustc_doc_primitive = "i16"]
 /// I love poneys!
 mod prim {}
diff --git a/tests/rustdoc/auxiliary/issue-15318.rs b/tests/rustdoc/auxiliary/issue-15318.rs
index 695fa58ef1d..a2f426c6352 100644
--- a/tests/rustdoc/auxiliary/issue-15318.rs
+++ b/tests/rustdoc/auxiliary/issue-15318.rs
@@ -2,6 +2,7 @@
 // compile-flags: -Cmetadata=aux
 #![crate_type = "rlib"]
 #![doc(html_root_url = "http://example.com/")]
+#![feature(rustc_attrs)]
 #![feature(lang_items)]
 #![no_std]
 
@@ -12,5 +13,5 @@ fn foo() {}
 fn bar(_: &core::panic::PanicInfo) -> ! { loop {} }
 
 /// dox
-#[doc(primitive = "pointer")]
+#[rustc_doc_primitive = "pointer"]
 pub mod ptr {}
diff --git a/tests/rustdoc/auxiliary/primitive-doc.rs b/tests/rustdoc/auxiliary/primitive-doc.rs
index e8da852a57e..d1785e42391 100644
--- a/tests/rustdoc/auxiliary/primitive-doc.rs
+++ b/tests/rustdoc/auxiliary/primitive-doc.rs
@@ -1,9 +1,10 @@
 // compile-flags: --crate-type lib --edition 2018
 
+#![feature(rustc_attrs)]
 #![feature(no_core)]
 #![no_core]
 
-#[doc(primitive = "usize")]
+#[rustc_doc_primitive = "usize"]
 /// This is the built-in type `usize`.
 mod usize {
 }
diff --git a/tests/rustdoc/check-source-code-urls-to-def.rs b/tests/rustdoc/check-source-code-urls-to-def.rs
index 5959f9c7c59..41b9d41fa44 100644
--- a/tests/rustdoc/check-source-code-urls-to-def.rs
+++ b/tests/rustdoc/check-source-code-urls-to-def.rs
@@ -2,7 +2,7 @@
 // aux-build:source_code.rs
 // build-aux-docs
 
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
 #![crate_name = "foo"]
 
@@ -65,5 +65,5 @@ pub fn foo4() {
 }
 
 // @has - '//pre[@class="rust"]//a[@href="../../foo/primitive.bool.html"]' 'bool'
-#[doc(primitive = "bool")]
+#[rustc_doc_primitive = "bool"]
 mod whatever {}
diff --git a/tests/rustdoc/intra-doc/auxiliary/my-core.rs b/tests/rustdoc/intra-doc/auxiliary/my-core.rs
index e22feb03ae6..c050929db96 100644
--- a/tests/rustdoc/intra-doc/auxiliary/my-core.rs
+++ b/tests/rustdoc/intra-doc/auxiliary/my-core.rs
@@ -3,7 +3,7 @@
 #![rustc_coherence_is_core]
 #![crate_type="rlib"]
 
-#[doc(primitive = "char")]
+#[rustc_doc_primitive = "char"]
 /// Some char docs
 mod char {}
 
diff --git a/tests/rustdoc/intra-doc/no-doc-primitive.rs b/tests/rustdoc/intra-doc/no-doc-primitive.rs
index e5eba1d8d48..711ac09ba9a 100644
--- a/tests/rustdoc/intra-doc/no-doc-primitive.rs
+++ b/tests/rustdoc/intra-doc/no-doc-primitive.rs
@@ -1,4 +1,4 @@
-// Crate tree without a `doc(primitive)` module for primitive type linked to by a doc link.
+// Crate tree without a `rustc_doc_primitive` module for primitive type linked to by a doc link.
 
 #![deny(rustdoc::broken_intra_doc_links)]
 #![feature(no_core, lang_items, rustc_attrs)]
diff --git a/tests/rustdoc/intra-doc/prim-methods-local.rs b/tests/rustdoc/intra-doc/prim-methods-local.rs
index 79d8df04515..6de4ec1802f 100644
--- a/tests/rustdoc/intra-doc/prim-methods-local.rs
+++ b/tests/rustdoc/intra-doc/prim-methods-local.rs
@@ -10,7 +10,7 @@
 
 //! A [prim@`char`] and its [`char::len_utf8`].
 
-#[doc(primitive = "char")]
+#[rustc_doc_primitive = "char"]
 mod char {}
 
 impl char {
diff --git a/tests/rustdoc/intra-doc/prim-self.rs b/tests/rustdoc/intra-doc/prim-self.rs
index c7ce71b15f3..d13858a53cf 100644
--- a/tests/rustdoc/intra-doc/prim-self.rs
+++ b/tests/rustdoc/intra-doc/prim-self.rs
@@ -25,7 +25,7 @@ impl usize {
     pub type ME = usize;
 }
 
-#[doc(primitive = "usize")]
+#[rustc_doc_primitive = "usize"]
 /// This has some docs.
 mod usize {}
 
diff --git a/tests/rustdoc/issue-15318-3.rs b/tests/rustdoc/issue-15318-3.rs
index 2fadc26b006..2dab8f94883 100644
--- a/tests/rustdoc/issue-15318-3.rs
+++ b/tests/rustdoc/issue-15318-3.rs
@@ -1,7 +1,7 @@
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
 // @has issue_15318_3/primitive.pointer.html
 
 /// dox
-#[doc(primitive = "pointer")]
+#[rustc_doc_primitive = "pointer"]
 pub mod ptr {}
diff --git a/tests/rustdoc/issue-23511.rs b/tests/rustdoc/issue-23511.rs
index 7576ebb0305..21d02842431 100644
--- a/tests/rustdoc/issue-23511.rs
+++ b/tests/rustdoc/issue-23511.rs
@@ -3,7 +3,7 @@
 #![no_std]
 
 pub mod str {
-    #![doc(primitive = "str")]
+    #![rustc_doc_primitive = "str"]
 
     impl str {
         // @hasraw search-index.js foo
diff --git a/tests/rustdoc/notable-trait/doc-notable_trait-mut_t_is_not_an_iterator.rs b/tests/rustdoc/notable-trait/doc-notable_trait-mut_t_is_not_an_iterator.rs
index bfce46cf444..5af5f7616b5 100644
--- a/tests/rustdoc/notable-trait/doc-notable_trait-mut_t_is_not_an_iterator.rs
+++ b/tests/rustdoc/notable-trait/doc-notable_trait-mut_t_is_not_an_iterator.rs
@@ -6,10 +6,10 @@
 //!
 //! [#80737]: https://github.com/rust-lang/rust/issues/80737
 
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 #![no_std]
 
-#[doc(primitive = "reference")]
+#[rustc_doc_primitive = "reference"]
 /// Some useless docs, wouhou!
 ///
 /// We need to put this in here, because notable traits
diff --git a/tests/rustdoc/notable-trait/doc-notable_trait-mut_t_is_not_ref_t.rs b/tests/rustdoc/notable-trait/doc-notable_trait-mut_t_is_not_ref_t.rs
index b359dcea0ff..6c980aaa2b1 100644
--- a/tests/rustdoc/notable-trait/doc-notable_trait-mut_t_is_not_ref_t.rs
+++ b/tests/rustdoc/notable-trait/doc-notable_trait-mut_t_is_not_ref_t.rs
@@ -5,9 +5,9 @@
 //!
 //! [#78160]: https://github.com/rust-lang/rust/issues/78160
 
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
-#[doc(primitive = "reference")]
+#[rustc_doc_primitive = "reference"]
 /// Some useless docs, wouhou!
 ///
 /// We need to put this in here, because notable traits
diff --git a/tests/rustdoc/primitive-reference.rs b/tests/rustdoc/primitive-reference.rs
index 10efbefd2b1..6f034224df5 100644
--- a/tests/rustdoc/primitive-reference.rs
+++ b/tests/rustdoc/primitive-reference.rs
@@ -1,6 +1,6 @@
 #![crate_name = "foo"]
 
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
 // @has foo/index.html
 // @has - '//h2[@id="primitives"]' 'Primitive Types'
@@ -16,7 +16,7 @@
 // @count - '//*[@class="impl"]' 1
 // @has - '//*[@id="impl-Foo%3C%26A%3E-for-%26B"]/*[@class="code-header"]' \
 //        'impl<A, B> Foo<&A> for &B'
-#[doc(primitive = "reference")]
+#[rustc_doc_primitive = "reference"]
 /// this is a test!
 mod reference {}
 
diff --git a/tests/rustdoc/primitive-slice-auto-trait.rs b/tests/rustdoc/primitive-slice-auto-trait.rs
index 77922414676..ba15a73ca1d 100644
--- a/tests/rustdoc/primitive-slice-auto-trait.rs
+++ b/tests/rustdoc/primitive-slice-auto-trait.rs
@@ -1,7 +1,7 @@
 // compile-flags: --crate-type lib --edition 2018
 
 #![crate_name = "foo"]
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
 // @has foo/primitive.slice.html '//a[@class="primitive"]' 'slice'
 // @has - '//h1' 'Primitive Type slice'
@@ -9,6 +9,6 @@
 // @has - '//h2[@id="synthetic-implementations"]' 'Auto Trait Implementations'
 // @has - '//div[@id="synthetic-implementations-list"]//h3' 'impl<T> Send for [T]where T: Send'
 // @has - '//div[@id="synthetic-implementations-list"]//h3' 'impl<T> Sync for [T]where T: Sync'
-#[doc(primitive = "slice")]
+#[rustc_doc_primitive = "slice"]
 /// this is a test!
 mod slice_prim {}
diff --git a/tests/rustdoc/primitive-tuple-auto-trait.rs b/tests/rustdoc/primitive-tuple-auto-trait.rs
index 4344d24f986..2b407b586a3 100644
--- a/tests/rustdoc/primitive-tuple-auto-trait.rs
+++ b/tests/rustdoc/primitive-tuple-auto-trait.rs
@@ -1,7 +1,7 @@
 // compile-flags: --crate-type lib --edition 2018
 
 #![crate_name = "foo"]
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
 // @has foo/primitive.tuple.html '//a[@class="primitive"]' 'tuple'
 // @has - '//h1' 'Primitive Type tuple'
@@ -9,7 +9,7 @@
 // @has - '//h2[@id="synthetic-implementations"]' 'Auto Trait Implementations'
 // @has - '//div[@id="synthetic-implementations-list"]//h3' 'Send'
 // @has - '//div[@id="synthetic-implementations-list"]//h3' 'Sync'
-#[doc(primitive = "tuple")]
+#[rustc_doc_primitive = "tuple"]
 /// this is a test!
 ///
 // Hardcoded anchor to header written in library/core/src/primitive_docs.rs
diff --git a/tests/rustdoc/primitive-unit-auto-trait.rs b/tests/rustdoc/primitive-unit-auto-trait.rs
index 61850e2462d..5a56f1fd83b 100644
--- a/tests/rustdoc/primitive-unit-auto-trait.rs
+++ b/tests/rustdoc/primitive-unit-auto-trait.rs
@@ -1,7 +1,7 @@
 // compile-flags: --crate-type lib --edition 2018
 
 #![crate_name = "foo"]
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
 // @has foo/primitive.unit.html '//a[@class="primitive"]' 'unit'
 // @has - '//h1' 'Primitive Type unit'
@@ -9,6 +9,6 @@
 // @has - '//h2[@id="synthetic-implementations"]' 'Auto Trait Implementations'
 // @has - '//div[@id="synthetic-implementations-list"]//h3' 'impl Send for ()'
 // @has - '//div[@id="synthetic-implementations-list"]//h3' 'impl Sync for ()'
-#[doc(primitive = "unit")]
+#[rustc_doc_primitive = "unit"]
 /// this is a test!
 mod unit_prim {}
diff --git a/tests/rustdoc/primitive/primitive-generic-impl.rs b/tests/rustdoc/primitive/primitive-generic-impl.rs
index 7b336b39810..2da8ae6ff38 100644
--- a/tests/rustdoc/primitive/primitive-generic-impl.rs
+++ b/tests/rustdoc/primitive/primitive-generic-impl.rs
@@ -1,8 +1,8 @@
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 #![crate_name = "foo"]
 
 // @has foo/primitive.i32.html '//*[@id="impl-ToString-for-i32"]//h3[@class="code-header"]' 'impl<T> ToString for T'
 
-#[doc(primitive = "i32")]
+#[rustc_doc_primitive = "i32"]
 /// Some useless docs, wouhou!
 mod i32 {}
diff --git a/tests/rustdoc/primitive/primitive.rs b/tests/rustdoc/primitive/primitive.rs
index 516c7c0c6fe..32af2636c18 100644
--- a/tests/rustdoc/primitive/primitive.rs
+++ b/tests/rustdoc/primitive/primitive.rs
@@ -1,6 +1,6 @@
 #![crate_name = "foo"]
 
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
 // @has foo/index.html '//h2[@id="primitives"]' 'Primitive Types'
 // @has foo/index.html '//a[@href="primitive.i32.html"]' 'i32'
@@ -11,11 +11,11 @@
 // @has foo/primitive.i32.html '//section[@id="main-content"]//div[@class="docblock"]//p' 'this is a test!'
 // @has foo/index.html '//a/@href' '../foo/index.html'
 // @!has foo/index.html '//span' '🔒'
-#[doc(primitive = "i32")]
+#[rustc_doc_primitive = "i32"]
 /// this is a test!
 mod i32{}
 
 // @has foo/primitive.bool.html '//section[@id="main-content"]//div[@class="docblock"]//p' 'hello'
-#[doc(primitive = "bool")]
+#[rustc_doc_primitive = "bool"]
 /// hello
 mod bool {}
diff --git a/tests/rustdoc/sidebar-all-page.rs b/tests/rustdoc/sidebar-all-page.rs
index e74b981de64..45a6ba8ed2e 100644
--- a/tests/rustdoc/sidebar-all-page.rs
+++ b/tests/rustdoc/sidebar-all-page.rs
@@ -1,6 +1,6 @@
 #![crate_name = "foo"]
 
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
 // @has 'foo/all.html'
 // @has - '//*[@class="sidebar-elems"]//li' 'Structs'
@@ -31,5 +31,5 @@ macro_rules! foo {
 pub type Type = u8;
 pub const FOO: u8 = 0;
 pub static BAR: u8 = 0;
-#[doc(primitive = "u8")]
+#[rustc_doc_primitive = "u8"]
 mod u8 {}
diff --git a/tests/rustdoc/tab_title.rs b/tests/rustdoc/tab_title.rs
index 0cc4f147e1c..8d781b40e46 100644
--- a/tests/rustdoc/tab_title.rs
+++ b/tests/rustdoc/tab_title.rs
@@ -1,4 +1,5 @@
 #![crate_name = "foo"]
+#![feature(rustc_attrs)]
 #![feature(rustdoc_internals)]
 
 // tests for the html <title> element
@@ -39,6 +40,6 @@ mod continue_keyword {}
 
 // @has foo/primitive.u8.html '//head/title' 'u8 - Rust'
 // @!has - '//head/title' 'foo'
-#[doc(primitive = "u8")]
+#[rustc_doc_primitive = "u8"]
 /// `u8` docs
 mod u8 {}
diff --git a/tests/rustdoc/titles.rs b/tests/rustdoc/titles.rs
index 69e8b856b0a..e1feb1cd64f 100644
--- a/tests/rustdoc/titles.rs
+++ b/tests/rustdoc/titles.rs
@@ -1,5 +1,5 @@
 #![crate_name = "foo"]
-#![feature(rustdoc_internals)]
+#![feature(rustc_attrs)]
 
 // @matches 'foo/index.html' '//h1' 'Crate foo'
 // @matches 'foo/index.html' '//h2[@class="location"]' 'Crate foo'
@@ -41,7 +41,7 @@ macro_rules! foo_macro {
 }
 
 // @matches 'foo/primitive.bool.html' '//h1' 'Primitive Type bool'
-#[doc(primitive = "bool")]
+#[rustc_doc_primitive = "bool"]
 mod bool {}
 
 // @matches 'foo/static.FOO_STATIC.html' '//h1' 'Static foo::FOO_STATIC'
diff --git a/tests/ui-fulldeps/fluent-messages/test.rs b/tests/ui-fulldeps/fluent-messages/test.rs
index 1ee7227a8e9..6ba13387b04 100644
--- a/tests/ui-fulldeps/fluent-messages/test.rs
+++ b/tests/ui-fulldeps/fluent-messages/test.rs
@@ -1,4 +1,4 @@
-// normalize-stderr-test "note.*" -> "note: os-specific message"
+// normalize-stderr-test "could not open Fluent resource:.*" -> "could not open Fluent resource: os-specific message"
 
 #![feature(rustc_private)]
 #![crate_type = "lib"]
diff --git a/tests/ui-fulldeps/fluent-messages/test.stderr b/tests/ui-fulldeps/fluent-messages/test.stderr
index 8a6a4a91cc2..2affe621c11 100644
--- a/tests/ui-fulldeps/fluent-messages/test.stderr
+++ b/tests/ui-fulldeps/fluent-messages/test.stderr
@@ -1,18 +1,14 @@
-error: could not open Fluent resource
+error: could not open Fluent resource: os-specific message
   --> $DIR/test.rs:24:24
    |
 LL |     fluent_messages! { "/definitely_does_not_exist.ftl" }
    |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: os-specific message
 
-error: could not open Fluent resource
+error: could not open Fluent resource: os-specific message
   --> $DIR/test.rs:31:24
    |
 LL |     fluent_messages! { "../definitely_does_not_exist.ftl" }
    |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: os-specific message
 
 error: could not parse Fluent resource
   --> $DIR/test.rs:38:24
@@ -89,7 +85,7 @@ error: invalid escape `\n` in Fluent resource
 LL |     fluent_messages! { "./invalid-escape.ftl" }
    |                        ^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: os-specific message
+   = note: Fluent does not interpret these escape sequences (<https://projectfluent.org/fluent/guide/special.html>)
 
 error: invalid escape `\"` in Fluent resource
   --> $DIR/test.rs:99:24
@@ -97,7 +93,7 @@ error: invalid escape `\"` in Fluent resource
 LL |     fluent_messages! { "./invalid-escape.ftl" }
    |                        ^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: os-specific message
+   = note: Fluent does not interpret these escape sequences (<https://projectfluent.org/fluent/guide/special.html>)
 
 error: invalid escape `\'` in Fluent resource
   --> $DIR/test.rs:99:24
@@ -105,7 +101,7 @@ error: invalid escape `\'` in Fluent resource
 LL |     fluent_messages! { "./invalid-escape.ftl" }
    |                        ^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: os-specific message
+   = note: Fluent does not interpret these escape sequences (<https://projectfluent.org/fluent/guide/special.html>)
 
 error: aborting due to 13 previous errors
 
diff --git a/tests/ui/closures/2229_closure_analysis/array_subslice.rs b/tests/ui/closures/2229_closure_analysis/array_subslice.rs
new file mode 100644
index 00000000000..5f244ea8936
--- /dev/null
+++ b/tests/ui/closures/2229_closure_analysis/array_subslice.rs
@@ -0,0 +1,13 @@
+// regression test for #109298
+// edition: 2021
+
+pub fn subslice_array(x: [u8; 3]) {
+    let f = || {
+        let [_x @ ..] = x;
+        let [ref y, ref mut z @ ..] = x; //~ ERROR cannot borrow `x[..]` as mutable
+    };
+
+    f(); //~ ERROR cannot borrow `f` as mutable
+}
+
+fn main() {}
diff --git a/tests/ui/closures/2229_closure_analysis/array_subslice.stderr b/tests/ui/closures/2229_closure_analysis/array_subslice.stderr
new file mode 100644
index 00000000000..888c60d5e91
--- /dev/null
+++ b/tests/ui/closures/2229_closure_analysis/array_subslice.stderr
@@ -0,0 +1,26 @@
+error[E0596]: cannot borrow `x[..]` as mutable, as `x` is not declared as mutable
+  --> $DIR/array_subslice.rs:7:21
+   |
+LL | pub fn subslice_array(x: [u8; 3]) {
+   |                       - help: consider changing this to be mutable: `mut x`
+...
+LL |         let [ref y, ref mut z @ ..] = x;
+   |                     ^^^^^^^^^ cannot borrow as mutable
+
+error[E0596]: cannot borrow `f` as mutable, as it is not declared as mutable
+  --> $DIR/array_subslice.rs:10:5
+   |
+LL |         let [ref y, ref mut z @ ..] = x;
+   |                                       - calling `f` requires mutable binding due to mutable borrow of `x`
+...
+LL |     f();
+   |     ^ cannot borrow as mutable
+   |
+help: consider changing this to be mutable
+   |
+LL |     let mut f = || {
+   |         +++
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0596`.
diff --git a/tests/ui/rustdoc/doc-primitive.rs b/tests/ui/rustdoc/doc-primitive.rs
new file mode 100644
index 00000000000..4336961e3b5
--- /dev/null
+++ b/tests/ui/rustdoc/doc-primitive.rs
@@ -0,0 +1,8 @@
+#![deny(invalid_doc_attributes)]
+
+#[doc(primitive = "foo")]
+//~^ ERROR unknown `doc` attribute `primitive`
+//~| WARN
+mod bar {}
+
+fn main() {}
diff --git a/tests/ui/rustdoc/doc-primitive.stderr b/tests/ui/rustdoc/doc-primitive.stderr
new file mode 100644
index 00000000000..d61eb381647
--- /dev/null
+++ b/tests/ui/rustdoc/doc-primitive.stderr
@@ -0,0 +1,16 @@
+error: unknown `doc` attribute `primitive`
+  --> $DIR/doc-primitive.rs:3:7
+   |
+LL | #[doc(primitive = "foo")]
+   |       ^^^^^^^^^^^^^^^^^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
+note: the lint level is defined here
+  --> $DIR/doc-primitive.rs:1:9
+   |
+LL | #![deny(invalid_doc_attributes)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to previous error
+
diff --git a/tests/ui/rustdoc/feature-gate-doc_primitive.rs b/tests/ui/rustdoc/feature-gate-doc_primitive.rs
index 18e99e72f8b..78fcd90752e 100644
--- a/tests/ui/rustdoc/feature-gate-doc_primitive.rs
+++ b/tests/ui/rustdoc/feature-gate-doc_primitive.rs
@@ -1,7 +1,5 @@
-// check-pass
-#[doc(primitive = "usize")]
-//~^ WARNING `doc(primitive)` should never have been stable
-//~| WARNING hard error in a future release
+#[rustc_doc_primitive = "usize"]
+//~^ ERROR `rustc_doc_primitive` is a rustc internal attribute
 /// Some docs
 mod usize {}
 
diff --git a/tests/ui/rustdoc/feature-gate-doc_primitive.stderr b/tests/ui/rustdoc/feature-gate-doc_primitive.stderr
index 194b2d87db2..5920880675d 100644
--- a/tests/ui/rustdoc/feature-gate-doc_primitive.stderr
+++ b/tests/ui/rustdoc/feature-gate-doc_primitive.stderr
@@ -1,12 +1,11 @@
-warning: `doc(primitive)` should never have been stable
-  --> $DIR/feature-gate-doc_primitive.rs:2:7
+error[E0658]: `rustc_doc_primitive` is a rustc internal attribute
+  --> $DIR/feature-gate-doc_primitive.rs:1:1
    |
-LL | #[doc(primitive = "usize")]
-   |       ^^^^^^^^^^^^^^^^^^^
+LL | #[rustc_doc_primitive = "usize"]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
-   = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: `#[warn(invalid_doc_attributes)]` on by default
+   = help: add `#![feature(rustc_attrs)]` to the crate attributes to enable
 
-warning: 1 warning emitted
+error: aborting due to previous error
 
+For more information about this error, try `rustc --explain E0658`.