about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <476013+matthiaskrgr@users.noreply.github.com>2025-04-15 21:16:03 +0200
committerGitHub <noreply@github.com>2025-04-15 21:16:03 +0200
commitca0b7f4dcf2cbac004644831c70b0d7e3f645eeb (patch)
treefa7038a1ab1594d882da6c0ccad29a62fb7d60a2
parent40dacd50b7074783db748d73925ac5c3693a7ec1 (diff)
parent8dd1cbbdad2ba128c8237714665ef25b86e9945d (diff)
downloadrust-ca0b7f4dcf2cbac004644831c70b0d7e3f645eeb.tar.gz
rust-ca0b7f4dcf2cbac004644831c70b0d7e3f645eeb.zip
Rollup merge of #138455 - yotamofek:pr/rustdoc/more-impl-display, r=GuillaumeGomez
`librustdoc`: more `impl fmt::Display`

Continuation of #137425 and #136828 and #136784
Working towards getting rid of the `write_str` helper
r? `@GuillaumeGomez` (if you want!)
-rw-r--r--src/librustdoc/clean/types.rs2
-rw-r--r--src/librustdoc/html/format.rs55
-rw-r--r--src/librustdoc/html/render/context.rs10
-rw-r--r--src/librustdoc/html/render/mod.rs388
-rw-r--r--src/librustdoc/html/render/tests.rs3
5 files changed, 228 insertions, 230 deletions
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 7786b216112..31aa84535cc 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -517,7 +517,7 @@ impl Item {
                     Some(RenderedLink {
                         original_text: s.clone(),
                         new_text: link_text.clone(),
-                        tooltip: link_tooltip(*id, fragment, cx),
+                        tooltip: link_tooltip(*id, fragment, cx).to_string(),
                         href,
                     })
                 } else {
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 41e9a5a6651..4998c671b61 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -11,6 +11,7 @@ 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 rustc_abi::ExternAbi;
@@ -650,33 +651,35 @@ pub(crate) fn href_relative_parts<'fqp>(
     }
 }
 
-pub(crate) fn link_tooltip(did: DefId, fragment: &Option<UrlFragment>, cx: &Context<'_>) -> String {
-    let cache = cx.cache();
-    let Some((fqp, shortty)) = cache.paths.get(&did).or_else(|| cache.external_paths.get(&did))
-    else {
-        return String::new();
-    };
-    let mut buf = String::new();
-    let fqp = if *shortty == ItemType::Primitive {
-        // primitives are documented in a crate, but not actually part of it
-        &fqp[fqp.len() - 1..]
-    } else {
-        fqp
-    };
-    if let &Some(UrlFragment::Item(id)) = fragment {
-        write_str(&mut buf, format_args!("{} ", cx.tcx().def_descr(id)));
-        for component in fqp {
-            write_str(&mut buf, format_args!("{component}::"));
-        }
-        write_str(&mut buf, format_args!("{}", cx.tcx().item_name(id)));
-    } else if !fqp.is_empty() {
-        let mut fqp_it = fqp.iter();
-        write_str(&mut buf, format_args!("{shortty} {}", fqp_it.next().unwrap()));
-        for component in fqp_it {
-            write_str(&mut buf, format_args!("::{component}"));
+pub(crate) fn link_tooltip(
+    did: DefId,
+    fragment: &Option<UrlFragment>,
+    cx: &Context<'_>,
+) -> impl fmt::Display {
+    fmt::from_fn(move |f| {
+        let cache = cx.cache();
+        let Some((fqp, shortty)) = cache.paths.get(&did).or_else(|| cache.external_paths.get(&did))
+        else {
+            return Ok(());
+        };
+        let fqp = if *shortty == ItemType::Primitive {
+            // primitives are documented in a crate, but not actually part of it
+            slice::from_ref(fqp.last().unwrap())
+        } else {
+            fqp
+        };
+        if let &Some(UrlFragment::Item(id)) = fragment {
+            write!(f, "{} ", cx.tcx().def_descr(id))?;
+            for component in fqp {
+                write!(f, "{component}::")?;
+            }
+            write!(f, "{}", cx.tcx().item_name(id))?;
+        } else if !fqp.is_empty() {
+            write!(f, "{shortty} ")?;
+            fqp.iter().joined("::", f)?;
         }
-    }
-    buf
+        Ok(())
+    })
 }
 
 /// Used to render a [`clean::Path`].
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index e2d1f58a37e..596ac665fc3 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -650,15 +650,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
 
         bar.render_into(&mut sidebar).unwrap();
 
-        let v = layout::render(
-            &shared.layout,
-            &page,
-            sidebar,
-            BufDisplay(|buf: &mut String| {
-                all.print(buf);
-            }),
-            &shared.style_files,
-        );
+        let v = layout::render(&shared.layout, &page, sidebar, all.print(), &shared.style_files);
         shared.fs.write(final_file, v)?;
 
         // if to avoid writing help, settings files to doc root unless we're on the final invocation
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 21c823f49d1..94171ad6de8 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -40,6 +40,7 @@ mod span_map;
 mod type_layout;
 mod write_shared;
 
+use std::borrow::Cow;
 use std::collections::VecDeque;
 use std::fmt::{self, Display as _, Write};
 use std::iter::Peekable;
@@ -47,6 +48,7 @@ use std::path::PathBuf;
 use std::{fs, str};
 
 use askama::Template;
+use itertools::Either;
 use rustc_attr_parsing::{
     ConstStability, DeprecatedSince, Deprecation, RustcVersion, StabilityLevel, StableSince,
 };
@@ -98,6 +100,19 @@ enum AssocItemRender<'a> {
     DerefFor { trait_: &'a clean::Path, type_: &'a clean::Type, deref_mut_: bool },
 }
 
+impl AssocItemRender<'_> {
+    fn render_mode(&self) -> RenderMode {
+        match self {
+            Self::All => RenderMode::Normal,
+            &Self::DerefFor { deref_mut_, .. } => RenderMode::ForDeref { mut_: deref_mut_ },
+        }
+    }
+
+    fn class(&self) -> Option<&'static str> {
+        if let Self::DerefFor { .. } = self { Some("impl-items") } else { None }
+    }
+}
+
 /// For different handling of associated items from the Deref target of a type rather than the type
 /// itself.
 #[derive(Copy, Clone, PartialEq)]
