about summary refs log tree commit diff
path: root/src/librustdoc
diff options
context:
space:
mode:
authorLaurențiu Nicola <lnicola@dend.ro>2025-06-09 15:44:40 +0300
committerLaurențiu Nicola <lnicola@dend.ro>2025-06-09 15:44:40 +0300
commit88223c56d9352a14bf4e91d706d68ca3a696bcdf (patch)
tree1fa465adaaf07355079312d2e1aa3e8594acadc7 /src/librustdoc
parentcbe6fe86ef60ceedd46128df8f09da982f44191a (diff)
parent7c10378e1fee5ddc6573b916aeb884ab10e0de17 (diff)
Merge from rust-lang/rust
Diffstat (limited to 'src/librustdoc')
-rw-r--r--src/librustdoc/Cargo.toml2
-rw-r--r--src/librustdoc/clean/cfg.rs16
-rw-r--r--src/librustdoc/clean/mod.rs174
-rw-r--r--src/librustdoc/clean/simplify.rs7
-rw-r--r--src/librustdoc/clean/types.rs230
-rw-r--r--src/librustdoc/clean/utils.rs57
-rw-r--r--src/librustdoc/clean/utils/tests.rs42
-rw-r--r--src/librustdoc/core.rs4
-rw-r--r--src/librustdoc/doctest.rs75
-rw-r--r--src/librustdoc/doctest/extracted.rs20
-rw-r--r--src/librustdoc/doctest/make.rs167
-rw-r--r--src/librustdoc/doctest/markdown.rs15
-rw-r--r--src/librustdoc/doctest/runner.rs11
-rw-r--r--src/librustdoc/doctest/rust.rs52
-rw-r--r--src/librustdoc/doctest/tests.rs86
-rw-r--r--src/librustdoc/html/format.rs187
-rw-r--r--src/librustdoc/html/highlight.rs45
-rw-r--r--src/librustdoc/html/layout.rs20
-rw-r--r--src/librustdoc/html/markdown.rs15
-rw-r--r--src/librustdoc/html/render/context.rs15
-rw-r--r--src/librustdoc/html/render/mod.rs73
-rw-r--r--src/librustdoc/html/render/print_item.rs382
-rw-r--r--src/librustdoc/html/render/sidebar.rs14
-rw-r--r--src/librustdoc/html/render/write_shared.rs100
-rw-r--r--src/librustdoc/html/sources.rs21
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css5
-rw-r--r--src/librustdoc/html/static/js/main.js4
-rw-r--r--src/librustdoc/html/templates/item_union.html9
-rw-r--r--src/librustdoc/html/tests.rs22
-rw-r--r--src/librustdoc/html/url_parts_builder.rs1
-rw-r--r--src/librustdoc/json/conversions.rs25
-rw-r--r--src/librustdoc/lib.rs2
-rw-r--r--src/librustdoc/passes/calculate_doc_coverage.rs2
-rw-r--r--src/librustdoc/passes/check_doc_cfg.rs76
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs43
-rw-r--r--src/librustdoc/passes/lint/bare_urls.rs12
-rw-r--r--src/librustdoc/passes/mod.rs5
-rw-r--r--src/librustdoc/visit_ast.rs2
38 files changed, 1174 insertions, 864 deletions
diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml
index dbfdd8ebd16..bba8e630bcc 100644
--- a/src/librustdoc/Cargo.toml
+++ b/src/librustdoc/Cargo.toml
@@ -9,7 +9,7 @@ path = "lib.rs"
 
 [dependencies]
 arrayvec = { version = "0.7", default-features = false }
-askama = { version = "0.13", default-features = false, features = ["alloc", "config", "derive"] }
+askama = { version = "0.14", default-features = false, features = ["alloc", "config", "derive"] }
 base64 = "0.21.7"
 itertools = "0.12"
 indexmap = "2"
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
index 439777843fb..a3762e4117d 100644
--- a/src/librustdoc/clean/cfg.rs
+++ b/src/librustdoc/clean/cfg.rs
@@ -171,10 +171,15 @@ impl Cfg {
 
     /// Renders the configuration for long display, as a long HTML description.
     pub(crate) fn render_long_html(&self) -> String {
-        let on = if self.should_use_with_in_description() { "with" } else { "on" };
+        let on = if self.omit_preposition() {
+            ""
+        } else if self.should_use_with_in_description() {
+            "with "
+        } else {
+            "on "
+        };
 
-        let mut msg =
-            format!("Available {on} <strong>{}</strong>", Display(self, Format::LongHtml));
+        let mut msg = format!("Available {on}<strong>{}</strong>", Display(self, Format::LongHtml));
         if self.should_append_only_to_description() {
             msg.push_str(" only");
         }
@@ -244,6 +249,10 @@ impl Cfg {
             Some(self.clone())
         }
     }
+
+    fn omit_preposition(&self) -> bool {
+        matches!(self, Cfg::True | Cfg::False)
+    }
 }
 
 impl ops::Not for Cfg {
@@ -499,6 +508,7 @@ impl fmt::Display for Display<'_> {
                     (sym::target_arch, Some(arch)) => match arch.as_str() {
                         "aarch64" => "AArch64",
                         "arm" => "ARM",
+                        "loongarch32" => "LoongArch LA32",
                         "loongarch64" => "LoongArch LA64",
                         "m68k" => "M68k",
                         "csky" => "CSKY",
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 28dfa01534e..7658e7ad35f 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -371,10 +371,9 @@ fn clean_where_predicate<'tcx>(
             bounds: wrp.bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(),
         },
 
-        hir::WherePredicateKind::EqPredicate(wrp) => WherePredicate::EqPredicate {
-            lhs: clean_ty(wrp.lhs_ty, cx),
-            rhs: clean_ty(wrp.rhs_ty, cx).into(),
-        },
+        // We should never actually reach this case because these predicates should've already been
+        // rejected in an earlier compiler pass. This feature isn't fully implemented (#20041).
+        hir::WherePredicateKind::EqPredicate(_) => bug!("EqPredicate"),
     })
 }
 
