rustdoc: reduce allocs in FnDecl::inner_full_print

Instead of maintaining parallel buffers for both HTML and non-HTML output,
follow the idiom from the rest of format.rs that f.alternate() == true means
textual output. Also, add an argument to control line wrapping explicitly.

This allows the caller to render once with textual output and no line wrapping,
to decide whether line wrapping should be applied in the final HTML output.

Also, remove some format! and " ".repeat calls, and remove a dependency on
calling `String::replace` to switch from newlines to spaces.

This coincidentally fixes some minor bugs where the old code was undercounting
the number of characters for a declaration in text mode.
This commit is contained in:
Jacob Hoffman-Andrews 2023-03-09 14:23:56 -08:00
parent 6dfaa14366
commit 121ae1d29c

View File

@ -1,13 +1,15 @@
//! HTML formatting module //! HTML formatting module
//! //!
//! This module contains a large number of `fmt::Display` implementations for //! This module contains a large number of `fmt::Display` implementations for
//! various types in `rustdoc::clean`. These implementations all currently //! various types in `rustdoc::clean`.
//! assume that HTML output is desired, although it may be possible to redesign //!
//! them in the future to instead emit any format desired. //! These implementations all emit HTML. As an internal implementation detail,
//! some of them support an alternate format that emits text, but that should
//! not be used external to this module.
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::Cell; use std::cell::Cell;
use std::fmt; use std::fmt::{self, Write};
use std::iter::{self, once}; use std::iter::{self, once};
use rustc_ast as ast; use rustc_ast as ast;
@ -126,7 +128,6 @@ pub(crate) fn write_str(&mut self, s: &str) {
// the fmt::Result return type imposed by fmt::Write (and avoiding the trait // the fmt::Result return type imposed by fmt::Write (and avoiding the trait
// import). // import).
pub(crate) fn write_fmt(&mut self, v: fmt::Arguments<'_>) { pub(crate) fn write_fmt(&mut self, v: fmt::Arguments<'_>) {
use fmt::Write;
self.buffer.write_fmt(v).unwrap(); self.buffer.write_fmt(v).unwrap();
} }
@ -279,8 +280,6 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
indent: usize, indent: usize,
ending: Ending, ending: Ending,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl fmt::Display + 'a + Captures<'tcx> {
use fmt::Write;
display_fn(move |f| { display_fn(move |f| {
let mut where_predicates = gens.where_predicates.iter().filter(|pred| { let mut where_predicates = gens.where_predicates.iter().filter(|pred| {
!matches!(pred, clean::WherePredicate::BoundPredicate { bounds, .. } if bounds.is_empty()) !matches!(pred, clean::WherePredicate::BoundPredicate { bounds, .. } if bounds.is_empty())
@ -1306,6 +1305,28 @@ fn print_hrtb_with_space<'a, 'tcx: 'a>(
} }
} }
// Implements Write but only counts the bytes "written".
struct WriteCounter(usize);
impl std::fmt::Write for WriteCounter {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0 += s.len();
Ok(())
}
}
// Implements Display by emitting the given number of spaces.
struct Indent(usize);
impl fmt::Display for Indent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(0..self.0).for_each(|_| {
f.write_char(' ').unwrap();
});
Ok(())
}
}
impl clean::FnDecl { impl clean::FnDecl {
pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>( pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>(
&'a self, &'a self,
@ -1345,95 +1366,80 @@ pub(crate) fn full_print<'a, 'tcx: 'a>(
indent: usize, indent: usize,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl fmt::Display + 'a + Captures<'tcx> {
display_fn(move |f| self.inner_full_print(header_len, indent, f, cx)) display_fn(move |f| {
// First, generate the text form of the declaration, with no line wrapping, and count the bytes.
let mut counter = WriteCounter(0);
write!(&mut counter, "{:#}", display_fn(|f| { self.inner_full_print(None, f, cx) }))
.unwrap();
// If the text form was over 80 characters wide, we will line-wrap our output.
let line_wrapping_indent =
if header_len + counter.0 > 80 { Some(indent) } else { None };
// Generate the final output. This happens to accept `{:#}` formatting to get textual
// output but in practice it is only formatted with `{}` to get HTML output.
self.inner_full_print(line_wrapping_indent, f, cx)
})
} }
fn inner_full_print( fn inner_full_print(
&self, &self,
header_len: usize, // For None, the declaration will not be line-wrapped. For Some(n),
indent: usize, // the declaration will be line-wrapped, with an indent of n spaces.
line_wrapping_indent: Option<usize>,
f: &mut fmt::Formatter<'_>, f: &mut fmt::Formatter<'_>,
cx: &Context<'_>, cx: &Context<'_>,
) -> fmt::Result { ) -> fmt::Result {
let amp = if f.alternate() { "&" } else { "&amp;" }; let amp = if f.alternate() { "&" } else { "&amp;" };
let mut args = Buffer::html();
let mut args_plain = Buffer::new(); write!(f, "(")?;
if let Some(n) = line_wrapping_indent {
write!(f, "\n{}", Indent(n + 4))?;
}
for (i, input) in self.inputs.values.iter().enumerate() { for (i, input) in self.inputs.values.iter().enumerate() {
if i > 0 {
match line_wrapping_indent {
None => write!(f, ", ")?,
Some(n) => write!(f, ",\n{}", Indent(n + 4))?,
};
}
if let Some(selfty) = input.to_self() { if let Some(selfty) = input.to_self() {
match selfty { match selfty {
clean::SelfValue => { clean::SelfValue => {
args.push_str("self"); write!(f, "self")?;
args_plain.push_str("self");
} }
clean::SelfBorrowed(Some(ref lt), mtbl) => { clean::SelfBorrowed(Some(ref lt), mtbl) => {
write!(args, "{}{} {}self", amp, lt.print(), mtbl.print_with_space()); write!(f, "{}{} {}self", amp, lt.print(), mtbl.print_with_space())?;
write!(args_plain, "&{} {}self", lt.print(), mtbl.print_with_space());
} }
clean::SelfBorrowed(None, mtbl) => { clean::SelfBorrowed(None, mtbl) => {
write!(args, "{}{}self", amp, mtbl.print_with_space()); write!(f, "{}{}self", amp, mtbl.print_with_space())?;
write!(args_plain, "&{}self", mtbl.print_with_space());
} }
clean::SelfExplicit(ref typ) => { clean::SelfExplicit(ref typ) => {
if f.alternate() { write!(f, "self: ")?;
write!(args, "self: {:#}", typ.print(cx)); fmt::Display::fmt(&typ.print(cx), f)?;
} else {
write!(args, "self: {}", typ.print(cx));
}
write!(args_plain, "self: {:#}", typ.print(cx));
} }
} }
} else { } else {
if i > 0 {
args.push_str("\n");
}
if input.is_const { if input.is_const {
args.push_str("const "); write!(f, "const ")?;
args_plain.push_str("const ");
} }
write!(args, "{}: ", input.name); write!(f, "{}: ", input.name)?;
write!(args_plain, "{}: ", input.name); fmt::Display::fmt(&input.type_.print(cx), f)?;
if f.alternate() {
write!(args, "{:#}", input.type_.print(cx));
} else {
write!(args, "{}", input.type_.print(cx));
}
write!(args_plain, "{:#}", input.type_.print(cx));
}
if i + 1 < self.inputs.values.len() {
args.push_str(",");
args_plain.push_str(",");
} }
} }
let mut args_plain = format!("({})", args_plain.into_inner());
let mut args = args.into_inner();
if self.c_variadic { if self.c_variadic {
args.push_str(",\n ..."); match line_wrapping_indent {
args_plain.push_str(", ..."); None => write!(f, ", ...")?,
Some(n) => write!(f, "\n{}...", Indent(n + 4))?,
};
} }
let arrow_plain = format!("{:#}", self.output.print(cx)); match line_wrapping_indent {
let arrow = None => write!(f, ")")?,
if f.alternate() { arrow_plain.clone() } else { format!("{}", self.output.print(cx)) }; Some(n) => write!(f, "\n{})", Indent(n))?,
let declaration_len = header_len + args_plain.len() + arrow_plain.len();
let output = if declaration_len > 80 {
let full_pad = format!("\n{}", " ".repeat(indent + 4));
let close_pad = format!("\n{}", " ".repeat(indent));
format!(
"({pad}{args}{close}){arrow}",
pad = if self.inputs.values.is_empty() { "" } else { &full_pad },
args = args.replace('\n', &full_pad),
close = close_pad,
arrow = arrow
)
} else {
format!("({args}){arrow}", args = args.replace('\n', " "), arrow = arrow)
}; };
write!(f, "{}", output) fmt::Display::fmt(&self.output.print(cx), f)?;
Ok(())
} }
} }