diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
index b576f28176e5d..bec7fbe8f52bd 100644
--- a/src/librustdoc/clean/cfg.rs
+++ b/src/librustdoc/clean/cfg.rs
@@ -13,8 +13,8 @@ use rustc_session::parse::ParseSess;
use rustc_span::Span;
use rustc_span::symbol::{Symbol, sym};
+use crate::display::Joined as _;
use crate::html::escape::Escape;
-use crate::joined::Joined as _;
#[cfg(test)]
mod tests;
diff --git a/src/librustdoc/joined.rs b/src/librustdoc/display.rs
similarity index 67%
rename from src/librustdoc/joined.rs
rename to src/librustdoc/display.rs
index f369c6cf2371c..ee8dde013ee93 100644
--- a/src/librustdoc/joined.rs
+++ b/src/librustdoc/display.rs
@@ -1,3 +1,5 @@
+//! Various utilities for working with [`fmt::Display`] implementations.
+
use std::fmt::{self, Display, Formatter};
pub(crate) trait Joined: IntoIterator {
@@ -27,3 +29,19 @@ where
Ok(())
}
}
+
+pub(crate) trait MaybeDisplay {
+ /// For a given `Option`, returns a `Display` implementation that will display `t` if `Some(t)`, or nothing if `None`.
+ fn maybe_display(self) -> impl Display;
+}
+
+impl MaybeDisplay for Option {
+ fn maybe_display(self) -> impl Display {
+ fmt::from_fn(move |f| {
+ if let Some(t) = self.as_ref() {
+ t.fmt(f)?;
+ }
+ Ok(())
+ })
+ }
+}
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 086a85aa616f6..91b4b3ba1ebac 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -30,11 +30,11 @@ 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::formats::cache::Cache;
use crate::formats::item_type::ItemType;
use crate::html::escape::{Escape, EscapeBodyText};
use crate::html::render::Context;
-use crate::joined::Joined as _;
use crate::passes::collect_intra_doc_links::UrlFragment;
pub(crate) fn write_str(s: &mut String, f: fmt::Arguments<'_>) {
@@ -709,19 +709,22 @@ fn resolved_path(
if w.alternate() {
write!(w, "{}{:#}", last.name, last.args.print(cx))?;
} else {
- let path = if use_absolute {
- if let Ok((_, _, fqp)) = href(did, cx) {
- format!(
- "{path}::{anchor}",
- path = join_with_double_colon(&fqp[..fqp.len() - 1]),
- anchor = anchor(did, *fqp.last().unwrap(), cx)
- )
+ let path = fmt::from_fn(|f| {
+ if use_absolute {
+ if let Ok((_, _, fqp)) = href(did, cx) {
+ write!(
+ f,
+ "{path}::{anchor}",
+ path = join_with_double_colon(&fqp[..fqp.len() - 1]),
+ anchor = anchor(did, *fqp.last().unwrap(), cx)
+ )
+ } else {
+ write!(f, "{}", last.name)
+ }
} else {
- last.name.to_string()
+ write!(f, "{}", anchor(did, last.name, cx))
}
- } else {
- anchor(did, last.name, cx).to_string()
- };
+ });
write!(w, "{path}{args}", args = last.args.print(cx))?;
}
Ok(())
@@ -749,16 +752,20 @@ fn primitive_link_fragment(
match m.primitive_locations.get(&prim) {
Some(&def_id) if def_id.is_local() => {
let len = cx.current.len();
- let path = if len == 0 {
- let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
- format!("{cname_sym}/")
- } else {
- "../".repeat(len - 1)
- };
+ let path = fmt::from_fn(|f| {
+ if len == 0 {
+ let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
+ write!(f, "{cname_sym}/")?;
+ } else {
+ for _ in 0..(len - 1) {
+ f.write_str("../")?;
+ }
+ }
+ Ok(())
+ });
write!(
f,
- "",
- path,
+ "",
prim.as_sym()
)?;
needs_termination = true;
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index b774e60c62d3b..146bdd340697b 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -1,8 +1,9 @@
use std::cell::RefCell;
use std::collections::BTreeMap;
+use std::fmt::{self, Write as _};
+use std::io;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{Receiver, channel};
-use std::{fmt, io};
use rinja::Template;
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
@@ -265,12 +266,12 @@ impl<'tcx> Context<'tcx> {
// preventing an infinite redirection loop in the generated
// documentation.
- let mut path = String::new();
- for name in &names[..names.len() - 1] {
- path.push_str(name.as_str());
- path.push('/');
- }
- path.push_str(&item_path(ty, names.last().unwrap().as_str()));
+ let path = fmt::from_fn(|f| {
+ for name in &names[..names.len() - 1] {
+ write!(f, "{name}/")?;
+ }
+ write!(f, "{}", item_path(ty, names.last().unwrap().as_str()))
+ });
match self.shared.redirections {
Some(ref redirections) => {
let mut current_path = String::new();
@@ -278,8 +279,12 @@ impl<'tcx> Context<'tcx> {
current_path.push_str(name.as_str());
current_path.push('/');
}
- current_path.push_str(&item_path(ty, names.last().unwrap().as_str()));
- redirections.borrow_mut().insert(current_path, path);
+ let _ = write!(
+ current_path,
+ "{}",
+ item_path(ty, names.last().unwrap().as_str())
+ );
+ redirections.borrow_mut().insert(current_path, path.to_string());
}
None => {
return layout::redirect(&format!(
@@ -854,9 +859,9 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
if !buf.is_empty() {
let name = item.name.as_ref().unwrap();
let item_type = item.type_();
- let file_name = &item_path(item_type, name.as_str());
+ let file_name = item_path(item_type, name.as_str()).to_string();
self.shared.ensure_dir(&self.dst)?;
- let joint_dst = self.dst.join(file_name);
+ let joint_dst = self.dst.join(&file_name);
self.shared.fs.write(joint_dst, buf)?;
if !self.info.render_redirect_pages {
@@ -873,7 +878,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
format!("{crate_name}/{file_name}"),
);
} else {
- let v = layout::redirect(file_name);
+ let v = layout::redirect(&file_name);
let redir_dst = self.dst.join(redir_name);
self.shared.fs.write(redir_dst, v)?;
}
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 2f52a38c58173..204631063a23a 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -63,6 +63,7 @@ pub(crate) use self::context::*;
pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources};
pub(crate) use self::write_shared::*;
use crate::clean::{self, ItemId, RenderedLink};
+use crate::display::{Joined as _, MaybeDisplay as _};
use crate::error::Error;
use crate::formats::Impl;
use crate::formats::cache::Cache;
@@ -568,17 +569,27 @@ fn document_short<'a, 'cx: 'a>(
let (mut summary_html, has_more_content) =
MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content();
- if has_more_content {
- let link = format!(" Read more", assoc_href_attr(item, link, cx));
+ let link = if has_more_content {
+ let link = fmt::from_fn(|f| {
+ write!(
+ f,
+ " Read more",
+ assoc_href_attr(item, link, cx).maybe_display()
+ )
+ });
if let Some(idx) = summary_html.rfind("
") {
- summary_html.insert_str(idx, &link);
+ summary_html.insert_str(idx, &link.to_string());
+ None
} else {
- summary_html.push_str(&link);
+ Some(link)
}
+ } else {
+ None
}
+ .maybe_display();
- write!(f, "{summary_html}
")?;
+ write!(f, "{summary_html}{link}
")?;
}
Ok(())
})
@@ -788,13 +799,23 @@ pub(crate) fn render_impls(
}
/// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
-fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String {
+fn assoc_href_attr<'a, 'tcx>(
+ it: &clean::Item,
+ link: AssocItemLink<'a>,
+ cx: &Context<'tcx>,
+) -> Option> {
let name = it.name.unwrap();
let item_type = it.type_();
+ enum Href<'a> {
+ AnchorId(&'a str),
+ Anchor(ItemType),
+ Url(String, ItemType),
+ }
+
let href = match link {
- AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{id}")),
- AssocItemLink::Anchor(None) => Some(format!("#{item_type}.{name}")),
+ AssocItemLink::Anchor(Some(ref id)) => Href::AnchorId(id),
+ AssocItemLink::Anchor(None) => Href::Anchor(item_type),
AssocItemLink::GotoSource(did, provided_methods) => {
// We're creating a link from the implementation of an associated item to its
// declaration in the trait declaration.
@@ -814,7 +835,7 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>)
};
match href(did.expect_def_id(), cx) {
- Ok((url, ..)) => Some(format!("{url}#{item_type}.{name}")),
+ Ok((url, ..)) => Href::Url(url, item_type),
// The link is broken since it points to an external crate that wasn't documented.
// Do not create any link in such case. This is better than falling back to a
// dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item
@@ -826,15 +847,25 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>)
// those two items are distinct!
// In this scenario, the actual `id` of this impl item would be
// `#{item_type}.{name}-{n}` for some number `n` (a disambiguator).
- Err(HrefError::DocumentationNotBuilt) => None,
- Err(_) => Some(format!("#{item_type}.{name}")),
+ Err(HrefError::DocumentationNotBuilt) => return None,
+ Err(_) => Href::Anchor(item_type),
}
}
};
+ let href = fmt::from_fn(move |f| match &href {
+ Href::AnchorId(id) => write!(f, "#{id}"),
+ Href::Url(url, item_type) => {
+ write!(f, "{url}#{item_type}.{name}")
+ }
+ Href::Anchor(item_type) => {
+ write!(f, "#{item_type}.{name}")
+ }
+ });
+
// If there is no `href` for the reason explained above, simply do not render it which is valid:
// https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements
- href.map(|href| format!(" href=\"{href}\"")).unwrap_or_default()
+ Some(fmt::from_fn(move |f| write!(f, " href=\"{href}\"")))
}
#[derive(Debug)]
@@ -865,7 +896,7 @@ fn assoc_const(
"{indent}{vis}const {name}{generics}: {ty}",
indent = " ".repeat(indent),
vis = visibility_print_with_space(it, cx),
- href = assoc_href_attr(it, link, cx),
+ href = assoc_href_attr(it, link, cx).maybe_display(),
name = it.name.as_ref().unwrap(),
generics = generics.print(cx),
ty = ty.print(cx),
@@ -905,7 +936,7 @@ fn assoc_type(
"{indent}{vis}type {name}{generics}",
indent = " ".repeat(indent),
vis = visibility_print_with_space(it, cx),
- href = assoc_href_attr(it, link, cx),
+ href = assoc_href_attr(it, link, cx).maybe_display(),
name = it.name.as_ref().unwrap(),
generics = generics.print(cx),
),
@@ -948,7 +979,7 @@ fn assoc_method(
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);
+ 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();
@@ -962,7 +993,7 @@ fn assoc_method(
+ name.as_str().len()
+ generics_len;
- let notable_traits = notable_traits_button(&d.output, cx);
+ let notable_traits = notable_traits_button(&d.output, cx).maybe_display();
let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
header_len += 4;
@@ -990,7 +1021,6 @@ fn assoc_method(
name = name,
generics = g.print(cx),
decl = d.full_print(header_len, indent, cx),
- notable_traits = notable_traits.unwrap_or_default(),
where_clause = print_where_clause(g, cx, indent, end_newline),
),
);
@@ -1438,7 +1468,10 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) ->
}
}
-pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option {
+pub(crate) fn notable_traits_button<'a, 'tcx>(
+ ty: &'a clean::Type,
+ cx: &'a Context<'tcx>,
+) -> Option> {
let mut has_notable_trait = false;
if ty.is_unit() {
@@ -1480,15 +1513,16 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Optio
}
}
- if has_notable_trait {
+ has_notable_trait.then(|| {
cx.types_with_notable_traits.borrow_mut().insert(ty.clone());
- Some(format!(
- " ⓘ",
- ty = Escape(&format!("{:#}", ty.print(cx))),
- ))
- } else {
- None
- }
+ fmt::from_fn(|f| {
+ write!(
+ f,
+ " ⓘ",
+ ty = Escape(&format!("{:#}", ty.print(cx))),
+ )
+ })
+ })
}
fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
@@ -2117,11 +2151,11 @@ pub(crate) fn render_impl_summary(
) {
let inner_impl = i.inner_impl();
let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id));
- let aliases = if aliases.is_empty() {
- String::new()
- } else {
- format!(" data-aliases=\"{}\"", aliases.join(","))
- };
+ 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(
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 9c1afef75ef3b..d2d7415261b9f 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, Write};
+use std::fmt::Display;
use itertools::Itertools;
use rinja::Template;
@@ -27,6 +27,7 @@ use super::{
};
use crate::clean;
use crate::config::ModuleSorting;
+use crate::display::{Joined as _, MaybeDisplay as _};
use crate::formats::Impl;
use crate::formats::item_type::ItemType;
use crate::html::escape::{Escape, EscapeBodyTextWithWbr};
@@ -37,7 +38,6 @@ use crate::html::format::{
use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
use crate::html::render::{document_full, document_item_info};
use crate::html::url_parts_builder::UrlPartsBuilder;
-use crate::joined::Joined as _;
/// Generates a Rinja template struct for rendering items with common methods.
///
@@ -619,7 +619,7 @@ fn item_function(w: &mut String, cx: &Context<'_>, it: &clean::Item, f: &clean::
+ name.as_str().len()
+ generics_len;
- let notable_traits = notable_traits_button(&f.decl.output, cx);
+ let notable_traits = notable_traits_button(&f.decl.output, cx).maybe_display();
wrap_item(w, |w| {
w.reserve(header_len);
@@ -638,7 +638,6 @@ fn item_function(w: &mut String, cx: &Context<'_>, it: &clean::Item, f: &clean::
generics = f.generics.print(cx),
where_clause = print_where_clause(&f.generics, cx, 0, Ending::Newline),
decl = f.decl.full_print(header_len, 0, cx),
- notable_traits = notable_traits.unwrap_or_default(),
),
);
});
@@ -2116,34 +2115,33 @@ pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String {
s
}
-pub(super) fn item_path(ty: ItemType, name: &str) -> String {
- match ty {
- ItemType::Module => format!("{}index.html", ensure_trailing_slash(name)),
- _ => format!("{ty}.{name}.html"),
- }
+pub(super) fn item_path(ty: ItemType, name: &str) -> impl Display + '_ {
+ fmt::from_fn(move |f| match ty {
+ ItemType::Module => write!(f, "{}index.html", ensure_trailing_slash(name)),
+ _ => write!(f, "{ty}.{name}.html"),
+ })
}
-fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>) -> String {
- let mut bounds = String::new();
- if t_bounds.is_empty() {
- return bounds;
- }
- let has_lots_of_bounds = t_bounds.len() > 2;
- let inter_str = if has_lots_of_bounds { "\n + " } else { " + " };
- if !trait_alias {
- if has_lots_of_bounds {
- bounds.push_str(":\n ");
- } else {
- bounds.push_str(": ");
- }
- }
- write!(
- bounds,
- "{}",
- fmt::from_fn(|f| t_bounds.iter().map(|p| p.print(cx)).joined(inter_str, f))
- )
- .unwrap();
- bounds
+fn bounds<'a, 'tcx>(
+ bounds: &'a [clean::GenericBound],
+ trait_alias: bool,
+ cx: &'a Context<'tcx>,
+) -> impl Display + 'a + Captures<'tcx> {
+ (!bounds.is_empty())
+ .then_some(fmt::from_fn(move |f| {
+ let has_lots_of_bounds = bounds.len() > 2;
+ let inter_str = if has_lots_of_bounds { "\n + " } else { " + " };
+ if !trait_alias {
+ if has_lots_of_bounds {
+ f.write_str(":\n ")?;
+ } else {
+ f.write_str(": ")?;
+ }
+ }
+
+ bounds.iter().map(|p| p.print(cx)).joined(inter_str, f)
+ }))
+ .maybe_display()
}
fn wrap_item(w: &mut W, f: F)
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 4e4cff40686ef..e4acbcf2c626f 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -106,6 +106,7 @@ macro_rules! map {
mod clean;
mod config;
mod core;
+mod display;
mod docfs;
mod doctest;
mod error;
@@ -114,7 +115,6 @@ mod fold;
mod formats;
// used by the error-index generator, so it needs to be public
pub mod html;
-mod joined;
mod json;
pub(crate) mod lint;
mod markdown;