@@ -439,44 +454,49 @@ impl AllTypes {
         sections
     }
 
-    fn print(&self, f: &mut String) {
-        fn print_entries(f: &mut String, e: &FxIndexSet<ItemEntry>, kind: ItemSection) {
-            if !e.is_empty() {
+    fn print(&self) -> impl fmt::Display {
+        fn print_entries(e: &FxIndexSet<ItemEntry>, kind: ItemSection) -> impl fmt::Display {
+            fmt::from_fn(move |f| {
+                if e.is_empty() {
+                    return Ok(());
+                }
+
                 let mut e: Vec<&ItemEntry> = e.iter().collect();
                 e.sort();
-                write_str(
+                write!(
                     f,
-                    format_args!(
-                        "<h3 id=\"{id}\">{title}</h3><ul class=\"all-items\">",
-                        id = kind.id(),
-                        title = kind.name(),
-                    ),
-                );
+                    "<h3 id=\"{id}\">{title}</h3><ul class=\"all-items\">",
+                    id = kind.id(),
+                    title = kind.name(),
+                )?;
 
                 for s in e.iter() {
-                    write_str(f, format_args!("<li>{}</li>", s.print()));
+                    write!(f, "<li>{}</li>", s.print())?;
                 }
 
-                f.push_str("</ul>");
-            }
+                f.write_str("</ul>")
+            })
         }
 
-        f.push_str("<h1>List of all items</h1>");
-        // Note: print_entries does not escape the title, because we know the current set of titles
-        // doesn't require escaping.
-        print_entries(f, &self.structs, ItemSection::Structs);
-        print_entries(f, &self.enums, ItemSection::Enums);
-        print_entries(f, &self.unions, ItemSection::Unions);
-        print_entries(f, &self.primitives, ItemSection::PrimitiveTypes);
-        print_entries(f, &self.traits, ItemSection::Traits);
-        print_entries(f, &self.macros, ItemSection::Macros);
-        print_entries(f, &self.attribute_macros, ItemSection::AttributeMacros);
-        print_entries(f, &self.derive_macros, ItemSection::DeriveMacros);
-        print_entries(f, &self.functions, ItemSection::Functions);
-        print_entries(f, &self.type_aliases, ItemSection::TypeAliases);
-        print_entries(f, &self.trait_aliases, ItemSection::TraitAliases);
-        print_entries(f, &self.statics, ItemSection::Statics);
-        print_entries(f, &self.constants, ItemSection::Constants);
+        fmt::from_fn(|f| {
+            f.write_str("<h1>List of all items</h1>")?;
+            // Note: print_entries does not escape the title, because we know the current set of titles
+            // doesn't require escaping.
+            print_entries(&self.structs, ItemSection::Structs).fmt(f)?;
+            print_entries(&self.enums, ItemSection::Enums).fmt(f)?;
+            print_entries(&self.unions, ItemSection::Unions).fmt(f)?;
+            print_entries(&self.primitives, ItemSection::PrimitiveTypes).fmt(f)?;
+            print_entries(&self.traits, ItemSection::Traits).fmt(f)?;
+            print_entries(&self.macros, ItemSection::Macros).fmt(f)?;
+            print_entries(&self.attribute_macros, ItemSection::AttributeMacros).fmt(f)?;
+            print_entries(&self.derive_macros, ItemSection::DeriveMacros).fmt(f)?;
+            print_entries(&self.functions, ItemSection::Functions).fmt(f)?;
+            print_entries(&self.type_aliases, ItemSection::TypeAliases).fmt(f)?;
+            print_entries(&self.trait_aliases, ItemSection::TraitAliases).fmt(f)?;
+            print_entries(&self.statics, ItemSection::Statics).fmt(f)?;
+            print_entries(&self.constants, ItemSection::Constants).fmt(f)?;
+            Ok(())
+        })
     }
 }
 
@@ -1205,7 +1225,7 @@ impl<'a> AssocItemLink<'a> {
 }
 
 fn write_section_heading(
-    title: &str,
+    title: impl fmt::Display,
     id: &str,
     extra_class: Option<&str>,
     extra: impl fmt::Display,
@@ -1225,7 +1245,7 @@ fn write_section_heading(
     })
 }
 