@@ -449,7 +448,7 @@ fn clean_middle_term<'tcx>(
     term: ty::Binder<'tcx, ty::Term<'tcx>>,
     cx: &mut DocContext<'tcx>,
 ) -> Term {
-    match term.skip_binder().unpack() {
+    match term.skip_binder().kind() {
         ty::TermKind::Ty(ty) => Term::Type(clean_middle_ty(term.rebind(ty), cx, None, None)),
         ty::TermKind::Const(c) => Term::Constant(clean_middle_const(term.rebind(c), cx)),
     }
@@ -470,49 +469,37 @@ fn clean_projection_predicate<'tcx>(
     cx: &mut DocContext<'tcx>,
 ) -> WherePredicate {
     WherePredicate::EqPredicate {
-        lhs: clean_projection(
-            pred.map_bound(|p| {
-                // FIXME: This needs to be made resilient for `AliasTerm`s that
-                // are associated consts.
-                p.projection_term.expect_ty(cx.tcx)
-            }),
-            cx,
-            None,
-        ),
+        lhs: clean_projection(pred.map_bound(|p| p.projection_term), cx, None),
         rhs: clean_middle_term(pred.map_bound(|p| p.term), cx),
     }
 }
 
 fn clean_projection<'tcx>(
-    ty: ty::Binder<'tcx, ty::AliasTy<'tcx>>,
+    proj: ty::Binder<'tcx, ty::AliasTerm<'tcx>>,
     cx: &mut DocContext<'tcx>,
-    def_id: Option<DefId>,
-) -> Type {
-    if cx.tcx.is_impl_trait_in_trait(ty.skip_binder().def_id) {
-        return clean_middle_opaque_bounds(cx, ty.skip_binder().def_id, ty.skip_binder().args);
-    }
-
+    parent_def_id: Option<DefId>,
+) -> QPathData {
     let trait_ = clean_trait_ref_with_constraints(
         cx,
-        ty.map_bound(|ty| ty.trait_ref(cx.tcx)),
+        proj.map_bound(|proj| proj.trait_ref(cx.tcx)),
         ThinVec::new(),
     );
-    let self_type = clean_middle_ty(ty.map_bound(|ty| ty.self_ty()), cx, None, None);
-    let self_def_id = if let Some(def_id) = def_id {
-        cx.tcx.opt_parent(def_id).or(Some(def_id))
-    } else {
-        self_type.def_id(&cx.cache)
+    let self_type = clean_middle_ty(proj.map_bound(|proj| proj.self_ty()), cx, None, None);
+    let self_def_id = match parent_def_id {
+        Some(parent_def_id) => cx.tcx.opt_parent(parent_def_id).or(Some(parent_def_id)),
+        None => self_type.def_id(&cx.cache),
     };
-    let should_show_cast = compute_should_show_cast(self_def_id, &trait_, &self_type);
-    Type::QPath(Box::new(QPathData {
-        assoc: projection_to_path_segment(ty, cx),
-        should_show_cast,
+    let should_fully_qualify = should_fully_qualify_path(self_def_id, &trait_, &self_type);
+
+    QPathData {
+        assoc: projection_to_path_segment(proj, cx),
         self_type,
+        should_fully_qualify,
         trait_: Some(trait_),
-    }))
+    }
 }
 
-fn compute_should_show_cast(self_def_id: Option<DefId>, trait_: &Path, self_type: &Type) -> bool {
+fn should_fully_qualify_path(self_def_id: Option<DefId>, trait_: &Path, self_type: &Type) -> bool {
     !trait_.segments.is_empty()
         && self_def_id
             .zip(Some(trait_.def_id()))
@@ -520,18 +507,17 @@ fn compute_should_show_cast(self_def_id: Option<DefId>, trait_: &Path, self_type
 }
 
 fn projection_to_path_segment<'tcx>(
-    ty: ty::Binder<'tcx, ty::AliasTy<'tcx>>,
+    proj: ty::Binder<'tcx, ty::AliasTerm<'tcx>>,
     cx: &mut DocContext<'tcx>,
 ) -> PathSegment {
-    let def_id = ty.skip_binder().def_id;
-    let item = cx.tcx.associated_item(def_id);
+    let def_id = proj.skip_binder().def_id;
     let generics = cx.tcx.generics_of(def_id);
     PathSegment {
-        name: item.name(),
+        name: cx.tcx.item_name(def_id),
         args: GenericArgs::AngleBracketed {
             args: clean_middle_generic_args(
                 cx,
-                ty.map_bound(|ty| &ty.args[generics.parent_count..]),
+                proj.map_bound(|ty| &ty.args[generics.parent_count..]),
                 false,
                 def_id,
             ),
@@ -845,7 +831,7 @@ fn clean_ty_generics<'tcx>(
         .predicates
         .iter()
         .flat_map(|(pred, _)| {
-            let mut projection = None;
+            let mut proj_pred = None;
             let param_idx = {
                 let bound_p = pred.kind();
                 match bound_p.skip_binder() {
@@ -860,7 +846,7 @@ fn clean_ty_generics<'tcx>(
                     ty::ClauseKind::Projection(p)
                         if let ty::Param(param) = p.projection_term.self_ty().kind() =>
                     {
-                        projection = Some(bound_p.rebind(p));
+                        proj_pred = Some(bound_p.rebind(p));
                         Some(param.index)
                     }
                     _ => None,
@@ -874,22 +860,12 @@ fn clean_ty_generics<'tcx>(
 
                 bounds.extend(pred.get_bounds().into_iter().flatten().cloned());
 
-                if let Some(proj) = projection
-                    && let lhs = clean_projection(
-                        proj.map_bound(|p| {
-                            // FIXME: This needs to be made resilient for `AliasTerm`s that
-                            // are associated consts.
-                            p.projection_term.expect_ty(cx.tcx)
-                        }),
-                        cx,
-                        None,
-                    )
-                    && let Some((_, trait_did, name)) = lhs.projection()
-                {
+                if let Some(pred) = proj_pred {
+                    let lhs = clean_projection(pred.map_bound(|p| p.projection_term), cx, None);
                     impl_trait_proj.entry(param_idx).or_default().push((
-                        trait_did,
-                        name,
-                        proj.map_bound(|p| p.term),
+                        lhs.trait_.unwrap().def_id(),
+                        lhs.assoc,
+                        pred.map_bound(|p| p.term),
                     ));
                 }
 
@@ -1606,7 +1582,7 @@ fn first_non_private<'tcx>(
                     && let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(local_use_def_id)
                     && let hir::ItemKind::Use(path, hir::UseKind::Single(_)) = item.kind
                 {
-                    for res in &path.res {
+                    for res in path.res.present_items() {
                         if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = res {
                             continue;
                         }
@@ -1695,10 +1671,11 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type
             register_res(cx, trait_.res);
             let self_def_id = DefId::local(qself.hir_id.owner.def_id.local_def_index);
             let self_type = clean_ty(qself, cx);
-            let should_show_cast = compute_should_show_cast(Some(self_def_id), &trait_, &self_type);
+            let should_fully_qualify =
+                should_fully_qualify_path(Some(self_def_id), &trait_, &self_type);
             Type::QPath(Box::new(QPathData {
                 assoc: clean_path_segment(p.segments.last().expect("segments were empty"), cx),
-                should_show_cast,
+                should_fully_qualify,
                 self_type,
                 trait_: Some(trait_),
             }))
@@ -1707,16 +1684,16 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type
             let ty = lower_ty(cx.tcx, hir_ty);
             let self_type = clean_ty(qself, cx);
 
-            let (trait_, should_show_cast) = match ty.kind() {
+            let (trait_, should_fully_qualify) = match ty.kind() {
                 ty::Alias(ty::Projection, proj) => {
                     let res = Res::Def(DefKind::Trait, proj.trait_ref(cx.tcx).def_id);
                     let trait_ = clean_path(&hir::Path { span, res, segments: &[] }, cx);
                     register_res(cx, trait_.res);
                     let self_def_id = res.opt_def_id();
-                    let should_show_cast =
-                        compute_should_show_cast(self_def_id, &trait_, &self_type);
+                    let should_fully_qualify =
+                        should_fully_qualify_path(self_def_id, &trait_, &self_type);
 
-                    (Some(trait_), should_show_cast)
+                    (Some(trait_), should_fully_qualify)
                 }
                 ty::Alias(ty::Inherent, _) => (None, false),
                 // Rustdoc handles `ty::Error`s by turning them into `Type::Infer`s.
@@ -1726,7 +1703,7 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type
 
             Type::QPath(Box::new(QPathData {
                 assoc: clean_path_segment(segment, cx),
-                should_show_cast,
+                should_fully_qualify,
                 self_type,
                 trait_,
             }))
@@ -1749,7 +1726,7 @@ fn maybe_expand_private_type_alias<'tcx>(
     } else {
         return None;
     };
-    let hir::ItemKind::TyAlias(_, ty, generics) = alias else { return None };
+    let hir::ItemKind::TyAlias(_, generics, ty) = alias else { return None };
 
     let final_seg = &path.segments.last().expect("segments were empty");
     let mut args = DefIdMap::default();
@@ -2145,14 +2122,8 @@ pub(crate) fn clean_middle_ty<'tcx>(
                 .map(|pb| AssocItemConstraint {
                     assoc: projection_to_path_segment(
                         pb.map_bound(|pb| {
-                            pb
-                                // HACK(compiler-errors): Doesn't actually matter what self
-                                // type we put here, because we're only using the GAT's args.
-                                .with_self_ty(cx.tcx, cx.tcx.types.self_param)
+                            pb.with_self_ty(cx.tcx, cx.tcx.types.trait_object_dummy_self)
                                 .projection_term
-                                // FIXME: This needs to be made resilient for `AliasTerm`s
-                                // that are associated consts.
-                                .expect_ty(cx.tcx)
                         }),
                         cx,
                     ),
@@ -2185,18 +2156,25 @@ pub(crate) fn clean_middle_ty<'tcx>(
             Tuple(t.iter().map(|t| clean_middle_ty(bound_ty.rebind(t), cx, None, None)).collect())
         }
 
-        ty::Alias(ty::Projection, data) => {
-            clean_projection(bound_ty.rebind(data), cx, parent_def_id)
+        ty::Alias(ty::Projection, alias_ty @ ty::AliasTy { def_id, args, .. }) => {
+            if cx.tcx.is_impl_trait_in_trait(def_id) {
+                clean_middle_opaque_bounds(cx, def_id, args)
+            } else {
+                Type::QPath(Box::new(clean_projection(
+                    bound_ty.rebind(alias_ty.into()),
+                    cx,
+                    parent_def_id,
+                )))
+            }
         }
 
-        ty::Alias(ty::Inherent, alias_ty) => {
-            let def_id = alias_ty.def_id;
+        ty::Alias(ty::Inherent, alias_ty @ ty::AliasTy { def_id, .. }) => {
             let alias_ty = bound_ty.rebind(alias_ty);
             let self_type = clean_middle_ty(alias_ty.map_bound(|ty| ty.self_ty()), cx, None, None);
 
             Type::QPath(Box::new(QPathData {
                 assoc: PathSegment {
-                    name: cx.tcx.associated_item(def_id).name(),
+                    name: cx.tcx.item_name(def_id),
                     args: GenericArgs::AngleBracketed {
                         args: clean_middle_generic_args(
                             cx,
@@ -2207,26 +2185,21 @@ pub(crate) fn clean_middle_ty<'tcx>(
                         constraints: Default::default(),
                     },
                 },
-                should_show_cast: false,
+                should_fully_qualify: false,
                 self_type,
                 trait_: None,
             }))
         }
 
-        ty::Alias(ty::Free, data) => {
+        ty::Alias(ty::Free, ty::AliasTy { def_id, args, .. }) => {
             if cx.tcx.features().lazy_type_alias() {
                 // Free type alias `data` represents the `type X` in `type X = Y`. If we need `Y`,
                 // we need to use `type_of`.
-                let path = clean_middle_path(
-                    cx,
-                    data.def_id,
-                    false,
-                    ThinVec::new(),
-                    bound_ty.rebind(data.args),
-                );
+                let path =
+                    clean_middle_path(cx, def_id, false, ThinVec::new(), bound_ty.rebind(args));
                 Type::Path { path }
             } else {
-                let ty = cx.tcx.type_of(data.def_id).instantiate(cx.tcx, data.args);
+                let ty = cx.tcx.type_of(def_id).instantiate(cx.tcx, args);
                 clean_middle_ty(bound_ty.rebind(ty), cx, None, None)
             }
         }
@@ -2313,18 +2286,17 @@ fn clean_middle_opaque_bounds<'tcx>(
             let bindings: ThinVec<_> = bounds
                 .iter()
                 .filter_map(|(bound, _)| {
-                    if let ty::ClauseKind::Projection(proj) = bound.kind().skip_binder()
-                        && proj.projection_term.trait_ref(cx.tcx) == trait_ref.skip_binder()
+                    let bound = bound.kind();
+                    if let ty::ClauseKind::Projection(proj_pred) = bound.skip_binder()
+                        && proj_pred.projection_term.trait_ref(cx.tcx) == trait_ref.skip_binder()
                     {
                         return Some(AssocItemConstraint {
                             assoc: projection_to_path_segment(
-                                // FIXME: This needs to be made resilient for `AliasTerm`s that
-                                // are associated consts.
-                                bound.kind().rebind(proj.projection_term.expect_ty(cx.tcx)),
+                                bound.rebind(proj_pred.projection_term),
                                 cx,
                             ),
                             kind: AssocItemConstraintKind::Equality {
-                                term: clean_middle_term(bound.kind().rebind(proj.term), cx),
+                                term: clean_middle_term(bound.rebind(proj_pred.term), cx),
                             },
                         });
                     }
@@ -2803,21 +2775,21 @@ fn clean_maybe_renamed_item<'tcx>(
         let mut name = get_name(cx, item, renamed).unwrap();
 
         let kind = match item.kind {
-            ItemKind::Static(_, ty, mutability, body_id) => StaticItem(Static {
+            ItemKind::Static(mutability, _, ty, body_id) => StaticItem(Static {
                 type_: Box::new(clean_ty(ty, cx)),
                 mutability,
                 expr: Some(body_id),
             }),
-            ItemKind::Const(_, ty, generics, body_id) => ConstantItem(Box::new(Constant {
+            ItemKind::Const(_, generics, ty, body_id) => ConstantItem(Box::new(Constant {
                 generics: clean_generics(generics, cx),
                 type_: clean_ty(ty, cx),
                 kind: ConstantKind::Local { body: body_id, def_id },
             })),
-            ItemKind::TyAlias(_, hir_ty, generics) => {
+            ItemKind::TyAlias(_, generics, ty) => {
                 *cx.current_type_aliases.entry(def_id).or_insert(0) += 1;
-                let rustdoc_ty = clean_ty(hir_ty, cx);
+                let rustdoc_ty = clean_ty(ty, cx);
                 let type_ =
-                    clean_middle_ty(ty::Binder::dummy(lower_ty(cx.tcx, hir_ty)), cx, None, None);
+                    clean_middle_ty(ty::Binder::dummy(lower_ty(cx.tcx, ty)), cx, None, None);
                 let generics = clean_generics(generics, cx);
                 if let Some(count) = cx.current_type_aliases.get_mut(&def_id) {
                     *count -= 1;
@@ -2846,7 +2818,7 @@ fn clean_maybe_renamed_item<'tcx>(
                 ));
                 return ret;
             }
-            ItemKind::Enum(_, def, generics) => EnumItem(Enum {
+            ItemKind::Enum(_, generics, def) => EnumItem(Enum {
                 variants: def.variants.iter().map(|v| clean_variant(v, cx)).collect(),
                 generics: clean_generics(generics, cx),
             }),
@@ -2854,11 +2826,11 @@ fn clean_maybe_renamed_item<'tcx>(
                 generics: clean_generics(generics, cx),
                 bounds: bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(),
             }),
-            ItemKind::Union(_, variant_data, generics) => UnionItem(Union {
+            ItemKind::Union(_, generics, variant_data) => UnionItem(Union {
                 generics: clean_generics(generics, cx),
                 fields: variant_data.fields().iter().map(|x| clean_field(x, cx)).collect(),
             }),
-            ItemKind::Struct(_, variant_data, generics) => StructItem(Struct {
+            ItemKind::Struct(_, generics, variant_data) => StructItem(Struct {
                 ctor_kind: variant_data.ctor_kind(),
                 generics: clean_generics(generics, cx),
                 fields: variant_data.fields().iter().map(|x| clean_field(x, cx)).collect(),
@@ -3014,7 +2986,7 @@ fn clean_use_statement<'tcx>(
 ) -> Vec<Item> {
     let mut items = Vec::new();
     let hir::UsePath { segments, ref res, span } = *path;
-    for &res in res {
+    for res in res.present_items() {
         let path = hir::Path { segments, res, span };
         items.append(&mut clean_use_statement_inner(import, name, &path, kind, cx, inlined_names));
     }
diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs
index 41943b94d1e..40efa997868 100644
--- a/src/librustdoc/clean/simplify.rs
+++ b/src/librustdoc/clean/simplify.rs
@@ -46,11 +46,8 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: ThinVec<WP>) -> ThinVe
     // Look for equality predicates on associated types that can be merged into
     // general bound predicates.
     equalities.retain(|(lhs, rhs)| {
-        let Some((ty, trait_did, name)) = lhs.projection() else {
-            return true;
-        };
-        let Some((bounds, _)) = tybounds.get_mut(ty) else { return true };
-        merge_bounds(cx, bounds, trait_did, name, rhs)
+        let Some((bounds, _)) = tybounds.get_mut(&lhs.self_type) else { return true };
+        merge_bounds(cx, bounds, lhs.trait_.as_ref().unwrap().def_id(), lhs.assoc.clone(), rhs)
     });
 
     // And finally, let's reassemble everything
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index e45f28444fe..bde1a2e5e66 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -106,7 +106,7 @@ impl From<DefId> for ItemId {
 }
 
 /// The crate currently being documented.
-#[derive(Clone, Debug)]
+#[derive(Debug)]
 pub(crate) struct Crate {
     pub(crate) module: Item,
     /// Only here so that they can be filtered through the rustdoc passes.
@@ -610,6 +610,9 @@ impl Item {
             UnionItem(ref union_) => Some(union_.has_stripped_entries()),
             EnumItem(ref enum_) => Some(enum_.has_stripped_entries()),
             VariantItem(ref v) => v.has_stripped_entries(),
+            TypeAliasItem(ref type_alias) => {
+                type_alias.inner_type.as_ref().and_then(|t| t.has_stripped_entries())
+            }
             _ => None,
         }
     }
@@ -761,33 +764,21 @@ impl Item {
         Some(tcx.visibility(def_id))
     }
 
-    pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Vec<String> {
+    pub(crate) fn attributes_without_repr(&self, tcx: TyCtxt<'_>, is_json: bool) -> Vec<String> {
         const ALLOWED_ATTRIBUTES: &[Symbol] =
             &[sym::export_name, sym::link_section, sym::no_mangle, sym::non_exhaustive];
 
-        use rustc_abi::IntegerType;
-
-        let mut attrs: Vec<String> = self
-            .attrs
+        self.attrs
             .other_attrs
             .iter()
             .filter_map(|attr| {
                 if is_json {
                     match attr {
-                        hir::Attribute::Parsed(AttributeKind::Deprecation { .. }) => {
-                            // rustdoc-json stores this in `Item::deprecation`, so we
-                            // don't want it it `Item::attrs`.
-                            None
-                        }
-                        rustc_hir::Attribute::Parsed(
-                            rustc_attr_data_structures::AttributeKind::Repr(..),
-                        ) => {
-                            // We have separate pretty-printing logic for `#[repr(..)]` attributes.
-                            // For example, there are circumstances where `#[repr(transparent)]`
-                            // is applied but should not be publicly shown in rustdoc
-                            // because it isn't public API.
-                            None
-                        }
+                        // rustdoc-json stores this in `Item::deprecation`, so we
+                        // don't want it it `Item::attrs`.
+                        hir::Attribute::Parsed(AttributeKind::Deprecation { .. }) => None,
+                        // We have separate pretty-printing logic for `#[repr(..)]` attributes.
+                        hir::Attribute::Parsed(AttributeKind::Repr(..)) => None,
                         _ => Some({
                             let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr);
                             assert_eq!(s.pop(), Some('\n'));
@@ -805,73 +796,28 @@ impl Item {
                     None
                 }
             })
-            .collect();
+            .collect()
+    }
 
-        // Add #[repr(...)]
-        if let Some(def_id) = self.def_id()
-            && let ItemType::Struct | ItemType::Enum | ItemType::Union = self.type_()
-        {
-            let adt = tcx.adt_def(def_id);
-            let repr = adt.repr();
-            let mut out = Vec::new();
-            if repr.c() {
-                out.push("C");
-            }
-            if repr.transparent() {
-                // Render `repr(transparent)` iff the non-1-ZST field is public or at least one
-                // field is public in case all fields are 1-ZST fields.
-                let render_transparent = cache.document_private
-                    || adt
-                        .all_fields()
-                        .find(|field| {
-                            let ty =
-                                field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
-                            tcx.layout_of(
-                                ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty),
-                            )
-                            .is_ok_and(|layout| !layout.is_1zst())
-                        })
-                        .map_or_else(
-                            || adt.all_fields().any(|field| field.vis.is_public()),
-                            |field| field.vis.is_public(),
-                        );
+    pub(crate) fn attributes_and_repr(
+        &self,
+        tcx: TyCtxt<'_>,
+        cache: &Cache,
+        is_json: bool,
+    ) -> Vec<String> {
+        let mut attrs = self.attributes_without_repr(tcx, is_json);
 
-                if render_transparent {
-                    out.push("transparent");
-                }
-            }
-            if repr.simd() {
-                out.push("simd");
-            }
-            let pack_s;
-            if let Some(pack) = repr.pack {
-                pack_s = format!("packed({})", pack.bytes());
-                out.push(&pack_s);
-            }
-            let align_s;
-            if let Some(align) = repr.align {
-                align_s = format!("align({})", align.bytes());
-                out.push(&align_s);
-            }
-            let int_s;
-            if let Some(int) = repr.int {
-                int_s = match int {
-                    IntegerType::Pointer(is_signed) => {
-                        format!("{}size", if is_signed { 'i' } else { 'u' })
-                    }
-                    IntegerType::Fixed(size, is_signed) => {
-                        format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
-                    }
-                };
-                out.push(&int_s);
-            }
-            if !out.is_empty() {
-                attrs.push(format!("#[repr({})]", out.join(", ")));
-            }
+        if let Some(repr_attr) = self.repr(tcx, cache, is_json) {
+            attrs.push(repr_attr);
         }
         attrs
     }
 
+    /// Returns a stringified `#[repr(...)]` attribute.
+    pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Option<String> {
+        repr_attributes(tcx, cache, self.def_id()?, self.type_(), is_json)
+    }
+
     pub fn is_doc_hidden(&self) -> bool {
         self.attrs.is_doc_hidden()
     }
@@ -881,6 +827,73 @@ impl Item {
     }
 }
 
+pub(crate) fn repr_attributes(
+    tcx: TyCtxt<'_>,
+    cache: &Cache,
+    def_id: DefId,
+    item_type: ItemType,
+    is_json: bool,
+) -> Option<String> {
+    use rustc_abi::IntegerType;
+
+    if !matches!(item_type, ItemType::Struct | ItemType::Enum | ItemType::Union) {
+        return None;
+    }
+    let adt = tcx.adt_def(def_id);
+    let repr = adt.repr();
+    let mut out = Vec::new();
+    if repr.c() {
+        out.push("C");
+    }
+    if repr.transparent() {
+        // Render `repr(transparent)` iff the non-1-ZST field is public or at least one
+        // field is public in case all fields are 1-ZST fields.
+        let render_transparent = cache.document_private
+            || is_json
+            || adt
+                .all_fields()
+                .find(|field| {
+                    let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
+                    tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty))
+                        .is_ok_and(|layout| !layout.is_1zst())
+                })
+                .map_or_else(
+                    || adt.all_fields().any(|field| field.vis.is_public()),
+                    |field| field.vis.is_public(),
+                );
+
+        if render_transparent {
+            out.push("transparent");
+        }
+    }
+    if repr.simd() {
+        out.push("simd");
+    }
+    let pack_s;
+    if let Some(pack) = repr.pack {
+        pack_s = format!("packed({})", pack.bytes());
+        out.push(&pack_s);
+    }
+    let align_s;
+    if let Some(align) = repr.align {
+        align_s = format!("align({})", align.bytes());
+        out.push(&align_s);
+    }
+    let int_s;
+    if let Some(int) = repr.int {
+        int_s = match int {
+            IntegerType::Pointer(is_signed) => {
+                format!("{}size", if is_signed { 'i' } else { 'u' })
+            }
+            IntegerType::Fixed(size, is_signed) => {
+                format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
+            }
+        };
+        out.push(&int_s);
+    }
+    if !out.is_empty() { Some(format!("#[repr({})]", out.join(", "))) } else { None }
+}
+
 #[derive(Clone, Debug)]
 pub(crate) enum ItemKind {
     ExternCrateItem {
@@ -1043,17 +1056,11 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
             .peekable();
         if doc_cfg.peek().is_some() && doc_cfg_active {
             let sess = tcx.sess;
+
             doc_cfg.fold(Cfg::True, |mut cfg, item| {
                 if let Some(cfg_mi) =
                     item.meta_item().and_then(|item| rustc_expand::config::parse_cfg(item, sess))
                 {
-                    // The result is unused here but we can gate unstable predicates
-                    rustc_attr_parsing::cfg_matches(
-                        cfg_mi,
-                        tcx.sess,
-                        rustc_ast::CRATE_NODE_ID,
-                        Some(tcx.features()),
-                    );
                     match Cfg::parse(cfg_mi) {
                         Ok(new_cfg) => cfg &= new_cfg,
                         Err(e) => {
@@ -1270,7 +1277,7 @@ impl GenericBound {
     }
 
     fn sized_with(cx: &mut DocContext<'_>, modifiers: hir::TraitBoundModifiers) -> GenericBound {
-        let did = cx.tcx.require_lang_item(LangItem::Sized, None);
+        let did = cx.tcx.require_lang_item(LangItem::Sized, DUMMY_SP);
         let empty = ty::Binder::dummy(ty::GenericArgs::empty());
         let path = clean_middle_path(cx, did, false, ThinVec::new(), empty);
         inline::record_extern_fqn(cx, did, ItemType::Trait);
@@ -1334,7 +1341,7 @@ impl PreciseCapturingArg {
 pub(crate) enum WherePredicate {
     BoundPredicate { ty: Type, bounds: Vec<GenericBound>, bound_params: Vec<GenericParamDef> },
     RegionPredicate { lifetime: Lifetime, bounds: Vec<GenericBound> },
-    EqPredicate { lhs: Type, rhs: Term },
+    EqPredicate { lhs: QPathData, rhs: Term },
 }
 
 impl WherePredicate {
@@ -1609,9 +1616,7 @@ impl Type {
                 a.def_id() == b.def_id()
                     && a.generics()
                         .zip(b.generics())
-                        .map(|(ag, bg)| {
-                            ag.iter().zip(bg.iter()).all(|(at, bt)| at.is_doc_subtype_of(bt, cache))
-                        })
+                        .map(|(ag, bg)| ag.zip(bg).all(|(at, bt)| at.is_doc_subtype_of(bt, cache)))
                         .unwrap_or(true)
             }
             // Other cases, such as primitives, just use recursion.
@@ -1684,7 +1689,7 @@ impl Type {
         }
     }
 
-    pub(crate) fn generics(&self) -> Option<Vec<&Type>> {
+    pub(crate) fn generics<'a>(&'a self) -> Option<impl Iterator<Item = &'a Type>> {
         match self {
             Type::Path { path, .. } => path.generics(),
             _ => None,
@@ -1699,14 +1704,6 @@ impl Type {
         matches!(self, Type::Tuple(v) if v.is_empty())
     }
 
-    pub(crate) fn projection(&self) -> Option<(&Type, DefId, PathSegment)> {
-        if let QPath(box QPathData { self_type, trait_, assoc, .. }) = self {
-            Some((self_type, trait_.as_ref()?.def_id(), assoc.clone()))
-        } else {
-            None
-        }
-    }
-
     /// Use this method to get the [DefId] of a [clean] AST node, including [PrimitiveType]s.
     ///
     /// [clean]: crate::clean
@@ -1741,7 +1738,7 @@ pub(crate) struct QPathData {
     pub assoc: PathSegment,
     pub self_type: Type,
     /// FIXME: compute this field on demand.
-    pub should_show_cast: bool,
+    pub should_fully_qualify: bool,
     pub trait_: Option<Path>,
 }
 
@@ -2115,7 +2112,7 @@ impl Enum {
         self.variants.iter().any(|f| f.is_stripped())
     }
 
-    pub(crate) fn variants(&self) -> impl Iterator<Item = &Item> {
+    pub(crate) fn non_stripped_variants(&self) -> impl Iterator<Item = &Item> {
         self.variants.iter().filter(|v| !v.is_stripped())
     }
 }
@@ -2242,17 +2239,13 @@ impl Path {
         self.segments.last().map(|seg| &seg.args)
     }
 
-    pub(crate) fn generics(&self) -> Option<Vec<&Type>> {
+    pub(crate) fn generics<'a>(&'a self) -> Option<impl Iterator<Item = &'a Type>> {
         self.segments.last().and_then(|seg| {
             if let GenericArgs::AngleBracketed { ref args, .. } = seg.args {
-                Some(
-                    args.iter()
-                        .filter_map(|arg| match arg {
-                            GenericArg::Type(ty) => Some(ty),
-                            _ => None,
-                        })
-                        .collect(),
-                )
+                Some(args.iter().filter_map(|arg| match arg {
+                    GenericArg::Type(ty) => Some(ty),
+                    _ => None,
+                }))
             } else {
                 None
             }
@@ -2353,6 +2346,17 @@ pub(crate) enum TypeAliasInnerType {
     Struct { ctor_kind: Option<CtorKind>, fields: Vec<Item> },
 }
 
+impl TypeAliasInnerType {
+    fn has_stripped_entries(&self) -> Option<bool> {
+        Some(match self {
+            Self::Enum { variants, .. } => variants.iter().any(|v| v.is_stripped()),
+            Self::Union { fields } | Self::Struct { fields, .. } => {
+                fields.iter().any(|f| f.is_stripped())
+            }
+        })
+    }
+}
+
 #[derive(Clone, Debug)]
 pub(crate) struct TypeAlias {
     pub(crate) type_: Type,
diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs
index af7986d030e..c58b07a5b67 100644
--- a/src/librustdoc/clean/utils.rs
+++ b/src/librustdoc/clean/utils.rs
@@ -1,7 +1,7 @@
 use std::assert_matches::debug_assert_matches;
 use std::fmt::{self, Display, Write as _};
-use std::mem;
 use std::sync::LazyLock as Lazy;
+use std::{ascii, mem};
 
 use rustc_ast::tokenstream::TokenTree;
 use rustc_hir::def::{DefKind, Res};
@@ -24,7 +24,7 @@ use crate::clean::{
     clean_middle_ty, inline,
 };
 use crate::core::DocContext;
-use crate::display::Joined as _;
+use crate::display::{Joined as _, MaybeDisplay as _};
 
 #[cfg(test)]
 mod tests;
@@ -124,7 +124,7 @@ pub(crate) fn clean_middle_generic_args<'tcx>(
             elision_has_failed_once_before = true;
         }
 
-        match arg.skip_binder().unpack() {
+        match arg.skip_binder().kind() {
             GenericArgKind::Lifetime(lt) => {
                 Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided())))
             }
@@ -161,7 +161,7 @@ fn can_elide_generic_arg<'tcx>(
     default: ty::Binder<'tcx, ty::GenericArg<'tcx>>,
 ) -> bool {
     debug_assert_matches!(
-        (actual.skip_binder().unpack(), default.skip_binder().unpack()),
+        (actual.skip_binder().kind(), default.skip_binder().kind()),
         (ty::GenericArgKind::Lifetime(_), ty::GenericArgKind::Lifetime(_))
             | (ty::GenericArgKind::Type(_), ty::GenericArgKind::Type(_))
             | (ty::GenericArgKind::Const(_), ty::GenericArgKind::Const(_))
@@ -254,14 +254,7 @@ pub(crate) fn qpath_to_string(p: &hir::QPath<'_>) -> String {
     fmt::from_fn(|f| {
         segments
             .iter()
-            .map(|seg| {
-                fmt::from_fn(|f| {
-                    if seg.ident.name != kw::PathRoot {
-                        write!(f, "{}", seg.ident)?;
-                    }
-                    Ok(())
-                })
-            })
+            .map(|seg| (seg.ident.name != kw::PathRoot).then_some(seg.ident).maybe_display())
             .joined("::", f)
     })
     .to_string()
@@ -391,30 +384,12 @@ pub(crate) fn print_evaluated_const(
     })
 }
 
-fn format_integer_with_underscore_sep(num: &str) -> String {
-    let num_chars: Vec<_> = num.chars().collect();
-    let mut num_start_index = if num_chars.first() == Some(&'-') { 1 } else { 0 };
-    let chunk_size = match &num.as_bytes()[num_start_index..] {
-        [b'0', b'b' | b'x', ..] => {
-            num_start_index += 2;
-            4
-        }
-        [b'0', b'o', ..] => {
-            num_start_index += 2;
-            let remaining_chars = num_chars.len() - num_start_index;
-            if remaining_chars <= 6 {
-                // don't add underscores to Unix permissions like 0755 or 100755
-                return num.to_string();
-            }
-            3
-        }
-        _ => 3,
-    };
-
-    num_chars[..num_start_index]
-        .iter()
-        .chain(num_chars[num_start_index..].rchunks(chunk_size).rev().intersperse(&['_']).flatten())
-        .collect()
+fn format_integer_with_underscore_sep(num: u128, is_negative: bool) -> String {
+    let num = num.to_string();
+    let chars = num.as_ascii().unwrap();
+    let mut result = if is_negative { "-".to_string() } else { String::new() };
+    result.extend(chars.rchunks(3).rev().intersperse(&[ascii::Char::LowLine]).flatten());
+    result
 }
 
 fn print_const_with_custom_print_scalar<'tcx>(
@@ -428,7 +403,10 @@ fn print_const_with_custom_print_scalar<'tcx>(
     match (ct, ct.ty().kind()) {
         (mir::Const::Val(mir::ConstValue::Scalar(int), _), ty::Uint(ui)) => {
             let mut output = if with_underscores {
-                format_integer_with_underscore_sep(&int.to_string())
+                format_integer_with_underscore_sep(
+                    int.assert_scalar_int().to_bits_unchecked(),
+                    false,
+                )
             } else {
                 int.to_string()
             };
@@ -445,7 +423,10 @@ fn print_const_with_custom_print_scalar<'tcx>(
                 .size;
             let sign_extended_data = int.assert_scalar_int().to_int(size);
             let mut output = if with_underscores {
-                format_integer_with_underscore_sep(&sign_extended_data.to_string())
+                format_integer_with_underscore_sep(
+                    sign_extended_data.unsigned_abs(),
+                    sign_extended_data.is_negative(),
+                )
             } else {
                 sign_extended_data.to_string()
             };
diff --git a/src/librustdoc/clean/utils/tests.rs b/src/librustdoc/clean/utils/tests.rs
index ebf4b495483..65c8255b2f2 100644
--- a/src/librustdoc/clean/utils/tests.rs
+++ b/src/librustdoc/clean/utils/tests.rs
@@ -2,40 +2,10 @@ use super::*;
 
 #[test]
 fn int_format_decimal() {
-    assert_eq!(format_integer_with_underscore_sep("12345678"), "12_345_678");
-    assert_eq!(format_integer_with_underscore_sep("123"), "123");
-    assert_eq!(format_integer_with_underscore_sep("123459"), "123_459");
-    assert_eq!(format_integer_with_underscore_sep("-12345678"), "-12_345_678");
-    assert_eq!(format_integer_with_underscore_sep("-123"), "-123");
-    assert_eq!(format_integer_with_underscore_sep("-123459"), "-123_459");
-}
-
-#[test]
-fn int_format_hex() {
-    assert_eq!(format_integer_with_underscore_sep("0xab3"), "0xab3");
-    assert_eq!(format_integer_with_underscore_sep("0xa2345b"), "0xa2_345b");
-    assert_eq!(format_integer_with_underscore_sep("0xa2e6345b"), "0xa2e6_345b");
-    assert_eq!(format_integer_with_underscore_sep("-0xab3"), "-0xab3");
-    assert_eq!(format_integer_with_underscore_sep("-0xa2345b"), "-0xa2_345b");
-    assert_eq!(format_integer_with_underscore_sep("-0xa2e6345b"), "-0xa2e6_345b");
-}
-
-#[test]
-fn int_format_binary() {
-    assert_eq!(format_integer_with_underscore_sep("0o12345671"), "0o12_345_671");
-    assert_eq!(format_integer_with_underscore_sep("0o123"), "0o123");
-    assert_eq!(format_integer_with_underscore_sep("0o123451"), "0o123451");
-    assert_eq!(format_integer_with_underscore_sep("-0o12345671"), "-0o12_345_671");
-    assert_eq!(format_integer_with_underscore_sep("-0o123"), "-0o123");
-    assert_eq!(format_integer_with_underscore_sep("-0o123451"), "-0o123451");
-}
-
-#[test]
-fn int_format_octal() {
-    assert_eq!(format_integer_with_underscore_sep("0b101"), "0b101");
-    assert_eq!(format_integer_with_underscore_sep("0b101101011"), "0b1_0110_1011");
-    assert_eq!(format_integer_with_underscore_sep("0b01101011"), "0b0110_1011");
-    assert_eq!(format_integer_with_underscore_sep("-0b101"), "-0b101");
-    assert_eq!(format_integer_with_underscore_sep("-0b101101011"), "-0b1_0110_1011");
-    assert_eq!(format_integer_with_underscore_sep("-0b01101011"), "-0b0110_1011");
+    assert_eq!(format_integer_with_underscore_sep(12345678, false), "12_345_678");
+    assert_eq!(format_integer_with_underscore_sep(123, false), "123");
+    assert_eq!(format_integer_with_underscore_sep(123459, false), "123_459");
+    assert_eq!(format_integer_with_underscore_sep(12345678, true), "-12_345_678");
+    assert_eq!(format_integer_with_underscore_sep(123, true), "-123");
+    assert_eq!(format_integer_with_underscore_sep(123459, true), "-123_459");
 }
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 9d1c9ff00b1..204f8decffc 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -345,9 +345,7 @@ pub(crate) fn run_global_ctxt(
     // (see `override_queries` in the `config`)
 
     // NOTE: These are copy/pasted from typeck/lib.rs and should be kept in sync with those changes.
-    let _ = tcx.sess.time("wf_checking", || {
-        tcx.try_par_hir_for_each_module(|module| tcx.ensure_ok().check_mod_type_wf(module))
-    });
+    let _ = tcx.sess.time("wf_checking", || tcx.ensure_ok().check_type_wf(()));
 
     tcx.dcx().abort_if_errors();
 
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index ef70b862185..a81d6020f71 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -5,6 +5,7 @@ mod runner;
 mod rust;
 
 use std::fs::File;
+use std::hash::{Hash, Hasher};
 use std::io::{self, Write};
 use std::path::{Path, PathBuf};
 use std::process::{self, Command, Stdio};
@@ -12,9 +13,9 @@ use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::{Arc, Mutex};
 use std::{panic, str};
 
-pub(crate) use make::DocTestBuilder;
+pub(crate) use make::{BuildDocTestBuilder, DocTestBuilder};
 pub(crate) use markdown::test as test_markdown;
-use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
+use rustc_data_structures::fx::{FxHashMap, FxHasher, FxIndexMap, FxIndexSet};
 use rustc_errors::emitter::HumanReadableErrorType;
 use rustc_errors::{ColorConfig, DiagCtxtHandle};
 use rustc_hir as hir;
@@ -23,9 +24,9 @@ use rustc_hir::def_id::LOCAL_CRATE;
 use rustc_interface::interface;
 use rustc_session::config::{self, CrateType, ErrorOutputType, Input};
 use rustc_session::lint;
-use rustc_span::FileName;
 use rustc_span::edition::Edition;
 use rustc_span::symbol::sym;
+use rustc_span::{FileName, Span};
 use rustc_target::spec::{Target, TargetTuple};
 use tempfile::{Builder as TempFileBuilder, TempDir};
 use tracing::debug;
@@ -45,8 +46,6 @@ pub(crate) struct GlobalTestOptions {
     /// Whether inserting extra indent spaces in code block,
     /// default is `false`, only `true` for generating code link of Rust playground
     pub(crate) insert_indent_space: bool,
-    /// Additional crate-level attributes to add to doctests.
-    pub(crate) attrs: Vec<String>,
     /// Path to file containing arguments for the invocation of rustc.
     pub(crate) args_file: PathBuf,
 }
@@ -197,7 +196,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions
                 }
             } else {
                 let mut collector = CreateRunnableDocTests::new(options, opts);
-                tests.into_iter().for_each(|t| collector.add_test(t));
+                tests.into_iter().for_each(|t| collector.add_test(t, Some(compiler.sess.dcx())));
 
                 Ok(Some(collector))
             }
@@ -283,7 +282,7 @@ pub(crate) fn run_tests(
     rustdoc_options: &Arc<RustdocOptions>,
     unused_extern_reports: &Arc<Mutex<Vec<UnusedExterns>>>,
     mut standalone_tests: Vec<test::TestDescAndFn>,
-    mergeable_tests: FxIndexMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>,
+    mergeable_tests: FxIndexMap<MergeableTestKey, Vec<(DocTestBuilder, ScrapedDocTest)>>,
     // We pass this argument so we can drop it manually before using `exit`.
     mut temp_dir: Option<TempDir>,
 ) {
@@ -298,7 +297,7 @@ pub(crate) fn run_tests(
     let mut ran_edition_tests = 0;
     let target_str = rustdoc_options.target.to_string();
 
-    for (edition, mut doctests) in mergeable_tests {
+    for (MergeableTestKey { edition, global_crate_attrs_hash }, mut doctests) in mergeable_tests {
         if doctests.is_empty() {
             continue;
         }
@@ -308,8 +307,8 @@ pub(crate) fn run_tests(
 
         let rustdoc_test_options = IndividualTestOptions::new(
             rustdoc_options,
-            &Some(format!("merged_doctest_{edition}")),
-            PathBuf::from(format!("doctest_{edition}.rs")),
+            &Some(format!("merged_doctest_{edition}_{global_crate_attrs_hash}")),
+            PathBuf::from(format!("doctest_{edition}_{global_crate_attrs_hash}.rs")),
         );
 
         for (doctest, scraped_test) in &doctests {
@@ -371,12 +370,9 @@ fn scrape_test_config(
     attrs: &[hir::Attribute],
     args_file: PathBuf,
 ) -> GlobalTestOptions {
-    use rustc_ast_pretty::pprust;
-
     let mut opts = GlobalTestOptions {
         crate_name,
         no_crate_inject: false,
-        attrs: Vec::new(),
         insert_indent_space: false,
         args_file,
     };
@@ -393,13 +389,7 @@ fn scrape_test_config(
         if attr.has_name(sym::no_crate_inject) {
             opts.no_crate_inject = true;
         }
-        if attr.has_name(sym::attr)
-            && let Some(l) = attr.meta_item_list()
-        {
-            for item in l {
-                opts.attrs.push(pprust::meta_list_item_to_string(item));
-            }
-        }
+        // NOTE: `test(attr(..))` is handled when discovering the individual tests
     }
 
     opts
@@ -847,6 +837,8 @@ pub(crate) struct ScrapedDocTest {
     langstr: LangString,
     text: String,
     name: String,
+    span: Span,
+    global_crate_attrs: Vec<String>,
 }
 
 impl ScrapedDocTest {
@@ -856,6 +848,8 @@ impl ScrapedDocTest {
         logical_path: Vec<String>,
         langstr: LangString,
         text: String,
+        span: Span,
+        global_crate_attrs: Vec<String>,
     ) -> Self {
         let mut item_path = logical_path.join("::");
         item_path.retain(|c| c != ' ');
@@ -865,7 +859,7 @@ impl ScrapedDocTest {
         let name =
             format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly());
 
-        Self { filename, line, langstr, text, name }
+        Self { filename, line, langstr, text, name, span, global_crate_attrs }
     }
     fn edition(&self, opts: &RustdocOptions) -> Edition {
         self.langstr.edition.unwrap_or(opts.edition)
@@ -894,9 +888,15 @@ pub(crate) trait DocTestVisitor {
     fn visit_header(&mut self, _name: &str, _level: u32) {}
 }
 
+#[derive(Clone, Debug, Hash, Eq, PartialEq)]
+pub(crate) struct MergeableTestKey {
+    edition: Edition,
+    global_crate_attrs_hash: u64,
+}
+
 struct CreateRunnableDocTests {
     standalone_tests: Vec<test::TestDescAndFn>,
-    mergeable_tests: FxIndexMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>,
+    mergeable_tests: FxIndexMap<MergeableTestKey, Vec<(DocTestBuilder, ScrapedDocTest)>>,
 
     rustdoc_options: Arc<RustdocOptions>,
     opts: GlobalTestOptions,
@@ -921,7 +921,7 @@ impl CreateRunnableDocTests {
         }
     }
 
-    fn add_test(&mut self, scraped_test: ScrapedDocTest) {
+    fn add_test(&mut self, scraped_test: ScrapedDocTest, dcx: Option<DiagCtxtHandle<'_>>) {
         // For example `module/file.rs` would become `module_file_rs`
         let file = scraped_test
             .filename
@@ -945,14 +945,15 @@ impl CreateRunnableDocTests {
         );
 
         let edition = scraped_test.edition(&self.rustdoc_options);
-        let doctest = DocTestBuilder::new(
-            &scraped_test.text,
-            Some(&self.opts.crate_name),
-            edition,
-            self.can_merge_doctests,
-            Some(test_id),
-            Some(&scraped_test.langstr),
-        );
+        let doctest = BuildDocTestBuilder::new(&scraped_test.text)
+            .crate_name(&self.opts.crate_name)
+            .global_crate_attrs(scraped_test.global_crate_attrs.clone())
+            .edition(edition)
+            .can_merge_doctests(self.can_merge_doctests)
+            .test_id(test_id)
+            .lang_str(&scraped_test.langstr)
+            .span(scraped_test.span)
+            .build(dcx);
         let is_standalone = !doctest.can_be_merged
             || scraped_test.langstr.compile_fail
             || scraped_test.langstr.test_harness
@@ -963,7 +964,17 @@ impl CreateRunnableDocTests {
             let test_desc = self.generate_test_desc_and_fn(doctest, scraped_test);
             self.standalone_tests.push(test_desc);
         } else {
-            self.mergeable_tests.entry(edition).or_default().push((doctest, scraped_test));
+            self.mergeable_tests
+                .entry(MergeableTestKey {
+                    edition,
+                    global_crate_attrs_hash: {
+                        let mut hasher = FxHasher::default();
+                        scraped_test.global_crate_attrs.hash(&mut hasher);
+                        hasher.finish()
+                    },
+                })
+                .or_default()
+                .push((doctest, scraped_test));
         }
     }
 
diff --git a/src/librustdoc/doctest/extracted.rs b/src/librustdoc/doctest/extracted.rs
index ce362eabfc4..ebe6bfd22ba 100644
--- a/src/librustdoc/doctest/extracted.rs
+++ b/src/librustdoc/doctest/extracted.rs
@@ -5,7 +5,7 @@
 
 use serde::Serialize;
 
-use super::{DocTestBuilder, ScrapedDocTest};
+use super::{BuildDocTestBuilder, ScrapedDocTest};
 use crate::config::Options as RustdocOptions;
 use crate::html::markdown;
 
@@ -35,16 +35,16 @@ impl ExtractedDocTests {
     ) {
         let edition = scraped_test.edition(options);
 
-        let ScrapedDocTest { filename, line, langstr, text, name } = scraped_test;
+        let ScrapedDocTest { filename, line, langstr, text, name, global_crate_attrs, .. } =
+            scraped_test;
+
+        let doctest = BuildDocTestBuilder::new(&text)
+            .crate_name(&opts.crate_name)
+            .global_crate_attrs(global_crate_attrs)
+            .edition(edition)
+            .lang_str(&langstr)
+            .build(None);
 
-        let doctest = DocTestBuilder::new(
-            &text,
-            Some(&opts.crate_name),
-            edition,
-            false,
-            None,
-            Some(&langstr),
-        );
         let (full_test_code, size) = doctest.generate_unique_doctest(
             &text,
             langstr.test_harness,
diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs
index d4fbfb12582..5e571613d6f 100644
--- a/src/librustdoc/doctest/make.rs
+++ b/src/librustdoc/doctest/make.rs
@@ -8,14 +8,14 @@ use std::sync::Arc;
 use rustc_ast::token::{Delimiter, TokenKind};
 use rustc_ast::tokenstream::TokenTree;
 use rustc_ast::{self as ast, AttrStyle, HasAttrs, StmtKind};
-use rustc_errors::ColorConfig;
 use rustc_errors::emitter::stderr_destination;
+use rustc_errors::{ColorConfig, DiagCtxtHandle};
 use rustc_parse::new_parser_from_source_str;
 use rustc_session::parse::ParseSess;
-use rustc_span::edition::Edition;
+use rustc_span::edition::{DEFAULT_EDITION, Edition};
 use rustc_span::source_map::SourceMap;
 use rustc_span::symbol::sym;
-use rustc_span::{FileName, kw};
+use rustc_span::{DUMMY_SP, FileName, Span, kw};
 use tracing::debug;
 
 use super::GlobalTestOptions;
@@ -35,33 +35,87 @@ struct ParseSourceInfo {
     maybe_crate_attrs: String,
 }
 
-/// This struct contains information about the doctest itself which is then used to generate
-/// doctest source code appropriately.
-pub(crate) struct DocTestBuilder {
-    pub(crate) supports_color: bool,
-    pub(crate) already_has_extern_crate: bool,
-    pub(crate) has_main_fn: bool,
-    pub(crate) crate_attrs: String,
-    /// If this is a merged doctest, it will be put into `everything_else`, otherwise it will
-    /// put into `crate_attrs`.
-    pub(crate) maybe_crate_attrs: String,
-    pub(crate) crates: String,
-    pub(crate) everything_else: String,
-    pub(crate) test_id: Option<String>,
-    pub(crate) invalid_ast: bool,
-    pub(crate) can_be_merged: bool,
+/// Builder type for `DocTestBuilder`.
+pub(crate) struct BuildDocTestBuilder<'a> {
+    source: &'a str,
+    crate_name: Option<&'a str>,
+    edition: Edition,
+    can_merge_doctests: bool,
+    // If `test_id` is `None`, it means we're generating code for a code example "run" link.
+    test_id: Option<String>,
+    lang_str: Option<&'a LangString>,
+    span: Span,
+    global_crate_attrs: Vec<String>,
 }
 
-impl DocTestBuilder {
-    pub(crate) fn new(
-        source: &str,
-        crate_name: Option<&str>,
-        edition: Edition,
-        can_merge_doctests: bool,
-        // If `test_id` is `None`, it means we're generating code for a code example "run" link.
-        test_id: Option<String>,
-        lang_str: Option<&LangString>,
-    ) -> Self {
+impl<'a> BuildDocTestBuilder<'a> {
+    pub(crate) fn new(source: &'a str) -> Self {
+        Self {
+            source,
+            crate_name: None,
+            edition: DEFAULT_EDITION,
+            can_merge_doctests: false,
+            test_id: None,
+            lang_str: None,
+            span: DUMMY_SP,
+            global_crate_attrs: Vec::new(),
+        }
+    }
+
+    #[inline]
+    pub(crate) fn crate_name(mut self, crate_name: &'a str) -> Self {
+        self.crate_name = Some(crate_name);
+        self
+    }
+
+    #[inline]
+    pub(crate) fn can_merge_doctests(mut self, can_merge_doctests: bool) -> Self {
+        self.can_merge_doctests = can_merge_doctests;
+        self
+    }
+
+    #[inline]
+    pub(crate) fn test_id(mut self, test_id: String) -> Self {
+        self.test_id = Some(test_id);
+        self
+    }
+
+    #[inline]
+    pub(crate) fn lang_str(mut self, lang_str: &'a LangString) -> Self {
+        self.lang_str = Some(lang_str);
+        self
+    }
+
+    #[inline]
+    pub(crate) fn span(mut self, span: Span) -> Self {
+        self.span = span;
+        self
+    }
+
+    #[inline]
+    pub(crate) fn edition(mut self, edition: Edition) -> Self {
+        self.edition = edition;
+        self
+    }
+
+    #[inline]
+    pub(crate) fn global_crate_attrs(mut self, global_crate_attrs: Vec<String>) -> Self {
+        self.global_crate_attrs = global_crate_attrs;
+        self
+    }
+
+    pub(crate) fn build(self, dcx: Option<DiagCtxtHandle<'_>>) -> DocTestBuilder {
+        let BuildDocTestBuilder {
+            source,
+            crate_name,
+            edition,
+            can_merge_doctests,
+            // If `test_id` is `None`, it means we're generating code for a code example "run" link.
+            test_id,
+            lang_str,
+            span,
+            global_crate_attrs,
+        } = self;
         let can_merge_doctests = can_merge_doctests
             && lang_str.is_some_and(|lang_str| {
                 !lang_str.compile_fail && !lang_str.test_harness && !lang_str.standalone_crate
@@ -69,7 +123,7 @@ impl DocTestBuilder {
 
         let result = rustc_driver::catch_fatal_errors(|| {
             rustc_span::create_session_if_not_set_then(edition, |_| {
-                parse_source(source, &crate_name)
+                parse_source(source, &crate_name, dcx, span)
             })
         });
 
@@ -87,7 +141,8 @@ impl DocTestBuilder {
         else {
             // If the AST returned an error, we don't want this doctest to be merged with the
             // others.
-            return Self::invalid(
+            return DocTestBuilder::invalid(
+                Vec::new(),
                 String::new(),
                 String::new(),
                 String::new(),
@@ -107,9 +162,10 @@ impl DocTestBuilder {
             // If this is a merged doctest and a defined macro uses `$crate`, then the path will
             // not work, so better not put it into merged doctests.
             && !(has_macro_def && everything_else.contains("$crate"));
-        Self {
+        DocTestBuilder {
             supports_color,
             has_main_fn,
+            global_crate_attrs,
             crate_attrs,
             maybe_crate_attrs,
             crates,
@@ -120,8 +176,29 @@ impl DocTestBuilder {
             can_be_merged,
         }
     }
+}
+
+/// This struct contains information about the doctest itself which is then used to generate
+/// doctest source code appropriately.
+pub(crate) struct DocTestBuilder {
+    pub(crate) supports_color: bool,
+    pub(crate) already_has_extern_crate: bool,
+    pub(crate) has_main_fn: bool,
+    pub(crate) global_crate_attrs: Vec<String>,
+    pub(crate) crate_attrs: String,
+    /// If this is a merged doctest, it will be put into `everything_else`, otherwise it will
+    /// put into `crate_attrs`.
+    pub(crate) maybe_crate_attrs: String,
+    pub(crate) crates: String,
+    pub(crate) everything_else: String,
+    pub(crate) test_id: Option<String>,
+    pub(crate) invalid_ast: bool,
+    pub(crate) can_be_merged: bool,
+}
 
+impl DocTestBuilder {
     fn invalid(
+        global_crate_attrs: Vec<String>,
         crate_attrs: String,
         maybe_crate_attrs: String,
         crates: String,
@@ -131,6 +208,7 @@ impl DocTestBuilder {
         Self {
             supports_color: false,
             has_main_fn: false,
+            global_crate_attrs,
             crate_attrs,
             maybe_crate_attrs,
             crates,
@@ -160,7 +238,8 @@ impl DocTestBuilder {
         let mut line_offset = 0;
         let mut prog = String::new();
         let everything_else = self.everything_else.trim();
-        if opts.attrs.is_empty() {
+
+        if self.global_crate_attrs.is_empty() {
             // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
             // lints that are commonly triggered in doctests. The crate-level test attributes are
             // commonly used to make tests fail in case they trigger warnings, so having this there in
@@ -169,8 +248,8 @@ impl DocTestBuilder {
             line_offset += 1;
         }
 
-        // Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
-        for attr in &opts.attrs {
+        // Next, any attributes that came from #![doc(test(attr(...)))].
+        for attr in &self.global_crate_attrs {
             prog.push_str(&format!("#![{attr}]\n"));
             line_offset += 1;
         }
@@ -289,7 +368,12 @@ fn reset_error_count(psess: &ParseSess) {
 
 const DOCTEST_CODE_WRAPPER: &str = "fn f(){";
 
-fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceInfo, ()> {
+fn parse_source(
+    source: &str,
+    crate_name: &Option<&str>,
+    parent_dcx: Option<DiagCtxtHandle<'_>>,
+    span: Span,
+) -> Result<ParseSourceInfo, ()> {
     use rustc_errors::DiagCtxt;
     use rustc_errors::emitter::{Emitter, HumanEmitter};
     use rustc_span::source_map::FilePathMapping;
@@ -466,8 +550,17 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
                 }
             }
             if has_non_items {
-                // FIXME: if `info.has_main_fn` is `true`, emit a warning here to mention that
-                // this code will not be called.
+                if info.has_main_fn
+                    && let Some(dcx) = parent_dcx
+                    && !span.is_dummy()
+                {
+                    dcx.span_warn(
+                        span,
+                        "the `main` function of this doctest won't be run as it contains \
+                         expressions at the top level, meaning that the whole doctest code will be \
+                         wrapped in a function",
+                    );
+                }
                 info.has_main_fn = false;
             }
             Ok(info)
diff --git a/src/librustdoc/doctest/markdown.rs b/src/librustdoc/doctest/markdown.rs
index b3a3ce08a05..7f26605f256 100644
--- a/src/librustdoc/doctest/markdown.rs
+++ b/src/librustdoc/doctest/markdown.rs
@@ -4,7 +4,7 @@ use std::fs::read_to_string;
 use std::sync::{Arc, Mutex};
 
 use rustc_session::config::Input;
-use rustc_span::FileName;
+use rustc_span::{DUMMY_SP, FileName};
 use tempfile::tempdir;
 
 use super::{
@@ -24,7 +24,15 @@ impl DocTestVisitor for MdCollector {
         let filename = self.filename.clone();
         // First line of Markdown is line 1.
         let line = 1 + rel_line.offset();
-        self.tests.push(ScrapedDocTest::new(filename, line, self.cur_path.clone(), config, test));
+        self.tests.push(ScrapedDocTest::new(
+            filename,
+            line,
+            self.cur_path.clone(),
+            config,
+            test,
+            DUMMY_SP,
+            Vec::new(),
+        ));
     }
 
     fn visit_header(&mut self, name: &str, level: u32) {
@@ -89,7 +97,6 @@ pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> {
         crate_name,
         no_crate_inject: true,
         insert_indent_space: false,
-        attrs: vec![],
         args_file,
     };
 
@@ -107,7 +114,7 @@ pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> {
     find_testable_code(&input_str, &mut md_collector, codes, None);
 
     let mut collector = CreateRunnableDocTests::new(options.clone(), opts);
-    md_collector.tests.into_iter().for_each(|t| collector.add_test(t));
+    md_collector.tests.into_iter().for_each(|t| collector.add_test(t, None));
     let CreateRunnableDocTests { opts, rustdoc_options, standalone_tests, mergeable_tests, .. } =
         collector;
     crate::doctest::run_tests(
diff --git a/src/librustdoc/doctest/runner.rs b/src/librustdoc/doctest/runner.rs
index 39a4f23560a..f0914474c79 100644
--- a/src/librustdoc/doctest/runner.rs
+++ b/src/librustdoc/doctest/runner.rs
@@ -12,6 +12,7 @@ use crate::html::markdown::{Ignore, LangString};
 /// Convenient type to merge compatible doctests into one.
 pub(crate) struct DocTestRunner {
     crate_attrs: FxIndexSet<String>,
+    global_crate_attrs: FxIndexSet<String>,
     ids: String,
     output: String,
     output_merged_tests: String,
@@ -23,6 +24,7 @@ impl DocTestRunner {
     pub(crate) fn new() -> Self {
         Self {
             crate_attrs: FxIndexSet::default(),
+            global_crate_attrs: FxIndexSet::default(),
             ids: String::new(),
             output: String::new(),
             output_merged_tests: String::new(),
@@ -46,6 +48,9 @@ impl DocTestRunner {
             for line in doctest.crate_attrs.split('\n') {
                 self.crate_attrs.insert(line.to_string());
             }
+            for line in &doctest.global_crate_attrs {
+                self.global_crate_attrs.insert(line.to_string());
+            }
         }
         self.ids.push_str(&format!(
             "tests.push({}::TEST);\n",
@@ -85,7 +90,7 @@ impl DocTestRunner {
             code_prefix.push('\n');
         }
 
-        if opts.attrs.is_empty() {
+        if self.global_crate_attrs.is_empty() {
             // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
             // lints that are commonly triggered in doctests. The crate-level test attributes are
             // commonly used to make tests fail in case they trigger warnings, so having this there in
@@ -93,8 +98,8 @@ impl DocTestRunner {
             code_prefix.push_str("#![allow(unused)]\n");
         }
 
-        // Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
-        for attr in &opts.attrs {
+        // Next, any attributes that came from #![doc(test(attr(...)))].
+        for attr in &self.global_crate_attrs {
             code_prefix.push_str(&format!("#![{attr}]\n"));
         }
 
diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs
index 43dcfab880b..96975105ac5 100644
--- a/src/librustdoc/doctest/rust.rs
+++ b/src/librustdoc/doctest/rust.rs
@@ -1,8 +1,10 @@
 //! Doctest functionality used only for doctests in `.rs` source files.
 
+use std::cell::Cell;
 use std::env;
 use std::sync::Arc;
 
+use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
 use rustc_hir::{self as hir, CRATE_HIR_ID, intravisit};
@@ -10,7 +12,7 @@ use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::TyCtxt;
 use rustc_resolve::rustdoc::span_of_fragments;
 use rustc_span::source_map::SourceMap;
-use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span};
+use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span, sym};
 
 use super::{DocTestVisitor, ScrapedDocTest};
 use crate::clean::{Attributes, extract_cfg_from_attrs};
@@ -21,6 +23,7 @@ struct RustCollector {
     tests: Vec<ScrapedDocTest>,
     cur_path: Vec<String>,
     position: Span,
+    global_crate_attrs: Vec<String>,
 }
 
 impl RustCollector {
@@ -47,13 +50,34 @@ impl RustCollector {
 
 impl DocTestVisitor for RustCollector {
     fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine) {
-        let line = self.get_base_line() + rel_line.offset();
+        let base_line = self.get_base_line();
+        let line = base_line + rel_line.offset();
+        let count = Cell::new(base_line);
+        let span = if line > base_line {
+            match self.source_map.span_extend_while(self.position, |c| {
+                if c == '\n' {
+                    let count_v = count.get();
+                    count.set(count_v + 1);
+                    if count_v >= line {
+                        return false;
+                    }
+                }
+                true
+            }) {
+                Ok(sp) => self.source_map.span_extend_to_line(sp.shrink_to_hi()),
+                _ => self.position,
+            }
+        } else {
+            self.position
+        };
         self.tests.push(ScrapedDocTest::new(
             self.get_filename(),
             line,
             self.cur_path.clone(),
             config,
             test,
+            span,
+            self.global_crate_attrs.clone(),
         ));
     }
 
@@ -73,6 +97,7 @@ impl<'tcx> HirCollector<'tcx> {
             cur_path: vec![],
             position: DUMMY_SP,
             tests: vec![],
+            global_crate_attrs: Vec::new(),
         };
         Self { codes, tcx, collector }
     }
@@ -102,6 +127,26 @@ impl HirCollector<'_> {
             return;
         }
 
+        // Try collecting `#[doc(test(attr(...)))]`
+        let old_global_crate_attrs_len = self.collector.global_crate_attrs.len();
+        for doc_test_attrs in ast_attrs
+            .iter()
+            .filter(|a| a.has_name(sym::doc))
+            .flat_map(|a| a.meta_item_list().unwrap_or_default())
+            .filter(|a| a.has_name(sym::test))
+        {
+            let Some(doc_test_attrs) = doc_test_attrs.meta_item_list() else { continue };
+            for attr in doc_test_attrs
+                .iter()
+                .filter(|a| a.has_name(sym::attr))
+                .flat_map(|a| a.meta_item_list().unwrap_or_default())
+                .map(|i| pprust::meta_list_item_to_string(i))
+            {
+                // Add the additional attributes to the global_crate_attrs vector
+                self.collector.global_crate_attrs.push(attr);
+            }
+        }
+
         let mut has_name = false;
         if let Some(name) = name {
             self.collector.cur_path.push(name);
@@ -136,6 +181,9 @@ impl HirCollector<'_> {
 
         nested(self);
 
+        // Restore global_crate_attrs to it's previous size/content
+        self.collector.global_crate_attrs.truncate(old_global_crate_attrs_len);
+
         if has_name {
             self.collector.cur_path.pop();
         }
diff --git a/src/librustdoc/doctest/tests.rs b/src/librustdoc/doctest/tests.rs
index 618c2041b43..ce2984ced79 100644
--- a/src/librustdoc/doctest/tests.rs
+++ b/src/librustdoc/doctest/tests.rs
@@ -1,24 +1,24 @@
 use std::path::PathBuf;
 
-use rustc_span::edition::DEFAULT_EDITION;
-
-use super::{DocTestBuilder, GlobalTestOptions};
+use super::{BuildDocTestBuilder, GlobalTestOptions};
 
 fn make_test(
     test_code: &str,
     crate_name: Option<&str>,
     dont_insert_main: bool,
     opts: &GlobalTestOptions,
+    global_crate_attrs: Vec<&str>,
     test_id: Option<&str>,
 ) -> (String, usize) {
-    let doctest = DocTestBuilder::new(
-        test_code,
-        crate_name,
-        DEFAULT_EDITION,
-        false,
-        test_id.map(|s| s.to_string()),
-        None,
-    );
+    let mut builder = BuildDocTestBuilder::new(test_code)
+        .global_crate_attrs(global_crate_attrs.into_iter().map(|a| a.to_string()).collect());
+    if let Some(crate_name) = crate_name {
+        builder = builder.crate_name(crate_name);
+    }
+    if let Some(test_id) = test_id {
+        builder = builder.test_id(test_id.to_string());
+    }
+    let doctest = builder.build(None);
     let (code, line_offset) =
         doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name);
     (code, line_offset)
@@ -30,7 +30,6 @@ fn default_global_opts(crate_name: impl Into<String>) -> GlobalTestOptions {
         crate_name: crate_name.into(),
         no_crate_inject: false,
         insert_indent_space: false,
-        attrs: vec![],
         args_file: PathBuf::new(),
     }
 }
@@ -45,7 +44,7 @@ fn main() {
 assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, None, false, &opts, None);
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -60,7 +59,7 @@ fn main() {
 assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
+    let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -79,7 +78,7 @@ use asdf::qwop;
 assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
+    let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 3));
 }
 
@@ -96,7 +95,7 @@ use asdf::qwop;
 assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
+    let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -114,7 +113,7 @@ use std::*;
 assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, Some("std"), false, &opts, None);
+    let (output, len) = make_test(input, Some("std"), false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -133,7 +132,7 @@ use asdf::qwop;
 assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
+    let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -150,7 +149,7 @@ use asdf::qwop;
 assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
+    let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -158,8 +157,7 @@ assert_eq!(2+2, 4);
 fn make_test_opts_attrs() {
     // If you supplied some doctest attributes with `#![doc(test(attr(...)))]`, it will use
     // those instead of the stock `#![allow(unused)]`.
-    let mut opts = default_global_opts("asdf");
-    opts.attrs.push("feature(sick_rad)".to_string());
+    let opts = default_global_opts("asdf");
     let input = "use asdf::qwop;
 assert_eq!(2+2, 4);";
     let expected = "#![feature(sick_rad)]
@@ -170,11 +168,10 @@ use asdf::qwop;
 assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
+    let (output, len) =
+        make_test(input, Some("asdf"), false, &opts, vec!["feature(sick_rad)"], None);
     assert_eq!((output, len), (expected, 3));
 
-    // Adding more will also bump the returned line offset.
-    opts.attrs.push("feature(hella_dope)".to_string());
     let expected = "#![feature(sick_rad)]
 #![feature(hella_dope)]
 #[allow(unused_extern_crates)]
@@ -184,7 +181,18 @@ use asdf::qwop;
 assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
+    let (output, len) = make_test(
+        input,
+        Some("asdf"),
+        false,
+        &opts,
+        vec![
+            "feature(sick_rad)",
+            // Adding more will also bump the returned line offset.
+            "feature(hella_dope)",
+        ],
+        None,
+    );
     assert_eq!((output, len), (expected, 4));
 }
 
@@ -202,7 +210,7 @@ fn main() {
 assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, None, false, &opts, None);
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -218,7 +226,7 @@ fn main() {
     assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, None, false, &opts, None);
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 1));
 }
 
@@ -234,7 +242,7 @@ fn main() {
 assert_eq!(2+2, 4);
 }"
     .to_string();
-    let (output, len) = make_test(input, None, false, &opts, None);
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -248,7 +256,7 @@ assert_eq!(2+2, 4);";
 //Ceci n'est pas une `fn main`
 assert_eq!(2+2, 4);"
         .to_string();
-    let (output, len) = make_test(input, None, true, &opts, None);
+    let (output, len) = make_test(input, None, true, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 1));
 }
 
@@ -266,7 +274,7 @@ assert_eq!(2+2, 4);
 }"
     .to_string();
 
-    let (output, len) = make_test(input, None, false, &opts, None);
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -286,7 +294,7 @@ assert_eq!(asdf::foo, 4);
 }"
     .to_string();
 
-    let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
+    let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 3));
 }
 
@@ -304,7 +312,7 @@ test_wrapper! {
 }"
     .to_string();
 
-    let (output, len) = make_test(input, Some("my_crate"), false, &opts, None);
+    let (output, len) = make_test(input, Some("my_crate"), false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 1));
 }
 
@@ -324,7 +332,7 @@ io::stdin().read_line(&mut input)?;
 Ok::<(), io:Error>(())
 } _inner().unwrap() }"
         .to_string();
-    let (output, len) = make_test(input, None, false, &opts, None);
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -338,7 +346,7 @@ fn main() { #[allow(non_snake_case)] fn _doctest_main__some_unique_name() {
 assert_eq!(2+2, 4);
 } _doctest_main__some_unique_name() }"
         .to_string();
-    let (output, len) = make_test(input, None, false, &opts, Some("_some_unique_name"));
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), Some("_some_unique_name"));
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -357,7 +365,7 @@ fn main() {
     eprintln!(\"hello anan\");
 }"
     .to_string();
-    let (output, len) = make_test(input, None, false, &opts, None);
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
 
@@ -377,7 +385,7 @@ fn main() {
     eprintln!(\"hello anan\");
 }"
     .to_string();
-    let (output, len) = make_test(input, None, false, &opts, None);
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 1));
 }
 
@@ -402,7 +410,7 @@ fn main() {
 
 }"
     .to_string();
-    let (output, len) = make_test(input, None, false, &opts, None);
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 
     // And same, if there is a `main` function provided by the user, we ensure that it's
@@ -422,7 +430,7 @@ fn main() {}";
 
 fn main() {}"
         .to_string();
-    let (output, len) = make_test(input, None, false, &opts, None);
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 1));
 }
 
@@ -450,6 +458,6 @@ pub mod outer_module {
 }
 }"
     .to_string();
-    let (output, len) = make_test(input, None, false, &opts, None);
+    let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 486d4ae932d..6ab1520386d 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -7,13 +7,12 @@
 //! some of them support an alternate format that emits text, but that should
 //! not be used external to this module.
 
-use std::borrow::Cow;
 use std::cmp::Ordering;
 use std::fmt::{self, Display, Write};
 use std::iter::{self, once};
 use std::slice;
 
-use itertools::Either;
+use itertools::{Either, Itertools};
 use rustc_abi::ExternAbi;
 use rustc_attr_data_structures::{ConstStability, StabilityLevel, StableSince};
 use rustc_data_structures::fx::FxHashSet;
@@ -483,12 +482,12 @@ fn generate_item_def_id_path(
     let mut is_remote = false;
 
     let url_parts = url_parts(cx.cache(), def_id, module_fqp, &cx.current, &mut is_remote)?;
-    let (url_parts, shortty, fqp) = make_href(root_path, shortty, url_parts, &fqp, is_remote)?;
-    if def_id == original_def_id {
-        return Ok((url_parts, shortty, fqp));
-    }
-    let kind = ItemType::from_def_kind(original_def_kind, Some(def_kind));
-    Ok((format!("{url_parts}#{kind}.{}", tcx.item_name(original_def_id)), shortty, fqp))
+    let mut url_parts = make_href(root_path, shortty, url_parts, &fqp, is_remote);
+    if def_id != original_def_id {
+        let kind = ItemType::from_def_kind(original_def_kind, Some(def_kind));
+        url_parts = format!("{url_parts}#{kind}.{}", tcx.item_name(original_def_id))
+    };
+    Ok((url_parts, shortty, fqp))
 }
 
 fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
@@ -510,7 +509,7 @@ fn url_parts(
             builder.extend(module_fqp.iter().copied());
             Ok(builder)
         }
-        ExternalLocation::Local => Ok(href_relative_parts(module_fqp, relative_to).collect()),
+        ExternalLocation::Local => Ok(href_relative_parts(module_fqp, relative_to)),
         ExternalLocation::Unknown => Err(HrefError::DocumentationNotBuilt),
     }
 }
@@ -521,7 +520,7 @@ fn make_href(
     mut url_parts: UrlPartsBuilder,
     fqp: &[Symbol],
     is_remote: bool,
-) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
+) -> String {
     if !is_remote && let Some(root_path) = root_path {
         let root = root_path.trim_end_matches('/');
         url_parts.push_front(root);
@@ -536,7 +535,7 @@ fn make_href(
             url_parts.push_fmt(format_args!("{shortty}.{last}.html"));
         }
     }
-    Ok((url_parts.finish(), shortty, fqp.to_vec()))
+    url_parts.finish()
 }
 
 pub(crate) fn href_with_root_path(
@@ -587,7 +586,7 @@ pub(crate) fn href_with_root_path(
         Some(&(ref fqp, shortty)) => (fqp, shortty, {
             let module_fqp = to_module_fqp(shortty, fqp.as_slice());
             debug!(?fqp, ?shortty, ?module_fqp);
-            href_relative_parts(module_fqp, relative_to).collect()
+            href_relative_parts(module_fqp, relative_to)
         }),
         None => {
             // Associated items are handled differently with "jump to def". The anchor is generated
@@ -606,7 +605,8 @@ pub(crate) fn href_with_root_path(
             }
         }
     };
-    make_href(root_path, shortty, url_parts, fqp, is_remote)
+    let url_parts = make_href(root_path, shortty, url_parts, &fqp, is_remote);
+    Ok((url_parts, shortty, fqp.clone()))
 }
 
 pub(crate) fn href(
@@ -619,34 +619,30 @@ pub(crate) fn href(
 /// Both paths should only be modules.
 /// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will
 /// both need `../iter/trait.Iterator.html` to get at the iterator trait.
-pub(crate) fn href_relative_parts<'fqp>(
-    fqp: &'fqp [Symbol],
-    relative_to_fqp: &[Symbol],
-) -> Box<dyn Iterator<Item = Symbol> + 'fqp> {
+pub(crate) fn href_relative_parts(fqp: &[Symbol], relative_to_fqp: &[Symbol]) -> UrlPartsBuilder {
     for (i, (f, r)) in fqp.iter().zip(relative_to_fqp.iter()).enumerate() {
         // e.g. linking to std::iter from std::vec (`dissimilar_part_count` will be 1)
         if f != r {
             let dissimilar_part_count = relative_to_fqp.len() - i;
             let fqp_module = &fqp[i..];
-            return Box::new(
-                iter::repeat_n(sym::dotdot, dissimilar_part_count)
-                    .chain(fqp_module.iter().copied()),
-            );
+            return iter::repeat_n(sym::dotdot, dissimilar_part_count)
+                .chain(fqp_module.iter().copied())
+                .collect();
         }
     }
     match relative_to_fqp.len().cmp(&fqp.len()) {
         Ordering::Less => {
             // e.g. linking to std::sync::atomic from std::sync
-            Box::new(fqp[relative_to_fqp.len()..fqp.len()].iter().copied())
+            fqp[relative_to_fqp.len()..fqp.len()].iter().copied().collect()
         }
         Ordering::Greater => {
             // e.g. linking to std::sync from std::sync::atomic
             let dissimilar_part_count = relative_to_fqp.len() - fqp.len();
-            Box::new(iter::repeat_n(sym::dotdot, dissimilar_part_count))
+            iter::repeat_n(sym::dotdot, dissimilar_part_count).collect()
         }
         Ordering::Equal => {
             // linking to the same module
-            Box::new(iter::empty())
+            UrlPartsBuilder::new()
         }
     }
 }
@@ -708,13 +704,13 @@ fn resolved_path(
                         f,
                         "{path}::{anchor}",
                         path = join_with_double_colon(&fqp[..fqp.len() - 1]),
-                        anchor = anchor(did, *fqp.last().unwrap(), cx)
+                        anchor = print_anchor(did, *fqp.last().unwrap(), cx)
                     )
                 } else {
                     write!(f, "{}", last.name)
                 }
             } else {
-                write!(f, "{}", anchor(did, last.name, cx))
+                write!(f, "{}", print_anchor(did, last.name, cx))
             }
         });
         write!(w, "{path}{args}", args = last.args.print(cx))?;
@@ -800,7 +796,7 @@ fn primitive_link_fragment(
     Ok(())
 }
 
-fn tybounds(
+fn print_tybounds(
     bounds: &[clean::PolyTrait],
     lt: &Option<clean::Lifetime>,
     cx: &Context<'_>,
@@ -832,7 +828,7 @@ fn print_higher_ranked_params_with_space(
     })
 }
 
-pub(crate) fn anchor(did: DefId, text: Symbol, cx: &Context<'_>) -> impl Display {
+pub(crate) fn print_anchor(did: DefId, text: Symbol, cx: &Context<'_>) -> impl Display {
     fmt::from_fn(move |f| {
         let parts = href(did, cx);
         if let Ok((url, short_ty, fqp)) = parts {
@@ -866,7 +862,7 @@ fn fmt_type(
         }
         clean::DynTrait(bounds, lt) => {
             f.write_str("dyn ")?;
-            tybounds(bounds, lt, cx).fmt(f)
+            print_tybounds(bounds, lt, cx).fmt(f)
         }
         clean::Infer => write!(f, "_"),
         clean::Primitive(clean::PrimitiveType::Never) => {
@@ -1023,18 +1019,33 @@ fn fmt_type(
             f.write_str("impl ")?;
             print_generic_bounds(bounds, cx).fmt(f)
         }
-        &clean::QPath(box clean::QPathData {
-            ref assoc,
-            ref self_type,
-            ref trait_,
-            should_show_cast,
-        }) => {
+        clean::QPath(qpath) => qpath.print(cx).fmt(f),
+    }
+}
+
+impl clean::Type {
+    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
+        fmt::from_fn(move |f| fmt_type(self, f, false, cx))
+    }
+}
+
+impl clean::Path {
+    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
+        fmt::from_fn(move |f| resolved_path(f, self.def_id(), self, false, false, cx))
+    }
+}
+
+impl clean::QPathData {
+    fn print(&self, cx: &Context<'_>) -> impl Display {
+        let Self { ref assoc, ref self_type, should_fully_qualify, ref trait_ } = *self;
+
+        fmt::from_fn(move |f| {
             // FIXME(inherent_associated_types): Once we support non-ADT self-types (#106719),
             // we need to surround them with angle brackets in some cases (e.g. `<dyn …>::P`).
 
             if f.alternate() {
                 if let Some(trait_) = trait_
-                    && should_show_cast
+                    && should_fully_qualify
                 {
                     write!(f, "<{:#} as {:#}>::", self_type.print(cx), trait_.print(cx))?
                 } else {
@@ -1042,7 +1053,7 @@ fn fmt_type(
                 }
             } else {
                 if let Some(trait_) = trait_
-                    && should_show_cast
+                    && should_fully_qualify
                 {
                     write!(f, "&lt;{} as {}&gt;::", self_type.print(cx), trait_.print(cx))?
                 } else {
@@ -1094,19 +1105,7 @@ fn fmt_type(
             }?;
 
             assoc.args.print(cx).fmt(f)
-        }
-    }
-}
-
-impl clean::Type {
-    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
-        fmt::from_fn(move |f| fmt_type(self, f, false, cx))
-    }
-}
-
-impl clean::Path {
-    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
-        fmt::from_fn(move |f| resolved_path(f, self.def_id(), self, false, false, cx))
+        })
     }
 }
 
@@ -1122,8 +1121,8 @@ impl clean::Impl {
                     write!(f, "!")?;
                 }
                 if self.kind.is_fake_variadic()
-                    && let generics = ty.generics()
-                    && let &[inner_type] = generics.as_ref().map_or(&[][..], |v| &v[..])
+                    && let Some(generics) = ty.generics()
+                    && let Ok(inner_type) = generics.exactly_one()
                 {
                     let last = ty.last();
                     if f.alternate() {
@@ -1131,7 +1130,7 @@ impl clean::Impl {
                         self.print_type(inner_type, f, use_absolute, cx)?;
                         write!(f, ">")?;
                     } else {
-                        write!(f, "{}&lt;", anchor(ty.def_id(), last, cx))?;
+                        write!(f, "{}&lt;", print_anchor(ty.def_id(), last, cx))?;
                         self.print_type(inner_type, f, use_absolute, cx)?;
                         write!(f, "&gt;")?;
                     }
@@ -1202,11 +1201,10 @@ impl clean::Impl {
             }
         } else if let clean::Type::Path { path } = type_
             && let Some(generics) = path.generics()
-            && generics.len() == 1
+            && let Ok(ty) = generics.exactly_one()
             && self.kind.is_fake_variadic()
         {
-            let ty = generics[0];
-            let wrapper = anchor(path.def_id(), path.last(), cx);
+            let wrapper = print_anchor(path.def_id(), path.last(), cx);
             if f.alternate() {
                 write!(f, "{wrapper:#}&lt;")?;
             } else {
@@ -1394,50 +1392,47 @@ impl clean::FnDecl {
 }
 
 pub(crate) fn visibility_print_with_space(item: &clean::Item, cx: &Context<'_>) -> impl Display {
-    use std::fmt::Write as _;
-    let vis: Cow<'static, str> = match item.visibility(cx.tcx()) {
-        None => "".into(),
-        Some(ty::Visibility::Public) => "pub ".into(),
-        Some(ty::Visibility::Restricted(vis_did)) => {
-            // FIXME(camelid): This may not work correctly if `item_did` is a module.
-            //                 However, rustdoc currently never displays a module's
-            //                 visibility, so it shouldn't matter.
-            let parent_module = find_nearest_parent_module(cx.tcx(), item.item_id.expect_def_id());
-
-            if vis_did.is_crate_root() {
-                "pub(crate) ".into()
-            } else if parent_module == Some(vis_did) {
-                // `pub(in foo)` where `foo` is the parent module
-                // is the same as no visibility modifier
-                "".into()
-            } else if parent_module.and_then(|parent| find_nearest_parent_module(cx.tcx(), parent))
-                == Some(vis_did)
-            {
-                "pub(super) ".into()
-            } else {
-                let path = cx.tcx().def_path(vis_did);
-                debug!("path={path:?}");
-                // modified from `resolved_path()` to work with `DefPathData`
-                let last_name = path.data.last().unwrap().data.get_opt_name().unwrap();
-                let anchor = anchor(vis_did, last_name, cx);
-
-                let mut s = "pub(in ".to_owned();
-                for seg in &path.data[..path.data.len() - 1] {
-                    let _ = write!(s, "{}::", seg.data.get_opt_name().unwrap());
-                }
-                let _ = write!(s, "{anchor}) ");
-                s.into()
-            }
-        }
-    };
-
-    let is_doc_hidden = item.is_doc_hidden();
     fmt::from_fn(move |f| {
-        if is_doc_hidden {
+        if item.is_doc_hidden() {
             f.write_str("#[doc(hidden)] ")?;
         }
 
-        f.write_str(&vis)
+        match item.visibility(cx.tcx()) {
+            None => {}
+            Some(ty::Visibility::Public) => f.write_str("pub ")?,
+            Some(ty::Visibility::Restricted(vis_did)) => {
+                // FIXME(camelid): This may not work correctly if `item_did` is a module.
+                //                 However, rustdoc currently never displays a module's
+                //                 visibility, so it shouldn't matter.
+                let parent_module =
+                    find_nearest_parent_module(cx.tcx(), item.item_id.expect_def_id());
+
+                if vis_did.is_crate_root() {
+                    f.write_str("pub(crate) ")?;
+                } else if parent_module == Some(vis_did) {
+                    // `pub(in foo)` where `foo` is the parent module
+                    // is the same as no visibility modifier; do nothing
+                } else if parent_module
+                    .and_then(|parent| find_nearest_parent_module(cx.tcx(), parent))
+                    == Some(vis_did)
+                {
+                    f.write_str("pub(super) ")?;
+                } else {
+                    let path = cx.tcx().def_path(vis_did);
+                    debug!("path={path:?}");
+                    // modified from `resolved_path()` to work with `DefPathData`
+                    let last_name = path.data.last().unwrap().data.get_opt_name().unwrap();
+                    let anchor = print_anchor(vis_did, last_name, cx);
+
+                    f.write_str("pub(in ")?;
+                    for seg in &path.data[..path.data.len() - 1] {
+                        write!(f, "{}::", seg.data.get_opt_name().unwrap())?;
+                    }
+                    write!(f, "{anchor}) ")?;
+                }
+            }
+        }
+        Ok(())
     })
 }
 
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index 2db1ea8450c..b2feee36c93 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -6,7 +6,7 @@
 //! Use the `render_with_highlighting` to highlight some rust code.
 
 use std::collections::VecDeque;
-use std::fmt::{Display, Write};
+use std::fmt::{self, Display, Write};
 
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_lexer::{Cursor, FrontmatterAllowed, LiteralKind, TokenKind};
@@ -36,9 +36,10 @@ pub(crate) struct HrefContext<'a, 'tcx> {
 #[derive(Default)]
 pub(crate) struct DecorationInfo(pub(crate) FxIndexMap<&'static str, Vec<(u32, u32)>>);
 
-#[derive(Eq, PartialEq, Clone, Copy)]
+#[derive(Eq, PartialEq, Clone)]
 pub(crate) enum Tooltip {
-    Ignore,
+    IgnoreAll,
+    IgnoreSome(Vec<String>),
     CompileFail,
     ShouldPanic,
     Edition(Edition),
@@ -70,7 +71,7 @@ fn write_header(
         format_args!(
             "<div class=\"example-wrap{}\">",
             match tooltip {
-                Tooltip::Ignore => " ignore",
+                Tooltip::IgnoreAll | Tooltip::IgnoreSome(_) => " ignore",
                 Tooltip::CompileFail => " compile_fail",
                 Tooltip::ShouldPanic => " should_panic",
                 Tooltip::Edition(_) => " edition",
@@ -80,23 +81,29 @@ fn write_header(
     );
 
     if tooltip != Tooltip::None {
-        let edition_code;
-        write_str(
-            out,
-            format_args!(
-                "<a href=\"#\" class=\"tooltip\" title=\"{}\">ⓘ</a>",
-                match tooltip {
-                    Tooltip::Ignore => "This example is not tested",
-                    Tooltip::CompileFail => "This example deliberately fails to compile",
-                    Tooltip::ShouldPanic => "This example panics",
-                    Tooltip::Edition(edition) => {
-                        edition_code = format!("This example runs with edition {edition}");
-                        &edition_code
+        let tooltip = fmt::from_fn(|f| match &tooltip {
+            Tooltip::IgnoreAll => f.write_str("This example is not tested"),
+            Tooltip::IgnoreSome(platforms) => {
+                f.write_str("This example is not tested on ")?;
+                match &platforms[..] {
+                    [] => unreachable!(),
+                    [platform] => f.write_str(platform)?,
+                    [first, second] => write!(f, "{first} or {second}")?,
+                    [platforms @ .., last] => {
+                        for platform in platforms {
+                            write!(f, "{platform}, ")?;
+                        }
+                        write!(f, "or {last}")?;
                     }
-                    Tooltip::None => unreachable!(),
                 }
-            ),
-        );
+                Ok(())
+            }
+            Tooltip::CompileFail => f.write_str("This example deliberately fails to compile"),
+            Tooltip::ShouldPanic => f.write_str("This example panics"),
+            Tooltip::Edition(edition) => write!(f, "This example runs with edition {edition}"),
+            Tooltip::None => unreachable!(),
+        });
+        write_str(out, format_args!("<a href=\"#\" class=\"tooltip\" title=\"{tooltip}\">ⓘ</a>"));
     }
 
     if let Some(extra) = extra_content {
diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs
index 44b3be23914..50320cb231d 100644
--- a/src/librustdoc/html/layout.rs
+++ b/src/librustdoc/html/layout.rs
@@ -1,4 +1,4 @@
-use std::fmt::{self, Display};
+use std::fmt::Display;
 use std::path::PathBuf;
 
 use askama::Template;
@@ -8,7 +8,6 @@ use super::static_files::{STATIC_FILES, StaticFiles};
 use crate::externalfiles::ExternalHtml;
 use crate::html::render::{StylePath, ensure_trailing_slash};
 
-#[derive(Clone)]
 pub(crate) struct Layout {
     pub(crate) logo: String,
     pub(crate) favicon: String,
@@ -71,23 +70,6 @@ struct PageLayout<'a> {
 
 pub(crate) use crate::html::render::sidebar::filters;
 
-/// Implements [`Display`] for a function that accepts a mutable reference to a [`String`], and (optionally) writes to it.
-///
-/// The wrapped function will receive an empty string, and can modify it,
-/// and the `Display` implementation will write the contents of the string after the function has finished.
-pub(crate) struct BufDisplay<F>(pub F);
-
-impl<F> Display for BufDisplay<F>
-where
-    F: Fn(&mut String),
-{
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let mut buf = String::new();
-        self.0(&mut buf);
-        f.write_str(&buf)
-    }
-}
-
 pub(crate) fn render<T: Display, S: Display>(
     layout: &Layout,
     page: &Page<'_>,
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index fc46293e7ea..d3701784f9d 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -195,7 +195,7 @@ fn slugify(c: char) -> Option<char> {
     }
 }
 
-#[derive(Clone, Debug)]
+#[derive(Debug)]
 pub struct Playground {
     pub crate_name: Option<Symbol>,
     pub url: String,
@@ -300,10 +300,13 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
                 crate_name: krate.map(String::from).unwrap_or_default(),
                 no_crate_inject: false,
                 insert_indent_space: true,
-                attrs: vec![],
                 args_file: PathBuf::new(),
             };
-            let doctest = doctest::DocTestBuilder::new(&test, krate, edition, false, None, None);
+            let mut builder = doctest::BuildDocTestBuilder::new(&test).edition(edition);
+            if let Some(krate) = krate {
+                builder = builder.crate_name(krate);
+            }
+            let doctest = builder.build(None);
             let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
             let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };
 
@@ -316,8 +319,10 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
             ))
         });
 
-        let tooltip = if ignore != Ignore::None {
-            highlight::Tooltip::Ignore
+        let tooltip = if ignore == Ignore::All {
+            highlight::Tooltip::IgnoreAll
+        } else if let Ignore::Some(platforms) = ignore {
+            highlight::Tooltip::IgnoreSome(platforms)
         } else if compile_fail {
             highlight::Tooltip::CompileFail
         } else if should_panic {
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index f22935df96c..5984dcd74ca 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -14,7 +14,7 @@ use rustc_span::edition::Edition;
 use rustc_span::{FileName, Symbol, sym};
 use tracing::info;
 
-use super::print_item::{full_path, item_path, print_item};
+use super::print_item::{full_path, print_item, print_item_path};
 use super::sidebar::{ModuleLike, Sidebar, print_sidebar, sidebar_module_like};
 use super::{AllTypes, LinkFromSrc, StylePath, collect_spans_and_sources, scrape_examples_help};
 use crate::clean::types::ExternalLocation;
@@ -28,11 +28,10 @@ use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
 use crate::html::escape::Escape;
 use crate::html::format::join_with_double_colon;
-use crate::html::layout::{self, BufDisplay};
 use crate::html::markdown::{self, ErrorCodes, IdMap, plain_text_summary};
 use crate::html::render::write_shared::write_shared;
 use crate::html::url_parts_builder::UrlPartsBuilder;
-use crate::html::{sources, static_files};
+use crate::html::{layout, sources, static_files};
 use crate::scrape_examples::AllCallLocations;
 use crate::{DOC_RUST_LANG_ORG_VERSION, try_err};
 
@@ -250,9 +249,7 @@ impl<'tcx> Context<'tcx> {
             layout::render(
                 &self.shared.layout,
                 &page,
-                BufDisplay(|buf: &mut String| {
-                    print_sidebar(self, it, buf);
-                }),
+                fmt::from_fn(|f| print_sidebar(self, it, f)),
                 content,
                 &self.shared.style_files,
             )
@@ -269,7 +266,7 @@ impl<'tcx> Context<'tcx> {
                         for name in &names[..names.len() - 1] {
                             write!(f, "{name}/")?;
                         }
-                        write!(f, "{}", item_path(ty, names.last().unwrap().as_str()))
+                        write!(f, "{}", print_item_path(ty, names.last().unwrap().as_str()))
                     });
                     match self.shared.redirections {
                         Some(ref redirections) => {
@@ -281,7 +278,7 @@ impl<'tcx> Context<'tcx> {
                             let _ = write!(
                                 current_path,
                                 "{}",
-                                item_path(ty, names.last().unwrap().as_str())
+                                print_item_path(ty, names.last().unwrap().as_str())
                             );
                             redirections.borrow_mut().insert(current_path, path.to_string());
                         }
@@ -850,7 +847,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         if !buf.is_empty() {
             let name = item.name.as_ref().unwrap();
             let item_type = item.type_();
-            let file_name = item_path(item_type, name.as_str()).to_string();
+            let file_name = print_item_path(item_type, name.as_str()).to_string();
             self.shared.ensure_dir(&self.dst)?;
             let joint_dst = self.dst.join(&file_name);
             self.shared.fs.write(joint_dst, buf)?;
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 3492df99955..66d5aafa3c1 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -538,7 +538,7 @@ fn document(
     }
 
     fmt::from_fn(move |f| {
-        document_item_info(cx, item, parent).render_into(f).unwrap();
+        document_item_info(cx, item, parent).render_into(f)?;
         if parent.is_none() {
             write!(f, "{}", document_full_collapsible(item, cx, heading_offset))
         } else {
@@ -582,7 +582,7 @@ fn document_short(
     show_def_docs: bool,
 ) -> impl fmt::Display {
     fmt::from_fn(move |f| {
-        document_item_info(cx, item, Some(parent)).render_into(f).unwrap();
+        document_item_info(cx, item, Some(parent)).render_into(f)?;
         if !show_def_docs {
             return Ok(());
         }
@@ -661,7 +661,7 @@ fn document_full_inner(
         };
 
         if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind {
-            render_call_locations(f, cx, item);
+            render_call_locations(f, cx, item)?;
         }
         Ok(())
     })
@@ -1194,18 +1194,36 @@ fn render_assoc_item(
 // a whitespace prefix and newline.
 fn render_attributes_in_pre(it: &clean::Item, prefix: &str, cx: &Context<'_>) -> impl fmt::Display {
     fmt::from_fn(move |f| {
-        for a in it.attributes(cx.tcx(), cx.cache(), false) {
+        for a in it.attributes_and_repr(cx.tcx(), cx.cache(), false) {
             writeln!(f, "{prefix}{a}")?;
         }
         Ok(())
     })
 }
 
+struct CodeAttribute(String);
+
+fn render_code_attribute(code_attr: CodeAttribute, w: &mut impl fmt::Write) {
+    write!(w, "<div class=\"code-attribute\">{}</div>", code_attr.0).unwrap();
+}
+
 // When an attribute is rendered inside a <code> tag, it is formatted using
 // a div to produce a newline after it.
 fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) {
-    for attr in it.attributes(cx.tcx(), cx.cache(), false) {
-        write!(w, "<div class=\"code-attribute\">{attr}</div>").unwrap();
+    for attr in it.attributes_and_repr(cx.tcx(), cx.cache(), false) {
+        render_code_attribute(CodeAttribute(attr), w);
+    }
+}
+
+/// used for type aliases to only render their `repr` attribute.
+fn render_repr_attributes_in_code(
+    w: &mut impl fmt::Write,
+    cx: &Context<'_>,
+    def_id: DefId,
+    item_type: ItemType,
+) {
+    if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type, false) {
+        render_code_attribute(CodeAttribute(repr), w);
     }
 }
 
@@ -2530,7 +2548,7 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection {
 /// types are re-exported, we don't use the corresponding
 /// entry from the js file, as inlining will have already
 /// picked up the impl
-fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> {
+fn collect_paths_for_type(first_ty: &clean::Type, cache: &Cache) -> Vec<String> {
     let mut out = Vec::new();
     let mut visited = FxHashSet::default();
     let mut work = VecDeque::new();
@@ -2547,7 +2565,7 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> {
     work.push_back(first_ty);
 
     while let Some(ty) = work.pop_front() {
-        if !visited.insert(ty.clone()) {
+        if !visited.insert(ty) {
             continue;
         }
 
@@ -2557,16 +2575,16 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> {
                 work.extend(tys.into_iter());
             }
             clean::Type::Slice(ty) => {
-                work.push_back(*ty);
+                work.push_back(ty);
             }
             clean::Type::Array(ty, _) => {
-                work.push_back(*ty);
+                work.push_back(ty);
             }
             clean::Type::RawPointer(_, ty) => {
-                work.push_back(*ty);
+                work.push_back(ty);
             }
             clean::Type::BorrowedRef { type_, .. } => {
-                work.push_back(*type_);
+                work.push_back(type_);
             }
             clean::Type::QPath(box clean::QPathData { self_type, trait_, .. }) => {
                 work.push_back(self_type);
@@ -2584,11 +2602,15 @@ const MAX_FULL_EXAMPLES: usize = 5;
 const NUM_VISIBLE_LINES: usize = 10;
 
 /// Generates the HTML for example call locations generated via the --scrape-examples flag.
-fn render_call_locations<W: fmt::Write>(mut w: W, cx: &Context<'_>, item: &clean::Item) {
+fn render_call_locations<W: fmt::Write>(
+    mut w: W,
+    cx: &Context<'_>,
+    item: &clean::Item,
+) -> fmt::Result {
     let tcx = cx.tcx();
     let def_id = item.item_id.expect_def_id();
     let key = tcx.def_path_hash(def_id);
-    let Some(call_locations) = cx.shared.call_locations.get(&key) else { return };
+    let Some(call_locations) = cx.shared.call_locations.get(&key) else { return Ok(()) };
 
     // Generate a unique ID so users can link to this section for a given method
     let id = cx.derive_id("scraped-examples");
@@ -2602,8 +2624,7 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &Context<'_>, item: &clean
           </h5>",
         root_path = cx.root_path(),
         id = id
-    )
-    .unwrap();
+    )?;
 
     // Create a URL to a particular location in a reverse-dependency's source file
     let link_to_loc = |call_data: &CallData, loc: &CallLocation| -> (String, String) {
@@ -2705,7 +2726,8 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &Context<'_>, item: &clean
                 title: init_title,
                 locations: locations_encoded,
             }),
-        );
+        )
+        .unwrap();
 
         true
     };
@@ -2761,8 +2783,7 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &Context<'_>, item: &clean
                   <div class=\"hide-more\">Hide additional examples</div>\
                   <div class=\"more-scraped-examples\">\
                     <div class=\"toggle-line\"><div class=\"toggle-line-inner\"></div></div>"
-        )
-        .unwrap();
+        )?;
 
         // Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could
         // make the page arbitrarily huge!
@@ -2774,9 +2795,8 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &Context<'_>, item: &clean
         if it.peek().is_some() {
             w.write_str(
                 r#"<div class="example-links">Additional examples can be found in:<br><ul>"#,
-            )
-            .unwrap();
-            it.for_each(|(_, call_data)| {
+            )?;
+            it.try_for_each(|(_, call_data)| {
                 let (url, _) = link_to_loc(call_data, &call_data.locations[0]);
                 write!(
                     w,
@@ -2784,13 +2804,12 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &Context<'_>, item: &clean
                     url = url,
                     name = call_data.display_name
                 )
-                .unwrap();
-            });
-            w.write_str("</ul></div>").unwrap();
+            })?;
+            w.write_str("</ul></div>")?;
         }
 
-        w.write_str("</div></details>").unwrap();
+        w.write_str("</div></details>")?;
     }
 
-    w.write_str("</div>").unwrap();
+    w.write_str("</div>")
 }
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 39a631b637b..a75088d27cc 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -20,7 +20,7 @@ use super::{
     collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference,
     item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
     render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
-    render_impl, render_rightside, render_stability_since_raw,
+    render_impl, render_repr_attributes_in_code, render_rightside, render_stability_since_raw,
     render_stability_since_raw_with_extra, write_section_heading,
 };
 use crate::clean;
@@ -413,7 +413,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
 
             match myitem.kind {
                 clean::ExternCrateItem { ref src } => {
-                    use crate::html::format::anchor;
+                    use crate::html::format::print_anchor;
 
                     match *src {
                         Some(src) => {
@@ -421,7 +421,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
                                 w,
                                 "<dt><code>{}extern crate {} as {};",
                                 visibility_print_with_space(myitem, cx),
-                                anchor(myitem.item_id.expect_def_id(), src, cx),
+                                print_anchor(myitem.item_id.expect_def_id(), src, cx),
                                 EscapeBodyTextWithWbr(myitem.name.unwrap().as_str())
                             )?;
                         }
@@ -430,7 +430,11 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
                                 w,
                                 "<dt><code>{}extern crate {};",
                                 visibility_print_with_space(myitem, cx),
-                                anchor(myitem.item_id.expect_def_id(), myitem.name.unwrap(), cx)
+                                print_anchor(
+                                    myitem.item_id.expect_def_id(),
+                                    myitem.name.unwrap(),
+                                    cx
+                                )
                             )?;
                         }
                     }
@@ -439,7 +443,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
 
                 clean::ImportItem(ref import) => {
                     let stab_tags = import.source.did.map_or_else(String::new, |import_def_id| {
-                        extra_info_tags(tcx, myitem, item, Some(import_def_id)).to_string()
+                        print_extra_info_tags(tcx, myitem, item, Some(import_def_id)).to_string()
                     });
 
                     let id = match import.kind {
@@ -497,7 +501,9 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
                     write!(
                         w,
                         "<dt>\
-                            <a class=\"{class}\" href=\"{href}\" title=\"{title}\">{name}</a>\
+                            <a class=\"{class}\" href=\"{href}\" title=\"{title1} {title2}\">\
+                            {name}\
+                            </a>\
                             {visibility_and_hidden}\
                             {unsafety_flag}\
                             {stab_tags}\
@@ -505,11 +511,12 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
                         {docs_before}{docs}{docs_after}",
                         name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()),
                         visibility_and_hidden = visibility_and_hidden,
-                        stab_tags = extra_info_tags(tcx, myitem, item, None),
+                        stab_tags = print_extra_info_tags(tcx, myitem, item, None),
                         class = myitem.type_(),
                         unsafety_flag = unsafety_flag,
-                        href = item_path(myitem.type_(), myitem.name.unwrap().as_str()),
-                        title = format_args!("{} {}", myitem.type_(), full_path(cx, myitem)),
+                        href = print_item_path(myitem.type_(), myitem.name.unwrap().as_str()),
+                        title1 = myitem.type_(),
+                        title2 = full_path(cx, myitem),
                     )?;
                 }
             }
@@ -524,7 +531,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
 
 /// Render the stability, deprecation and portability tags that are displayed in the item's summary
 /// at the module level.
-fn extra_info_tags(
+fn print_extra_info_tags(
     tcx: TyCtxt<'_>,
     item: &clean::Item,
     parent: &clean::Item,
@@ -639,7 +646,7 @@ fn item_function(cx: &Context<'_>, it: &clean::Item, f: &clean::Function) -> imp
 fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt::Display {
     fmt::from_fn(|w| {
         let tcx = cx.tcx();
-        let bounds = bounds(&t.bounds, false, cx);
+        let bounds = print_bounds(&t.bounds, false, cx);
         let required_types =
             t.items.iter().filter(|m| m.is_required_associated_type()).collect::<Vec<_>>();
         let provided_types = t.items.iter().filter(|m| m.is_associated_type()).collect::<Vec<_>>();
@@ -652,7 +659,7 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt:
         let count_types = required_types.len() + provided_types.len();
         let count_consts = required_consts.len() + provided_consts.len();
         let count_methods = required_methods.len() + provided_methods.len();
-        let must_implement_one_of_functions = tcx.trait_def(t.def_id).must_implement_one_of.clone();
+        let must_implement_one_of_functions = &tcx.trait_def(t.def_id).must_implement_one_of;
 
         // Output the trait definition
         wrap_item(w, |mut w| {
@@ -1088,7 +1095,7 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt:
                             it,
                             &implementor_dups,
                             &collect_paths_for_type(
-                                implementor.inner_impl().for_.clone(),
+                                &implementor.inner_impl().for_,
                                 &cx.shared.cache,
                             ),
                         )
@@ -1236,7 +1243,7 @@ fn item_trait_alias(
                 attrs = render_attributes_in_pre(it, "", cx),
                 name = it.name.unwrap(),
                 generics = t.generics.print(cx),
-                bounds = bounds(&t.bounds, true, cx),
+                bounds = print_bounds(&t.bounds, true, cx),
                 where_clause =
                     print_where_clause(&t.generics, cx, 0, Ending::NoNewline).maybe_display(),
             )
@@ -1278,94 +1285,58 @@ fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) ->
 
             match inner_type {
                 clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive } => {
-                    let variants_iter = || variants.iter().filter(|i| !i.is_stripped());
                     let ty = cx.tcx().type_of(it.def_id().unwrap()).instantiate_identity();
                     let enum_def_id = ty.ty_adt_def().unwrap().did();
 
-                    wrap_item(w, |w| {
-                        let variants_len = variants.len();
-                        let variants_count = variants_iter().count();
-                        let has_stripped_entries = variants_len != variants_count;
-
-                        write!(
-                            w,
-                            "enum {}{}{}",
-                            it.name.unwrap(),
-                            t.generics.print(cx),
-                            render_enum_fields(
-                                cx,
-                                Some(&t.generics),
-                                variants,
-                                variants_count,
-                                has_stripped_entries,
-                                *is_non_exhaustive,
-                                enum_def_id,
-                            )
-                        )
-                    })?;
-                    write!(w, "{}", item_variants(cx, it, variants, enum_def_id))?;
+                    DisplayEnum {
+                        variants,
+                        generics: &t.generics,
+                        is_non_exhaustive: *is_non_exhaustive,
+                        def_id: enum_def_id,
+                    }
+                    .render_into(cx, it, true, w)?;
                 }
                 clean::TypeAliasInnerType::Union { fields } => {
-                    wrap_item(w, |w| {
-                        let fields_count = fields.iter().filter(|i| !i.is_stripped()).count();
-                        let has_stripped_fields = fields.len() != fields_count;
+                    let ty = cx.tcx().type_of(it.def_id().unwrap()).instantiate_identity();
+                    let union_def_id = ty.ty_adt_def().unwrap().did();
 
-                        write!(
-                            w,
-                            "union {}{}{}",
-                            it.name.unwrap(),
-                            t.generics.print(cx),
-                            render_struct_fields(
-                                Some(&t.generics),
-                                None,
-                                fields,
-                                "",
-                                true,
-                                has_stripped_fields,
-                                cx,
-                            ),
-                        )
-                    })?;
-                    write!(w, "{}", item_fields(cx, it, fields, None))?;
+                    ItemUnion {
+                        cx,
+                        it,
+                        fields,
+                        generics: &t.generics,
+                        is_type_alias: true,
+                        def_id: union_def_id,
+                    }
+                    .render_into(w)?;
                 }
                 clean::TypeAliasInnerType::Struct { ctor_kind, fields } => {
-                    wrap_item(w, |w| {
-                        let fields_count = fields.iter().filter(|i| !i.is_stripped()).count();
-                        let has_stripped_fields = fields.len() != fields_count;
+                    let ty = cx.tcx().type_of(it.def_id().unwrap()).instantiate_identity();
+                    let struct_def_id = ty.ty_adt_def().unwrap().did();
 
-                        write!(
-                            w,
-                            "struct {}{}{}",
-                            it.name.unwrap(),
-                            t.generics.print(cx),
-                            render_struct_fields(
-                                Some(&t.generics),
-                                *ctor_kind,
-                                fields,
-                                "",
-                                true,
-                                has_stripped_fields,
-                                cx,
-                            ),
-                        )
-                    })?;
-                    write!(w, "{}", item_fields(cx, it, fields, None))?;
+                    DisplayStruct {
+                        ctor_kind: *ctor_kind,
+                        generics: &t.generics,
+                        fields,
+                        def_id: struct_def_id,
+                    }
+                    .render_into(cx, it, true, w)?;
                 }
             }
+        } else {
+            let def_id = it.item_id.expect_def_id();
+            // Render any items associated directly to this alias, as otherwise they
+            // won't be visible anywhere in the docs. It would be nice to also show
+            // associated items from the aliased type (see discussion in #32077), but
+            // we need #14072 to make sense of the generics.
+            write!(
+                w,
+                "{}{}",
+                render_assoc_items(cx, it, def_id, AssocItemRender::All),
+                document_type_layout(cx, def_id)
+            )?;
         }
 
-        let def_id = it.item_id.expect_def_id();
-        // Render any items associated directly to this alias, as otherwise they
-        // won't be visible anywhere in the docs. It would be nice to also show
-        // associated items from the aliased type (see discussion in #32077), but
-        // we need #14072 to make sense of the generics.
-        write!(
-            w,
-            "{}{}",
-            render_assoc_items(cx, it, def_id, AssocItemRender::All),
-            document_type_layout(cx, def_id)
-        )?;
-
         // [RUSTDOCIMPL] type.impl
         //
         // Include type definitions from the alias target type.
@@ -1463,50 +1434,83 @@ fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) ->
     })
 }
 
-fn item_union(cx: &Context<'_>, it: &clean::Item, s: &clean::Union) -> impl fmt::Display {
-    item_template!(
-        #[template(path = "item_union.html")]
-        struct ItemUnion<'a, 'cx> {
-            cx: &'a Context<'cx>,
-            it: &'a clean::Item,
-            s: &'a clean::Union,
-        },
-        methods = [document, document_type_layout, render_attributes_in_pre, render_assoc_items]
-    );
+item_template!(
+    #[template(path = "item_union.html")]
+    struct ItemUnion<'a, 'cx> {
+        cx: &'a Context<'cx>,
+        it: &'a clean::Item,
+        fields: &'a [clean::Item],
+        generics: &'a clean::Generics,
+        is_type_alias: bool,
+        def_id: DefId,
+    },
+    methods = [document, document_type_layout, render_assoc_items]
+);
+
+impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
+    fn render_union(&self) -> impl Display {
+        render_union(self.it, Some(&self.generics), &self.fields, self.cx)
+    }
 
-    impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
-        fn render_union(&self) -> impl Display {
-            render_union(self.it, Some(&self.s.generics), &self.s.fields, self.cx)
-        }
+    fn document_field(&self, field: &'a clean::Item) -> impl Display {
+        document(self.cx, field, Some(self.it), HeadingOffset::H3)
+    }
 
-        fn document_field(&self, field: &'a clean::Item) -> impl Display {
-            document(self.cx, field, Some(self.it), HeadingOffset::H3)
-        }
+    fn stability_field(&self, field: &clean::Item) -> Option<String> {
+        field.stability_class(self.cx.tcx())
+    }
 
-        fn stability_field(&self, field: &clean::Item) -> Option<String> {
-            field.stability_class(self.cx.tcx())
-        }
+    fn print_ty(&self, ty: &'a clean::Type) -> impl Display {
+        ty.print(self.cx)
+    }
 
-        fn print_ty(&self, ty: &'a clean::Type) -> impl Display {
-            ty.print(self.cx)
-        }
+    // FIXME (GuillaumeGomez): When <https://github.com/askama-rs/askama/issues/452> is implemented,
+    // we can replace the returned value with:
+    //
+    // `iter::Peekable<impl Iterator<Item = (&'a clean::Item, &'a clean::Type)>>`
+    //
+    // And update `item_union.html`.
+    fn fields_iter(&self) -> impl Iterator<Item = (&'a clean::Item, &'a clean::Type)> {
+        self.fields.iter().filter_map(|f| match f.kind {
+            clean::StructFieldItem(ref ty) => Some((f, ty)),
+            _ => None,
+        })
+    }
 
-        fn fields_iter(
-            &self,
-        ) -> iter::Peekable<impl Iterator<Item = (&'a clean::Item, &'a clean::Type)>> {
-            self.s
-                .fields
-                .iter()
-                .filter_map(|f| match f.kind {
-                    clean::StructFieldItem(ref ty) => Some((f, ty)),
-                    _ => None,
-                })
-                .peekable()
-        }
+    fn render_attributes_in_pre(&self) -> impl fmt::Display {
+        fmt::from_fn(move |f| {
+            if self.is_type_alias {
+                // For now the only attributes we render for type aliases are `repr` attributes.
+                if let Some(repr) = clean::repr_attributes(
+                    self.cx.tcx(),
+                    self.cx.cache(),
+                    self.def_id,
+                    ItemType::Union,
+                    false,
+                ) {
+                    writeln!(f, "{repr}")?;
+                };
+            } else {
+                for a in self.it.attributes_and_repr(self.cx.tcx(), self.cx.cache(), false) {
+                    writeln!(f, "{a}")?;
+                }
+            }
+            Ok(())
+        })
     }
+}
 
+fn item_union(cx: &Context<'_>, it: &clean::Item, s: &clean::Union) -> impl fmt::Display {
     fmt::from_fn(|w| {
-        ItemUnion { cx, it, s }.render_into(w).unwrap();
+        ItemUnion {
+            cx,
+            it,
+            fields: &s.fields,
+            generics: &s.generics,
+            is_type_alias: false,
+            def_id: it.def_id().unwrap(),
+        }
+        .render_into(w)?;
         Ok(())
     })
 }
@@ -1533,41 +1537,81 @@ fn print_tuple_struct_fields(cx: &Context<'_>, s: &[clean::Item]) -> impl Displa
     })
 }
 
-fn item_enum(cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) -> impl fmt::Display {
-    fmt::from_fn(|w| {
-        let count_variants = e.variants().count();
+struct DisplayEnum<'clean> {
+    variants: &'clean IndexVec<VariantIdx, clean::Item>,
+    generics: &'clean clean::Generics,
+    is_non_exhaustive: bool,
+    def_id: DefId,
+}
+
+impl<'clean> DisplayEnum<'clean> {
+    fn render_into<W: fmt::Write>(
+        self,
+        cx: &Context<'_>,
+        it: &clean::Item,
+        is_type_alias: bool,
+        w: &mut W,
+    ) -> fmt::Result {
+        let non_stripped_variant_count = self.variants.iter().filter(|i| !i.is_stripped()).count();
+        let variants_len = self.variants.len();
+        let has_stripped_entries = variants_len != non_stripped_variant_count;
+
         wrap_item(w, |w| {
-            render_attributes_in_code(w, it, cx);
+            if is_type_alias {
+                // For now the only attributes we render for type aliases are `repr` attributes.
+                render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Enum);
+            } else {
+                render_attributes_in_code(w, it, cx);
+            }
             write!(
                 w,
                 "{}enum {}{}{}",
                 visibility_print_with_space(it, cx),
                 it.name.unwrap(),
-                e.generics.print(cx),
+                self.generics.print(cx),
                 render_enum_fields(
                     cx,
-                    Some(&e.generics),
-                    &e.variants,
-                    count_variants,
-                    e.has_stripped_entries(),
-                    it.is_non_exhaustive(),
-                    it.def_id().unwrap(),
+                    Some(self.generics),
+                    self.variants,
+                    non_stripped_variant_count,
+                    has_stripped_entries,
+                    self.is_non_exhaustive,
+                    self.def_id,
                 ),
             )
         })?;
 
-        write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?;
+        let def_id = it.item_id.expect_def_id();
+        let layout_def_id = if is_type_alias {
+            self.def_id
+        } else {
+            write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?;
+            // We don't return the same `DefId` since the layout size of the type alias might be
+            // different since we might have more information on the generics.
+            def_id
+        };
 
-        if count_variants != 0 {
-            write!(w, "{}", item_variants(cx, it, &e.variants, it.def_id().unwrap()))?;
+        if non_stripped_variant_count != 0 {
+            write!(w, "{}", item_variants(cx, it, self.variants, self.def_id))?;
         }
-        let def_id = it.item_id.expect_def_id();
         write!(
             w,
             "{}{}",
             render_assoc_items(cx, it, def_id, AssocItemRender::All),
-            document_type_layout(cx, def_id)
+            document_type_layout(cx, layout_def_id)
         )
+    }
+}
+
+fn item_enum(cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) -> impl fmt::Display {
+    fmt::from_fn(|w| {
+        DisplayEnum {
+            variants: &e.variants,
+            generics: &e.generics,
+            is_non_exhaustive: it.is_non_exhaustive(),
+            def_id: it.def_id().unwrap(),
+        }
+        .render_into(cx, it, false, w)
     })
 }
 
@@ -1955,27 +1999,59 @@ fn item_constant(
     })
 }
 
-fn item_struct(cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) -> impl fmt::Display {
-    fmt::from_fn(|w| {
+struct DisplayStruct<'a> {
+    ctor_kind: Option<CtorKind>,
+    generics: &'a clean::Generics,
+    fields: &'a [clean::Item],
+    def_id: DefId,
+}
+
+impl<'a> DisplayStruct<'a> {
+    fn render_into<W: fmt::Write>(
+        self,
+        cx: &Context<'_>,
+        it: &clean::Item,
+        is_type_alias: bool,
+        w: &mut W,
+    ) -> fmt::Result {
         wrap_item(w, |w| {
-            render_attributes_in_code(w, it, cx);
+            if is_type_alias {
+                // For now the only attributes we render for type aliases are `repr` attributes.
+                render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Struct);
+            } else {
+                render_attributes_in_code(w, it, cx);
+            }
             write!(
                 w,
                 "{}",
-                render_struct(it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx)
+                render_struct(it, Some(self.generics), self.ctor_kind, self.fields, "", true, cx)
             )
         })?;
 
-        let def_id = it.item_id.expect_def_id();
+        if !is_type_alias {
+            write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?;
+        }
 
+        let def_id = it.item_id.expect_def_id();
         write!(
             w,
-            "{}{}{}{}",
-            document(cx, it, None, HeadingOffset::H2),
-            item_fields(cx, it, &s.fields, s.ctor_kind),
+            "{}{}{}",
+            item_fields(cx, it, self.fields, self.ctor_kind),
             render_assoc_items(cx, it, def_id, AssocItemRender::All),
             document_type_layout(cx, def_id),
         )
+    }
+}
+
+fn item_struct(cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) -> impl fmt::Display {
+    fmt::from_fn(|w| {
+        DisplayStruct {
+            ctor_kind: s.ctor_kind,
+            generics: &s.generics,
+            fields: s.fields.as_slice(),
+            def_id: it.def_id().unwrap(),
+        }
+        .render_into(cx, it, false, w)
     })
 }
 
@@ -2185,14 +2261,18 @@ pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String {
     s
 }
 
-pub(super) fn item_path(ty: ItemType, name: &str) -> impl Display {
+pub(super) fn print_item_path(ty: ItemType, name: &str) -> impl Display {
     fmt::from_fn(move |f| match ty {
         ItemType::Module => write!(f, "{}index.html", ensure_trailing_slash(name)),
         _ => write!(f, "{ty}.{name}.html"),
     })
 }
 
-fn bounds(bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>) -> impl Display {
+fn print_bounds(
+    bounds: &[clean::GenericBound],
+    trait_alias: bool,
+    cx: &Context<'_>,
+) -> impl Display {
     (!bounds.is_empty())
         .then_some(fmt::from_fn(move |f| {
             let has_lots_of_bounds = bounds.len() > 2;
diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs
index cd0c9775f5c..91540e06e33 100644
--- a/src/librustdoc/html/render/sidebar.rs
+++ b/src/librustdoc/html/render/sidebar.rs
@@ -1,5 +1,6 @@
 use std::borrow::Cow;
 use std::cmp::Ordering;
+use std::fmt;
 
 use askama::Template;
 use rustc_data_structures::fx::FxHashSet;
@@ -126,7 +127,7 @@ pub(crate) mod filters {
     use askama::filters::Safe;
 
     use crate::html::escape::EscapeBodyTextWithWbr;
-    pub(crate) fn wrapped<T>(v: T) -> askama::Result<Safe<impl Display>>
+    pub(crate) fn wrapped<T, V: askama::Values>(v: T, _: V) -> askama::Result<Safe<impl Display>>
     where
         T: Display,
     {
@@ -135,7 +136,11 @@ pub(crate) mod filters {
     }
 }
 
-pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut String) {
+pub(super) fn print_sidebar(
+    cx: &Context<'_>,
+    it: &clean::Item,
+    mut buffer: impl fmt::Write,
+) -> fmt::Result {
     let mut ids = IdMap::new();
     let mut blocks: Vec<LinkBlock<'_>> = docblock_toc(cx, it, &mut ids).into_iter().collect();
     let deref_id_map = cx.deref_id_map.borrow();
@@ -195,7 +200,8 @@ pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Str
         blocks,
         path,
     };
-    sidebar.render_into(buffer).unwrap();
+    sidebar.render_into(&mut buffer)?;
+    Ok(())
 }
 
 fn get_struct_fields_name<'a>(fields: &'a [clean::Item]) -> Vec<Link<'a>> {
@@ -593,7 +599,7 @@ fn sidebar_enum<'a>(
     deref_id_map: &'a DefIdMap<String>,
 ) {
     let mut variants = e
-        .variants()
+        .non_stripped_variants()
         .filter_map(|v| v.name)
         .map(|name| Link::new(format!("variant.{name}"), name.to_string()))
         .collect::<Vec<_>>();
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index b2bbf4614bf..33738f7a242 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -607,68 +607,68 @@ impl TypeAliasPart {
         let cx = type_impl_collector.cx;
         let aliased_types = type_impl_collector.aliased_types;
         for aliased_type in aliased_types.values() {
-            let impls = aliased_type
-                .impl_
-                .values()
-                .flat_map(|AliasedTypeImpl { impl_, type_aliases }| {
-                    let mut ret = Vec::new();
-                    let trait_ = impl_
-                        .inner_impl()
-                        .trait_
-                        .as_ref()
-                        .map(|trait_| format!("{:#}", trait_.print(cx)));
+            let impls = aliased_type.impl_.values().filter_map(
+                |AliasedTypeImpl { impl_, type_aliases }| {
+                    let mut ret: Option<AliasSerializableImpl> = None;
                     // render_impl will filter out "impossible-to-call" methods
                     // to make that functionality work here, it needs to be called with
                     // each type alias, and if it gives a different result, split the impl
                     for &(type_alias_fqp, type_alias_item) in type_aliases {
                         cx.id_map.borrow_mut().clear();
                         cx.deref_id_map.borrow_mut().clear();
-                        let target_did = impl_
-                            .inner_impl()
-                            .trait_
-                            .as_ref()
-                            .map(|trait_| trait_.def_id())
-                            .or_else(|| impl_.inner_impl().for_.def_id(&cx.shared.cache));
-                        let provided_methods;
-                        let assoc_link = if let Some(target_did) = target_did {
-                            provided_methods = impl_.inner_impl().provided_trait_methods(cx.tcx());
-                            AssocItemLink::GotoSource(ItemId::DefId(target_did), &provided_methods)
-                        } else {
-                            AssocItemLink::Anchor(None)
-                        };
-                        let text = super::render_impl(
-                            cx,
-                            impl_,
-                            type_alias_item,
-                            assoc_link,
-                            RenderMode::Normal,
-                            None,
-                            &[],
-                            ImplRenderingParameters {
-                                show_def_docs: true,
-                                show_default_items: true,
-                                show_non_assoc_items: true,
-                                toggle_open_by_default: true,
-                            },
-                        )
-                        .to_string();
                         let type_alias_fqp = (*type_alias_fqp).iter().join("::");
-                        if Some(&text) == ret.last().map(|s: &AliasSerializableImpl| &s.text) {
-                            ret.last_mut()
-                                .expect("already established that ret.last() is Some()")
-                                .aliases
-                                .push(type_alias_fqp);
+                        if let Some(ret) = &mut ret {
+                            ret.aliases.push(type_alias_fqp);
                         } else {
-                            ret.push(AliasSerializableImpl {
+                            let target_did = impl_
+                                .inner_impl()
+                                .trait_
+                                .as_ref()
+                                .map(|trait_| trait_.def_id())
+                                .or_else(|| impl_.inner_impl().for_.def_id(&cx.shared.cache));
+                            let provided_methods;
+                            let assoc_link = if let Some(target_did) = target_did {
+                                provided_methods =
+                                    impl_.inner_impl().provided_trait_methods(cx.tcx());
+                                AssocItemLink::GotoSource(
+                                    ItemId::DefId(target_did),
+                                    &provided_methods,
+                                )
+                            } else {
+                                AssocItemLink::Anchor(None)
+                            };
+                            let text = super::render_impl(
+                                cx,
+                                impl_,
+                                type_alias_item,
+                                assoc_link,
+                                RenderMode::Normal,
+                                None,
+                                &[],
+                                ImplRenderingParameters {
+                                    show_def_docs: true,
+                                    show_default_items: true,
+                                    show_non_assoc_items: true,
+                                    toggle_open_by_default: true,
+                                },
+                            )
+                            .to_string();
+                            // The alternate display prints it as plaintext instead of HTML.
+                            let trait_ = impl_
+                                .inner_impl()
+                                .trait_
+                                .as_ref()
+                                .map(|trait_| format!("{:#}", trait_.print(cx)));
+                            ret = Some(AliasSerializableImpl {
                                 text,
-                                trait_: trait_.clone(),
+                                trait_,
                                 aliases: vec![type_alias_fqp],
                             })
                         }
                     }
                     ret
-                })
-                .collect::<Vec<_>>();
+                },
+            );
 
             let mut path = PathBuf::from("type.impl");
             for component in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] {
@@ -681,7 +681,7 @@ impl TypeAliasPart {
             ));
 
             let part = OrderedJson::array_sorted(
-                impls.iter().map(OrderedJson::serialize).collect::<Result<Vec<_>, _>>().unwrap(),
+                impls.map(|impl_| OrderedJson::serialize(impl_).unwrap()),
             );
             path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
         }
@@ -759,7 +759,7 @@ impl TraitAliasPart {
                         Some(Implementor {
                             text: imp.inner_impl().print(false, cx).to_string(),
                             synthetic: imp.inner_impl().kind.is_auto(),
-                            types: collect_paths_for_type(imp.inner_impl().for_.clone(), cache),
+                            types: collect_paths_for_type(&imp.inner_impl().for_, cache),
                         })
                     }
                 })
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
index 095795c711d..1fa6b5a60f3 100644
--- a/src/librustdoc/html/sources.rs
+++ b/src/librustdoc/html/sources.rs
@@ -11,9 +11,8 @@ use rustc_session::Session;
 use rustc_span::{FileName, FileNameDisplayPreference, RealFileName, sym};
 use tracing::info;
 
-use super::highlight;
-use super::layout::{self, BufDisplay};
 use super::render::Context;
+use super::{highlight, layout};
 use crate::clean;
 use crate::clean::utils::has_doc_flag;
 use crate::docfs::PathError;
@@ -243,16 +242,16 @@ impl SourceCollector<'_, '_> {
             &shared.layout,
             &page,
             "",
-            BufDisplay(|buf: &mut String| {
+            fmt::from_fn(|f| {
                 print_src(
-                    buf,
+                    f,
                     contents,
                     file_span,
                     self.cx,
                     &root_path,
                     &highlight::DecorationInfo::default(),
                     &source_context,
-                );
+                )
             }),
             &shared.style_files,
         );
@@ -331,7 +330,7 @@ pub(crate) fn print_src(
     root_path: &str,
     decoration_info: &highlight::DecorationInfo,
     source_context: &SourceContext<'_>,
-) {
+) -> fmt::Result {
     let mut lines = s.lines().count();
     let line_info = if let SourceContext::Embedded(info) = source_context {
         highlight::LineInfo::new_scraped(lines as u32, info.offset as u32)
@@ -367,12 +366,10 @@ pub(crate) fn print_src(
             },
             max_nb_digits,
         }
-        .render_into(&mut writer)
-        .unwrap(),
+        .render_into(&mut writer),
         SourceContext::Embedded(info) => {
-            ScrapedSource { info, code_html: code, max_nb_digits }
-                .render_into(&mut writer)
-                .unwrap();
+            ScrapedSource { info, code_html: code, max_nb_digits }.render_into(&mut writer)
         }
-    };
+    }?;
+    Ok(())
 }
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index a81d5c9c49b..7be83b65fbf 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -2527,9 +2527,12 @@ in src-script.js and main.js
 		z-index: 11;
 		/* Reduce height slightly to account for mobile topbar. */
 		height: calc(100vh - 45px);
-		width: 200px;
 		/* resize indicator: hide this when on touch or mobile */
 		border-right: none;
+		width: 100%;
+	}
+	.sidebar-elems .block li a {
+		white-space: wrap;
 	}
 
 	/* The source view uses a different design for the sidebar toggle, and doesn't have a topbar,
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index a7ce2bf9048..7b1a61a3ffa 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -1179,8 +1179,10 @@ function preLoadCss(cssUrl) {
 
     onEachLazy(document.querySelectorAll(".toggle > summary:not(.hideme)"), el => {
         // @ts-expect-error
+        // Clicking on the summary's contents should not collapse it,
+        // but links within should still fire.
         el.addEventListener("click", e => {
-            if (e.target.tagName !== "SUMMARY" && e.target.tagName !== "A") {
+            if (!e.target.matches("summary, a, a *")) {
                 e.preventDefault();
             }
         });
diff --git a/src/librustdoc/html/templates/item_union.html b/src/librustdoc/html/templates/item_union.html
index b1c1d5a63a0..b5d3367a6a1 100644
--- a/src/librustdoc/html/templates/item_union.html
+++ b/src/librustdoc/html/templates/item_union.html
@@ -2,15 +2,16 @@
     {{ self.render_attributes_in_pre()|safe }}
     {{ self.render_union()|safe }}
 </code></pre>
-{{ self.document()|safe }}
-{% if self.fields_iter().peek().is_some() %}
+{% if !self.is_type_alias %}
+    {{ self.document()|safe }}
+{% endif %}
+{% if self.fields_iter().next().is_some() %}
     <h2 id="fields" class="fields section-header"> {# #}
         Fields<a href="#fields" class="anchor">§</a> {# #}
     </h2>
     {% for (field, ty) in self.fields_iter() %}
         {% let name = field.name.expect("union field name") %}
-        <span id="structfield.{{ name }}" {#+ #}
-            class="{{ ItemType::StructField +}} section-header"> {# #}
+        <span id="structfield.{{ name }}" class="{{ ItemType::StructField +}} section-header"> {# #}
             <a href="#structfield.{{ name }}" class="anchor field">§</a> {# #}
             <code>{{ name }}: {{+ self.print_ty(ty)|safe }}</code> {# #}
         </span>
diff --git a/src/librustdoc/html/tests.rs b/src/librustdoc/html/tests.rs
index b568942bbcb..873462bbeba 100644
--- a/src/librustdoc/html/tests.rs
+++ b/src/librustdoc/html/tests.rs
@@ -1,51 +1,51 @@
-use rustc_span::{Symbol, sym};
+use rustc_span::{Symbol, create_default_session_globals_then, sym};
 
 use crate::html::format::href_relative_parts;
 
-fn assert_relative_path(expected: &[Symbol], relative_to_fqp: &[Symbol], fqp: &[Symbol]) {
-    // No `create_default_session_globals_then` call is needed here because all
-    // the symbols used are static, and no `Symbol::intern` calls occur.
-    assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp).collect::<Vec<_>>());
+fn assert_relative_path(expected: &str, relative_to_fqp: &[Symbol], fqp: &[Symbol]) {
+    create_default_session_globals_then(|| {
+        assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp).finish());
+    });
 }
 
 #[test]
 fn href_relative_parts_basic() {
     let relative_to_fqp = &[sym::std, sym::vec];
     let fqp = &[sym::std, sym::iter];
-    assert_relative_path(&[sym::dotdot, sym::iter], relative_to_fqp, fqp);
+    assert_relative_path("../iter", relative_to_fqp, fqp);
 }
 
 #[test]
 fn href_relative_parts_parent_module() {
     let relative_to_fqp = &[sym::std, sym::vec];
     let fqp = &[sym::std];
-    assert_relative_path(&[sym::dotdot], relative_to_fqp, fqp);
+    assert_relative_path("..", relative_to_fqp, fqp);
 }
 
 #[test]
 fn href_relative_parts_different_crate() {
     let relative_to_fqp = &[sym::std, sym::vec];
     let fqp = &[sym::core, sym::iter];
-    assert_relative_path(&[sym::dotdot, sym::dotdot, sym::core, sym::iter], relative_to_fqp, fqp);
+    assert_relative_path("../../core/iter", relative_to_fqp, fqp);
 }
 
 #[test]
 fn href_relative_parts_same_module() {
     let relative_to_fqp = &[sym::std, sym::vec];
     let fqp = &[sym::std, sym::vec];
-    assert_relative_path(&[], relative_to_fqp, fqp);
+    assert_relative_path("", relative_to_fqp, fqp);
 }
 
 #[test]
 fn href_relative_parts_child_module() {
     let relative_to_fqp = &[sym::std];
     let fqp = &[sym::std, sym::vec];
-    assert_relative_path(&[sym::vec], relative_to_fqp, fqp);
+    assert_relative_path("vec", relative_to_fqp, fqp);
 }
 
 #[test]
 fn href_relative_parts_root() {
     let relative_to_fqp = &[];
     let fqp = &[sym::std];
-    assert_relative_path(&[sym::std], relative_to_fqp, fqp);
+    assert_relative_path("std", relative_to_fqp, fqp);
 }
diff --git a/src/librustdoc/html/url_parts_builder.rs b/src/librustdoc/html/url_parts_builder.rs
index 1e6af6af63c..9a533827441 100644
--- a/src/librustdoc/html/url_parts_builder.rs
+++ b/src/librustdoc/html/url_parts_builder.rs
@@ -14,7 +14,6 @@ pub(crate) struct UrlPartsBuilder {
 
 impl UrlPartsBuilder {
     /// Create an empty buffer.
-    #[allow(dead_code)]
     pub(crate) fn new() -> Self {
         Self { buf: String::new() }
     }
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 705f9b2202c..3ade40940bc 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -40,7 +40,7 @@ impl JsonRenderer<'_> {
             })
             .collect();
         let docs = item.opt_doc_value();
-        let attrs = item.attributes(self.tcx, self.cache(), true);
+        let attrs = item.attributes_and_repr(self.tcx, self.cache(), true);
         let span = item.span(self.tcx);
         let visibility = item.visibility(self.tcx);
         let clean::ItemInner { name, item_id, .. } = *item.inner;
@@ -468,6 +468,9 @@ impl FromClean<clean::WherePredicate> for WherePredicate {
                     .collect(),
             },
             EqPredicate { lhs, rhs } => WherePredicate::EqPredicate {
+                // The LHS currently has type `Type` but it should be a `QualifiedPath` since it may
+                // refer to an associated const. However, `EqPredicate` shouldn't exist in the first
+                // place: <https://github.com/rust-lang/rust/141368>.
                 lhs: lhs.into_json(renderer),
                 rhs: rhs.into_json(renderer),
             },
@@ -557,12 +560,7 @@ impl FromClean<clean::Type> for Type {
                 is_mutable: mutability == ast::Mutability::Mut,
                 type_: Box::new((*type_).into_json(renderer)),
             },
-            QPath(box clean::QPathData { assoc, self_type, trait_, .. }) => Type::QualifiedPath {
-                name: assoc.name.to_string(),
-                args: Box::new(assoc.args.into_json(renderer)),
-                self_type: Box::new(self_type.into_json(renderer)),
-                trait_: trait_.map(|trait_| trait_.into_json(renderer)),
-            },
+            QPath(qpath) => (*qpath).into_json(renderer),
             // FIXME(unsafe_binder): Implement rustdoc-json.
             UnsafeBinder(_) => todo!(),
         }
@@ -579,6 +577,19 @@ impl FromClean<clean::Path> for Path {
     }
 }
 
+impl FromClean<clean::QPathData> for Type {
+    fn from_clean(qpath: clean::QPathData, renderer: &JsonRenderer<'_>) -> Self {
+        let clean::QPathData { assoc, self_type, should_fully_qualify: _, trait_ } = qpath;
+
+        Self::QualifiedPath {
+            name: assoc.name.to_string(),
+            args: Box::new(assoc.args.into_json(renderer)),
+            self_type: Box::new(self_type.into_json(renderer)),
+            trait_: trait_.map(|trait_| trait_.into_json(renderer)),
+        }
+    }
+}
+
 impl FromClean<clean::Term> for Term {
     fn from_clean(term: clean::Term, renderer: &JsonRenderer<'_>) -> Term {
         match term {
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 001668c54a7..025c135aff2 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -3,6 +3,8 @@
     html_playground_url = "https://play.rust-lang.org/"
 )]
 #![feature(rustc_private)]
+#![feature(ascii_char)]
+#![feature(ascii_char_variants)]
 #![feature(assert_matches)]
 #![feature(box_patterns)]
 #![feature(debug_closure_helpers)]
diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs
index c9f0baaaa4c..66d8b667a4c 100644
--- a/src/librustdoc/passes/calculate_doc_coverage.rs
+++ b/src/librustdoc/passes/calculate_doc_coverage.rs
@@ -241,7 +241,7 @@ impl DocVisitor<'_> for CoverageCalculator<'_, '_> {
                                 data: hir::VariantData::Tuple(_, _, _),
                                 ..
                             }) | hir::Node::Item(hir::Item {
-                                kind: hir::ItemKind::Struct(_, hir::VariantData::Tuple(_, _, _), _),
+                                kind: hir::ItemKind::Struct(_, _, hir::VariantData::Tuple(_, _, _)),
                                 ..
                             })
                         )
diff --git a/src/librustdoc/passes/check_doc_cfg.rs b/src/librustdoc/passes/check_doc_cfg.rs
new file mode 100644
index 00000000000..3284da77a02
--- /dev/null
+++ b/src/librustdoc/passes/check_doc_cfg.rs
@@ -0,0 +1,76 @@
+use rustc_hir::HirId;
+use rustc_hir::def_id::LocalDefId;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::sym;
+
+use super::Pass;
+use crate::clean::{Attributes, Crate, Item};
+use crate::core::DocContext;
+use crate::visit::DocVisitor;
+
+pub(crate) const CHECK_DOC_CFG: Pass = Pass {
+    name: "check-doc-cfg",
+    run: Some(check_doc_cfg),
+    description: "checks `#[doc(cfg(...))]` for stability feature and unexpected cfgs",
+};
+
+pub(crate) fn check_doc_cfg(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
+    let mut checker = DocCfgChecker { cx };
+    checker.visit_crate(&krate);
+    krate
+}
+
+struct RustdocCfgMatchesLintEmitter<'a>(TyCtxt<'a>, HirId);
+
+impl<'a> rustc_attr_parsing::CfgMatchesLintEmitter for RustdocCfgMatchesLintEmitter<'a> {
+    fn emit_span_lint(
+        &self,
+        sess: &rustc_session::Session,
+        lint: &'static rustc_lint::Lint,
+        sp: rustc_span::Span,
+        builtin_diag: rustc_lint_defs::BuiltinLintDiag,
+    ) {
+        self.0.node_span_lint(lint, self.1, sp, |diag| {
+            rustc_lint::decorate_builtin_lint(sess, Some(self.0), builtin_diag, diag)
+        });
+    }
+}
+
+struct DocCfgChecker<'a, 'tcx> {
+    cx: &'a mut DocContext<'tcx>,
+}
+
+impl DocCfgChecker<'_, '_> {
+    fn check_attrs(&mut self, attrs: &Attributes, did: LocalDefId) {
+        let doc_cfgs = attrs
+            .other_attrs
+            .iter()
+            .filter(|attr| attr.has_name(sym::doc))
+            .flat_map(|attr| attr.meta_item_list().unwrap_or_default())
+            .filter(|attr| attr.has_name(sym::cfg));
+
+        for doc_cfg in doc_cfgs {
+            if let Some([cfg_mi]) = doc_cfg.meta_item_list() {
+                let _ = rustc_attr_parsing::cfg_matches(
+                    cfg_mi,
+                    &self.cx.tcx.sess,
+                    RustdocCfgMatchesLintEmitter(
+                        self.cx.tcx,
+                        self.cx.tcx.local_def_id_to_hir_id(did),
+                    ),
+                    Some(self.cx.tcx.features()),
+                );
+            }
+        }
+    }
+}
+
+impl DocVisitor<'_> for DocCfgChecker<'_, '_> {
+    fn visit_item(&mut self, item: &'_ Item) {
+        if let Some(Some(local_did)) = item.def_id().map(|did| did.as_local()) {
+            self.check_attrs(&item.attrs, local_did);
+        }
+
+        self.visit_item_recur(item);
+    }
+}
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index f3e2138d1a5..1daaba3b86c 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -22,6 +22,7 @@ use rustc_resolve::rustdoc::{
     MalformedGenerics, has_primitive_or_keyword_docs, prepare_to_doc_link_resolution,
     source_span_for_markdown_range, strip_generics_from_path,
 };
+use rustc_session::config::CrateType;
 use rustc_session::lint::Lint;
 use rustc_span::BytePos;
 use rustc_span::hygiene::MacroKind;
@@ -59,12 +60,7 @@ fn filter_assoc_items_by_name_and_namespace(
     ident: Ident,
     ns: Namespace,
 ) -> impl Iterator<Item = &ty::AssocItem> {
-    let iter: Box<dyn Iterator<Item = &ty::AssocItem>> = if !ident.name.is_empty() {
-        Box::new(tcx.associated_items(assoc_items_of).filter_by_name_unhygienic(ident.name))
-    } else {
-        Box::new([].iter())
-    };
-    iter.filter(move |item| {
+    tcx.associated_items(assoc_items_of).filter_by_name_unhygienic(ident.name).filter(move |item| {
         item.namespace() == ns && tcx.hygienic_eq(ident, item.ident(tcx), assoc_items_of)
     })
 }
@@ -1174,7 +1170,6 @@ impl LinkCollector<'_, '_> {
     #[allow(rustc::potential_query_instability)]
     pub(crate) fn resolve_ambiguities(&mut self) {
         let mut ambiguous_links = mem::take(&mut self.ambiguous_links);
-
         for ((item_id, path_str), info_items) in ambiguous_links.iter_mut() {
             for info in info_items {
                 info.resolved.retain(|(res, _)| match res {
@@ -2232,15 +2227,35 @@ fn ambiguity_error(
     emit_error: bool,
 ) -> bool {
     let mut descrs = FxHashSet::default();
-    let kinds = candidates
+    // proc macro can exist in multiple namespaces at once, so we need to compare `DefIds`
+    //  to remove the candidate in the fn namespace.
+    let mut possible_proc_macro_id = None;
+    let is_proc_macro_crate = cx.tcx.crate_types() == &[CrateType::ProcMacro];
+    let mut 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()))
+        .map(|(res, def_id)| {
+            let r =
+                if let Some(def_id) = def_id { Res::from_def_id(cx.tcx, *def_id) } else { *res };
+            if is_proc_macro_crate && let Res::Def(DefKind::Macro(_), id) = r {
+                possible_proc_macro_id = Some(id);
+            }
+            r
+        })
         .collect::<Vec<_>>();
+    // In order to properly dedup proc macros, we have to do it in two passes:
+    // 1. Completing the full traversal to find the possible duplicate in the macro namespace,
+    // 2. Another full traversal to eliminate the candidate in the fn namespace.
+    //
+    // Thus, we have to do an iteration after collection is finished.
+    //
+    // As an optimization, we only deduplicate if we're in a proc-macro crate,
+    // and only if we already found something that looks like a proc macro.
+    if is_proc_macro_crate && let Some(macro_id) = possible_proc_macro_id {
+        kinds.retain(|res| !matches!(res, Res::Def(DefKind::Fn, fn_id) if macro_id == *fn_id));
+    }
+
+    kinds.retain(|res| descrs.insert(res.descr()));
+
     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.
diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs
index 1e07277d38e..3b3ce3e9220 100644
--- a/src/librustdoc/passes/lint/bare_urls.rs
+++ b/src/librustdoc/passes/lint/bare_urls.rs
@@ -18,12 +18,15 @@ use crate::html::markdown::main_body_opts;
 
 pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
     let report_diag = |cx: &DocContext<'_>, msg: &'static str, range: Range<usize>| {
-        let sp = source_span_for_markdown_range(cx.tcx, dox, &range, &item.attrs.doc_strings)
-            .unwrap_or_else(|| item.attr_span(cx.tcx));
+        let maybe_sp = source_span_for_markdown_range(cx.tcx, dox, &range, &item.attrs.doc_strings);
+        let sp = maybe_sp.unwrap_or_else(|| item.attr_span(cx.tcx));
         cx.tcx.node_span_lint(crate::lint::BARE_URLS, hir_id, sp, |lint| {
             lint.primary_message(msg)
-                .note("bare URLs are not automatically turned into clickable links")
-                .multipart_suggestion(
+                .note("bare URLs are not automatically turned into clickable links");
+            // The fallback of using the attribute span is suitable for
+            // highlighting where the error is, but not for placing the < and >
+            if let Some(sp) = maybe_sp {
+                lint.multipart_suggestion(
                     "use an automatic link instead",
                     vec![
                         (sp.shrink_to_lo(), "<".to_string()),
@@ -31,6 +34,7 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &
                     ],
                     Applicability::MachineApplicable,
                 );
+            }
         });
     };
 
diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs
index 9ba63d34144..475d05b7d0e 100644
--- a/src/librustdoc/passes/mod.rs
+++ b/src/librustdoc/passes/mod.rs
@@ -32,6 +32,9 @@ pub(crate) use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS;
 mod check_doc_test_visibility;
 pub(crate) use self::check_doc_test_visibility::CHECK_DOC_TEST_VISIBILITY;
 
+mod check_doc_cfg;
+pub(crate) use self::check_doc_cfg::CHECK_DOC_CFG;
+
 mod collect_trait_impls;
 pub(crate) use self::collect_trait_impls::COLLECT_TRAIT_IMPLS;
 
@@ -72,6 +75,7 @@ pub(crate) enum Condition {
 
 /// The full list of passes.
 pub(crate) const PASSES: &[Pass] = &[
+    CHECK_DOC_CFG,
     CHECK_DOC_TEST_VISIBILITY,
     STRIP_ALIASED_NON_LOCAL,
     STRIP_HIDDEN,
@@ -89,6 +93,7 @@ pub(crate) const PASSES: &[Pass] = &[
 pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[
     ConditionalPass::always(COLLECT_TRAIT_IMPLS),
     ConditionalPass::always(CHECK_DOC_TEST_VISIBILITY),
+    ConditionalPass::always(CHECK_DOC_CFG),
     ConditionalPass::always(STRIP_ALIASED_NON_LOCAL),
     ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
     ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index 254549e72c6..5b52e785b8f 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -440,7 +440,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             hir::ItemKind::GlobalAsm { .. } => {}
             hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
             hir::ItemKind::Use(path, kind) => {
-                for &res in &path.res {
+                for res in path.res.present_items() {
                     // Struct and variant constructors and proc macro stubs always show up alongside
                     // their definitions, we've already processed them so just discard these.
                     if should_ignore_res(res) {