diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 91b4b3ba1ebac..522ef1af376cb 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -30,7 +30,7 @@ use super::url_parts_builder::{UrlPartsBuilder, estimate_item_path_byte_length}; use crate::clean::types::ExternalLocation; use crate::clean::utils::find_nearest_parent_module; use crate::clean::{self, ExternalCrate, PrimitiveType}; -use crate::display::Joined as _; +use crate::display::{Joined as _, MaybeDisplay as _}; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::escape::{Escape, EscapeBodyText}; @@ -178,12 +178,12 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( cx: &'a Context<'tcx>, indent: usize, ending: Ending, -) -> impl Display + 'a + Captures<'tcx> { - fmt::from_fn(move |f| { - if gens.where_predicates.is_empty() { - return Ok(()); - } +) -> Option> { + if gens.where_predicates.is_empty() { + return None; + } + Some(fmt::from_fn(move |f| { let where_preds = fmt::from_fn(|f| { gens.where_predicates .iter() @@ -246,7 +246,7 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( } }; write!(f, "{clause}") - }) + })) } impl clean::Lifetime { @@ -1179,7 +1179,7 @@ impl clean::Impl { self.print_type(&self.for_, f, use_absolute, cx)?; } - print_where_clause(&self.generics, cx, 0, Ending::Newline).fmt(f) + print_where_clause(&self.generics, cx, 0, Ending::Newline).maybe_display().fmt(f) }) } fn print_type<'a, 'tcx: 'a>( diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 146bdd340697b..5f69e79f3ab1f 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -237,8 +237,7 @@ impl<'tcx> Context<'tcx> { }; if !render_redirect_pages { - let mut page_buffer = String::new(); - print_item(self, it, &mut page_buffer); + let content = print_item(self, it); let page = layout::Page { css_class: tyname_s, root_path: &self.root_path(), @@ -254,7 +253,7 @@ impl<'tcx> Context<'tcx> { BufDisplay(|buf: &mut String| { print_sidebar(self, it, buf); }), - page_buffer, + content, &self.shared.style_files, ) } else { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 204631063a23a..bd4af359404a0 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -38,7 +38,7 @@ mod type_layout; mod write_shared; use std::collections::VecDeque; -use std::fmt::{self, Write}; +use std::fmt::{self, Display as _, Write}; use std::iter::Peekable; use std::path::PathBuf; use std::{fs, str}; @@ -774,9 +774,7 @@ pub(crate) fn render_impls( let did = i.trait_did().unwrap(); let provided_trait_methods = i.inner_impl().provided_trait_methods(cx.tcx()); let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_trait_methods); - let mut buffer = String::new(); - render_impl( - &mut buffer, + let imp = render_impl( cx, i, containing_item, @@ -791,7 +789,7 @@ pub(crate) fn render_impls( toggle_open_by_default, }, ); - buffer + imp.to_string() }) .collect::>(); rendered_impls.sort(); @@ -879,20 +877,19 @@ enum AssocConstValue<'a> { None, } -fn assoc_const( - w: &mut String, - it: &clean::Item, - generics: &clean::Generics, - ty: &clean::Type, - value: AssocConstValue<'_>, - link: AssocItemLink<'_>, +fn assoc_const<'a, 'tcx>( + it: &'a clean::Item, + generics: &'a clean::Generics, + ty: &'a clean::Type, + value: AssocConstValue<'a>, + link: AssocItemLink<'a>, indent: usize, - cx: &Context<'_>, -) { + cx: &'a Context<'tcx>, +) -> impl fmt::Display + 'a + Captures<'tcx> { let tcx = cx.tcx(); - write_str( - w, - format_args!( + fmt::from_fn(move |w| { + write!( + w, "{indent}{vis}const {name}{generics}: {ty}", indent = " ".repeat(indent), vis = visibility_print_with_space(it, cx), @@ -900,67 +897,65 @@ fn assoc_const( name = it.name.as_ref().unwrap(), generics = generics.print(cx), ty = ty.print(cx), - ), - ); - if let AssocConstValue::TraitDefault(konst) | AssocConstValue::Impl(konst) = value { - // FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the - // hood which adds noisy underscores and a type suffix to number literals. - // This hurts readability in this context especially when more complex expressions - // are involved and it doesn't add much of value. - // Find a way to print constants here without all that jazz. - let repr = konst.value(tcx).unwrap_or_else(|| konst.expr(tcx)); - if match value { - AssocConstValue::TraitDefault(_) => true, // always show - AssocConstValue::Impl(_) => repr != "_", // show if there is a meaningful value to show - AssocConstValue::None => unreachable!(), - } { - write_str(w, format_args!(" = {}", Escape(&repr))); + )?; + if let AssocConstValue::TraitDefault(konst) | AssocConstValue::Impl(konst) = value { + // FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the + // hood which adds noisy underscores and a type suffix to number literals. + // This hurts readability in this context especially when more complex expressions + // are involved and it doesn't add much of value. + // Find a way to print constants here without all that jazz. + let repr = konst.value(tcx).unwrap_or_else(|| konst.expr(tcx)); + if match value { + AssocConstValue::TraitDefault(_) => true, // always show + AssocConstValue::Impl(_) => repr != "_", // show if there is a meaningful value to show + AssocConstValue::None => unreachable!(), + } { + write!(w, " = {}", Escape(&repr))?; + } } - } - write_str(w, format_args!("{}", print_where_clause(generics, cx, indent, Ending::NoNewline))); + write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline).maybe_display()) + }) } -fn assoc_type( - w: &mut String, - it: &clean::Item, - generics: &clean::Generics, - bounds: &[clean::GenericBound], - default: Option<&clean::Type>, - link: AssocItemLink<'_>, +fn assoc_type<'a, 'tcx>( + it: &'a clean::Item, + generics: &'a clean::Generics, + bounds: &'a [clean::GenericBound], + default: Option<&'a clean::Type>, + link: AssocItemLink<'a>, indent: usize, - cx: &Context<'_>, -) { - write_str( - w, - format_args!( + cx: &'a Context<'tcx>, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(move |w| { + write!( + w, "{indent}{vis}type {name}{generics}", indent = " ".repeat(indent), vis = visibility_print_with_space(it, cx), href = assoc_href_attr(it, link, cx).maybe_display(), name = it.name.as_ref().unwrap(), generics = generics.print(cx), - ), - ); - if !bounds.is_empty() { - write_str(w, format_args!(": {}", print_generic_bounds(bounds, cx))); - } - // Render the default before the where-clause which aligns with the new recommended style. See #89122. - if let Some(default) = default { - write_str(w, format_args!(" = {}", default.print(cx))); - } - write_str(w, format_args!("{}", print_where_clause(generics, cx, indent, Ending::NoNewline))); + )?; + if !bounds.is_empty() { + write!(w, ": {}", print_generic_bounds(bounds, cx))?; + } + // Render the default before the where-clause which aligns with the new recommended style. See #89122. + if let Some(default) = default { + write!(w, " = {}", default.print(cx))?; + } + write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline).maybe_display()) + }) } -fn assoc_method( - w: &mut String, - meth: &clean::Item, - g: &clean::Generics, - d: &clean::FnDecl, - link: AssocItemLink<'_>, +fn assoc_method<'a, 'tcx>( + meth: &'a clean::Item, + g: &'a clean::Generics, + d: &'a clean::FnDecl, + link: AssocItemLink<'a>, parent: ItemType, - cx: &Context<'_>, + cx: &'a Context<'tcx>, render_mode: RenderMode, -) { +) -> impl fmt::Display + 'a + Captures<'tcx> { let tcx = cx.tcx(); let header = meth.fn_header(tcx).expect("Trying to get header from a non-function item"); let name = meth.name.as_ref().unwrap(); @@ -976,61 +971,53 @@ fn assoc_method( ), RenderMode::ForDeref { .. } => "", }; - let asyncness = header.asyncness.print_with_space(); - let safety = header.safety.print_with_space(); - let abi = print_abi_with_space(header.abi).to_string(); - let href = assoc_href_attr(meth, link, cx).maybe_display(); - - // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`. - let generics_len = format!("{:#}", g.print(cx)).len(); - let mut header_len = "fn ".len() - + vis.len() - + defaultness.len() - + constness.len() - + asyncness.len() - + safety.len() - + abi.len() - + name.as_str().len() - + generics_len; - - let notable_traits = notable_traits_button(&d.output, cx).maybe_display(); - - let (indent, indent_str, end_newline) = if parent == ItemType::Trait { - header_len += 4; - let indent_str = " "; - write_str(w, format_args!("{}", render_attributes_in_pre(meth, indent_str, cx))); - (4, indent_str, Ending::NoNewline) - } else { - render_attributes_in_code(w, meth, cx); - (0, "", Ending::Newline) - }; - w.reserve(header_len + "{".len() + "".len()); - write_str( - w, - format_args!( + + fmt::from_fn(move |w| { + let asyncness = header.asyncness.print_with_space(); + let safety = header.safety.print_with_space(); + let abi = print_abi_with_space(header.abi).to_string(); + let href = assoc_href_attr(meth, link, cx).maybe_display(); + + // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`. + let generics_len = format!("{:#}", g.print(cx)).len(); + let mut header_len = "fn ".len() + + vis.len() + + defaultness.len() + + constness.len() + + asyncness.len() + + safety.len() + + abi.len() + + name.as_str().len() + + generics_len; + + let notable_traits = notable_traits_button(&d.output, cx).maybe_display(); + + let (indent, indent_str, end_newline) = if parent == ItemType::Trait { + header_len += 4; + let indent_str = " "; + write!(w, "{}", render_attributes_in_pre(meth, indent_str, cx))?; + (4, indent_str, Ending::NoNewline) + } else { + render_attributes_in_code(w, meth, cx); + (0, "", Ending::Newline) + }; + write!( + w, "{indent}{vis}{defaultness}{constness}{asyncness}{safety}{abi}fn \ - {name}{generics}{decl}{notable_traits}{where_clause}", + {name}{generics}{decl}{notable_traits}{where_clause}", indent = indent_str, - vis = vis, - defaultness = defaultness, - constness = constness, - asyncness = asyncness, - safety = safety, - abi = abi, - href = href, - name = name, generics = g.print(cx), decl = d.full_print(header_len, indent, cx), - where_clause = print_where_clause(g, cx, indent, end_newline), - ), - ); + where_clause = print_where_clause(g, cx, indent, end_newline).maybe_display(), + ) + }) } /// Writes a span containing the versions at which an item became stable and/or const-stable. For /// example, if the item became stable at 1.0.0, and const-stable at 1.45.0, this function would /// write a span containing "1.0.0 (const: 1.45.0)". /// -/// Returns `true` if a stability annotation was rendered. +/// Returns `None` if there is no stability annotation to be rendered. /// /// Stability and const-stability are considered separately. If the item is unstable, no version /// will be written. If the item is const-unstable, "const: unstable" will be appended to the @@ -1041,11 +1028,10 @@ fn assoc_method( /// will include the const-stable version, but no stable version will be emitted, as a natural /// consequence of the above rules. fn render_stability_since_raw_with_extra( - w: &mut String, stable_version: Option, const_stability: Option, extra_class: &str, -) -> bool { +) -> Option { let mut title = String::new(); let mut stability = String::new(); @@ -1095,14 +1081,9 @@ fn render_stability_since_raw_with_extra( } } - if !stability.is_empty() { - write_str( - w, - format_args!(r#"{stability}"#), - ); - } - - !stability.is_empty() + (!stability.is_empty()).then_some(fmt::from_fn(move |w| { + write!(w, r#"{stability}"#) + })) } fn since_to_string(since: &StableSince) -> Option { @@ -1115,31 +1096,25 @@ fn since_to_string(since: &StableSince) -> Option { #[inline] fn render_stability_since_raw( - w: &mut String, ver: Option, const_stability: Option, -) -> bool { - render_stability_since_raw_with_extra(w, ver, const_stability, "") +) -> Option { + render_stability_since_raw_with_extra(ver, const_stability, "") } -fn render_assoc_item( - w: &mut String, - item: &clean::Item, - link: AssocItemLink<'_>, +fn render_assoc_item<'a, 'tcx>( + item: &'a clean::Item, + link: AssocItemLink<'a>, parent: ItemType, - cx: &Context<'_>, + cx: &'a Context<'tcx>, render_mode: RenderMode, -) { - match &item.kind { - clean::StrippedItem(..) => {} - clean::RequiredMethodItem(m) => { - assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode) - } - clean::MethodItem(m, _) => { - assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode) +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(move |f| match &item.kind { + clean::StrippedItem(..) => Ok(()), + clean::RequiredMethodItem(m) | clean::MethodItem(m, _) => { + assoc_method(item, &m.generics, &m.decl, link, parent, cx, render_mode).fmt(f) } clean::RequiredAssocConstItem(generics, ty) => assoc_const( - w, item, generics, ty, @@ -1147,9 +1122,9 @@ fn render_assoc_item( link, if parent == ItemType::Trait { 4 } else { 0 }, cx, - ), + ) + .fmt(f), clean::ProvidedAssocConstItem(ci) => assoc_const( - w, item, &ci.generics, &ci.type_, @@ -1157,9 +1132,9 @@ fn render_assoc_item( link, if parent == ItemType::Trait { 4 } else { 0 }, cx, - ), + ) + .fmt(f), clean::ImplAssocConstItem(ci) => assoc_const( - w, item, &ci.generics, &ci.type_, @@ -1167,9 +1142,9 @@ fn render_assoc_item( link, if parent == ItemType::Trait { 4 } else { 0 }, cx, - ), + ) + .fmt(f), clean::RequiredAssocTypeItem(ref generics, ref bounds) => assoc_type( - w, item, generics, bounds, @@ -1177,9 +1152,9 @@ fn render_assoc_item( link, if parent == ItemType::Trait { 4 } else { 0 }, cx, - ), + ) + .fmt(f), clean::AssocTypeItem(ref ty, ref bounds) => assoc_type( - w, item, &ty.generics, bounds, @@ -1187,9 +1162,10 @@ fn render_assoc_item( link, if parent == ItemType::Trait { 4 } else { 0 }, cx, - ), + ) + .fmt(f), _ => panic!("render_assoc_item called on non-associated-item"), - } + }) } // When an attribute is rendered inside a `
` tag, it is formatted using
@@ -1230,29 +1206,29 @@ impl<'a> AssocItemLink<'a> {
     }
 }
 
-pub fn write_section_heading(
-    w: &mut impl fmt::Write,
-    title: &str,
-    id: &str,
-    extra_class: Option<&str>,
-    extra: impl fmt::Display,
-) {
-    let (extra_class, whitespace) = match extra_class {
-        Some(extra) => (extra, " "),
-        None => ("", ""),
-    };
-    write!(
-        w,
-        "

\ +pub fn write_section_heading<'a>( + title: &'a str, + id: &'a str, + extra_class: Option<&'a str>, + extra: impl fmt::Display + 'a, +) -> impl fmt::Display + 'a { + fmt::from_fn(move |w| { + let (extra_class, whitespace) = match extra_class { + Some(extra) => (extra, " "), + None => ("", ""), + }; + write!( + w, + "

\ {title}\ §\

{extra}", - ) - .unwrap(); + ) + }) } -fn write_impl_section_heading(w: &mut impl fmt::Write, title: &str, id: &str) { - write_section_heading(w, title, id, None, "") +fn write_impl_section_heading<'a>(title: &'a str, id: &'a str) -> impl fmt::Display + 'a { + write_section_heading(title, id, None, "") } pub(crate) fn render_all_impls( @@ -1269,24 +1245,32 @@ pub(crate) fn render_all_impls( buf }; if !impls.is_empty() { - write_impl_section_heading(&mut w, "Trait Implementations", "trait-implementations"); - write!(w, "
{impls}
").unwrap(); + write!( + w, + "{}
{impls}
", + write_impl_section_heading("Trait Implementations", "trait-implementations") + ) + .unwrap(); } if !synthetic.is_empty() { - write_impl_section_heading( - &mut w, - "Auto Trait Implementations", - "synthetic-implementations", - ); - w.write_str("
").unwrap(); + write!( + w, + "{}
", + write_impl_section_heading("Auto Trait Implementations", "synthetic-implementations",) + ) + .unwrap(); render_impls(cx, &mut w, synthetic, containing_item, false); w.write_str("
").unwrap(); } if !blanket_impl.is_empty() { - write_impl_section_heading(&mut w, "Blanket Implementations", "blanket-implementations"); - w.write_str("
").unwrap(); + write!( + w, + "{}
", + write_impl_section_heading("Blanket Implementations", "blanket-implementations") + ) + .unwrap(); render_impls(cx, &mut w, blanket_impl, containing_item, false); w.write_str("
").unwrap(); } @@ -1323,25 +1307,34 @@ fn render_assoc_items_inner( let mut tmp_buf = String::new(); let (render_mode, id, class_html) = match what { AssocItemRender::All => { - write_impl_section_heading(&mut tmp_buf, "Implementations", "implementations"); + 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 id = cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx)))); let derived_id = cx.derive_id(&id); - tmp_buf.push_str("
"); close_tags.push("
"); - write_impl_section_heading( + write_str( &mut tmp_buf, - &format!( - "Methods from {trait_}<Target = {type_}>", - trait_ = trait_.print(cx), - type_ = type_.print(cx), + format_args!( + "
{}", + write_impl_section_heading( + &format!( + "Methods from {trait_}<Target = {type_}>", + trait_ = trait_.print(cx), + type_ = type_.print(cx), + ), + &id, + ) ), - &id, ); - tmp_buf.push_str(""); if let Some(def_id) = type_.def_id(cx.cache()) { cx.deref_id_map.borrow_mut().insert(def_id, id); } @@ -1350,21 +1343,26 @@ fn render_assoc_items_inner( }; let mut impls_buf = String::new(); for i in &non_trait { - render_impl( + write_str( &mut impls_buf, - cx, - i, - containing_item, - AssocItemLink::Anchor(None), - render_mode, - None, - &[], - ImplRenderingParameters { - show_def_docs: true, - show_default_items: true, - show_non_assoc_items: true, - toggle_open_by_default: true, - }, + format_args!( + "{}", + render_impl( + cx, + i, + containing_item, + AssocItemLink::Anchor(None), + render_mode, + None, + &[], + ImplRenderingParameters { + show_def_docs: true, + show_default_items: true, + show_non_assoc_items: true, + toggle_open_by_default: true, + }, + ) + ), ); } if !impls_buf.is_empty() { @@ -1564,20 +1562,23 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { ); for it in &impl_.items { if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind { - out.push_str("
"); let empty_set = FxIndexSet::default(); let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set); - assoc_type( + write_str( &mut out, - it, - &tydef.generics, - &[], // intentionally leaving out bounds - Some(&tydef.type_), - src_link, - 0, - cx, + format_args!( + "
{};
", + assoc_type( + it, + &tydef.generics, + &[], // intentionally leaving out bounds + Some(&tydef.type_), + src_link, + 0, + cx, + ) + ), ); - out.push_str(";
"); } } } @@ -1622,592 +1623,627 @@ struct ImplRenderingParameters { toggle_open_by_default: bool, } -fn render_impl( - w: &mut String, - cx: &Context<'_>, - i: &Impl, - parent: &clean::Item, - link: AssocItemLink<'_>, +fn render_impl<'a, 'tcx>( + cx: &'a Context<'tcx>, + i: &'a Impl, + parent: &'a clean::Item, + link: AssocItemLink<'a>, render_mode: RenderMode, use_absolute: Option, - aliases: &[String], + aliases: &'a [String], rendering_params: ImplRenderingParameters, -) { - let cache = &cx.shared.cache; - let traits = &cache.traits; - let trait_ = i.trait_did().map(|did| &traits[&did]); - let mut close_tags = >::with_capacity(2); - - // For trait implementations, the `interesting` output contains all methods that have doc - // comments, and the `boring` output contains all methods that do not. The distinction is - // used to allow hiding the boring methods. - // `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, - cx: &Context<'_>, - item: &clean::Item, - parent: &clean::Item, - link: AssocItemLink<'_>, - render_mode: RenderMode, - is_default_item: bool, - trait_: Option<&clean::Trait>, - rendering_params: ImplRenderingParameters, - ) { - let item_type = item.type_(); - let name = item.name.as_ref().unwrap(); - - let render_method_item = rendering_params.show_non_assoc_items - && match render_mode { - RenderMode::Normal => true, - RenderMode::ForDeref { mut_: deref_mut_ } => { - should_render_item(item, deref_mut_, cx.tcx()) - } - }; +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(move |w| { + let cache = &cx.shared.cache; + let traits = &cache.traits; + let trait_ = i.trait_did().map(|did| &traits[&did]); + let mut close_tags = >::with_capacity(2); + + // For trait implementations, the `interesting` output contains all methods that have doc + // comments, and the `boring` output contains all methods that do not. The distinction is + // used to allow hiding the boring methods. + // `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, + cx: &Context<'_>, + item: &clean::Item, + parent: &clean::Item, + link: AssocItemLink<'_>, + render_mode: RenderMode, + is_default_item: bool, + trait_: Option<&clean::Trait>, + rendering_params: ImplRenderingParameters, + ) { + let item_type = item.type_(); + let name = item.name.as_ref().unwrap(); + + let render_method_item = rendering_params.show_non_assoc_items + && match render_mode { + RenderMode::Normal => true, + RenderMode::ForDeref { mut_: deref_mut_ } => { + should_render_item(item, deref_mut_, cx.tcx()) + } + }; - let in_trait_class = if trait_.is_some() { " trait-impl" } else { "" }; - - let mut doc_buffer = String::new(); - let mut info_buffer = String::new(); - let mut short_documented = true; - - if render_method_item { - if !is_default_item { - if let Some(t) = trait_ { - // The trait item may have been stripped so we might not - // find any documentation or stability for it. - if let Some(it) = t.items.iter().find(|i| i.name == item.name) { - // We need the stability of the item from the trait - // because impls can't have a stability. - if !item.doc_value().is_empty() { - document_item_info(cx, it, Some(parent)) - .render_into(&mut info_buffer) - .unwrap(); + let in_trait_class = if trait_.is_some() { " trait-impl" } else { "" }; + + let mut doc_buffer = String::new(); + let mut info_buffer = String::new(); + let mut short_documented = true; + + if render_method_item { + if !is_default_item { + if let Some(t) = trait_ { + // The trait item may have been stripped so we might not + // find any documentation or stability for it. + if let Some(it) = t.items.iter().find(|i| i.name == item.name) { + // We need the stability of the item from the trait + // because impls can't have a stability. + if !item.doc_value().is_empty() { + document_item_info(cx, it, Some(parent)) + .render_into(&mut info_buffer) + .unwrap(); + write_str( + &mut doc_buffer, + format_args!("{}", document_full(item, cx, HeadingOffset::H5)), + ); + short_documented = false; + } else { + // In case the item isn't documented, + // provide short documentation from the trait. + write_str( + &mut doc_buffer, + format_args!( + "{}", + document_short( + it, + cx, + link, + parent, + rendering_params.show_def_docs, + ) + ), + ); + } + } + } else { + document_item_info(cx, item, Some(parent)) + .render_into(&mut info_buffer) + .unwrap(); + if rendering_params.show_def_docs { write_str( &mut doc_buffer, format_args!("{}", document_full(item, cx, HeadingOffset::H5)), ); short_documented = false; - } else { - // In case the item isn't documented, - // provide short documentation from the trait. - write_str( - &mut doc_buffer, - format_args!( - "{}", - document_short( - it, - cx, - link, - parent, - rendering_params.show_def_docs, - ) - ), - ); } } } else { - document_item_info(cx, item, Some(parent)) - .render_into(&mut info_buffer) - .unwrap(); - if rendering_params.show_def_docs { - write_str( - &mut doc_buffer, - format_args!("{}", document_full(item, cx, HeadingOffset::H5)), - ); - short_documented = false; - } + write_str( + &mut doc_buffer, + format_args!( + "{}", + document_short(item, cx, link, parent, rendering_params.show_def_docs) + ), + ); } - } else { + } + let w = if short_documented && trait_.is_some() { interesting } else { boring }; + + let toggled = !doc_buffer.is_empty(); + if toggled { + let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" }; write_str( - &mut doc_buffer, - format_args!( - "{}", - document_short(item, cx, link, parent, rendering_params.show_def_docs) - ), + w, + format_args!("
"), ); } - } - let w = if short_documented && trait_.is_some() { interesting } else { 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!("
"), - ); - } - match &item.kind { - clean::MethodItem(..) | clean::RequiredMethodItem(_) => { - // Only render when the method is not static or we allow static methods - if render_method_item { - let id = cx.derive_id(format!("{item_type}.{name}")); - let source_id = trait_ - .and_then(|trait_| { - trait_ - .items - .iter() - .find(|item| item.name.map(|n| n == *name).unwrap_or(false)) - }) - .map(|item| format!("{}.{name}", item.type_())); + match &item.kind { + clean::MethodItem(..) | clean::RequiredMethodItem(_) => { + // Only render when the method is not static or we allow static methods + if render_method_item { + let id = cx.derive_id(format!("{item_type}.{name}")); + let source_id = trait_ + .and_then(|trait_| { + trait_ + .items + .iter() + .find(|item| item.name.map(|n| n == *name).unwrap_or(false)) + }) + .map(|item| format!("{}.{name}", item.type_())); + write_str( + w, + format_args!( + "
\ + {}", + render_rightside(cx, item, render_mode) + ), + ); + if trait_.is_some() { + // Anchors are only used on trait impls. + write_str(w, format_args!("§")); + } + write_str( + w, + format_args!( + "

{}

", + render_assoc_item( + item, + link.anchor(source_id.as_ref().unwrap_or(&id)), + ItemType::Impl, + cx, + render_mode, + ), + ), + ); + } + } + clean::RequiredAssocConstItem(ref generics, ref ty) => { + let source_id = format!("{item_type}.{name}"); + let id = cx.derive_id(&source_id); write_str( w, - format_args!("
"), + format_args!( + "
\ + {}", + render_rightside(cx, item, render_mode) + ), ); - render_rightside(w, cx, item, render_mode); if trait_.is_some() { // Anchors are only used on trait impls. write_str(w, format_args!("§")); } - w.push_str("

"); - render_assoc_item( + write_str( w, - item, - link.anchor(source_id.as_ref().unwrap_or(&id)), - ItemType::Impl, - cx, - render_mode, + format_args!( + "

{}

", + assoc_const( + item, + generics, + ty, + AssocConstValue::None, + link.anchor(if trait_.is_some() { &source_id } else { &id }), + 0, + cx, + ) + ), ); - w.push_str("
"); } - } - clean::RequiredAssocConstItem(ref generics, ref ty) => { - let source_id = format!("{item_type}.{name}"); - let id = cx.derive_id(&source_id); - write_str( - w, - format_args!("
"), - ); - render_rightside(w, cx, item, render_mode); - if trait_.is_some() { - // Anchors are only used on trait impls. - write_str(w, format_args!("§")); + clean::ProvidedAssocConstItem(ci) | clean::ImplAssocConstItem(ci) => { + let source_id = format!("{item_type}.{name}"); + let id = cx.derive_id(&source_id); + write_str( + w, + format_args!( + "
\ + {}", + render_rightside(cx, item, render_mode) + ), + ); + if trait_.is_some() { + // Anchors are only used on trait impls. + write_str(w, format_args!("§")); + } + write_str( + w, + format_args!( + "

{}

", + 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, + ) + ), + ); } - w.push_str("

"); - assoc_const( - w, - item, - generics, - ty, - AssocConstValue::None, - link.anchor(if trait_.is_some() { &source_id } else { &id }), - 0, - cx, - ); - w.push_str("

"); - } - clean::ProvidedAssocConstItem(ci) | clean::ImplAssocConstItem(ci) => { - let source_id = format!("{item_type}.{name}"); - let id = cx.derive_id(&source_id); - write_str( - w, - format_args!("
"), - ); - render_rightside(w, cx, item, render_mode); - if trait_.is_some() { - // Anchors are only used on trait impls. - write_str(w, format_args!("§")); + clean::RequiredAssocTypeItem(ref generics, ref bounds) => { + let source_id = format!("{item_type}.{name}"); + let id = cx.derive_id(&source_id); + write_str( + w, + format_args!( + "
\ + {}", + render_rightside(cx, item, render_mode) + ), + ); + if trait_.is_some() { + // Anchors are only used on trait impls. + write_str(w, format_args!("§")); + } + write_str( + w, + format_args!( + "

{}

", + assoc_type( + item, + generics, + bounds, + None, + link.anchor(if trait_.is_some() { &source_id } else { &id }), + 0, + cx, + ) + ), + ); } - w.push_str("

"); - assoc_const( - w, - 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, - ); - w.push_str("

"); + clean::AssocTypeItem(tydef, _bounds) => { + let source_id = format!("{item_type}.{name}"); + let id = cx.derive_id(&source_id); + write_str( + w, + format_args!( + "
\ + {}", + render_rightside(cx, item, render_mode) + ), + ); + if trait_.is_some() { + // Anchors are only used on trait impls. + write_str(w, format_args!("§")); + } + write_str( + w, + format_args!( + "

{}

", + 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, + _ => panic!("can't make docs for trait item with name {:?}", item.name), } - clean::RequiredAssocTypeItem(ref generics, ref bounds) => { - let source_id = format!("{item_type}.{name}"); - let id = cx.derive_id(&source_id); - write_str( - w, - format_args!("
"), - ); - render_rightside(w, cx, item, render_mode); - if trait_.is_some() { - // Anchors are only used on trait impls. - write_str(w, format_args!("§")); + + w.push_str(&info_buffer); + if toggled { + w.push_str("
"); + w.push_str(&doc_buffer); + w.push_str("
"); + } + } + + let mut impl_items = String::new(); + let mut default_impl_items = String::new(); + let impl_ = i.inner_impl(); + + // Impl items are grouped by kinds: + // + // 1. Constants + // 2. Types + // 3. Functions + // + // This order is because you can have associated constants used in associated types (like array + // length), and both in associcated functions. So with this order, when reading from top to + // bottom, you should see items definitions before they're actually used most of the time. + let mut assoc_types = Vec::new(); + let mut methods = Vec::new(); + + if !impl_.is_negative_trait_impl() { + for trait_item in &impl_.items { + match trait_item.kind { + clean::MethodItem(..) | clean::RequiredMethodItem(_) => { + methods.push(trait_item) + } + clean::RequiredAssocTypeItem(..) | clean::AssocTypeItem(..) => { + assoc_types.push(trait_item) + } + clean::RequiredAssocConstItem(..) + | clean::ProvidedAssocConstItem(_) + | clean::ImplAssocConstItem(_) => { + // We render it directly since they're supposed to come first. + doc_impl_item( + &mut default_impl_items, + &mut impl_items, + cx, + trait_item, + if trait_.is_some() { &i.impl_item } else { parent }, + link, + render_mode, + false, + trait_, + rendering_params, + ); + } + _ => {} } - w.push_str("

"); - assoc_type( - w, - item, - generics, - bounds, - None, - link.anchor(if trait_.is_some() { &source_id } else { &id }), - 0, + } + + for assoc_type in assoc_types { + doc_impl_item( + &mut default_impl_items, + &mut impl_items, cx, + assoc_type, + if trait_.is_some() { &i.impl_item } else { parent }, + link, + render_mode, + false, + trait_, + rendering_params, ); - w.push_str("

"); } - clean::AssocTypeItem(tydef, _bounds) => { - let source_id = format!("{item_type}.{name}"); - let id = cx.derive_id(&source_id); - write_str( - w, - format_args!("
"), - ); - render_rightside(w, cx, item, render_mode); - if trait_.is_some() { - // Anchors are only used on trait impls. - write_str(w, format_args!("§")); - } - w.push_str("

"); - assoc_type( - w, - 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, + for method in methods { + doc_impl_item( + &mut default_impl_items, + &mut impl_items, cx, + method, + if trait_.is_some() { &i.impl_item } else { parent }, + link, + render_mode, + false, + trait_, + rendering_params, ); - w.push_str("

"); } - clean::StrippedItem(..) => return, - _ => panic!("can't make docs for trait item with name {:?}", item.name), - } - - w.push_str(&info_buffer); - if toggled { - w.push_str("
"); - w.push_str(&doc_buffer); - w.push_str("
"); } - } - let mut impl_items = String::new(); - let mut default_impl_items = String::new(); - let impl_ = i.inner_impl(); - - // Impl items are grouped by kinds: - // - // 1. Constants - // 2. Types - // 3. Functions - // - // This order is because you can have associated constants used in associated types (like array - // length), and both in associcated functions. So with this order, when reading from top to - // bottom, you should see items definitions before they're actually used most of the time. - let mut assoc_types = Vec::new(); - let mut methods = Vec::new(); - - if !impl_.is_negative_trait_impl() { - for trait_item in &impl_.items { - match trait_item.kind { - clean::MethodItem(..) | clean::RequiredMethodItem(_) => methods.push(trait_item), - clean::RequiredAssocTypeItem(..) | clean::AssocTypeItem(..) => { - assoc_types.push(trait_item) + fn render_default_items( + boring: &mut String, + interesting: &mut String, + cx: &Context<'_>, + t: &clean::Trait, + i: &clean::Impl, + parent: &clean::Item, + render_mode: RenderMode, + rendering_params: ImplRenderingParameters, + ) { + 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). + if let Some(impl_def_id) = parent.item_id.as_def_id() + && let Some(trait_item_def_id) = trait_item.item_id.as_def_id() + && cx.tcx().is_impossible_associated_item((impl_def_id, trait_item_def_id)) + { + continue; } - clean::RequiredAssocConstItem(..) - | clean::ProvidedAssocConstItem(_) - | clean::ImplAssocConstItem(_) => { - // We render it directly since they're supposed to come first. - doc_impl_item( - &mut default_impl_items, - &mut impl_items, - cx, - trait_item, - if trait_.is_some() { &i.impl_item } else { parent }, - link, - render_mode, - false, - trait_, - rendering_params, - ); + + let n = trait_item.name; + if i.items.iter().any(|m| m.name == n) { + continue; } - _ => {} - } - } + let did = i.trait_.as_ref().unwrap().def_id(); + let provided_methods = i.provided_trait_methods(cx.tcx()); + let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_methods); - for assoc_type in assoc_types { - doc_impl_item( - &mut default_impl_items, - &mut impl_items, - cx, - assoc_type, - if trait_.is_some() { &i.impl_item } else { parent }, - link, - render_mode, - false, - trait_, - rendering_params, - ); - } - for method in methods { - doc_impl_item( - &mut default_impl_items, - &mut impl_items, - cx, - method, - if trait_.is_some() { &i.impl_item } else { parent }, - link, - render_mode, - false, - trait_, - rendering_params, - ); + doc_impl_item( + boring, + interesting, + cx, + trait_item, + parent, + assoc_link, + render_mode, + true, + Some(t), + rendering_params, + ); + } } - } - fn render_default_items( - boring: &mut String, - interesting: &mut String, - cx: &Context<'_>, - t: &clean::Trait, - i: &clean::Impl, - parent: &clean::Item, - render_mode: RenderMode, - rendering_params: ImplRenderingParameters, - ) { - 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). - if let Some(impl_def_id) = parent.item_id.as_def_id() - && let Some(trait_item_def_id) = trait_item.item_id.as_def_id() - && cx.tcx().is_impossible_associated_item((impl_def_id, trait_item_def_id)) + // If we've implemented a trait, then also emit documentation for all + // default items which weren't overridden in the implementation block. + // We don't emit documentation for default items if they appear in the + // Implementations on Foreign Types or Implementors sections. + if rendering_params.show_default_items { + if let Some(t) = trait_ + && !impl_.is_negative_trait_impl() { - continue; - } - - let n = trait_item.name; - if i.items.iter().any(|m| m.name == n) { - continue; + render_default_items( + &mut default_impl_items, + &mut impl_items, + cx, + t, + impl_, + &i.impl_item, + render_mode, + rendering_params, + ); } - let did = i.trait_.as_ref().unwrap().def_id(); - let provided_methods = i.provided_trait_methods(cx.tcx()); - let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_methods); - - doc_impl_item( - boring, - interesting, - cx, - trait_item, - parent, - assoc_link, - render_mode, - true, - Some(t), - rendering_params, - ); - } - } - - // If we've implemented a trait, then also emit documentation for all - // default items which weren't overridden in the implementation block. - // We don't emit documentation for default items if they appear in the - // Implementations on Foreign Types or Implementors sections. - if rendering_params.show_default_items { - if let Some(t) = trait_ - && !impl_.is_negative_trait_impl() - { - render_default_items( - &mut default_impl_items, - &mut impl_items, - cx, - t, - impl_, - &i.impl_item, - render_mode, - rendering_params, - ); } - } - if render_mode == RenderMode::Normal { - let toggled = !(impl_items.is_empty() && default_impl_items.is_empty()); - if toggled { - close_tags.push("
"); - write_str( - w, - format_args!( + if render_mode == RenderMode::Normal { + let toggled = !(impl_items.is_empty() && default_impl_items.is_empty()); + if toggled { + close_tags.push(""); + write!( + w, "
\ ", if rendering_params.toggle_open_by_default { " open" } else { "" } - ), - ); - } + )?; + } - let (before_dox, after_dox) = i - .impl_item - .opt_doc_value() - .map(|dox| { - Markdown { - content: &dox, - links: &i.impl_item.links(cx), - ids: &mut cx.id_map.borrow_mut(), - error_codes: cx.shared.codes, - edition: cx.shared.edition(), - playground: &cx.shared.playground, - heading_offset: HeadingOffset::H4, - } - .split_summary_and_content() - }) - .unwrap_or((None, None)); - render_impl_summary( - w, - cx, - i, - parent, - rendering_params.show_def_docs, - use_absolute, - aliases, - &before_dox, - ); - if toggled { - w.push_str(""); - } + let (before_dox, after_dox) = i + .impl_item + .opt_doc_value() + .map(|dox| { + Markdown { + content: &dox, + links: &i.impl_item.links(cx), + ids: &mut cx.id_map.borrow_mut(), + error_codes: cx.shared.codes, + edition: cx.shared.edition(), + playground: &cx.shared.playground, + heading_offset: HeadingOffset::H4, + } + .split_summary_and_content() + }) + .unwrap_or((None, None)); + write!( + w, + "{}", + render_impl_summary( + cx, + i, + parent, + rendering_params.show_def_docs, + use_absolute, + aliases, + before_dox.as_deref(), + ) + )?; + if toggled { + w.write_str("")?; + } - if before_dox.is_some() { - if trait_.is_none() && impl_.items.is_empty() { - w.push_str( - "
\ + if before_dox.is_some() { + if trait_.is_none() && impl_.items.is_empty() { + w.write_str( + "
\
This impl block contains no items.
\
", - ); + )?; + } + if let Some(after_dox) = after_dox { + write!(w, "
{after_dox}
")?; + } } - if let Some(after_dox) = after_dox { - write_str(w, format_args!("
{after_dox}
")); + if !default_impl_items.is_empty() || !impl_items.is_empty() { + w.write_str("
")?; + close_tags.push("
"); } } if !default_impl_items.is_empty() || !impl_items.is_empty() { - w.push_str("
"); - close_tags.push("
"); + w.write_str(&default_impl_items)?; + w.write_str(&impl_items)?; } - } - if !default_impl_items.is_empty() || !impl_items.is_empty() { - w.push_str(&default_impl_items); - w.push_str(&impl_items); - } - for tag in close_tags.into_iter().rev() { - w.push_str(tag); - } + for tag in close_tags.into_iter().rev() { + w.write_str(tag)?; + } + Ok(()) + }) } // Render the items that appear on the right side of methods, impls, and // associated types. For example "1.0.0 (const: 1.39.0) · source". -fn render_rightside(w: &mut String, cx: &Context<'_>, item: &clean::Item, render_mode: RenderMode) { +fn render_rightside<'a, 'tcx>( + cx: &'a Context<'tcx>, + item: &'a clean::Item, + render_mode: RenderMode, +) -> impl fmt::Display + 'a + Captures<'tcx> { let tcx = cx.tcx(); - // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove - // this condition. - let const_stability = match render_mode { - RenderMode::Normal => item.const_stability(tcx), - RenderMode::ForDeref { .. } => None, - }; - let src_href = cx.src_href(item); - let has_src_ref = src_href.is_some(); - - let mut rightside = String::new(); - let has_stability = render_stability_since_raw_with_extra( - &mut rightside, - item.stable_since(tcx), - const_stability, - if has_src_ref { "" } else { " rightside" }, - ); - if let Some(link) = src_href { - if has_stability { - write_str( - &mut rightside, - format_args!(" · Source"), - ); - } else { - write_str( - &mut rightside, - format_args!("Source"), - ); + fmt::from_fn(move |w| { + // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove + // this condition. + let const_stability = match render_mode { + RenderMode::Normal => item.const_stability(tcx), + RenderMode::ForDeref { .. } => None, + }; + let src_href = cx.src_href(item); + let stability = render_stability_since_raw_with_extra( + item.stable_since(tcx), + const_stability, + if src_href.is_some() { "" } else { " rightside" }, + ); + + match (stability, src_href) { + (Some(stability), Some(link)) => { + write!( + w, + "{stability} · Source", + ) + } + (Some(stability), None) => { + write!(w, "{stability}") + } + (None, Some(link)) => { + write!(w, "Source") + } + (None, None) => Ok(()), } - } - if has_stability && has_src_ref { - write_str(w, format_args!("{rightside}")); - } else { - w.push_str(&rightside); - } + }) } -pub(crate) fn render_impl_summary( - w: &mut String, - cx: &Context<'_>, - i: &Impl, - parent: &clean::Item, +pub(crate) fn render_impl_summary<'a, 'tcx>( + cx: &'a Context<'tcx>, + i: &'a Impl, + parent: &'a clean::Item, show_def_docs: bool, use_absolute: Option, // This argument is used to reference same type with different paths to avoid duplication // in documentation pages for trait with automatic implementations like "Send" and "Sync". - aliases: &[String], - doc: &Option, -) { - let inner_impl = i.inner_impl(); - let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id)); - let aliases = (!aliases.is_empty()) - .then_some(fmt::from_fn(|f| { - write!(f, " data-aliases=\"{}\"", fmt::from_fn(|f| aliases.iter().joined(",", f))) - })) - .maybe_display(); - write_str(w, format_args!("
")); - render_rightside(w, cx, &i.impl_item, RenderMode::Normal); - write_str( - w, - format_args!( - "§\ -

" - ), - ); - - if let Some(use_absolute) = use_absolute { - write_str(w, format_args!("{}", inner_impl.print(use_absolute, cx))); - if show_def_docs { - for it in &inner_impl.items { - if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind { - w.push_str("
"); - assoc_type( - w, - it, - &tydef.generics, - &[], // intentionally leaving out bounds - Some(&tydef.type_), - AssocItemLink::Anchor(None), - 0, - cx, - ); - w.push_str(";
"); + aliases: &'a [String], + doc: Option<&'a str>, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(move |w| { + let inner_impl = i.inner_impl(); + let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id)); + let aliases = (!aliases.is_empty()) + .then_some(fmt::from_fn(|f| { + write!(f, " data-aliases=\"{}\"", fmt::from_fn(|f| aliases.iter().joined(",", f))) + })) + .maybe_display(); + write!( + w, + "
\ + {}\ + §\ +

", + render_rightside(cx, &i.impl_item, RenderMode::Normal) + )?; + + if let Some(use_absolute) = use_absolute { + write!(w, "{}", inner_impl.print(use_absolute, cx))?; + if show_def_docs { + for it in &inner_impl.items { + if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind { + write!( + w, + "
{};
", + assoc_type( + it, + &tydef.generics, + &[], // intentionally leaving out bounds + Some(&tydef.type_), + AssocItemLink::Anchor(None), + 0, + cx, + ) + )?; + } } } + } else { + write!(w, "{}", inner_impl.print(false, cx))?; } - } else { - write_str(w, format_args!("{}", inner_impl.print(false, cx))); - } - w.push_str("

"); + w.write_str("

")?; - let is_trait = inner_impl.trait_.is_some(); - if is_trait && let Some(portability) = portability(&i.impl_item, Some(parent)) { - write_str( - w, - format_args!( + let is_trait = inner_impl.trait_.is_some(); + if is_trait && let Some(portability) = portability(&i.impl_item, Some(parent)) { + write!( + w, "\ -
{portability}
\ -
", - ), - ); - } +
{portability}
\ + ", + )?; + } - if let Some(doc) = doc { - write_str(w, format_args!("
{doc}
")); - } + if let Some(doc) = doc { + write!(w, "
{doc}
")?; + } - w.push_str("
"); + w.write_str("") + }) } pub(crate) fn small_url_encode(s: String) -> String { diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index f320114703996..b647b2aad75a7 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; use std::fmt; -use std::fmt::Display; +use std::fmt::{Display, Write as _}; use rinja::Template; use rustc_abi::VariantIdx; @@ -32,7 +32,7 @@ use crate::formats::item_type::ItemType; use crate::html::escape::{Escape, EscapeBodyTextWithWbr}; use crate::html::format::{ Ending, PrintWithSpace, join_with_double_colon, print_abi_with_space, - print_constness_with_space, print_where_clause, visibility_print_with_space, write_str, + print_constness_with_space, print_where_clause, visibility_print_with_space, }; use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine}; use crate::html::render::{document_full, document_item_info}; @@ -162,132 +162,139 @@ struct ItemVars<'a> { src_href: Option<&'a str>, } -/// Calls `print_where_clause` and returns `true` if a `where` clause was generated. -fn print_where_clause_and_check<'a, 'tcx: 'a>( - buffer: &mut String, - gens: &'a clean::Generics, +pub(super) fn print_item<'a, 'tcx>( cx: &'a Context<'tcx>, -) -> bool { - let len_before = buffer.len(); - write_str(buffer, format_args!("{}", print_where_clause(gens, cx, 0, Ending::Newline))); - len_before != buffer.len() -} - -pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut String) { + item: &'a clean::Item, +) -> impl fmt::Display + 'a + Captures<'tcx> { debug_assert!(!item.is_stripped()); - let typ = match item.kind { - clean::ModuleItem(_) => { - if item.is_crate() { - "Crate " - } else { - "Module " - } - } - clean::FunctionItem(..) | clean::ForeignFunctionItem(..) => "Function ", - clean::TraitItem(..) => "Trait ", - clean::StructItem(..) => "Struct ", - clean::UnionItem(..) => "Union ", - clean::EnumItem(..) => "Enum ", - clean::TypeAliasItem(..) => "Type Alias ", - clean::MacroItem(..) => "Macro ", - clean::ProcMacroItem(ref mac) => match mac.kind { - MacroKind::Bang => "Macro ", - MacroKind::Attr => "Attribute Macro ", - MacroKind::Derive => "Derive Macro ", - }, - clean::PrimitiveItem(..) => "Primitive Type ", - clean::StaticItem(..) | clean::ForeignStaticItem(..) => "Static ", - clean::ConstantItem(..) => "Constant ", - clean::ForeignTypeItem => "Foreign Type ", - clean::KeywordItem => "Keyword ", - clean::TraitAliasItem(..) => "Trait Alias ", - _ => { - // We don't generate pages for any other type. - unreachable!(); - } - }; - let stability_since_raw = { - let mut buf = String::new(); - render_stability_since_raw( - &mut buf, - item.stable_since(cx.tcx()), - item.const_stability(cx.tcx()), - ); - buf - }; - // Write source tag - // - // When this item is part of a `crate use` in a downstream crate, the - // source link in the downstream documentation will actually come back to - // this page, and this link will be auto-clicked. The `id` attribute is - // used to find the link to auto-click. - let src_href = - if cx.info.include_sources && !item.is_primitive() { cx.src_href(item) } else { None }; - - let path_components = if item.is_primitive() || item.is_keyword() { - vec![] - } else { - let cur = &cx.current; - let amt = if item.is_mod() { cur.len() - 1 } else { cur.len() }; - cur.iter() - .enumerate() - .take(amt) - .map(|(i, component)| PathComponent { - path: "../".repeat(cur.len() - i - 1), - name: *component, - }) - .collect() - }; + fmt::from_fn(|buf| { + let typ = match item.kind { + clean::ModuleItem(_) => { + if item.is_crate() { + "Crate " + } else { + "Module " + } + } + clean::FunctionItem(..) | clean::ForeignFunctionItem(..) => "Function ", + clean::TraitItem(..) => "Trait ", + clean::StructItem(..) => "Struct ", + clean::UnionItem(..) => "Union ", + clean::EnumItem(..) => "Enum ", + clean::TypeAliasItem(..) => "Type Alias ", + clean::MacroItem(..) => "Macro ", + clean::ProcMacroItem(ref mac) => match mac.kind { + MacroKind::Bang => "Macro ", + MacroKind::Attr => "Attribute Macro ", + MacroKind::Derive => "Derive Macro ", + }, + clean::PrimitiveItem(..) => "Primitive Type ", + clean::StaticItem(..) | clean::ForeignStaticItem(..) => "Static ", + clean::ConstantItem(..) => "Constant ", + clean::ForeignTypeItem => "Foreign Type ", + clean::KeywordItem => "Keyword ", + clean::TraitAliasItem(..) => "Trait Alias ", + _ => { + // We don't generate pages for any other type. + unreachable!(); + } + }; + let stability_since_raw = + render_stability_since_raw(item.stable_since(cx.tcx()), item.const_stability(cx.tcx())) + .maybe_display() + .to_string(); + + // Write source tag + // + // When this item is part of a `crate use` in a downstream crate, the + // source link in the downstream documentation will actually come back to + // this page, and this link will be auto-clicked. The `id` attribute is + // used to find the link to auto-click. + let src_href = + if cx.info.include_sources && !item.is_primitive() { cx.src_href(item) } else { None }; + + let path_components = if item.is_primitive() || item.is_keyword() { + vec![] + } else { + let cur = &cx.current; + let amt = if item.is_mod() { cur.len() - 1 } else { cur.len() }; + cur.iter() + .enumerate() + .take(amt) + .map(|(i, component)| PathComponent { + path: "../".repeat(cur.len() - i - 1), + name: *component, + }) + .collect() + }; - let item_vars = ItemVars { - typ, - name: item.name.as_ref().unwrap().as_str(), - item_type: &item.type_().to_string(), - path_components, - stability_since_raw: &stability_since_raw, - src_href: src_href.as_deref(), - }; + let item_vars = ItemVars { + typ, + name: item.name.as_ref().unwrap().as_str(), + item_type: &item.type_().to_string(), + path_components, + stability_since_raw: &stability_since_raw, + src_href: src_href.as_deref(), + }; - item_vars.render_into(buf).unwrap(); + item_vars.render_into(buf).unwrap(); - match &item.kind { - clean::ModuleItem(ref m) => item_module(buf, cx, item, &m.items), - clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f, _) => { - item_function(buf, cx, item, f) - } - clean::TraitItem(ref t) => item_trait(buf, cx, item, t), - clean::StructItem(ref s) => item_struct(buf, cx, item, s), - clean::UnionItem(ref s) => item_union(buf, cx, item, s), - clean::EnumItem(ref e) => item_enum(buf, cx, item, e), - clean::TypeAliasItem(ref t) => item_type_alias(buf, cx, item, t), - clean::MacroItem(ref m) => item_macro(buf, cx, item, m), - clean::ProcMacroItem(ref m) => item_proc_macro(buf, cx, item, m), - clean::PrimitiveItem(_) => item_primitive(buf, cx, item), - clean::StaticItem(ref i) => item_static(buf, cx, item, i, None), - clean::ForeignStaticItem(ref i, safety) => item_static(buf, cx, item, i, Some(*safety)), - clean::ConstantItem(ci) => item_constant(buf, cx, item, &ci.generics, &ci.type_, &ci.kind), - clean::ForeignTypeItem => item_foreign_type(buf, cx, item), - clean::KeywordItem => item_keyword(buf, cx, item), - clean::TraitAliasItem(ref ta) => item_trait_alias(buf, cx, item, ta), - _ => { - // We don't generate pages for any other type. - unreachable!(); - } - } + match &item.kind { + clean::ModuleItem(ref m) => { + write!(buf, "{}", item_module(cx, item, &m.items)) + } + clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f, _) => { + write!(buf, "{}", item_function(cx, item, f)) + } + clean::TraitItem(ref t) => write!(buf, "{}", item_trait(cx, item, t)), + clean::StructItem(ref s) => { + write!(buf, "{}", item_struct(cx, item, s)) + } + clean::UnionItem(ref s) => write!(buf, "{}", item_union(cx, item, s)), + clean::EnumItem(ref e) => write!(buf, "{}", item_enum(cx, item, e)), + clean::TypeAliasItem(ref t) => { + write!(buf, "{}", item_type_alias(cx, item, t)) + } + clean::MacroItem(ref m) => write!(buf, "{}", item_macro(cx, item, m)), + clean::ProcMacroItem(ref m) => { + write!(buf, "{}", item_proc_macro(cx, item, m)) + } + clean::PrimitiveItem(_) => write!(buf, "{}", item_primitive(cx, item)), + clean::StaticItem(ref i) => { + write!(buf, "{}", item_static(cx, item, i, None)) + } + clean::ForeignStaticItem(ref i, safety) => { + write!(buf, "{}", item_static(cx, item, i, Some(*safety))) + } + clean::ConstantItem(ci) => { + write!(buf, "{}", item_constant(cx, item, &ci.generics, &ci.type_, &ci.kind)) + } + clean::ForeignTypeItem => { + write!(buf, "{}", item_foreign_type(cx, item)) + } + clean::KeywordItem => write!(buf, "{}", item_keyword(cx, item)), + clean::TraitAliasItem(ref ta) => { + write!(buf, "{}", item_trait_alias(cx, item, ta)) + } + _ => { + // We don't generate pages for any other type. + unreachable!(); + } + }?; - // Render notable-traits.js used for all methods in this module. - let mut types_with_notable_traits = cx.types_with_notable_traits.borrow_mut(); - if !types_with_notable_traits.is_empty() { - write_str( - buf, - format_args!( + // Render notable-traits.js used for all methods in this module. + let mut types_with_notable_traits = cx.types_with_notable_traits.borrow_mut(); + if !types_with_notable_traits.is_empty() { + write!( + buf, r#""#, - notable_traits_json(types_with_notable_traits.iter(), cx) - ), - ); - types_with_notable_traits.clear(); - } + notable_traits_json(types_with_notable_traits.iter(), cx), + )?; + types_with_notable_traits.clear(); + } + Ok(()) + }) } /// For large structs, enums, unions, etc, determine whether to hide their fields @@ -314,192 +321,198 @@ trait ItemTemplate<'a, 'cx: 'a>: rinja::Template + Display { fn item_and_cx(&self) -> (&'a clean::Item, &'a Context<'cx>); } -fn item_module(w: &mut String, cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) { - write_str(w, format_args!("{}", document(cx, item, None, HeadingOffset::H2))); - - let mut not_stripped_items = - items.iter().filter(|i| !i.is_stripped()).enumerate().collect::>(); - - // the order of item types in the listing - fn reorder(ty: ItemType) -> u8 { - match ty { - ItemType::ExternCrate => 0, - ItemType::Import => 1, - ItemType::Primitive => 2, - ItemType::Module => 3, - ItemType::Macro => 4, - ItemType::Struct => 5, - ItemType::Enum => 6, - ItemType::Constant => 7, - ItemType::Static => 8, - ItemType::Trait => 9, - ItemType::Function => 10, - ItemType::TypeAlias => 12, - ItemType::Union => 13, - _ => 14 + ty as u8, +fn item_module<'a, 'tcx>( + cx: &'a Context<'tcx>, + item: &'a clean::Item, + items: &'a [clean::Item], +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + write!(w, "{}", document(cx, item, None, HeadingOffset::H2))?; + + let mut not_stripped_items = + items.iter().filter(|i| !i.is_stripped()).enumerate().collect::>(); + + // the order of item types in the listing + fn reorder(ty: ItemType) -> u8 { + match ty { + ItemType::ExternCrate => 0, + ItemType::Import => 1, + ItemType::Primitive => 2, + ItemType::Module => 3, + ItemType::Macro => 4, + ItemType::Struct => 5, + ItemType::Enum => 6, + ItemType::Constant => 7, + ItemType::Static => 8, + ItemType::Trait => 9, + ItemType::Function => 10, + ItemType::TypeAlias => 12, + ItemType::Union => 13, + _ => 14 + ty as u8, + } } - } - fn cmp(i1: &clean::Item, i2: &clean::Item, tcx: TyCtxt<'_>) -> Ordering { - let rty1 = reorder(i1.type_()); - let rty2 = reorder(i2.type_()); - if rty1 != rty2 { - return rty1.cmp(&rty2); - } - let is_stable1 = i1.stability(tcx).as_ref().map(|s| s.level.is_stable()).unwrap_or(true); - let is_stable2 = i2.stability(tcx).as_ref().map(|s| s.level.is_stable()).unwrap_or(true); - if is_stable1 != is_stable2 { - // true is bigger than false in the standard bool ordering, - // but we actually want stable items to come first - return is_stable2.cmp(&is_stable1); + fn cmp(i1: &clean::Item, i2: &clean::Item, tcx: TyCtxt<'_>) -> Ordering { + let rty1 = reorder(i1.type_()); + let rty2 = reorder(i2.type_()); + if rty1 != rty2 { + return rty1.cmp(&rty2); + } + let is_stable1 = + i1.stability(tcx).as_ref().map(|s| s.level.is_stable()).unwrap_or(true); + let is_stable2 = + i2.stability(tcx).as_ref().map(|s| s.level.is_stable()).unwrap_or(true); + if is_stable1 != is_stable2 { + // true is bigger than false in the standard bool ordering, + // but we actually want stable items to come first + return is_stable2.cmp(&is_stable1); + } + let lhs = i1.name.unwrap_or(kw::Empty); + let rhs = i2.name.unwrap_or(kw::Empty); + compare_names(lhs.as_str(), rhs.as_str()) } - let lhs = i1.name.unwrap_or(kw::Empty); - let rhs = i2.name.unwrap_or(kw::Empty); - compare_names(lhs.as_str(), rhs.as_str()) - } - let tcx = cx.tcx(); + let tcx = cx.tcx(); - match cx.shared.module_sorting { - ModuleSorting::Alphabetical => { - not_stripped_items.sort_by(|(_, i1), (_, i2)| cmp(i1, i2, tcx)); - } - ModuleSorting::DeclarationOrder => {} - } - // This call is to remove re-export duplicates in cases such as: - // - // ``` - // pub(crate) mod foo { - // pub(crate) mod bar { - // pub(crate) trait Double { fn foo(); } - // } - // } - // - // pub(crate) use foo::bar::*; - // pub(crate) use foo::*; - // ``` - // - // `Double` will appear twice in the generated docs. - // - // FIXME: This code is quite ugly and could be improved. Small issue: DefId - // can be identical even if the elements are different (mostly in imports). - // So in case this is an import, we keep everything by adding a "unique id" - // (which is the position in the vector). - not_stripped_items.dedup_by_key(|(idx, i)| { - ( - i.item_id, - if i.name.is_some() { Some(full_path(cx, i)) } else { None }, - i.type_(), - if i.is_import() { *idx } else { 0 }, - ) - }); + match cx.shared.module_sorting { + ModuleSorting::Alphabetical => { + not_stripped_items.sort_by(|(_, i1), (_, i2)| cmp(i1, i2, tcx)); + } + ModuleSorting::DeclarationOrder => {} + } + // This call is to remove re-export duplicates in cases such as: + // + // ``` + // pub(crate) mod foo { + // pub(crate) mod bar { + // pub(crate) trait Double { fn foo(); } + // } + // } + // + // pub(crate) use foo::bar::*; + // pub(crate) use foo::*; + // ``` + // + // `Double` will appear twice in the generated docs. + // + // FIXME: This code is quite ugly and could be improved. Small issue: DefId + // can be identical even if the elements are different (mostly in imports). + // So in case this is an import, we keep everything by adding a "unique id" + // (which is the position in the vector). + not_stripped_items.dedup_by_key(|(idx, i)| { + ( + i.item_id, + if i.name.is_some() { Some(full_path(cx, i)) } else { None }, + i.type_(), + if i.is_import() { *idx } else { 0 }, + ) + }); - debug!("{not_stripped_items:?}"); - let mut last_section = None; + debug!("{not_stripped_items:?}"); + let mut last_section = None; - for (_, myitem) in ¬_stripped_items { - let my_section = item_ty_to_section(myitem.type_()); - if Some(my_section) != last_section { - if last_section.is_some() { - w.push_str(ITEM_TABLE_CLOSE); + for (_, myitem) in ¬_stripped_items { + let my_section = item_ty_to_section(myitem.type_()); + if Some(my_section) != last_section { + if last_section.is_some() { + w.write_str(ITEM_TABLE_CLOSE)?; + } + last_section = Some(my_section); + let section_id = my_section.id(); + let tag = + if section_id == "reexports" { REEXPORTS_TABLE_OPEN } else { ITEM_TABLE_OPEN }; + write!( + w, + "{}", + write_section_heading(my_section.name(), &cx.derive_id(section_id), None, tag) + )?; } - last_section = Some(my_section); - let section_id = my_section.id(); - let tag = - if section_id == "reexports" { REEXPORTS_TABLE_OPEN } else { ITEM_TABLE_OPEN }; - write_section_heading(w, my_section.name(), &cx.derive_id(section_id), None, tag); - } - match myitem.kind { - clean::ExternCrateItem { ref src } => { - use crate::html::format::anchor; + match myitem.kind { + clean::ExternCrateItem { ref src } => { + use crate::html::format::anchor; - match *src { - Some(src) => { - write_str( - w, - format_args!( + match *src { + Some(src) => { + write!( + w, "
{}extern crate {} as {};", visibility_print_with_space(myitem, cx), anchor(myitem.item_id.expect_def_id(), src, cx), EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()) - ), - ); - } - None => { - write_str( - w, - format_args!( + )?; + } + None => { + write!( + w, "
{}extern crate {};", visibility_print_with_space(myitem, cx), anchor(myitem.item_id.expect_def_id(), myitem.name.unwrap(), cx) - ), - ); + )?; + } } + w.write_str("
")?; } - w.push_str(""); - } - 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() - }); + 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() + }); - let id = match import.kind { - clean::ImportKind::Simple(s) => { - format!(" id=\"{}\"", cx.derive_id(format!("reexport.{s}"))) - } - clean::ImportKind::Glob => String::new(), - }; - write_str( - w, - format_args!( + let id = match import.kind { + clean::ImportKind::Simple(s) => { + format!(" id=\"{}\"", cx.derive_id(format!("reexport.{s}"))) + } + clean::ImportKind::Glob => String::new(), + }; + write!( + w, "\ {vis}{imp}{stab_tags}\ ", vis = visibility_print_with_space(myitem, cx), imp = import.print(cx) - ), - ); - } - - _ => { - if myitem.name.is_none() { - continue; + )?; } - let unsafety_flag = match myitem.kind { - clean::FunctionItem(_) | clean::ForeignFunctionItem(..) - if myitem.fn_header(tcx).unwrap().is_unsafe() => - { - "" - } - clean::ForeignStaticItem(_, hir::Safety::Unsafe) => { - "" + _ => { + if myitem.name.is_none() { + continue; } - _ => "", - }; - let visibility_and_hidden = match myitem.visibility(tcx) { - Some(ty::Visibility::Restricted(_)) => { - if myitem.is_doc_hidden() { - // Don't separate with a space when there are two of them - " 🔒👻 " - } else { - " 🔒 " + let unsafety_flag = match myitem.kind { + clean::FunctionItem(_) | clean::ForeignFunctionItem(..) + if myitem.fn_header(tcx).unwrap().is_unsafe() => + { + "" } - } - _ if myitem.is_doc_hidden() => " 👻 ", - _ => "", - }; - - let docs = - MarkdownSummaryLine(&myitem.doc_value(), &myitem.links(cx)).into_string(); - let (docs_before, docs_after) = - if docs.is_empty() { ("", "") } else { ("
", "
") }; - write_str( - w, - format_args!( + clean::ForeignStaticItem(_, hir::Safety::Unsafe) => { + "" + } + _ => "", + }; + + let visibility_and_hidden = match myitem.visibility(tcx) { + Some(ty::Visibility::Restricted(_)) => { + if myitem.is_doc_hidden() { + // Don't separate with a space when there are two of them + " 🔒👻 " + } else { + " 🔒 " + } + } + _ if myitem.is_doc_hidden() => { + " 👻 " + } + _ => "", + }; + + let docs = + MarkdownSummaryLine(&myitem.doc_value(), &myitem.links(cx)).into_string(); + let (docs_before, docs_after) = + if docs.is_empty() { ("", "") } else { ("
", "
") }; + write!( + w, "
\ {name}\ {visibility_and_hidden}\ @@ -514,15 +527,16 @@ fn item_module(w: &mut String, cx: &Context<'_>, item: &clean::Item, items: &[cl unsafety_flag = unsafety_flag, href = item_path(myitem.type_(), myitem.name.unwrap().as_str()), title = format_args!("{} {}", myitem.type_(), full_path(cx, myitem)), - ), - ); + )?; + } } } - } - if last_section.is_some() { - w.push_str(ITEM_TABLE_CLOSE); - } + if last_section.is_some() { + w.write_str(ITEM_TABLE_CLOSE)?; + } + Ok(()) + }) } /// Render the stability, deprecation and portability tags that are displayed in the item's summary @@ -583,44 +597,47 @@ fn extra_info_tags<'a, 'tcx: 'a>( }) } -fn item_function(w: &mut String, cx: &Context<'_>, it: &clean::Item, f: &clean::Function) { - let tcx = cx.tcx(); - let header = it.fn_header(tcx).expect("printing a function which isn't a function"); - debug!( - "item_function/const: {:?} {:?} {:?} {:?}", - it.name, - &header.constness, - it.stable_since(tcx), - it.const_stability(tcx), - ); - let constness = print_constness_with_space( - &header.constness, - it.stable_since(tcx), - it.const_stability(tcx), - ); - let safety = header.safety.print_with_space(); - let abi = print_abi_with_space(header.abi).to_string(); - let asyncness = header.asyncness.print_with_space(); - let visibility = visibility_print_with_space(it, cx).to_string(); - let name = it.name.unwrap(); - - let generics_len = format!("{:#}", f.generics.print(cx)).len(); - let header_len = "fn ".len() - + visibility.len() - + constness.len() - + asyncness.len() - + safety.len() - + abi.len() - + name.as_str().len() - + generics_len; - - let notable_traits = notable_traits_button(&f.decl.output, cx).maybe_display(); - - wrap_item(w, |w| { - w.reserve(header_len); - write_str( - w, - format_args!( +fn item_function<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + f: &'a clean::Function, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + let tcx = cx.tcx(); + let header = it.fn_header(tcx).expect("printing a function which isn't a function"); + debug!( + "item_function/const: {:?} {:?} {:?} {:?}", + it.name, + &header.constness, + it.stable_since(tcx), + it.const_stability(tcx), + ); + let constness = print_constness_with_space( + &header.constness, + it.stable_since(tcx), + it.const_stability(tcx), + ); + let safety = header.safety.print_with_space(); + let abi = print_abi_with_space(header.abi).to_string(); + let asyncness = header.asyncness.print_with_space(); + let visibility = visibility_print_with_space(it, cx).to_string(); + let name = it.name.unwrap(); + + let generics_len = format!("{:#}", f.generics.print(cx)).len(); + let header_len = "fn ".len() + + visibility.len() + + constness.len() + + asyncness.len() + + safety.len() + + abi.len() + + name.as_str().len() + + generics_len; + + let notable_traits = notable_traits_button(&f.decl.output, cx).maybe_display(); + + wrap_item(w, |w| { + write!( + w, "{attrs}{vis}{constness}{asyncness}{safety}{abi}fn \ {name}{generics}{decl}{notable_traits}{where_clause}", attrs = render_attributes_in_pre(it, "", cx), @@ -631,35 +648,41 @@ fn item_function(w: &mut String, cx: &Context<'_>, it: &clean::Item, f: &clean:: abi = abi, name = name, generics = f.generics.print(cx), - where_clause = print_where_clause(&f.generics, cx, 0, Ending::Newline), + where_clause = + print_where_clause(&f.generics, cx, 0, Ending::Newline).maybe_display(), decl = f.decl.full_print(header_len, 0, cx), - ), - ); - }); - write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); + ) + })?; + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) + }) } -fn item_trait(w: &mut String, cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) { - let tcx = cx.tcx(); - let bounds = bounds(&t.bounds, false, cx); - let required_types = - t.items.iter().filter(|m| m.is_required_associated_type()).collect::>(); - let provided_types = t.items.iter().filter(|m| m.is_associated_type()).collect::>(); - let required_consts = - t.items.iter().filter(|m| m.is_required_associated_const()).collect::>(); - let provided_consts = t.items.iter().filter(|m| m.is_associated_const()).collect::>(); - let required_methods = t.items.iter().filter(|m| m.is_ty_method()).collect::>(); - let provided_methods = t.items.iter().filter(|m| m.is_method()).collect::>(); - 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(); - - // Output the trait definition - wrap_item(w, |mut w| { - write_str( - w, - format_args!( +fn item_trait<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + t: &'a clean::Trait, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + let tcx = cx.tcx(); + let bounds = bounds(&t.bounds, false, cx); + let required_types = + t.items.iter().filter(|m| m.is_required_associated_type()).collect::>(); + let provided_types = t.items.iter().filter(|m| m.is_associated_type()).collect::>(); + let required_consts = + t.items.iter().filter(|m| m.is_required_associated_const()).collect::>(); + let provided_consts = + t.items.iter().filter(|m| m.is_associated_const()).collect::>(); + let required_methods = t.items.iter().filter(|m| m.is_ty_method()).collect::>(); + let provided_methods = t.items.iter().filter(|m| m.is_method()).collect::>(); + 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(); + + // Output the trait definition + wrap_item(w, |mut w| { + write!( + w, "{attrs}{vis}{safety}{is_auto}trait {name}{generics}{bounds}", attrs = render_attributes_in_pre(it, "", cx), vis = visibility_print_with_space(it, cx), @@ -667,747 +690,820 @@ fn item_trait(w: &mut String, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra is_auto = if t.is_auto(tcx) { "auto " } else { "" }, name = it.name.unwrap(), generics = t.generics.print(cx), - ), - ); - - if !t.generics.where_predicates.is_empty() { - write_str( - w, - format_args!("{}", print_where_clause(&t.generics, cx, 0, Ending::Newline)), - ); - } else { - w.push_str(" "); - } + )?; - if t.items.is_empty() { - w.push_str("{ }"); - } else { - // FIXME: we should be using a derived_id for the Anchors here - w.push_str("{\n"); - let mut toggle = false; - - // If there are too many associated types, hide _everything_ - if should_hide_fields(count_types) { - toggle = true; - toggle_open( - &mut w, - format_args!("{} associated items", count_types + count_consts + count_methods), - ); + if !t.generics.where_predicates.is_empty() { + write!( + w, + "{}", + print_where_clause(&t.generics, cx, 0, Ending::Newline).maybe_display() + )?; + } else { + w.write_char(' ')?; } - for types in [&required_types, &provided_types] { - for t in types { - render_assoc_item( - w, - t, - AssocItemLink::Anchor(None), - ItemType::Trait, - cx, - RenderMode::Normal, + + if t.items.is_empty() { + w.write_str("{ }") + } else { + // FIXME: we should be using a derived_id for the Anchors here + w.write_str("{\n")?; + let mut toggle = false; + + // If there are too many associated types, hide _everything_ + if should_hide_fields(count_types) { + toggle = true; + toggle_open( + &mut w, + format_args!( + "{} associated items", + count_types + count_consts + count_methods + ), ); - w.push_str(";\n"); } - } - // If there are too many associated constants, hide everything after them - // We also do this if the types + consts is large because otherwise we could - // render a bunch of types and _then_ a bunch of consts just because both were - // _just_ under the limit - if !toggle && should_hide_fields(count_types + count_consts) { - toggle = true; - toggle_open( - &mut w, - format_args!( - "{count_consts} associated constant{plural_const} and \ + for types in [&required_types, &provided_types] { + for t in types { + writeln!( + w, + "{};", + render_assoc_item( + t, + AssocItemLink::Anchor(None), + ItemType::Trait, + cx, + RenderMode::Normal, + ) + )?; + } + } + // If there are too many associated constants, hide everything after them + // We also do this if the types + consts is large because otherwise we could + // render a bunch of types and _then_ a bunch of consts just because both were + // _just_ under the limit + if !toggle && should_hide_fields(count_types + count_consts) { + toggle = true; + toggle_open( + &mut w, + format_args!( + "{count_consts} associated constant{plural_const} and \ {count_methods} method{plural_method}", - plural_const = pluralize(count_consts), - plural_method = pluralize(count_methods), - ), - ); - } - if count_types != 0 && (count_consts != 0 || count_methods != 0) { - w.push_str("\n"); - } - for consts in [&required_consts, &provided_consts] { - for c in consts { - render_assoc_item( - w, - c, - AssocItemLink::Anchor(None), - ItemType::Trait, - cx, - RenderMode::Normal, + plural_const = pluralize(count_consts), + plural_method = pluralize(count_methods), + ), ); - w.push_str(";\n"); } - } - if !toggle && should_hide_fields(count_methods) { - toggle = true; - toggle_open(&mut w, format_args!("{count_methods} methods")); - } - if count_consts != 0 && count_methods != 0 { - w.push_str("\n"); - } - - if !required_methods.is_empty() { - write_str( - w, - format_args_nl!(" // Required method{}", pluralize(required_methods.len())), - ); - } - for (pos, m) in required_methods.iter().enumerate() { - render_assoc_item( - w, - m, - AssocItemLink::Anchor(None), - ItemType::Trait, - cx, - RenderMode::Normal, - ); - w.push_str(";\n"); + if count_types != 0 && (count_consts != 0 || count_methods != 0) { + w.write_str("\n")?; + } + for consts in [&required_consts, &provided_consts] { + for c in consts { + writeln!( + w, + "{};", + render_assoc_item( + c, + AssocItemLink::Anchor(None), + ItemType::Trait, + cx, + RenderMode::Normal, + ) + )?; + } + } + if !toggle && should_hide_fields(count_methods) { + toggle = true; + toggle_open(&mut w, format_args!("{count_methods} methods")); + } + if count_consts != 0 && count_methods != 0 { + w.write_str("\n")?; + } - if pos < required_methods.len() - 1 { - w.push_str(""); + if !required_methods.is_empty() { + writeln!(w, " // Required method{}", pluralize(required_methods.len()))?; } - } - if !required_methods.is_empty() && !provided_methods.is_empty() { - w.push_str("\n"); - } + for (pos, m) in required_methods.iter().enumerate() { + writeln!( + w, + "{};", + render_assoc_item( + m, + AssocItemLink::Anchor(None), + ItemType::Trait, + cx, + RenderMode::Normal, + ) + )?; - if !provided_methods.is_empty() { - write_str( - w, - format_args_nl!(" // Provided method{}", pluralize(provided_methods.len())), - ); - } - for (pos, m) in provided_methods.iter().enumerate() { - render_assoc_item( - w, - m, - AssocItemLink::Anchor(None), - ItemType::Trait, - cx, - RenderMode::Normal, - ); + if pos < required_methods.len() - 1 { + w.write_str("")?; + } + } + if !required_methods.is_empty() && !provided_methods.is_empty() { + w.write_str("\n")?; + } - w.push_str(" { ... }\n"); + if !provided_methods.is_empty() { + writeln!(w, " // Provided method{}", pluralize(provided_methods.len()))?; + } + for (pos, m) in provided_methods.iter().enumerate() { + writeln!( + w, + "{} {{ ... }}", + render_assoc_item( + m, + AssocItemLink::Anchor(None), + ItemType::Trait, + cx, + RenderMode::Normal, + ) + )?; - if pos < provided_methods.len() - 1 { - w.push_str(""); + if pos < provided_methods.len() - 1 { + w.write_str("")?; + } } + if toggle { + toggle_close(&mut w); + } + w.write_str("}") } - if toggle { - toggle_close(&mut w); - } - w.push_str("}"); + })?; + + // Trait documentation + write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?; + + fn trait_item<'a, 'tcx>( + cx: &'a Context<'tcx>, + m: &'a clean::Item, + t: &'a clean::Item, + ) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + let name = m.name.unwrap(); + info!("Documenting {name} on {ty_name:?}", ty_name = t.name); + let item_type = m.type_(); + let id = cx.derive_id(format!("{item_type}.{name}")); + + let content = document_full(m, cx, HeadingOffset::H5).to_string(); + + let toggled = !content.is_empty(); + if toggled { + let method_toggle_class = + if item_type.is_method() { " method-toggle" } else { "" }; + write!(w, "
")?; + } + write!( + w, + "
\ + {}\ +

{}

", + render_rightside(cx, m, RenderMode::Normal), + render_assoc_item( + m, + AssocItemLink::Anchor(Some(&id)), + ItemType::Impl, + cx, + RenderMode::Normal, + ) + )?; + document_item_info(cx, m, Some(t)).render_into(w).unwrap(); + if toggled { + write!(w, "
{content}
")?; + } + Ok(()) + }) } - }); - // Trait documentation - write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); - - fn trait_item(w: &mut String, cx: &Context<'_>, m: &clean::Item, t: &clean::Item) { - let name = m.name.unwrap(); - info!("Documenting {name} on {ty_name:?}", ty_name = t.name); - let item_type = m.type_(); - let id = cx.derive_id(format!("{item_type}.{name}")); - - let mut content = String::new(); - write_str(&mut content, format_args!("{}", document_full(m, cx, HeadingOffset::H5))); - - let toggled = !content.is_empty(); - if toggled { - let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" }; - write_str( + if !required_consts.is_empty() { + write!( w, - format_args!("
"), - ); - } - write_str(w, format_args!("
")); - render_rightside(w, cx, m, RenderMode::Normal); - write_str(w, format_args!("

")); - render_assoc_item( - w, - m, - AssocItemLink::Anchor(Some(&id)), - ItemType::Impl, - cx, - RenderMode::Normal, - ); - w.push_str("

"); - document_item_info(cx, m, Some(t)).render_into(w).unwrap(); - if toggled { - write_str(w, format_args!("
")); - w.push_str(&content); - write_str(w, format_args!("
")); - } - } - - if !required_consts.is_empty() { - write_section_heading( - w, - "Required Associated Constants", - "required-associated-consts", - None, - "
", - ); - for t in required_consts { - trait_item(w, cx, t, it); + "{}", + write_section_heading( + "Required Associated Constants", + "required-associated-consts", + None, + "
", + ) + )?; + for t in required_consts { + write!(w, "{}", trait_item(cx, t, it))?; + } + w.write_str("
")?; } - w.push_str("
"); - } - if !provided_consts.is_empty() { - write_section_heading( - w, - "Provided Associated Constants", - "provided-associated-consts", - None, - "
", - ); - for t in provided_consts { - trait_item(w, cx, t, it); + if !provided_consts.is_empty() { + write!( + w, + "{}", + write_section_heading( + "Provided Associated Constants", + "provided-associated-consts", + None, + "
", + ) + )?; + for t in provided_consts { + write!(w, "{}", trait_item(cx, t, it))?; + } + w.write_str("
")?; } - w.push_str("
"); - } - if !required_types.is_empty() { - write_section_heading( - w, - "Required Associated Types", - "required-associated-types", - None, - "
", - ); - for t in required_types { - trait_item(w, cx, t, it); + if !required_types.is_empty() { + write!( + w, + "{}", + write_section_heading( + "Required Associated Types", + "required-associated-types", + None, + "
", + ) + )?; + for t in required_types { + write!(w, "{}", trait_item(cx, t, it))?; + } + w.write_str("
")?; } - w.push_str("
"); - } - if !provided_types.is_empty() { - write_section_heading( - w, - "Provided Associated Types", - "provided-associated-types", - None, - "
", - ); - for t in provided_types { - trait_item(w, cx, t, it); + if !provided_types.is_empty() { + write!( + w, + "{}", + write_section_heading( + "Provided Associated Types", + "provided-associated-types", + None, + "
", + ) + )?; + for t in provided_types { + write!(w, "{}", trait_item(cx, t, it))?; + } + w.write_str("
")?; } - w.push_str("
"); - } - // Output the documentation for each function individually - if !required_methods.is_empty() || must_implement_one_of_functions.is_some() { - write_section_heading( - w, - "Required Methods", - "required-methods", - None, - "
", - ); - - if let Some(list) = must_implement_one_of_functions.as_deref() { - write_str( + // Output the documentation for each function individually + if !required_methods.is_empty() || must_implement_one_of_functions.is_some() { + write!( w, - format_args!( + "{}", + write_section_heading( + "Required Methods", + "required-methods", + None, + "
", + ) + )?; + + if let Some(list) = must_implement_one_of_functions.as_deref() { + write!( + w, "
At least one of the `{}` methods is required.
", - fmt::from_fn(|f| list.iter().joined("`, `", f)) - ), - ); - } + fmt::from_fn(|f| list.iter().joined("`, `", f)), + )?; + } - for m in required_methods { - trait_item(w, cx, m, it); + for m in required_methods { + write!(w, "{}", trait_item(cx, m, it))?; + } + w.write_str("
")?; } - w.push_str("
"); - } - if !provided_methods.is_empty() { - write_section_heading( - w, - "Provided Methods", - "provided-methods", - None, - "
", - ); - for m in provided_methods { - trait_item(w, cx, m, it); + if !provided_methods.is_empty() { + write!( + w, + "{}", + write_section_heading( + "Provided Methods", + "provided-methods", + None, + "
", + ) + )?; + for m in provided_methods { + write!(w, "{}", trait_item(cx, m, it))?; + } + w.write_str("
")?; } - w.push_str("
"); - } - // If there are methods directly on this trait object, render them here. - write_str( - w, - format_args!( + // If there are methods directly on this trait object, render them here. + write!( + w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All) - ), - ); + )?; - let mut extern_crates = FxIndexSet::default(); + let mut extern_crates = FxIndexSet::default(); - if !t.is_dyn_compatible(cx.tcx()) { - write_section_heading( - w, - "Dyn Compatibility", - "dyn-compatibility", - None, - format!( - "

This trait is not \ - dyn compatible.

\ -

In older versions of Rust, dyn compatibility was called \"object safety\", \ - so this trait is not object safe.

", - base = crate::clean::utils::DOC_RUST_LANG_ORG_VERSION - ), - ); - } + if !t.is_dyn_compatible(cx.tcx()) { + write!( + w, + "{}", + write_section_heading( + "Dyn Compatibility", + "dyn-compatibility", + None, + format!( + "

This trait is not \ + dyn compatible.

\ +

In older versions of Rust, dyn compatibility was called \"object safety\", \ + so this trait is not object safe.

", + base = crate::clean::utils::DOC_RUST_LANG_ORG_VERSION + ), + ), + )?; + } - if let Some(implementors) = cx.shared.cache.implementors.get(&it.item_id.expect_def_id()) { - // The DefId is for the first Type found with that name. The bool is - // if any Types with the same name but different DefId have been found. - let mut implementor_dups: FxHashMap = FxHashMap::default(); - for implementor in implementors { - if let Some(did) = - implementor.inner_impl().for_.without_borrowed_ref().def_id(&cx.shared.cache) - && !did.is_local() - { - extern_crates.insert(did.krate); - } - match implementor.inner_impl().for_.without_borrowed_ref() { - clean::Type::Path { ref path } if !path.is_assoc_ty() => { - let did = path.def_id(); - let &mut (prev_did, ref mut has_duplicates) = - implementor_dups.entry(path.last()).or_insert((did, false)); - if prev_did != did { - *has_duplicates = true; + if let Some(implementors) = cx.shared.cache.implementors.get(&it.item_id.expect_def_id()) { + // The DefId is for the first Type found with that name. The bool is + // if any Types with the same name but different DefId have been found. + let mut implementor_dups: FxHashMap = FxHashMap::default(); + for implementor in implementors { + if let Some(did) = + implementor.inner_impl().for_.without_borrowed_ref().def_id(&cx.shared.cache) + && !did.is_local() + { + extern_crates.insert(did.krate); + } + match implementor.inner_impl().for_.without_borrowed_ref() { + clean::Type::Path { ref path } if !path.is_assoc_ty() => { + let did = path.def_id(); + let &mut (prev_did, ref mut has_duplicates) = + implementor_dups.entry(path.last()).or_insert((did, false)); + if prev_did != did { + *has_duplicates = true; + } } + _ => {} } - _ => {} } - } - - let (local, mut foreign) = - implementors.iter().partition::, _>(|i| i.is_on_local_type(cx)); - let (mut synthetic, mut concrete): (Vec<&&Impl>, Vec<&&Impl>) = - local.iter().partition(|i| i.inner_impl().kind.is_auto()); + let (local, mut foreign) = + implementors.iter().partition::, _>(|i| i.is_on_local_type(cx)); - synthetic.sort_by_cached_key(|i| ImplString::new(i, cx)); - concrete.sort_by_cached_key(|i| ImplString::new(i, cx)); - foreign.sort_by_cached_key(|i| ImplString::new(i, cx)); + let (mut synthetic, mut concrete): (Vec<&&Impl>, Vec<&&Impl>) = + local.iter().partition(|i| i.inner_impl().kind.is_auto()); - if !foreign.is_empty() { - write_section_heading(w, "Implementations on Foreign Types", "foreign-impls", None, ""); + synthetic.sort_by_cached_key(|i| ImplString::new(i, cx)); + concrete.sort_by_cached_key(|i| ImplString::new(i, cx)); + foreign.sort_by_cached_key(|i| ImplString::new(i, cx)); - for implementor in foreign { - let provided_methods = implementor.inner_impl().provided_trait_methods(tcx); - let assoc_link = - AssocItemLink::GotoSource(implementor.impl_item.item_id, &provided_methods); - render_impl( + if !foreign.is_empty() { + write!( w, - cx, - implementor, - it, - assoc_link, - RenderMode::Normal, - None, - &[], - ImplRenderingParameters { - show_def_docs: false, - show_default_items: false, - show_non_assoc_items: true, - toggle_open_by_default: false, - }, - ); - } - } + "{}", + write_section_heading( + "Implementations on Foreign Types", + "foreign-impls", + None, + "" + ) + )?; - write_section_heading( - w, - "Implementors", - "implementors", - None, - "
", - ); - for implementor in concrete { - render_implementor(cx, implementor, it, w, &implementor_dups, &[]); - } - w.push_str("
"); + for implementor in foreign { + let provided_methods = implementor.inner_impl().provided_trait_methods(tcx); + let assoc_link = + AssocItemLink::GotoSource(implementor.impl_item.item_id, &provided_methods); + write!( + w, + "{}", + render_impl( + cx, + implementor, + it, + assoc_link, + RenderMode::Normal, + None, + &[], + ImplRenderingParameters { + show_def_docs: false, + show_default_items: false, + show_non_assoc_items: true, + toggle_open_by_default: false, + }, + ) + )?; + } + } - if t.is_auto(tcx) { - write_section_heading( + write!( w, - "Auto implementors", - "synthetic-implementors", - None, - "
", - ); - for implementor in synthetic { - render_implementor( - cx, - implementor, - it, - w, - &implementor_dups, - &collect_paths_for_type( - implementor.inner_impl().for_.clone(), - &cx.shared.cache, - ), - ); + "{}", + write_section_heading( + "Implementors", + "implementors", + None, + "
", + ) + )?; + for implementor in concrete { + write!(w, "{}", render_implementor(cx, implementor, it, &implementor_dups, &[]))?; } - w.push_str("
"); - } - } else { - // even without any implementations to write in, we still want the heading and list, so the - // implementors javascript file pulled in below has somewhere to write the impls into - write_section_heading( - w, - "Implementors", - "implementors", - None, - "
", - ); + w.write_str("
")?; - if t.is_auto(tcx) { - write_section_heading( + if t.is_auto(tcx) { + write!( + w, + "{}", + write_section_heading( + "Auto implementors", + "synthetic-implementors", + None, + "
", + ) + )?; + for implementor in synthetic { + write!( + w, + "{}", + render_implementor( + cx, + implementor, + it, + &implementor_dups, + &collect_paths_for_type( + implementor.inner_impl().for_.clone(), + &cx.shared.cache, + ), + ) + )?; + } + w.write_str("
")?; + } + } else { + // even without any implementations to write in, we still want the heading and list, so the + // implementors javascript file pulled in below has somewhere to write the impls into + write!( w, - "Auto implementors", - "synthetic-implementors", - None, - "
", - ); - } - } + "{}", + write_section_heading( + "Implementors", + "implementors", + None, + "
", + ) + )?; - // [RUSTDOCIMPL] trait.impl - // - // Include implementors in crates that depend on the current crate. - // - // This is complicated by the way rustdoc is invoked, which is basically - // the same way rustc is invoked: it gets called, one at a time, for each - // crate. When building the rustdocs for the current crate, rustdoc can - // see crate metadata for its dependencies, but cannot see metadata for its - // dependents. - // - // To make this work, we generate a "hook" at this stage, and our - // dependents can "plug in" to it when they build. For simplicity's sake, - // it's [JSONP]: a JavaScript file with the data we need (and can parse), - // surrounded by a tiny wrapper that the Rust side ignores, but allows the - // JavaScript side to include without having to worry about Same Origin - // Policy. The code for *that* is in `write_shared.rs`. - // - // This is further complicated by `#[doc(inline)]`. We want all copies - // of an inlined trait to reference the same JS file, to address complex - // dependency graphs like this one (lower crates depend on higher crates): - // - // ```text - // -------------------------------------------- - // | crate A: trait Foo | - // -------------------------------------------- - // | | - // -------------------------------- | - // | crate B: impl A::Foo for Bar | | - // -------------------------------- | - // | | - // --------------------------------------------- - // | crate C: #[doc(inline)] use A::Foo as Baz | - // | impl Baz for Quux | - // --------------------------------------------- - // ``` - // - // Basically, we want `C::Baz` and `A::Foo` to show the same set of - // impls, which is easier if they both treat `/trait.impl/A/trait.Foo.js` - // as the Single Source of Truth. - // - // We also want the `impl Baz for Quux` to be written to - // `trait.Foo.js`. However, when we generate plain HTML for `C::Baz`, - // we're going to want to generate plain HTML for `impl Baz for Quux` too, - // because that'll load faster, and it's better for SEO. And we don't want - // the same impl to show up twice on the same page. - // - // To make this work, the trait.impl/A/trait.Foo.js JS file has a structure kinda - // like this: - // - // ```js - // JSONP({ - // "B": {"impl A::Foo for Bar"}, - // "C": {"impl Baz for Quux"}, - // }); - // ``` - // - // First of all, this means we can rebuild a crate, and it'll replace its own - // data if something changes. That is, `rustdoc` is idempotent. The other - // advantage is that we can list the crates that get included in the HTML, - // and ignore them when doing the JavaScript-based part of rendering. - // So C's HTML will have something like this: - // - // ```html - // - // ``` - // - // And, when the JS runs, anything in data-ignore-extern-crates is known - // to already be in the HTML, and will be ignored. - // - // [JSONP]: https://en.wikipedia.org/wiki/JSONP - let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..") - .take(cx.current.len()) - .chain(std::iter::once("trait.impl")) - .collect(); - if let Some(did) = it.item_id.as_def_id() - && let get_extern = { || cx.shared.cache.external_paths.get(&did).map(|s| &s.0) } - && let Some(fqp) = cx.shared.cache.exact_paths.get(&did).or_else(get_extern) - { - js_src_path.extend(fqp[..fqp.len() - 1].iter().copied()); - js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), fqp.last().unwrap())); - } else { - js_src_path.extend(cx.current.iter().copied()); - js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), it.name.unwrap())); - } - let extern_crates = fmt::from_fn(|f| { - if !extern_crates.is_empty() { - f.write_str(" data-ignore-extern-crates=\"")?; - extern_crates.iter().map(|&cnum| tcx.crate_name(cnum)).joined(",", f)?; - f.write_str("\"")?; + if t.is_auto(tcx) { + write!( + w, + "{}", + write_section_heading( + "Auto implementors", + "synthetic-implementors", + None, + "
", + ) + )?; + } } - Ok(()) - }); - write_str( - w, - format_args!( + + // [RUSTDOCIMPL] trait.impl + // + // Include implementors in crates that depend on the current crate. + // + // This is complicated by the way rustdoc is invoked, which is basically + // the same way rustc is invoked: it gets called, one at a time, for each + // crate. When building the rustdocs for the current crate, rustdoc can + // see crate metadata for its dependencies, but cannot see metadata for its + // dependents. + // + // To make this work, we generate a "hook" at this stage, and our + // dependents can "plug in" to it when they build. For simplicity's sake, + // it's [JSONP]: a JavaScript file with the data we need (and can parse), + // surrounded by a tiny wrapper that the Rust side ignores, but allows the + // JavaScript side to include without having to worry about Same Origin + // Policy. The code for *that* is in `write_shared.rs`. + // + // This is further complicated by `#[doc(inline)]`. We want all copies + // of an inlined trait to reference the same JS file, to address complex + // dependency graphs like this one (lower crates depend on higher crates): + // + // ```text + // -------------------------------------------- + // | crate A: trait Foo | + // -------------------------------------------- + // | | + // -------------------------------- | + // | crate B: impl A::Foo for Bar | | + // -------------------------------- | + // | | + // --------------------------------------------- + // | crate C: #[doc(inline)] use A::Foo as Baz | + // | impl Baz for Quux | + // --------------------------------------------- + // ``` + // + // Basically, we want `C::Baz` and `A::Foo` to show the same set of + // impls, which is easier if they both treat `/trait.impl/A/trait.Foo.js` + // as the Single Source of Truth. + // + // We also want the `impl Baz for Quux` to be written to + // `trait.Foo.js`. However, when we generate plain HTML for `C::Baz`, + // we're going to want to generate plain HTML for `impl Baz for Quux` too, + // because that'll load faster, and it's better for SEO. And we don't want + // the same impl to show up twice on the same page. + // + // To make this work, the trait.impl/A/trait.Foo.js JS file has a structure kinda + // like this: + // + // ```js + // JSONP({ + // "B": {"impl A::Foo for Bar"}, + // "C": {"impl Baz for Quux"}, + // }); + // ``` + // + // First of all, this means we can rebuild a crate, and it'll replace its own + // data if something changes. That is, `rustdoc` is idempotent. The other + // advantage is that we can list the crates that get included in the HTML, + // and ignore them when doing the JavaScript-based part of rendering. + // So C's HTML will have something like this: + // + // ```html + // + // ``` + // + // And, when the JS runs, anything in data-ignore-extern-crates is known + // to already be in the HTML, and will be ignored. + // + // [JSONP]: https://en.wikipedia.org/wiki/JSONP + let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..") + .take(cx.current.len()) + .chain(std::iter::once("trait.impl")) + .collect(); + if let Some(did) = it.item_id.as_def_id() + && let get_extern = { || cx.shared.cache.external_paths.get(&did).map(|s| &s.0) } + && let Some(fqp) = cx.shared.cache.exact_paths.get(&did).or_else(get_extern) + { + js_src_path.extend(fqp[..fqp.len() - 1].iter().copied()); + js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), fqp.last().unwrap())); + } else { + js_src_path.extend(cx.current.iter().copied()); + js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), it.name.unwrap())); + } + let extern_crates = fmt::from_fn(|f| { + if !extern_crates.is_empty() { + f.write_str(" data-ignore-extern-crates=\"")?; + extern_crates.iter().map(|&cnum| tcx.crate_name(cnum)).joined(",", f)?; + f.write_str("\"")?; + } + Ok(()) + }); + write!( + w, "", src = js_src_path.finish() - ), - ); + ) + }) } -fn item_trait_alias( - w: &mut impl fmt::Write, - cx: &Context<'_>, - it: &clean::Item, - t: &clean::TraitAlias, -) { - wrap_item(w, |w| { +fn item_trait_alias<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + t: &'a clean::TraitAlias, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + wrap_item(w, |w| { + write!( + w, + "{attrs}trait {name}{generics}{where_b} = {bounds};", + attrs = render_attributes_in_pre(it, "", cx), + name = it.name.unwrap(), + generics = t.generics.print(cx), + where_b = print_where_clause(&t.generics, cx, 0, Ending::Newline).maybe_display(), + bounds = bounds(&t.bounds, true, cx), + ) + })?; + + write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?; + // 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, - "{attrs}trait {name}{generics}{where_b} = {bounds};", - attrs = render_attributes_in_pre(it, "", cx), - name = it.name.unwrap(), - generics = t.generics.print(cx), - where_b = print_where_clause(&t.generics, cx, 0, Ending::Newline), - bounds = bounds(&t.bounds, true, cx), + "{}", + render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All) ) - .unwrap(); - }); - - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); - // 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, it.item_id.expect_def_id(), AssocItemRender::All)) - .unwrap(); + }) } -fn item_type_alias(w: &mut String, cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) { - wrap_item(w, |w| { - write_str( - w, - format_args!( +fn item_type_alias<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + t: &'a clean::TypeAlias, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + wrap_item(w, |w| { + write!( + w, "{attrs}{vis}type {name}{generics}{where_clause} = {type_};", attrs = render_attributes_in_pre(it, "", cx), vis = visibility_print_with_space(it, cx), name = it.name.unwrap(), generics = t.generics.print(cx), - where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), + where_clause = + print_where_clause(&t.generics, cx, 0, Ending::Newline).maybe_display(), type_ = t.type_.print(cx), - ), - ); - }); + ) + })?; - write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?; - if let Some(inner_type) = &t.inner_type { - write_section_heading(w, "Aliased Type", "aliased-type", None, ""); + if let Some(inner_type) = &t.inner_type { + write!(w, "{}", write_section_heading("Aliased Type", "aliased-type", None, ""),)?; - 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(); + 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; + wrap_item(w, |w| { + let variants_len = variants.len(); + let variants_count = variants_iter().count(); + let has_stripped_entries = variants_len != variants_count; - write_str(w, format_args!("enum {}{}", it.name.unwrap(), t.generics.print(cx))); - render_enum_fields( - w, - cx, - Some(&t.generics), - variants, - variants_count, - has_stripped_entries, - *is_non_exhaustive, - enum_def_id, - ) - }); - item_variants(w, cx, it, variants, enum_def_id); - } - 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; + 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))?; + } + 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; - write_str( - w, - format_args!("union {}{}", it.name.unwrap(), t.generics.print(cx)), - ); - render_struct_fields( - w, - Some(&t.generics), - None, - fields, - "", - true, - has_stripped_fields, - cx, - ); - }); - item_fields(w, cx, it, fields, None); - } - 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; + 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))?; + } + 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; - write_str( - w, - format_args!("struct {}{}", it.name.unwrap(), t.generics.print(cx)), - ); - render_struct_fields( - w, - Some(&t.generics), - *ctor_kind, - fields, - "", - true, - has_stripped_fields, - cx, - ); - }); - item_fields(w, cx, it, fields, None); + 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))?; + } } } - } - 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_str(w, format_args!("{}", render_assoc_items(cx, it, def_id, AssocItemRender::All))); - write_str(w, format_args!("{}", document_type_layout(cx, def_id))); - - // [RUSTDOCIMPL] type.impl - // - // Include type definitions from the alias target type. - // - // Earlier versions of this code worked by having `render_assoc_items` - // include this data directly. That generates *O*`(types*impls)` of HTML - // text, and some real crates have a lot of types and impls. - // - // To create the same UX without generating half a gigabyte of HTML for a - // crate that only contains 20 megabytes of actual documentation[^115718], - // rustdoc stashes these type-alias-inlined docs in a [JSONP] - // "database-lite". The file itself is generated in `write_shared.rs`, - // and hooks into functions provided by `main.js`. - // - // The format of `trait.impl` and `type.impl` JS files are superficially - // similar. Each line, except the JSONP wrapper itself, belongs to a crate, - // and they are otherwise separate (rustdoc should be idempotent). The - // "meat" of the file is HTML strings, so the frontend code is very simple. - // Links are relative to the doc root, though, so the frontend needs to fix - // that up, and inlined docs can reuse these files. - // - // However, there are a few differences, caused by the sophisticated - // features that type aliases have. Consider this crate graph: - // - // ```text - // --------------------------------- - // | crate A: struct Foo | - // | type Bar = Foo | - // | impl X for Foo | - // | impl Y for Foo | - // --------------------------------- - // | - // ---------------------------------- - // | crate B: type Baz = A::Foo | - // | type Xyy = A::Foo | - // | impl Z for Xyy | - // ---------------------------------- - // ``` - // - // The type.impl/A/struct.Foo.js JS file has a structure kinda like this: - // - // ```js - // JSONP({ - // "A": [["impl Y for Foo", "Y", "A::Bar"]], - // "B": [["impl X for Foo", "X", "B::Baz", "B::Xyy"], ["impl Z for Xyy", "Z", "B::Baz"]], - // }); - // ``` - // - // When the type.impl file is loaded, only the current crate's docs are - // actually used. The main reason to bundle them together is that there's - // enough duplication in them for DEFLATE to remove the redundancy. - // - // The contents of a crate are a list of impl blocks, themselves - // represented as lists. The first item in the sublist is the HTML block, - // the second item is the name of the trait (which goes in the sidebar), - // and all others are the names of type aliases that successfully match. - // - // This way: - // - // - There's no need to generate these files for types that have no aliases - // in the current crate. If a dependent crate makes a type alias, it'll - // take care of generating its own docs. - // - There's no need to reimplement parts of the type checker in - // JavaScript. The Rust backend does the checking, and includes its - // results in the file. - // - Docs defined directly on the type alias are dropped directly in the - // HTML by `render_assoc_items`, and are accessible without JavaScript. - // The JSONP file will not list impl items that are known to be part - // of the main HTML file already. - // - // [JSONP]: https://en.wikipedia.org/wiki/JSONP - // [^115718]: https://github.com/rust-lang/rust/issues/115718 - let cache = &cx.shared.cache; - if let Some(target_did) = t.type_.def_id(cache) && - let get_extern = { || cache.external_paths.get(&target_did) } && - let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern) && - target_type.is_adt() && // primitives cannot be inlined - let Some(self_did) = it.item_id.as_def_id() && - let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) } && - let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) - { - let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..") - .take(cx.current.len()) - .chain(std::iter::once("type.impl")) - .collect(); - js_src_path.extend(target_fqp[..target_fqp.len() - 1].iter().copied()); - js_src_path.push_fmt(format_args!("{target_type}.{}.js", target_fqp.last().unwrap())); - let self_path = fmt::from_fn(|f| self_fqp.iter().joined("::", f)); - write_str( + 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, - format_args!( + "{}{}", + 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. + // + // Earlier versions of this code worked by having `render_assoc_items` + // include this data directly. That generates *O*`(types*impls)` of HTML + // text, and some real crates have a lot of types and impls. + // + // To create the same UX without generating half a gigabyte of HTML for a + // crate that only contains 20 megabytes of actual documentation[^115718], + // rustdoc stashes these type-alias-inlined docs in a [JSONP] + // "database-lite". The file itself is generated in `write_shared.rs`, + // and hooks into functions provided by `main.js`. + // + // The format of `trait.impl` and `type.impl` JS files are superficially + // similar. Each line, except the JSONP wrapper itself, belongs to a crate, + // and they are otherwise separate (rustdoc should be idempotent). The + // "meat" of the file is HTML strings, so the frontend code is very simple. + // Links are relative to the doc root, though, so the frontend needs to fix + // that up, and inlined docs can reuse these files. + // + // However, there are a few differences, caused by the sophisticated + // features that type aliases have. Consider this crate graph: + // + // ```text + // --------------------------------- + // | crate A: struct Foo | + // | type Bar = Foo | + // | impl X for Foo | + // | impl Y for Foo | + // --------------------------------- + // | + // ---------------------------------- + // | crate B: type Baz = A::Foo | + // | type Xyy = A::Foo | + // | impl Z for Xyy | + // ---------------------------------- + // ``` + // + // The type.impl/A/struct.Foo.js JS file has a structure kinda like this: + // + // ```js + // JSONP({ + // "A": [["impl Y for Foo", "Y", "A::Bar"]], + // "B": [["impl X for Foo", "X", "B::Baz", "B::Xyy"], ["impl Z for Xyy", "Z", "B::Baz"]], + // }); + // ``` + // + // When the type.impl file is loaded, only the current crate's docs are + // actually used. The main reason to bundle them together is that there's + // enough duplication in them for DEFLATE to remove the redundancy. + // + // The contents of a crate are a list of impl blocks, themselves + // represented as lists. The first item in the sublist is the HTML block, + // the second item is the name of the trait (which goes in the sidebar), + // and all others are the names of type aliases that successfully match. + // + // This way: + // + // - There's no need to generate these files for types that have no aliases + // in the current crate. If a dependent crate makes a type alias, it'll + // take care of generating its own docs. + // - There's no need to reimplement parts of the type checker in + // JavaScript. The Rust backend does the checking, and includes its + // results in the file. + // - Docs defined directly on the type alias are dropped directly in the + // HTML by `render_assoc_items`, and are accessible without JavaScript. + // The JSONP file will not list impl items that are known to be part + // of the main HTML file already. + // + // [JSONP]: https://en.wikipedia.org/wiki/JSONP + // [^115718]: https://github.com/rust-lang/rust/issues/115718 + let cache = &cx.shared.cache; + if let Some(target_did) = t.type_.def_id(cache) + && let get_extern = { || cache.external_paths.get(&target_did) } + && let Some(&(ref target_fqp, target_type)) = + cache.paths.get(&target_did).or_else(get_extern) + && target_type.is_adt() // primitives cannot be inlined + && let Some(self_did) = it.item_id.as_def_id() + && let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) } + && let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) + { + let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..") + .take(cx.current.len()) + .chain(std::iter::once("type.impl")) + .collect(); + js_src_path.extend(target_fqp[..target_fqp.len() - 1].iter().copied()); + js_src_path.push_fmt(format_args!("{target_type}.{}.js", target_fqp.last().unwrap())); + let self_path = fmt::from_fn(|f| self_fqp.iter().joined("::", f)); + write!( + w, "", - src = js_src_path.finish() - ), - ); - } + src = js_src_path.finish(), + )?; + } + Ok(()) + }) } -fn item_union(w: &mut String, cx: &Context<'_>, it: &clean::Item, s: &clean::Union) { +fn item_union<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + s: &'a clean::Union, +) -> impl fmt::Display + 'a + Captures<'tcx> { item_template!( #[template(path = "item_union.html")] struct ItemUnion<'a, 'cx> { @@ -1464,7 +1560,10 @@ fn item_union(w: &mut String, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni } } - ItemUnion { cx, it, s }.render_into(w).unwrap(); + fmt::from_fn(|w| { + ItemUnion { cx, it, s }.render_into(w).unwrap(); + Ok(()) + }) } fn print_tuple_struct_fields<'a, 'cx: 'a>( @@ -1492,40 +1591,46 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>( }) } -fn item_enum(w: &mut String, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) { - let count_variants = e.variants().count(); - wrap_item(w, |w| { - render_attributes_in_code(w, it, cx); - write_str( - w, - format_args!( - "{}enum {}{}", +fn item_enum<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + e: &'a clean::Enum, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + let count_variants = e.variants().count(); + wrap_item(w, |w| { + render_attributes_in_code(w, it, cx); + write!( + w, + "{}enum {}{}{}", visibility_print_with_space(it, cx), it.name.unwrap(), e.generics.print(cx), - ), - ); - - render_enum_fields( - w, - cx, - Some(&e.generics), - &e.variants, - count_variants, - e.has_stripped_entries(), - it.is_non_exhaustive(), - it.def_id().unwrap(), - ); - }); + render_enum_fields( + cx, + Some(&e.generics), + &e.variants, + count_variants, + e.has_stripped_entries(), + it.is_non_exhaustive(), + it.def_id().unwrap(), + ), + ) + })?; - write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?; - if count_variants != 0 { - item_variants(w, cx, it, &e.variants, it.def_id().unwrap()); - } - let def_id = it.item_id.expect_def_id(); - write_str(w, format_args!("{}", render_assoc_items(cx, it, def_id, AssocItemRender::All))); - write_str(w, format_args!("{}", document_type_layout(cx, def_id))); + if count_variants != 0 { + write!(w, "{}", item_variants(cx, it, &e.variants, it.def_id().unwrap()))?; + } + 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) + ) + }) } /// It'll return false if any variant is not a C-like variant. Otherwise it'll return true if at @@ -1553,456 +1658,508 @@ fn should_show_enum_discriminant( repr.c() || repr.int.is_some() } -fn display_c_like_variant( - w: &mut String, - cx: &Context<'_>, - item: &clean::Item, - variant: &clean::Variant, +fn display_c_like_variant<'a, 'tcx>( + cx: &'a Context<'tcx>, + item: &'a clean::Item, + variant: &'a clean::Variant, index: VariantIdx, should_show_enum_discriminant: bool, enum_def_id: DefId, -) { - let name = item.name.unwrap(); - if let Some(ref value) = variant.discriminant { - write_str(w, format_args!("{} = {}", name.as_str(), value.value(cx.tcx(), true))); - } else if should_show_enum_discriminant { - let adt_def = cx.tcx().adt_def(enum_def_id); - let discr = adt_def.discriminant_for_variant(cx.tcx(), index); - if discr.ty.is_signed() { - write_str(w, format_args!("{} = {}", name.as_str(), discr.val as i128)); +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(move |w| { + let name = item.name.unwrap(); + if let Some(ref value) = variant.discriminant { + write!(w, "{} = {}", name.as_str(), value.value(cx.tcx(), true))?; + } else if should_show_enum_discriminant { + let adt_def = cx.tcx().adt_def(enum_def_id); + let discr = adt_def.discriminant_for_variant(cx.tcx(), index); + if discr.ty.is_signed() { + write!(w, "{} = {}", name.as_str(), discr.val as i128)?; + } else { + write!(w, "{} = {}", name.as_str(), discr.val)?; + } } else { - write_str(w, format_args!("{} = {}", name.as_str(), discr.val)); + write!(w, "{name}")?; } - } else { - w.push_str(name.as_str()); - } + Ok(()) + }) } -fn render_enum_fields( - mut w: &mut String, - cx: &Context<'_>, - g: Option<&clean::Generics>, - variants: &IndexVec, +fn render_enum_fields<'a, 'tcx>( + cx: &'a Context<'tcx>, + g: Option<&'a clean::Generics>, + variants: &'a IndexVec, count_variants: usize, has_stripped_entries: bool, is_non_exhaustive: bool, enum_def_id: DefId, -) { - let should_show_enum_discriminant = should_show_enum_discriminant(cx, enum_def_id, variants); - if !g.is_some_and(|g| print_where_clause_and_check(w, g, cx)) { - // If there wasn't a `where` clause, we add a whitespace. - w.push_str(" "); - } - - let variants_stripped = has_stripped_entries; - if count_variants == 0 && !variants_stripped { - w.push_str("{}"); - } else { - w.push_str("{\n"); - let toggle = should_hide_fields(count_variants); - if toggle { - toggle_open(&mut w, format_args!("{count_variants} variants")); +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(move |w| { + let should_show_enum_discriminant = + should_show_enum_discriminant(cx, enum_def_id, variants); + if let Some(generics) = g + && let Some(where_clause) = print_where_clause(generics, cx, 0, Ending::Newline) + { + write!(w, "{where_clause}")?; + } else { + // If there wasn't a `where` clause, we add a whitespace. + w.write_char(' ')?; } - const TAB: &str = " "; - for (index, v) in variants.iter_enumerated() { - if v.is_stripped() { - continue; + + let variants_stripped = has_stripped_entries; + if count_variants == 0 && !variants_stripped { + w.write_str("{}") + } else { + w.write_str("{\n")?; + let toggle = should_hide_fields(count_variants); + if toggle { + toggle_open(&mut *w, format_args!("{count_variants} variants")); } - w.push_str(TAB); - match v.kind { - clean::VariantItem(ref var) => match var.kind { - clean::VariantKind::CLike => display_c_like_variant( - w, - cx, - v, - var, - index, - should_show_enum_discriminant, - enum_def_id, - ), - clean::VariantKind::Tuple(ref s) => { - write_str( - w, - format_args!( - "{}({})", - v.name.unwrap(), - print_tuple_struct_fields(cx, s) - ), - ); - } - clean::VariantKind::Struct(ref s) => { - render_struct(w, v, None, None, &s.fields, TAB, false, cx); - } - }, - _ => unreachable!(), + const TAB: &str = " "; + for (index, v) in variants.iter_enumerated() { + if v.is_stripped() { + continue; + } + w.write_str(TAB)?; + match v.kind { + clean::VariantItem(ref var) => match var.kind { + clean::VariantKind::CLike => { + write!( + w, + "{}", + display_c_like_variant( + cx, + v, + var, + index, + should_show_enum_discriminant, + enum_def_id, + ) + )?; + } + clean::VariantKind::Tuple(ref s) => { + write!(w, "{}({})", v.name.unwrap(), print_tuple_struct_fields(cx, s))?; + } + clean::VariantKind::Struct(ref s) => { + write!( + w, + "{}", + render_struct(v, None, None, &s.fields, TAB, false, cx) + )?; + } + }, + _ => unreachable!(), + } + w.write_str(",\n")?; } - w.push_str(",\n"); - } - if variants_stripped && !is_non_exhaustive { - w.push_str(" // some variants omitted\n"); - } - if toggle { - toggle_close(&mut w); + if variants_stripped && !is_non_exhaustive { + w.write_str(" // some variants omitted\n")?; + } + if toggle { + toggle_close(&mut *w); + } + w.write_str("}") } - w.push_str("}"); - } + }) } -fn item_variants( - w: &mut String, - cx: &Context<'_>, - it: &clean::Item, - variants: &IndexVec, +fn item_variants<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + variants: &'a IndexVec, enum_def_id: DefId, -) { - let tcx = cx.tcx(); - write_section_heading( - w, - &format!("Variants{}", document_non_exhaustive_header(it)), - "variants", - Some("variants"), - format!("{}
", document_non_exhaustive(it)), - ); - - let should_show_enum_discriminant = should_show_enum_discriminant(cx, enum_def_id, variants); - for (index, variant) in variants.iter_enumerated() { - if variant.is_stripped() { - continue; - } - let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap())); - write_str( +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(move |w| { + let tcx = cx.tcx(); + write!( w, - format_args!( - "
\ - §" + "{}", + write_section_heading( + &format!("Variants{}", document_non_exhaustive_header(it)), + "variants", + Some("variants"), + format!("{}
", document_non_exhaustive(it)), ), - ); - render_stability_since_raw_with_extra( - w, - variant.stable_since(tcx), - variant.const_stability(tcx), - " rightside", - ); - w.push_str("

"); - if let clean::VariantItem(ref var) = variant.kind - && let clean::VariantKind::CLike = var.kind - { - display_c_like_variant( + )?; + + let should_show_enum_discriminant = + should_show_enum_discriminant(cx, enum_def_id, variants); + for (index, variant) in variants.iter_enumerated() { + if variant.is_stripped() { + continue; + } + let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap())); + write!( w, - cx, - variant, - var, - index, - should_show_enum_discriminant, - enum_def_id, - ); - } else { - w.push_str(variant.name.unwrap().as_str()); - } + "
\ + §\ + {}\ +

", + render_stability_since_raw_with_extra( + variant.stable_since(tcx), + variant.const_stability(tcx), + " rightside", + ) + .maybe_display() + )?; + if let clean::VariantItem(ref var) = variant.kind + && let clean::VariantKind::CLike = var.kind + { + write!( + w, + "{}", + display_c_like_variant( + cx, + variant, + var, + index, + should_show_enum_discriminant, + enum_def_id, + ) + )?; + } else { + w.write_str(variant.name.unwrap().as_str())?; + } - let clean::VariantItem(variant_data) = &variant.kind else { unreachable!() }; + let clean::VariantItem(variant_data) = &variant.kind else { unreachable!() }; - if let clean::VariantKind::Tuple(ref s) = variant_data.kind { - write_str(w, format_args!("({})", print_tuple_struct_fields(cx, s))); - } - w.push_str("

"); + if let clean::VariantKind::Tuple(ref s) = variant_data.kind { + write!(w, "({})", print_tuple_struct_fields(cx, s))?; + } + w.write_str("

")?; - write_str(w, format_args!("{}", document(cx, variant, Some(it), HeadingOffset::H4))); + write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4))?; - let heading_and_fields = match &variant_data.kind { - clean::VariantKind::Struct(s) => { - // If there is no field to display, no need to add the heading. - if s.fields.iter().any(|f| !f.is_doc_hidden()) { - Some(("Fields", &s.fields)) - } else { - None + let heading_and_fields = match &variant_data.kind { + clean::VariantKind::Struct(s) => { + // If there is no field to display, no need to add the heading. + if s.fields.iter().any(|f| !f.is_doc_hidden()) { + Some(("Fields", &s.fields)) + } else { + None + } } - } - clean::VariantKind::Tuple(fields) => { - // Documentation on tuple variant fields is rare, so to reduce noise we only emit - // the section if at least one field is documented. - if fields.iter().any(|f| !f.doc_value().is_empty()) { - Some(("Tuple Fields", fields)) - } else { - None + clean::VariantKind::Tuple(fields) => { + // Documentation on tuple variant fields is rare, so to reduce noise we only emit + // the section if at least one field is documented. + if fields.iter().any(|f| !f.doc_value().is_empty()) { + Some(("Tuple Fields", fields)) + } else { + None + } } - } - clean::VariantKind::CLike => None, - }; + clean::VariantKind::CLike => None, + }; - if let Some((heading, fields)) = heading_and_fields { - let variant_id = - cx.derive_id(format!("{}.{}.fields", ItemType::Variant, variant.name.unwrap())); - write_str( - w, - format_args!( + if let Some((heading, fields)) = heading_and_fields { + let variant_id = + cx.derive_id(format!("{}.{}.fields", ItemType::Variant, variant.name.unwrap())); + write!( + w, "
\

{heading}

\ {}", document_non_exhaustive(variant) - ), - ); - for field in fields { - match field.kind { - clean::StrippedItem(box clean::StructFieldItem(_)) => {} - clean::StructFieldItem(ref ty) => { - let id = cx.derive_id(format!( - "variant.{}.field.{}", - variant.name.unwrap(), - field.name.unwrap() - )); - write_str( - w, - format_args!( + )?; + for field in fields { + match field.kind { + clean::StrippedItem(box clean::StructFieldItem(_)) => {} + clean::StructFieldItem(ref ty) => { + let id = cx.derive_id(format!( + "variant.{}.field.{}", + variant.name.unwrap(), + field.name.unwrap() + )); + write!( + w, "
\ \ §\ {f}: {t}\ - ", + \ + {doc}\ +
", f = field.name.unwrap(), t = ty.print(cx), - ), - ); - write_str( - w, - format_args!( - "{}
", - document(cx, field, Some(variant), HeadingOffset::H5), - ), - ); + doc = document(cx, field, Some(variant), HeadingOffset::H5), + )?; + } + _ => unreachable!(), } - _ => unreachable!(), } + w.write_str("
")?; } - w.push_str("
"); } - } - write_str(w, format_args!("
")); + w.write_str("
") + }) } -fn item_macro(w: &mut String, cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) { - wrap_item(w, |w| { - // FIXME: Also print `#[doc(hidden)]` for `macro_rules!` if it `is_doc_hidden`. - if !t.macro_rules { - write_str(w, format_args!("{}", visibility_print_with_space(it, cx))); - } - write_str(w, format_args!("{}", Escape(&t.source))); - }); - write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); +fn item_macro<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + t: &'a clean::Macro, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + wrap_item(w, |w| { + // FIXME: Also print `#[doc(hidden)]` for `macro_rules!` if it `is_doc_hidden`. + if !t.macro_rules { + write!(w, "{}", visibility_print_with_space(it, cx))?; + } + write!(w, "{}", Escape(&t.source)) + })?; + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) + }) } -fn item_proc_macro( - w: &mut impl fmt::Write, - cx: &Context<'_>, - it: &clean::Item, - m: &clean::ProcMacro, -) { - wrap_item(w, |buffer| { - let name = it.name.expect("proc-macros always have names"); - match m.kind { - MacroKind::Bang => { - write!(buffer, "{name}!() {{ /* proc-macro */ }}") - .unwrap(); - } - MacroKind::Attr => { - write!(buffer, "#[{name}]").unwrap(); - } - MacroKind::Derive => { - write!(buffer, "#[derive({name})]").unwrap(); - if !m.helpers.is_empty() { - buffer - .write_str( +fn item_proc_macro<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + m: &'a clean::ProcMacro, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + wrap_item(w, |w| { + let name = it.name.expect("proc-macros always have names"); + match m.kind { + MacroKind::Bang => { + write!(w, "{name}!() {{ /* proc-macro */ }}")?; + } + MacroKind::Attr => { + write!(w, "#[{name}]")?; + } + MacroKind::Derive => { + write!(w, "#[derive({name})]")?; + if !m.helpers.is_empty() { + w.write_str( "\n{\n \ - // Attributes available to this derive:\n", - ) - .unwrap(); - for attr in &m.helpers { - writeln!(buffer, " #[{attr}]").unwrap(); + // Attributes available to this derive:\n", + )?; + for attr in &m.helpers { + writeln!(w, " #[{attr}]")?; + } + w.write_str("}\n")?; } - buffer.write_str("}\n").unwrap(); } } - } - }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); + Ok(()) + })?; + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) + }) } -fn item_primitive(w: &mut impl fmt::Write, cx: &Context<'_>, it: &clean::Item) { - let def_id = it.item_id.expect_def_id(); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); - if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { - write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)).unwrap(); - } else { - // We handle the "reference" primitive type on its own because we only want to list - // implementations on generic types. - let (concrete, synthetic, blanket_impl) = get_filtered_impls_for_reference(&cx.shared, it); - - render_all_impls(w, cx, it, &concrete, &synthetic, &blanket_impl); - } +fn item_primitive<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + let def_id = it.item_id.expect_def_id(); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?; + if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All))?; + } else { + // We handle the "reference" primitive type on its own because we only want to list + // implementations on generic types. + let (concrete, synthetic, blanket_impl) = + get_filtered_impls_for_reference(&cx.shared, it); + + render_all_impls(w, cx, it, &concrete, &synthetic, &blanket_impl); + } + Ok(()) + }) } -fn item_constant( - w: &mut String, - cx: &Context<'_>, - it: &clean::Item, - generics: &clean::Generics, - ty: &clean::Type, - c: &clean::ConstantKind, -) { - wrap_item(w, |w| { - let tcx = cx.tcx(); - render_attributes_in_code(w, it, cx); +fn item_constant<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + generics: &'a clean::Generics, + ty: &'a clean::Type, + c: &'a clean::ConstantKind, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + wrap_item(w, |w| { + let tcx = cx.tcx(); + render_attributes_in_code(w, it, cx); - write_str( - w, - format_args!( + write!( + w, "{vis}const {name}{generics}: {typ}{where_clause}", vis = visibility_print_with_space(it, cx), name = it.name.unwrap(), generics = generics.print(cx), typ = ty.print(cx), - where_clause = print_where_clause(generics, cx, 0, Ending::NoNewline) - ), - ); + where_clause = + print_where_clause(generics, cx, 0, Ending::NoNewline).maybe_display(), + )?; - // FIXME: The code below now prints - // ` = _; // 100i32` - // if the expression is - // `50 + 50` - // which looks just wrong. - // Should we print - // ` = 100i32;` - // instead? - - let value = c.value(tcx); - let is_literal = c.is_literal(tcx); - let expr = c.expr(tcx); - if value.is_some() || is_literal { - write_str(w, format_args!(" = {expr};", expr = Escape(&expr))); - } else { - w.push_str(";"); - } + // FIXME: The code below now prints + // ` = _; // 100i32` + // if the expression is + // `50 + 50` + // which looks just wrong. + // Should we print + // ` = 100i32;` + // instead? + + let value = c.value(tcx); + let is_literal = c.is_literal(tcx); + let expr = c.expr(tcx); + if value.is_some() || is_literal { + write!(w, " = {expr};", expr = Escape(&expr))?; + } else { + w.write_str(";")?; + } - if !is_literal { - if let Some(value) = &value { - let value_lowercase = value.to_lowercase(); - let expr_lowercase = expr.to_lowercase(); + if !is_literal { + if let Some(value) = &value { + let value_lowercase = value.to_lowercase(); + let expr_lowercase = expr.to_lowercase(); - if value_lowercase != expr_lowercase - && value_lowercase.trim_end_matches("i32") != expr_lowercase - { - write_str(w, format_args!(" // {value}", value = Escape(value))); + if value_lowercase != expr_lowercase + && value_lowercase.trim_end_matches("i32") != expr_lowercase + { + write!(w, " // {value}", value = Escape(value))?; + } } } - } - }); + Ok(()) + })?; - write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) + }) } -fn item_struct(w: &mut String, cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) { - wrap_item(w, |w| { - render_attributes_in_code(w, it, cx); - render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx); - }); - - write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); +fn item_struct<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + s: &'a clean::Struct, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + wrap_item(w, |w| { + render_attributes_in_code(w, it, cx); + write!( + w, + "{}", + render_struct(it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx) + ) + })?; - item_fields(w, cx, it, &s.fields, s.ctor_kind); + let def_id = it.item_id.expect_def_id(); - let def_id = it.item_id.expect_def_id(); - write_str(w, format_args!("{}", render_assoc_items(cx, it, def_id, AssocItemRender::All))); - write_str(w, format_args!("{}", document_type_layout(cx, def_id))); + write!( + w, + "{}{}{}{}", + document(cx, it, None, HeadingOffset::H2), + item_fields(cx, it, &s.fields, s.ctor_kind), + render_assoc_items(cx, it, def_id, AssocItemRender::All), + document_type_layout(cx, def_id), + ) + }) } -fn item_fields( - w: &mut String, - cx: &Context<'_>, - it: &clean::Item, - fields: &[clean::Item], +fn item_fields<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + fields: &'a [clean::Item], ctor_kind: Option, -) { - let mut fields = fields - .iter() - .filter_map(|f| match f.kind { - clean::StructFieldItem(ref ty) => Some((f, ty)), - _ => None, - }) - .peekable(); - if let None | Some(CtorKind::Fn) = ctor_kind { - if fields.peek().is_some() { - let title = format!( - "{}{}", - if ctor_kind.is_none() { "Fields" } else { "Tuple Fields" }, - document_non_exhaustive_header(it), - ); - write_section_heading(w, &title, "fields", Some("fields"), document_non_exhaustive(it)); - for (index, (field, ty)) in fields.enumerate() { - let field_name = - field.name.map_or_else(|| index.to_string(), |sym| sym.as_str().to_string()); - let id = cx.derive_id(format!("{typ}.{field_name}", typ = ItemType::StructField)); - write_str( +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(move |w| { + let mut fields = fields + .iter() + .filter_map(|f| match f.kind { + clean::StructFieldItem(ref ty) => Some((f, ty)), + _ => None, + }) + .peekable(); + if let None | Some(CtorKind::Fn) = ctor_kind { + if fields.peek().is_some() { + let title = format!( + "{}{}", + if ctor_kind.is_none() { "Fields" } else { "Tuple Fields" }, + document_non_exhaustive_header(it), + ); + write!( w, - format_args!( + "{}", + write_section_heading( + &title, + "fields", + Some("fields"), + document_non_exhaustive(it) + ) + )?; + for (index, (field, ty)) in fields.enumerate() { + let field_name = field + .name + .map_or_else(|| index.to_string(), |sym| sym.as_str().to_string()); + let id = + cx.derive_id(format!("{typ}.{field_name}", typ = ItemType::StructField)); + write!( + w, "\ §\ {field_name}: {ty}\ - ", + \ + {doc}", item_type = ItemType::StructField, - ty = ty.print(cx) - ), - ); - write_str(w, format_args!("{}", document(cx, field, Some(it), HeadingOffset::H3))); + ty = ty.print(cx), + doc = document(cx, field, Some(it), HeadingOffset::H3), + )?; + } } } - } + Ok(()) + }) } -fn item_static( - w: &mut impl fmt::Write, - cx: &Context<'_>, - it: &clean::Item, - s: &clean::Static, +fn item_static<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, + s: &'a clean::Static, safety: Option, -) { - wrap_item(w, |buffer| { - render_attributes_in_code(buffer, it, cx); - write!( - buffer, - "{vis}{safe}static {mutability}{name}: {typ}", - vis = visibility_print_with_space(it, cx), - safe = safety.map(|safe| safe.prefix_str()).unwrap_or(""), - mutability = s.mutability.print_with_space(), - name = it.name.unwrap(), - typ = s.type_.print(cx) - ) - .unwrap(); - }); +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(move |w| { + wrap_item(w, |w| { + render_attributes_in_code(w, it, cx); + write!( + w, + "{vis}{safe}static {mutability}{name}: {typ}", + vis = visibility_print_with_space(it, cx), + safe = safety.map(|safe| safe.prefix_str()).unwrap_or(""), + mutability = s.mutability.print_with_space(), + name = it.name.unwrap(), + typ = s.type_.print(cx) + ) + })?; - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) + }) } -fn item_foreign_type(w: &mut impl fmt::Write, cx: &Context<'_>, it: &clean::Item) { - wrap_item(w, |buffer| { - buffer.write_str("extern {\n").unwrap(); - render_attributes_in_code(buffer, it, cx); +fn item_foreign_type<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(|w| { + wrap_item(w, |w| { + w.write_str("extern {\n")?; + render_attributes_in_code(w, it, cx); + write!(w, " {}type {};\n}}", visibility_print_with_space(it, cx), it.name.unwrap(),) + })?; + write!( - buffer, - " {}type {};\n}}", - visibility_print_with_space(it, cx), - it.name.unwrap(), + w, + "{}{}", + document(cx, it, None, HeadingOffset::H2), + render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All) ) - .unwrap(); - }); - - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); - write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All)) - .unwrap(); + }) } -fn item_keyword(w: &mut String, cx: &Context<'_>, it: &clean::Item) { - write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); +fn item_keyword<'a, 'tcx>( + cx: &'a Context<'tcx>, + it: &'a clean::Item, +) -> impl fmt::Display + 'a + Captures<'tcx> { + document(cx, it, None, HeadingOffset::H2) } /// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order). @@ -2140,14 +2297,15 @@ fn bounds<'a, 'tcx>( .maybe_display() } -fn wrap_item(w: &mut W, f: F) +fn wrap_item(w: &mut W, f: F) -> T where W: fmt::Write, - F: FnOnce(&mut W), + F: FnOnce(&mut W) -> T, { write!(w, r#"
"#).unwrap();
-    f(w);
+    let res = f(w);
     write!(w, "
").unwrap(); + res } #[derive(PartialEq, Eq)] @@ -2171,14 +2329,13 @@ impl Ord for ImplString { } } -fn render_implementor( - cx: &Context<'_>, - implementor: &Impl, - trait_: &clean::Item, - w: &mut String, - implementor_dups: &FxHashMap, - aliases: &[String], -) { +fn render_implementor<'a, 'tcx>( + cx: &'a Context<'tcx>, + implementor: &'a Impl, + trait_: &'a clean::Item, + implementor_dups: &'a FxHashMap, + aliases: &'a [String], +) -> impl fmt::Display + 'a + Captures<'tcx> { // If there's already another implementor that has the same abridged name, use the // full path, for example in `std::iter::ExactSizeIterator` let use_absolute = match implementor.inner_impl().for_ { @@ -2191,7 +2348,6 @@ fn render_implementor( _ => false, }; render_impl( - w, cx, implementor, trait_, @@ -2205,7 +2361,7 @@ fn render_implementor( show_non_assoc_items: false, toggle_open_by_default: false, }, - ); + ) } fn render_union<'a, 'cx: 'a>( @@ -2217,14 +2373,17 @@ fn render_union<'a, 'cx: 'a>( fmt::from_fn(move |mut f| { write!(f, "{}union {}", visibility_print_with_space(it, cx), it.name.unwrap(),)?; - let where_displayed = g - .map(|g| { - let mut buf = g.print(cx).to_string(); - let where_displayed = print_where_clause_and_check(&mut buf, g, cx); - f.write_str(&buf).unwrap(); - where_displayed - }) - .unwrap_or(false); + let where_displayed = if let Some(generics) = g { + write!(f, "{}", generics.print(cx))?; + if let Some(where_clause) = print_where_clause(generics, cx, 0, Ending::Newline) { + write!(f, "{where_clause}")?; + true + } else { + false + } + } else { + false + }; // If there wasn't a `where` clause, we add a whitespace. if !where_displayed { @@ -2262,148 +2421,160 @@ fn render_union<'a, 'cx: 'a>( }) } -fn render_struct( - w: &mut String, - it: &clean::Item, - g: Option<&clean::Generics>, +fn render_struct<'a, 'tcx>( + it: &'a clean::Item, + g: Option<&'a clean::Generics>, ty: Option, - fields: &[clean::Item], - tab: &str, + fields: &'a [clean::Item], + tab: &'a str, structhead: bool, - cx: &Context<'_>, -) { - write_str( - w, - format_args!( + cx: &'a Context<'tcx>, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(move |w| { + write!( + w, "{}{}{}", visibility_print_with_space(it, cx), if structhead { "struct " } else { "" }, it.name.unwrap() - ), - ); - if let Some(g) = g { - write_str(w, format_args!("{}", g.print(cx))); - } - render_struct_fields( - w, - g, - ty, - fields, - tab, - structhead, - it.has_stripped_entries().unwrap_or(false), - cx, - ) + )?; + if let Some(g) = g { + write!(w, "{}", g.print(cx))?; + } + write!( + w, + "{}", + render_struct_fields( + g, + ty, + fields, + tab, + structhead, + it.has_stripped_entries().unwrap_or(false), + cx, + ) + ) + }) } -fn render_struct_fields( - mut w: &mut String, - g: Option<&clean::Generics>, +fn render_struct_fields<'a, 'tcx>( + g: Option<&'a clean::Generics>, ty: Option, - fields: &[clean::Item], - tab: &str, + fields: &'a [clean::Item], + tab: &'a str, structhead: bool, has_stripped_entries: bool, - cx: &Context<'_>, -) { - match ty { - None => { - let where_displayed = - g.map(|g| print_where_clause_and_check(w, g, cx)).unwrap_or(false); + cx: &'a Context<'tcx>, +) -> impl fmt::Display + 'a + Captures<'tcx> { + fmt::from_fn(move |w| { + match ty { + None => { + let where_displayed = if let Some(generics) = g + && let Some(where_clause) = print_where_clause(generics, cx, 0, Ending::Newline) + { + write!(w, "{where_clause}")?; + true + } else { + false + }; - // If there wasn't a `where` clause, we add a whitespace. - if !where_displayed { - w.push_str(" {"); - } else { - w.push_str("{"); - } - let count_fields = - fields.iter().filter(|f| matches!(f.kind, clean::StructFieldItem(..))).count(); - let has_visible_fields = count_fields > 0; - let toggle = should_hide_fields(count_fields); - if toggle { - toggle_open(&mut w, format_args!("{count_fields} fields")); - } - for field in fields { - if let clean::StructFieldItem(ref ty) = field.kind { - write_str( - w, - format_args!( + // If there wasn't a `where` clause, we add a whitespace. + if !where_displayed { + w.write_str(" {")?; + } else { + w.write_str("{")?; + } + let count_fields = + fields.iter().filter(|f| matches!(f.kind, clean::StructFieldItem(..))).count(); + let has_visible_fields = count_fields > 0; + let toggle = should_hide_fields(count_fields); + if toggle { + toggle_open(&mut *w, format_args!("{count_fields} fields")); + } + for field in fields { + if let clean::StructFieldItem(ref ty) = field.kind { + write!( + w, "\n{tab} {vis}{name}: {ty},", vis = visibility_print_with_space(field, cx), name = field.name.unwrap(), ty = ty.print(cx) - ), - ); + )?; + } } - } - if has_visible_fields { - if has_stripped_entries { - write_str( - w, - format_args!( + if has_visible_fields { + if has_stripped_entries { + write!( + w, "\n{tab} /* private fields */" - ), - ); + )?; + } + write!(w, "\n{tab}")?; + } else if has_stripped_entries { + write!(w, " /* private fields */ ")?; } - write_str(w, format_args!("\n{tab}")); - } else if has_stripped_entries { - write_str(w, format_args!(" /* private fields */ ")); - } - if toggle { - toggle_close(&mut w); + if toggle { + toggle_close(&mut *w); + } + w.write_str("}")?; } - w.push_str("}"); - } - Some(CtorKind::Fn) => { - w.push_str("("); - if !fields.is_empty() - && fields.iter().all(|field| { - matches!(field.kind, clean::StrippedItem(box clean::StructFieldItem(..))) - }) - { - write_str(w, format_args!("/* private fields */")); - } else { - for (i, field) in fields.iter().enumerate() { - if i > 0 { - w.push_str(", "); - } - match field.kind { - clean::StrippedItem(box clean::StructFieldItem(..)) => { - write_str(w, format_args!("_")); + Some(CtorKind::Fn) => { + w.write_str("(")?; + if !fields.is_empty() + && fields.iter().all(|field| { + matches!(field.kind, clean::StrippedItem(box clean::StructFieldItem(..))) + }) + { + write!(w, "/* private fields */")?; + } else { + for (i, field) in fields.iter().enumerate() { + if i > 0 { + w.write_str(", ")?; } - clean::StructFieldItem(ref ty) => { - write_str( - w, - format_args!( + match field.kind { + clean::StrippedItem(box clean::StructFieldItem(..)) => { + write!(w, "_")?; + } + clean::StructFieldItem(ref ty) => { + write!( + w, "{}{}", visibility_print_with_space(field, cx), ty.print(cx) - ), - ); + )?; + } + _ => unreachable!(), } - _ => unreachable!(), } } + w.write_str(")")?; + if let Some(g) = g { + write!( + w, + "{}", + print_where_clause(g, cx, 0, Ending::NoNewline).maybe_display() + )?; + } + // We only want a ";" when we are displaying a tuple struct, not a variant tuple struct. + if structhead { + w.write_str(";")?; + } } - w.push_str(")"); - if let Some(g) = g { - write_str(w, format_args!("{}", print_where_clause(g, cx, 0, Ending::NoNewline))); - } - // We only want a ";" when we are displaying a tuple struct, not a variant tuple struct. - if structhead { - w.push_str(";"); - } - } - Some(CtorKind::Const) => { - // Needed for PhantomData. - if let Some(g) = g { - write_str(w, format_args!("{}", print_where_clause(g, cx, 0, Ending::NoNewline))); + Some(CtorKind::Const) => { + // Needed for PhantomData. + if let Some(g) = g { + write!( + w, + "{}", + print_where_clause(g, cx, 0, Ending::NoNewline).maybe_display() + )?; + } + w.write_str(";")?; } - w.push_str(";"); } - } + Ok(()) + }) } fn document_non_exhaustive_header(item: &clean::Item) -> &str { diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index a4dec013fc040..0185d0c3bb54b 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -636,26 +636,22 @@ impl TypeAliasPart { } else { AssocItemLink::Anchor(None) }; - let text = { - let mut buf = String::new(); - super::render_impl( - &mut buf, - 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, - }, - ); - buf - }; + 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()