-fn write_impl_section_heading(title: &str, id: &str) -> impl fmt::Display {
+fn write_impl_section_heading(title: impl fmt::Display, id: &str) -> impl fmt::Display {
     write_section_heading(title, id, None, "")
 }
 
@@ -1302,20 +1322,17 @@ fn render_assoc_items_inner(
     let (mut non_trait, traits): (Vec<_>, _) =
         v.iter().partition(|i| i.inner_impl().trait_.is_none());
     if !non_trait.is_empty() {
-        let mut close_tags = <Vec<&str>>::with_capacity(1);
-        let mut tmp_buf = String::new();
-        let (render_mode, id, class_html) = match what {
-            AssocItemRender::All => {
-                write_str(
-                    &mut tmp_buf,
-                    format_args!(
-                        "{}",
-                        write_impl_section_heading("Implementations", "implementations")
-                    ),
-                );
-                (RenderMode::Normal, "implementations-list".to_owned(), "")
-            }
-            AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => {
+        let render_mode = what.render_mode();
+        let class_html = what
+            .class()
+            .map(|class| fmt::from_fn(move |f| write!(f, r#" class="{class}""#)))
+            .maybe_display();
+        let (section_heading, id) = match what {
+            AssocItemRender::All => (
+                Either::Left(write_impl_section_heading("Implementations", "implementations")),
+                Cow::Borrowed("implementations-list"),
+            ),
+            AssocItemRender::DerefFor { trait_, type_, .. } => {
                 let id =
                     cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx))));
                 // the `impls.get` above only looks at the outermost type,
@@ -1329,25 +1346,27 @@ fn render_assoc_items_inner(
                     type_.is_doc_subtype_of(&impl_.inner_impl().for_, &cx.shared.cache)
                 });
                 let derived_id = cx.derive_id(&id);
-                close_tags.push("</details>");
-                write_str(
-                    &mut tmp_buf,
-                    format_args!(
-                        "<details class=\"toggle big-toggle\" open><summary>{}</summary>",
-                        write_impl_section_heading(
-                            &format!(
-                                "<span>Methods from {trait_}&lt;Target = {type_}&gt;</span>",
-                                trait_ = trait_.print(cx),
-                                type_ = type_.print(cx),
-                            ),
-                            &id,
-                        )
-                    ),
-                );
                 if let Some(def_id) = type_.def_id(cx.cache()) {
-                    cx.deref_id_map.borrow_mut().insert(def_id, id);
+                    cx.deref_id_map.borrow_mut().insert(def_id, id.clone());
                 }
-                (RenderMode::ForDeref { mut_: deref_mut_ }, derived_id, r#" class="impl-items""#)
+                (
+                    Either::Right(fmt::from_fn(move |f| {
+                        write!(
+                            f,
+                            "<details class=\"toggle big-toggle\" open><summary>{}</summary>",
+                            write_impl_section_heading(
+                                fmt::from_fn(|f| write!(
+                                    f,
+                                    "<span>Methods from {trait_}&lt;Target = {type_}&gt;</span>",
+                                    trait_ = trait_.print(cx),
+                                    type_ = type_.print(cx),
+                                )),
+                                &id,
+                            )
+                        )
+                    })),
+                    Cow::Owned(derived_id),
+                )
             }
         };
         let mut impls_buf = String::new();
@@ -1375,10 +1394,14 @@ fn render_assoc_items_inner(
             );
         }
         if !impls_buf.is_empty() {
-            write!(w, "{tmp_buf}<div id=\"{id}\"{class_html}>{impls_buf}</div>").unwrap();
-            for tag in close_tags.into_iter().rev() {
-                w.write_str(tag).unwrap();
-            }
+            write!(
+                w,
+                "{section_heading}<div id=\"{id}\"{class_html}>{impls_buf}</div>{}",
+                matches!(what, AssocItemRender::DerefFor { .. })
+                    .then_some("</details>")
+                    .maybe_display(),
+            )
+            .unwrap();
         }
     }
 
@@ -1639,8 +1662,8 @@ fn render_impl(
         // `containing_item` is used for rendering stability info. If the parent is a trait impl,
         // `containing_item` will the grandparent, since trait impls can't have stability attached.
         fn doc_impl_item(
-            boring: &mut String,
-            interesting: &mut String,
+            boring: impl fmt::Write,
+            interesting: impl fmt::Write,
             cx: &Context<'_>,
             item: &clean::Item,
             parent: &clean::Item,
@@ -1649,7 +1672,7 @@ fn render_impl(
             is_default_item: bool,
             trait_: Option<&clean::Trait>,
             rendering_params: ImplRenderingParameters,
-        ) {
+        ) -> fmt::Result {
             let item_type = item.type_();
             let name = item.name.as_ref().unwrap();
 
@@ -1724,15 +1747,16 @@ fn render_impl(
                     );
                 }
             }
-            let w = if short_documented && trait_.is_some() { interesting } else { boring };
+            let mut w = if short_documented && trait_.is_some() {
+                Either::Left(interesting)
+            } else {
+                Either::Right(boring)
+            };
 
             let toggled = !doc_buffer.is_empty();
             if toggled {
                 let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" };
-                write_str(
-                    w,
-                    format_args!("<details class=\"toggle{method_toggle_class}\" open><summary>"),
-                );
+                write!(w, "<details class=\"toggle{method_toggle_class}\" open><summary>")?;
             }
             match &item.kind {
                 clean::MethodItem(..) | clean::RequiredMethodItem(_) => {
@@ -1747,172 +1771,151 @@ fn render_impl(
                                     .find(|item| item.name.map(|n| n == *name).unwrap_or(false))
                             })
                             .map(|item| format!("{}.{name}", item.type_()));
-                        write_str(
+                        write!(
                             w,
-                            format_args!(
-                                "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">\
+                            "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">\
                                 {}",
-                                render_rightside(cx, item, render_mode)
-                            ),
-                        );
+                            render_rightside(cx, item, render_mode)
+                        )?;
                         if trait_.is_some() {
                             // Anchors are only used on trait impls.
-                            write_str(w, format_args!("<a href=\"#{id}\" class=\"anchor\">§</a>"));
+                            write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>")?;
                         }
-                        write_str(
+                        write!(
                             w,
-                            format_args!(
-                                "<h4 class=\"code-header\">{}</h4></section>",
-                                render_assoc_item(
-                                    item,
-                                    link.anchor(source_id.as_ref().unwrap_or(&id)),
-                                    ItemType::Impl,
-                                    cx,
-                                    render_mode,
-                                ),
+                            "<h4 class=\"code-header\">{}</h4></section>",
+                            render_assoc_item(
+                                item,
+                                link.anchor(source_id.as_ref().unwrap_or(&id)),
+                                ItemType::Impl,
+                                cx,
+                                render_mode,
                             ),
-                        );
+                        )?;
                     }
                 }
                 clean::RequiredAssocConstItem(generics, ty) => {
                     let source_id = format!("{item_type}.{name}");
                     let id = cx.derive_id(&source_id);
-                    write_str(
+                    write!(
                         w,
-                        format_args!(
-                            "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">\
+                        "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">\
                             {}",
-                            render_rightside(cx, item, render_mode)
-                        ),
-                    );
+                        render_rightside(cx, item, render_mode)
+                    )?;
                     if trait_.is_some() {
                         // Anchors are only used on trait impls.
-                        write_str(w, format_args!("<a href=\"#{id}\" class=\"anchor\">§</a>"));
+                        write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>")?;
                     }
-                    write_str(
+                    write!(
                         w,
-                        format_args!(
-                            "<h4 class=\"code-header\">{}</h4></section>",
-                            assoc_const(
-                                item,
-                                generics,
-                                ty,
-                                AssocConstValue::None,
-                                link.anchor(if trait_.is_some() { &source_id } else { &id }),
-                                0,
-                                cx,
-                            )
+                        "<h4 class=\"code-header\">{}</h4></section>",
+                        assoc_const(
+                            item,
+                            generics,
+                            ty,
+                            AssocConstValue::None,
+                            link.anchor(if trait_.is_some() { &source_id } else { &id }),
+                            0,
+                            cx,
                         ),
-                    );
+                    )?;
                 }
                 clean::ProvidedAssocConstItem(ci) | clean::ImplAssocConstItem(ci) => {
                     let source_id = format!("{item_type}.{name}");
                     let id = cx.derive_id(&source_id);
-                    write_str(
+                    write!(
                         w,
-                        format_args!(
-                            "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">\
+                        "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">\
                             {}",
-                            render_rightside(cx, item, render_mode)
-                        ),
-                    );
+                        render_rightside(cx, item, render_mode),
+                    )?;
                     if trait_.is_some() {
                         // Anchors are only used on trait impls.
-                        write_str(w, format_args!("<a href=\"#{id}\" class=\"anchor\">§</a>"));
+                        write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>")?;
                     }
-                    write_str(
+                    write!(
                         w,
-                        format_args!(
-                            "<h4 class=\"code-header\">{}</h4></section>",
-                            assoc_const(
-                                item,
-                                &ci.generics,
-                                &ci.type_,
-                                match item.kind {
-                                    clean::ProvidedAssocConstItem(_) =>
-                                        AssocConstValue::TraitDefault(&ci.kind),
-                                    clean::ImplAssocConstItem(_) => AssocConstValue::Impl(&ci.kind),
-                                    _ => unreachable!(),
-                                },
-                                link.anchor(if trait_.is_some() { &source_id } else { &id }),
-                                0,
-                                cx,
-                            )
+                        "<h4 class=\"code-header\">{}</h4></section>",
+                        assoc_const(
+                            item,
+                            &ci.generics,
+                            &ci.type_,
+                            match item.kind {
+                                clean::ProvidedAssocConstItem(_) =>
+                                    AssocConstValue::TraitDefault(&ci.kind),
+                                clean::ImplAssocConstItem(_) => AssocConstValue::Impl(&ci.kind),
+                                _ => unreachable!(),
+                            },
+                            link.anchor(if trait_.is_some() { &source_id } else { &id }),
+                            0,
+                            cx,
                         ),
-                    );
+                    )?;
                 }
                 clean::RequiredAssocTypeItem(generics, bounds) => {
                     let source_id = format!("{item_type}.{name}");
                     let id = cx.derive_id(&source_id);
-                    write_str(
+                    write!(
                         w,
-                        format_args!(
-                            "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">\
+                        "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">\
                             {}",
-                            render_rightside(cx, item, render_mode)
-                        ),
-                    );
+                        render_rightside(cx, item, render_mode),
+                    )?;
                     if trait_.is_some() {
                         // Anchors are only used on trait impls.
-                        write_str(w, format_args!("<a href=\"#{id}\" class=\"anchor\">§</a>"));
+                        write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>")?;
                     }
-                    write_str(
+                    write!(
                         w,
-                        format_args!(
-                            "<h4 class=\"code-header\">{}</h4></section>",
-                            assoc_type(
-                                item,
-                                generics,
-                                bounds,
-                                None,
-                                link.anchor(if trait_.is_some() { &source_id } else { &id }),
-                                0,
-                                cx,
-                            )
+                        "<h4 class=\"code-header\">{}</h4></section>",
+                        assoc_type(
+                            item,
+                            generics,
+                            bounds,
+                            None,
+                            link.anchor(if trait_.is_some() { &source_id } else { &id }),
+                            0,
+                            cx,
                         ),
-                    );
+                    )?;
                 }
                 clean::AssocTypeItem(tydef, _bounds) => {
                     let source_id = format!("{item_type}.{name}");
                     let id = cx.derive_id(&source_id);
-                    write_str(
+                    write!(
                         w,
-                        format_args!(
-                            "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">\
+                        "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">\
                             {}",
-                            render_rightside(cx, item, render_mode)
-                        ),
-                    );
+                        render_rightside(cx, item, render_mode),
+                    )?;
                     if trait_.is_some() {
                         // Anchors are only used on trait impls.
-                        write_str(w, format_args!("<a href=\"#{id}\" class=\"anchor\">§</a>"));
+                        write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>")?;
                     }
-                    write_str(
+                    write!(
                         w,
-                        format_args!(
-                            "<h4 class=\"code-header\">{}</h4></section>",
-                            assoc_type(
-                                item,
-                                &tydef.generics,
-                                &[], // intentionally leaving out bounds
-                                Some(tydef.item_type.as_ref().unwrap_or(&tydef.type_)),
-                                link.anchor(if trait_.is_some() { &source_id } else { &id }),
-                                0,
-                                cx,
-                            )
+                        "<h4 class=\"code-header\">{}</h4></section>",
+                        assoc_type(
+                            item,
+                            &tydef.generics,
+                            &[], // intentionally leaving out bounds
+                            Some(tydef.item_type.as_ref().unwrap_or(&tydef.type_)),
+                            link.anchor(if trait_.is_some() { &source_id } else { &id }),
+                            0,
+                            cx,
                         ),
-                    );
+                    )?;
                 }
-                clean::StrippedItem(..) => return,
+                clean::StrippedItem(..) => return Ok(()),
                 _ => panic!("can't make docs for trait item with name {:?}", item.name),
             }
 
-            w.push_str(&info_buffer);
+            w.write_str(&info_buffer)?;
             if toggled {
-                w.push_str("</summary>");
-                w.push_str(&doc_buffer);
-                w.push_str("</details>");
+                write!(w, "</summary>{doc_buffer}</details>")?;
             }
+            Ok(())
         }
 
         let mut impl_items = String::new();
@@ -1955,7 +1958,7 @@ fn render_impl(
                             false,
                             trait_,
                             rendering_params,
-                        );
+                        )?;
                     }
                     _ => {}
                 }
@@ -1973,7 +1976,7 @@ fn render_impl(
                     false,
                     trait_,
                     rendering_params,
-                );
+                )?;
             }
             for method in methods {
                 doc_impl_item(
@@ -1987,20 +1990,20 @@ fn render_impl(
                     false,
                     trait_,
                     rendering_params,
-                );
+                )?;
             }
         }
 
         fn render_default_items(
-            boring: &mut String,
-            interesting: &mut String,
+            mut boring: impl fmt::Write,
+            mut interesting: impl fmt::Write,
             cx: &Context<'_>,
             t: &clean::Trait,
             i: &clean::Impl,
             parent: &clean::Item,
             render_mode: RenderMode,
             rendering_params: ImplRenderingParameters,
-        ) {
+        ) -> fmt::Result {
             for trait_item in &t.items {
                 // Skip over any default trait items that are impossible to reference
                 // (e.g. if it has a `Self: Sized` bound on an unsized type).
@@ -2020,8 +2023,8 @@ fn render_impl(
                 let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_methods);
 
                 doc_impl_item(
-                    boring,
-                    interesting,
+                    &mut boring,
+                    &mut interesting,
                     cx,
                     trait_item,
                     parent,
@@ -2030,8 +2033,9 @@ fn render_impl(
                     true,
                     Some(t),
                     rendering_params,
-                );
+                )?;
             }
+            Ok(())
         }
 
         // If we've implemented a trait, then also emit documentation for all
@@ -2051,7 +2055,7 @@ fn render_impl(
                     &i.impl_item,
                     render_mode,
                     rendering_params,
-                );
+                )?;
             }
         }
         if render_mode == RenderMode::Normal {
diff --git a/src/librustdoc/html/render/tests.rs b/src/librustdoc/html/render/tests.rs
index 657cd3c82aa..327a30887b1 100644
--- a/src/librustdoc/html/render/tests.rs
+++ b/src/librustdoc/html/render/tests.rs
@@ -47,8 +47,7 @@ fn test_all_types_prints_header_once() {
     // Regression test for #82477
     let all_types = AllTypes::new();
 
-    let mut buffer = String::new();
-    all_types.print(&mut buffer);
+    let buffer = all_types.print().to_string();
 
     assert_eq!(1, buffer.matches("List of all items").count());
 }