From b5ba3c9645b801ac14b6b13dfc6b6da35893bd09 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Sun, 19 Mar 2023 11:52:04 -0700 Subject: [PATCH 1/5] rustdoc: add HtmlRemover and Plain --- src/librustdoc/html/format.rs | 123 +++++++++++++++++++++++++++++++++- src/librustdoc/html/tests.rs | 34 ++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index f2b9c0bcf3ee..8f91c0a057ef 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -1311,7 +1311,94 @@ impl clean::BareFunctionDecl { } } -// Implements Write but only counts the bytes "written". +/// This is a simplified HTML processor, intended for counting the number of characters +/// of text that a stream of HTML is equivalent to. This is used to calculate the width +/// (in characters) of a function declaration, to decide whether to line-wrap it like +/// rustfmt would do. It's only valid for use with HTML emitted from within this module, +/// so it is intentionally not pub(crate). +/// +/// This makes some assumptions that are specifically tied to the HTML emitted in format.rs: +/// - Whitespace is significant. +/// - All tags display their contents as text. +/// - Each call to write() contains a sequence of bytes that is valid UTF-8 on its own. +/// - All '<' in HTML attributes are escaped. +/// - HTML attributes are quoted with double quotes. +/// - The only HTML entities used are `<`, `>`, `&`, `"`, and `'` +#[derive(Debug, Clone)] +struct HtmlRemover { + inner: W, + state: HtmlTextCounterState, +} + +impl HtmlRemover { + fn new(w: W) -> Self { + HtmlRemover { inner: w, state: HtmlTextCounterState::Text } + } +} + +// A state machine that tracks our progress through the HTML. +#[derive(Debug, Clone)] +enum HtmlTextCounterState { + Text, + // A small buffer to store the entity name + Entity(u8, [u8; 4]), + Tag, +} + +impl fmt::Write for HtmlRemover { + fn write_str(&mut self, s: &str) -> fmt::Result { + use HtmlTextCounterState::*; + for c in s.chars() { + match (&mut self.state, c) { + (Text, '<') => self.state = Tag, + (Text, '&') => self.state = Entity(0, Default::default()), + (Text, _) => write!(self.inner, "{c}")?, + // Note: `>` can occur in attribute values, but we always escape + // them internally, so we don't have to have an extra state for + // "in attribute value." + // https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#syntax-attributes + (Tag, '>') => self.state = Text, + (Tag, '<') => Err(fmt::Error)?, + // Within a tag, do nothing. + (Tag, _) => {} + // Finish an entity + (Entity(len, arr), ';') => { + let emit = match std::str::from_utf8(&arr[0..*len as usize]).unwrap() { + "lt" => '<', + "gt" => '>', + "amp" => '&', + "quot" => '"', + "#39" => '\'', + _ => Err(fmt::Error)?, + }; + write!(self.inner, "{emit}")?; + self.state = Text; + } + // Read one character of an entity name + (Entity(ref mut len, ref mut arr), c) => { + if *len as usize > arr.len() - 1 { + Err(fmt::Error)?; + } + arr[*len as usize] = c as u8; + *len += 1; + } + } + } + Ok(()) + } +} + +/// This generates the plain text form of a marked-up HTML input, using HtmlRemover. +struct Plain(D); + +impl fmt::Display for Plain { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut remover = HtmlRemover::new(f); + write!(&mut remover, "{}", self.0) + } +} + +/// Implements Write but only counts the bytes "written". struct WriteCounter(usize); impl std::fmt::Write for WriteCounter { @@ -1714,3 +1801,37 @@ pub(crate) fn display_fn( WithFormatter(Cell::new(Some(f))) } + +#[test] +fn test_html_remover() { + use std::fmt::Write; + + fn assert_removed_eq(input: &str, output: &str) { + let mut remover = HtmlRemover::new(String::new()); + write!(&mut remover, "{}", input).unwrap(); + assert_eq!(&remover.inner, output); + } + + assert_removed_eq("ab", "ab"); + assert_removed_eq("alpha <bet>", "alpha "); + assert_removed_eq("", ""); + assert_removed_eq(">text<", ">text<"); + + let mut remover = HtmlRemover::new(String::new()); + assert!(write!(&mut remover, "&ent;").is_err()); + + let mut remover = HtmlRemover::new(String::new()); + assert!(write!(&mut remover, "&entity").is_err()); + + let mut remover = HtmlRemover::new(String::new()); + assert!(write!(&mut remover, "&&").is_err()); + + let mut remover = HtmlRemover::new(String::new()); + assert!(write!(&mut remover, "alpha <bet>"); + assert_eq!(&d.to_string(), "alpha "); +} diff --git a/src/librustdoc/html/tests.rs b/src/librustdoc/html/tests.rs index 437d3995e29f..cfcbbcd7edc0 100644 --- a/src/librustdoc/html/tests.rs +++ b/src/librustdoc/html/tests.rs @@ -48,3 +48,37 @@ fn href_relative_parts_root() { let fqp = &[sym::std]; assert_relative_path(&[sym::std], relative_to_fqp, fqp); } + +#[test] +fn test_html_remover() { + use super::format::HtmlRemover; + use std::fmt::Write; + + fn assert_removed_eq(input: &str, output: &str) { + let mut remover = HtmlRemover::new(String::new()); + write!(&mut remover, "{}", input).unwrap(); + assert_eq!(&remover.into_inner(), output); + } + + assert_removed_eq("ab", "ab"); + assert_removed_eq("alpha <bet>", "alpha "); + assert_removed_eq("", ""); + assert_removed_eq(">text<", ">text<"); + assert_removed_eq(""'", "\"'"); + + let mut remover = HtmlRemover::new(String::new()); + assert!(write!(&mut remover, "&ent;").is_err()); + + let mut remover = HtmlRemover::new(String::new()); + assert!(write!(&mut remover, "&longentity").is_err()); + + let mut remover = HtmlRemover::new(String::new()); + assert!(write!(&mut remover, "alpha <bet>"); + assert_eq!(&d.to_string(), "alpha "); +} From b078972d6a6c7c586addcac07aa001e90f3df4c6 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Sun, 19 Mar 2023 11:54:00 -0700 Subject: [PATCH 2/5] rustdoc: implement and use print_plain --- src/librustdoc/html/format.rs | 61 +++++++++++---------------- src/librustdoc/html/render/mod.rs | 26 ++++++------ src/librustdoc/html/render/sidebar.rs | 8 ++-- 3 files changed, 41 insertions(+), 54 deletions(-) diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 8f91c0a057ef..31e52e59ed44 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -1169,6 +1169,14 @@ impl clean::Type { ) -> impl fmt::Display + 'b + Captures<'tcx> { display_fn(move |f| fmt_type(self, f, false, cx)) } + + /// Emit this type in plain text form (no HTML). + pub(crate) fn print_plain<'b, 'a: 'b, 'tcx: 'a>( + &'a self, + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'b + Captures<'tcx> { + Plain(self.print(cx)) + } } impl clean::Path { @@ -1178,6 +1186,14 @@ impl clean::Path { ) -> impl fmt::Display + 'b + Captures<'tcx> { display_fn(move |f| resolved_path(f, self.def_id(), self, false, false, cx)) } + + /// Emit this type in plain text form (no HTML). + pub(crate) fn print_plain<'b, 'a: 'b, 'tcx: 'a>( + &'a self, + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'b + Captures<'tcx> { + Plain(self.print(cx)) + } } impl clean::Impl { @@ -1325,15 +1341,20 @@ impl clean::BareFunctionDecl { /// - HTML attributes are quoted with double quotes. /// - The only HTML entities used are `<`, `>`, `&`, `"`, and `'` #[derive(Debug, Clone)] -struct HtmlRemover { +pub(super) struct HtmlRemover { inner: W, state: HtmlTextCounterState, } impl HtmlRemover { - fn new(w: W) -> Self { + pub(super) fn new(w: W) -> Self { HtmlRemover { inner: w, state: HtmlTextCounterState::Text } } + + #[cfg(test)] + pub(super) fn into_inner(self) -> W { + self.inner + } } // A state machine that tracks our progress through the HTML. @@ -1389,7 +1410,7 @@ impl fmt::Write for HtmlRemover { } /// This generates the plain text form of a marked-up HTML input, using HtmlRemover. -struct Plain(D); +pub(super) struct Plain(pub(super) D); impl fmt::Display for Plain { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -1801,37 +1822,3 @@ pub(crate) fn display_fn( WithFormatter(Cell::new(Some(f))) } - -#[test] -fn test_html_remover() { - use std::fmt::Write; - - fn assert_removed_eq(input: &str, output: &str) { - let mut remover = HtmlRemover::new(String::new()); - write!(&mut remover, "{}", input).unwrap(); - assert_eq!(&remover.inner, output); - } - - assert_removed_eq("ab", "ab"); - assert_removed_eq("alpha <bet>", "alpha "); - assert_removed_eq("", ""); - assert_removed_eq(">text<", ">text<"); - - let mut remover = HtmlRemover::new(String::new()); - assert!(write!(&mut remover, "&ent;").is_err()); - - let mut remover = HtmlRemover::new(String::new()); - assert!(write!(&mut remover, "&entity").is_err()); - - let mut remover = HtmlRemover::new(String::new()); - assert!(write!(&mut remover, "&&").is_err()); - - let mut remover = HtmlRemover::new(String::new()); - assert!(write!(&mut remover, "alpha <bet>"); - assert_eq!(&d.to_string(), "alpha "); -} diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index d75d03071f89..8052eb06906c 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -1141,8 +1141,10 @@ fn render_assoc_items_inner( (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 id = cx.derive_id(small_url_encode(format!( + "deref-methods-{}", + type_.print_plain(cx) + ))); if let Some(def_id) = type_.def_id(cx.cache()) { cx.deref_id_map.insert(def_id, id.clone()); } @@ -1314,7 +1316,7 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> O cx.types_with_notable_traits.insert(ty.clone()); Some(format!( " ", - ty = Escape(&format!("{:#}", ty.print(cx))), + ty = Escape(&ty.print_plain(cx).to_string()), )) } else { None @@ -1379,7 +1381,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { write!(&mut out, "",); } - (format!("{:#}", ty.print(cx)), out.into_inner()) + (ty.print_plain(cx).to_string(), out.into_inner()) } pub(crate) fn notable_traits_json<'a>( @@ -1947,20 +1949,18 @@ pub(crate) fn small_url_encode(s: String) -> String { fn get_id_for_impl(for_: &clean::Type, trait_: Option<&clean::Path>, cx: &Context<'_>) -> String { match trait_ { - Some(t) => small_url_encode(format!("impl-{:#}-for-{:#}", t.print(cx), for_.print(cx))), - None => small_url_encode(format!("impl-{:#}", for_.print(cx))), + Some(t) => { + small_url_encode(format!("impl-{}-for-{}", t.print_plain(cx), for_.print_plain(cx))) + } + None => small_url_encode(format!("impl-{}", for_.print_plain(cx))), } } fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> { match *item.kind { - clean::ItemKind::ImplItem(ref i) => { - i.trait_.as_ref().map(|trait_| { - // Alternative format produces no URLs, - // so this parameter does nothing. - (format!("{:#}", i.for_.print(cx)), get_id_for_impl(&i.for_, Some(trait_), cx)) - }) - } + clean::ItemKind::ImplItem(ref i) => i.trait_.as_ref().map(|trait_| { + (i.for_.print_plain(cx).to_string(), get_id_for_impl(&i.for_, Some(trait_), cx)) + }), _ => None, } } diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 94ad4753d7cb..3237a0b6e70e 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -378,9 +378,9 @@ fn sidebar_deref_methods<'a>( Cow::Borrowed("deref-methods") }; let title = format!( - "Methods from {:#}", - impl_.inner_impl().trait_.as_ref().unwrap().print(cx), - real_target.print(cx), + "Methods from {}", + impl_.inner_impl().trait_.as_ref().unwrap().print_plain(cx), + real_target.print_plain(cx), ); // We want links' order to be reproducible so we don't use unstable sort. ret.sort(); @@ -487,7 +487,7 @@ fn sidebar_render_assoc_items( ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "", ty::ImplPolarity::Negative => "!", }; - let generated = Link::new(encoded, format!("{prefix}{:#}", trait_.print(cx))); + let generated = Link::new(encoded, format!("{prefix}{}", trait_.print_plain(cx))); if links.insert(generated.clone()) { Some(generated) } else { None } }) .collect::>>(); From 5bfad690de551fafcdd35c6a95c4e4de808bfdce Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Fri, 10 Mar 2023 23:53:01 -0800 Subject: [PATCH 3/5] rustdoc: remove f.alternate handling --- src/librustdoc/html/format.rs | 535 +++++++++++----------------------- 1 file changed, 175 insertions(+), 360 deletions(-) diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 31e52e59ed44..4174dbeed6db 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -206,36 +206,20 @@ impl clean::GenericParamDef { f.write_str(self.name.as_str())?; if !bounds.is_empty() { - if f.alternate() { - write!(f, ": {:#}", print_generic_bounds(bounds, cx))?; - } else { - write!(f, ": {}", print_generic_bounds(bounds, cx))?; - } + write!(f, ": {}", print_generic_bounds(bounds, cx))?; } if let Some(ref ty) = default { - if f.alternate() { - write!(f, " = {:#}", ty.print(cx))?; - } else { - write!(f, " = {}", ty.print(cx))?; - } + write!(f, " = {}", ty.print(cx))?; } Ok(()) } clean::GenericParamDefKind::Const { ty, default, .. } => { - if f.alternate() { - write!(f, "const {}: {:#}", self.name, ty.print(cx))?; - } else { - write!(f, "const {}: {}", self.name, ty.print(cx))?; - } + write!(f, "const {}: {}", self.name, ty.print(cx))?; if let Some(default) = default { - if f.alternate() { - write!(f, " = {:#}", default)?; - } else { - write!(f, " = {}", default)?; - } + write!(f, " = {}", default)?; } Ok(()) @@ -256,11 +240,7 @@ impl clean::Generics { return Ok(()); } - if f.alternate() { - write!(f, "<{:#}>", comma_sep(real_params.map(|g| g.print(cx)), true)) - } else { - write!(f, "<{}>", comma_sep(real_params.map(|g| g.print(cx)), true)) - } + write!(f, "<{}>", comma_sep(real_params.map(|g| g.print(cx)), true)) }) } } @@ -285,11 +265,7 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( !matches!(pred, clean::WherePredicate::BoundPredicate { bounds, .. } if bounds.is_empty()) }).map(|pred| { display_fn(move |f| { - if f.alternate() { - f.write_str(" ")?; - } else { - f.write_str("\n")?; - } + f.write_str("\n")?; match pred { clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => { @@ -297,25 +273,13 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( let generic_bounds = print_generic_bounds(bounds, cx); if bound_params.is_empty() { - if f.alternate() { - write!(f, "{ty_cx:#}: {generic_bounds:#}") - } else { - write!(f, "{ty_cx}: {generic_bounds}") - } + write!(f, "{ty_cx}: {generic_bounds}") } else { - if f.alternate() { - write!( - f, - "for<{:#}> {ty_cx:#}: {generic_bounds:#}", - comma_sep(bound_params.iter().map(|lt| lt.print()), true) - ) - } else { - write!( - f, - "for<{}> {ty_cx}: {generic_bounds}", - comma_sep(bound_params.iter().map(|lt| lt.print()), true) - ) - } + write!( + f, + "for<{}> {ty_cx}: {generic_bounds}", + comma_sep(bound_params.iter().map(|lt| lt.print()), true) + ) } } clean::WherePredicate::RegionPredicate { lifetime, bounds } => { @@ -328,11 +292,7 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( } // FIXME(fmease): Render bound params. clean::WherePredicate::EqPredicate { lhs, rhs, bound_params: _ } => { - if f.alternate() { - write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx)) - } else { - write!(f, "{} == {}", lhs.print(cx), rhs.print(cx)) - } + write!(f, "{} == {}", lhs.print(cx), rhs.print(cx)) } } }) @@ -343,42 +303,34 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( } let where_preds = comma_sep(where_predicates, false); - let clause = if f.alternate() { - if ending == Ending::Newline { - format!(" where{where_preds},") - } else { - format!(" where{where_preds}") - } - } else { - let mut br_with_padding = String::with_capacity(6 * indent + 28); - br_with_padding.push_str("\n"); + let mut br_with_padding = String::with_capacity(6 * indent + 28); + br_with_padding.push_str("\n"); - let padding_amout = - if ending == Ending::Newline { indent + 4 } else { indent + "fn where ".len() }; + let padding_amout = + if ending == Ending::Newline { indent + 4 } else { indent + "fn where ".len() }; - for _ in 0..padding_amout { - br_with_padding.push_str(" "); - } - let where_preds = where_preds.to_string().replace('\n', &br_with_padding); + for _ in 0..padding_amout { + br_with_padding.push_str(" "); + } + let where_preds = where_preds.to_string().replace('\n', &br_with_padding); - if ending == Ending::Newline { - let mut clause = " ".repeat(indent.saturating_sub(1)); - write!(clause, "where{where_preds},")?; - clause + let clause = if ending == Ending::Newline { + let mut clause = " ".repeat(indent.saturating_sub(1)); + write!(clause, "where{where_preds},")?; + clause + } else { + // insert a newline after a single space but before multiple spaces at the start + if indent == 0 { + format!("\nwhere{where_preds}") } else { - // insert a newline after a single space but before multiple spaces at the start - if indent == 0 { - format!("\nwhere{where_preds}") - } else { - // put the first one on the same line as the 'where' keyword - let where_preds = where_preds.replacen(&br_with_padding, " ", 1); + // put the first one on the same line as the 'where' keyword + let where_preds = where_preds.replacen(&br_with_padding, " ", 1); - let mut clause = br_with_padding; - clause.truncate(clause.len() - "where ".len()); + let mut clause = br_with_padding; + clause.truncate(clause.len() - "where ".len()); - write!(clause, "where{where_preds}")?; - clause - } + write!(clause, "where{where_preds}")?; + clause } }; write!(f, "{clause}") @@ -394,11 +346,7 @@ impl clean::Lifetime { impl clean::Constant { pub(crate) fn print(&self, tcx: TyCtxt<'_>) -> impl fmt::Display + '_ { let expr = self.expr(tcx); - display_fn( - move |f| { - if f.alternate() { f.write_str(&expr) } else { write!(f, "{}", Escape(&expr)) } - }, - ) + display_fn(move |f| write!(f, "{}", Escape(&expr))) } } @@ -409,25 +357,13 @@ impl clean::PolyTrait { ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { if !self.generic_params.is_empty() { - if f.alternate() { - write!( - f, - "for<{:#}> ", - comma_sep(self.generic_params.iter().map(|g| g.print(cx)), true) - )?; - } else { - write!( - f, - "for<{}> ", - comma_sep(self.generic_params.iter().map(|g| g.print(cx)), true) - )?; - } - } - if f.alternate() { - write!(f, "{:#}", self.trait_.print(cx)) - } else { - write!(f, "{}", self.trait_.print(cx)) + write!( + f, + "for<{}> ", + comma_sep(self.generic_params.iter().map(|g| g.print(cx)), true) + )?; } + write!(f, "{}", self.trait_.print(cx)) }) } } @@ -446,11 +382,7 @@ impl clean::GenericBound { // ~const is experimental; do not display those bounds in rustdoc hir::TraitBoundModifier::MaybeConst => "", }; - if f.alternate() { - write!(f, "{}{:#}", modifier_str, ty.print(cx)) - } else { - write!(f, "{}{}", modifier_str, ty.print(cx)) - } + write!(f, "{}{}", modifier_str, ty.print(cx)) } }) } @@ -465,39 +397,23 @@ impl clean::GenericArgs { match self { clean::GenericArgs::AngleBracketed { args, bindings } => { if !args.is_empty() || !bindings.is_empty() { - if f.alternate() { - f.write_str("<")?; - } else { - f.write_str("<")?; - } + f.write_str("<")?; let mut comma = false; for arg in args.iter() { if comma { f.write_str(", ")?; } comma = true; - if f.alternate() { - write!(f, "{:#}", arg.print(cx))?; - } else { - write!(f, "{}", arg.print(cx))?; - } + write!(f, "{}", arg.print(cx))?; } for binding in bindings.iter() { if comma { f.write_str(", ")?; } comma = true; - if f.alternate() { - write!(f, "{:#}", binding.print(cx))?; - } else { - write!(f, "{}", binding.print(cx))?; - } - } - if f.alternate() { - f.write_str(">")?; - } else { - f.write_str(">")?; + write!(f, "{}", binding.print(cx))?; } + f.write_str(">")?; } } clean::GenericArgs::Parenthesized { inputs, output } => { @@ -508,19 +424,11 @@ impl clean::GenericArgs { f.write_str(", ")?; } comma = true; - if f.alternate() { - write!(f, "{:#}", ty.print(cx))?; - } else { - write!(f, "{}", ty.print(cx))?; - } + write!(f, "{}", ty.print(cx))?; } f.write_str(")")?; if let Some(ref ty) = *output { - if f.alternate() { - write!(f, " -> {:#}", ty.print(cx))?; - } else { - write!(f, " -> {}", ty.print(cx))?; - } + write!(f, " -> {}", ty.print(cx))?; } } } @@ -809,24 +717,20 @@ fn resolved_path<'cx>( write!(w, "{}::", if seg.name == kw::PathRoot { "" } else { seg.name.as_str() })?; } } - 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!( - "{}::{}", - join_with_double_colon(&fqp[..fqp.len() - 1]), - anchor(did, *fqp.last().unwrap(), cx) - ) - } else { - last.name.to_string() - } + let path = if use_absolute { + if let Ok((_, _, fqp)) = href(did, cx) { + format!( + "{}::{}", + join_with_double_colon(&fqp[..fqp.len() - 1]), + anchor(did, *fqp.last().unwrap(), cx) + ) } else { - anchor(did, last.name, cx).to_string() - }; - write!(w, "{}{}", path, last.args.print(cx))?; - } + last.name.to_string() + } + } else { + anchor(did, last.name, cx).to_string() + }; + write!(w, "{}{}", path, last.args.print(cx))?; Ok(()) } @@ -848,50 +752,48 @@ fn primitive_link_fragment( ) -> fmt::Result { let m = &cx.cache(); let mut needs_termination = false; - if !f.alternate() { - match m.primitive_locations.get(&prim) { - Some(&def_id) if def_id.is_local() => { - let len = cx.current.len(); - let len = if len == 0 { 0 } else { len - 1 }; - write!( - f, - "", - "../".repeat(len), - prim.as_sym() - )?; - needs_termination = true; - } - Some(&def_id) => { - let loc = match m.extern_locations[&def_id.krate] { - ExternalLocation::Remote(ref s) => { - let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx()); - let builder: UrlPartsBuilder = - [s.as_str().trim_end_matches('/'), cname_sym.as_str()] - .into_iter() - .collect(); - Some(builder) - } - ExternalLocation::Local => { - let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx()); - Some(if cx.current.first() == Some(&cname_sym) { - iter::repeat(sym::dotdot).take(cx.current.len() - 1).collect() - } else { - iter::repeat(sym::dotdot) - .take(cx.current.len()) - .chain(iter::once(cname_sym)) - .collect() - }) - } - ExternalLocation::Unknown => None, - }; - if let Some(mut loc) = loc { - loc.push_fmt(format_args!("primitive.{}.html", prim.as_sym())); - write!(f, "", loc.finish())?; - needs_termination = true; + match m.primitive_locations.get(&prim) { + Some(&def_id) if def_id.is_local() => { + let len = cx.current.len(); + let len = if len == 0 { 0 } else { len - 1 }; + write!( + f, + "", + "../".repeat(len), + prim.as_sym() + )?; + needs_termination = true; + } + Some(&def_id) => { + let loc = match m.extern_locations[&def_id.krate] { + ExternalLocation::Remote(ref s) => { + let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx()); + let builder: UrlPartsBuilder = + [s.as_str().trim_end_matches('/'), cname_sym.as_str()] + .into_iter() + .collect(); + Some(builder) + } + ExternalLocation::Local => { + let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx()); + Some(if cx.current.first() == Some(&cname_sym) { + iter::repeat(sym::dotdot).take(cx.current.len() - 1).collect() + } else { + iter::repeat(sym::dotdot) + .take(cx.current.len()) + .chain(iter::once(cname_sym)) + .collect() + }) } + ExternalLocation::Unknown => None, + }; + if let Some(mut loc) = loc { + loc.push_fmt(format_args!("primitive.{}.html", prim.as_sym())); + write!(f, "", loc.finish())?; + needs_termination = true; } - None => {} } + None => {} } write!(f, "{}", name)?; if needs_termination { @@ -971,96 +873,69 @@ fn fmt_type<'cx>( } clean::Primitive(prim) => primitive_link(f, prim, prim.as_sym().as_str(), cx), clean::BareFunction(ref decl) => { - if f.alternate() { - write!( - f, - "{:#}{}{:#}fn{:#}", - decl.print_hrtb_with_space(cx), - decl.unsafety.print_with_space(), - print_abi_with_space(decl.abi), - decl.decl.print(cx), - ) - } else { - write!( - f, - "{}{}{}", - decl.print_hrtb_with_space(cx), - decl.unsafety.print_with_space(), - print_abi_with_space(decl.abi) - )?; - primitive_link(f, PrimitiveType::Fn, "fn", cx)?; - write!(f, "{}", decl.decl.print(cx)) - } + write!( + f, + "{}{}{}", + decl.print_hrtb_with_space(cx), + decl.unsafety.print_with_space(), + print_abi_with_space(decl.abi) + )?; + primitive_link(f, PrimitiveType::Fn, "fn", cx)?; + write!(f, "{}", decl.decl.print(cx)) } - clean::Tuple(ref typs) => { - match &typs[..] { - &[] => primitive_link(f, PrimitiveType::Unit, "()", cx), - [one] => { - if let clean::Generic(name) = one { - primitive_link(f, PrimitiveType::Tuple, &format!("({name},)"), cx) - } else { - write!(f, "(")?; - // Carry `f.alternate()` into this display w/o branching manually. - fmt::Display::fmt(&one.print(cx), f)?; - write!(f, ",)") - } + clean::Tuple(ref typs) => match &typs[..] { + &[] => primitive_link(f, PrimitiveType::Unit, "()", cx), + [one] => { + if let clean::Generic(name) = one { + primitive_link(f, PrimitiveType::Tuple, &format!("({name},)"), cx) + } else { + write!(f, "({},)", &one.print(cx)) } - many => { - let generic_names: Vec = many - .iter() - .filter_map(|t| match t { - clean::Generic(name) => Some(*name), - _ => None, - }) - .collect(); - let is_generic = generic_names.len() == many.len(); - if is_generic { - primitive_link( - f, - PrimitiveType::Tuple, - &format!("({})", generic_names.iter().map(|s| s.as_str()).join(", ")), - cx, - ) - } else { - write!(f, "(")?; - for (i, item) in many.iter().enumerate() { - if i != 0 { - write!(f, ", ")?; - } - // Carry `f.alternate()` into this display w/o branching manually. - fmt::Display::fmt(&item.print(cx), f)?; + } + many => { + let generic_names: Vec = many + .iter() + .filter_map(|t| match t { + clean::Generic(name) => Some(*name), + _ => None, + }) + .collect(); + let is_generic = generic_names.len() == many.len(); + if is_generic { + primitive_link( + f, + PrimitiveType::Tuple, + &format!("({})", generic_names.iter().map(|s| s.as_str()).join(", ")), + cx, + ) + } else { + write!(f, "(")?; + for (i, item) in many.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; } - write!(f, ")") + write!(f, "{}", &item.print(cx))?; } + write!(f, ")") } } - } + }, clean::Slice(ref t) => match **t { clean::Generic(name) => { primitive_link(f, PrimitiveType::Slice, &format!("[{name}]"), cx) } - _ => { - write!(f, "[")?; - fmt::Display::fmt(&t.print(cx), f)?; - write!(f, "]") - } + _ => write!(f, "[{}]", &t.print(cx)), }, clean::Array(ref t, ref n) => match **t { - clean::Generic(name) if !f.alternate() => primitive_link( + clean::Generic(name) => primitive_link( f, PrimitiveType::Array, &format!("[{name}; {n}]", n = Escape(n)), cx, ), _ => { - write!(f, "[")?; - fmt::Display::fmt(&t.print(cx), f)?; - if f.alternate() { - write!(f, "; {n}")?; - } else { - write!(f, "; ")?; - primitive_link(f, PrimitiveType::Array, &format!("{n}", n = Escape(n)), cx)?; - } + write!(f, "[{}; ", &t.print(cx))?; + primitive_link(f, PrimitiveType::Array, &format!("{n}", n = Escape(n)), cx)?; write!(f, "]") } }, @@ -1071,11 +946,7 @@ fn fmt_type<'cx>( }; if matches!(**t, clean::Generic(_)) || t.is_assoc_ty() { - let text = if f.alternate() { - format!("*{} {:#}", m, t.print(cx)) - } else { - format!("*{} {}", m, t.print(cx)) - }; + let text = format!("*{} {}", m, t.print(cx)); primitive_link(f, clean::PrimitiveType::RawPointer, &text, cx) } else { primitive_link(f, clean::PrimitiveType::RawPointer, &format!("*{} ", m), cx)?; @@ -1088,7 +959,7 @@ fn fmt_type<'cx>( _ => String::new(), }; let m = mutability.print_with_space(); - let amp = if f.alternate() { "&" } else { "&" }; + let amp = "&"; match **ty { clean::DynTrait(ref bounds, ref trait_lt) if bounds.len() > 1 || trait_lt.is_some() => @@ -1107,11 +978,7 @@ fn fmt_type<'cx>( } } clean::ImplTrait(ref bounds) => { - if f.alternate() { - write!(f, "impl {:#}", print_generic_bounds(bounds, cx)) - } else { - write!(f, "impl {}", print_generic_bounds(bounds, cx)) - } + write!(f, "impl {}", print_generic_bounds(bounds, cx)) } clean::QPath(box clean::QPathData { ref assoc, @@ -1119,19 +986,11 @@ fn fmt_type<'cx>( ref trait_, should_show_cast, }) => { - if f.alternate() { - if should_show_cast { - write!(f, "<{:#} as {:#}>::", self_type.print(cx), trait_.print(cx))? - } else { - write!(f, "{:#}::", self_type.print(cx))? - } + if should_show_cast { + write!(f, "<{} as {}>::", self_type.print(cx), trait_.print(cx))? } else { - if should_show_cast { - write!(f, "<{} as {}>::", self_type.print(cx), trait_.print(cx))? - } else { - write!(f, "{}::", self_type.print(cx))? - } - }; + write!(f, "{}::", self_type.print(cx))? + } // It's pretty unsightly to look at `::C` in output, and // we've got hyperlinking on our side, so try to avoid longer // notation as much as possible by making `C` a hyperlink to trait @@ -1143,7 +1002,7 @@ fn fmt_type<'cx>( // everything comes in as a fully resolved QPath (hard to // look at). match href(trait_.def_id(), cx) { - Ok((ref url, _, ref path)) if !f.alternate() => { + Ok((ref url, _, ref path)) => { write!( f, ", ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { - if f.alternate() { - write!(f, "impl{:#} ", self.generics.print(cx))?; - } else { - write!(f, "impl{} ", self.generics.print(cx))?; - } + write!(f, "impl{} ", self.generics.print(cx))?; if let Some(ref ty) = self.trait_ { match self.polarity { @@ -1235,17 +1090,10 @@ impl clean::Impl { let hrtb = bare_fn.print_hrtb_with_space(cx); let unsafety = bare_fn.unsafety.print_with_space(); let abi = print_abi_with_space(bare_fn.abi); - if f.alternate() { - write!( - f, - "{hrtb:#}{unsafety}{abi:#}", - )?; - } else { - write!( - f, - "{hrtb}{unsafety}{abi}", - )?; - } + write!( + f, + "{hrtb}{unsafety}{abi}", + )?; let ellipsis = if bare_fn.decl.c_variadic { ", ..." } else { @@ -1276,13 +1124,7 @@ impl clean::Arguments { ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { for (i, input) in self.values.iter().enumerate() { - write!(f, "{}: ", input.name)?; - - if f.alternate() { - write!(f, "{:#}", input.type_.print(cx))?; - } else { - write!(f, "{}", input.type_.print(cx))?; - } + write!(f, "{}: {}", input.name, input.type_.print(cx))?; if i + 1 < self.values.len() { write!(f, ", ")?; } @@ -1299,9 +1141,6 @@ impl clean::FnRetTy { ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| match self { clean::Return(clean::Tuple(tys)) if tys.is_empty() => Ok(()), - clean::Return(ty) if f.alternate() => { - write!(f, " -> {:#}", ty.print(cx)) - } clean::Return(ty) => write!(f, " -> {}", ty.print(cx)), clean::DefaultReturn => Ok(()), }) @@ -1448,23 +1287,13 @@ impl clean::FnDecl { ) -> impl fmt::Display + 'b + Captures<'tcx> { display_fn(move |f| { let ellipsis = if self.c_variadic { ", ..." } else { "" }; - if f.alternate() { - write!( - f, - "({args:#}{ellipsis}){arrow:#}", - args = self.inputs.print(cx), - ellipsis = ellipsis, - arrow = self.output.print(cx) - ) - } else { - write!( - f, - "({args}{ellipsis}){arrow}", - args = self.inputs.print(cx), - ellipsis = ellipsis, - arrow = self.output.print(cx) - ) - } + write!( + f, + "({args}{ellipsis}){arrow}", + args = self.inputs.print(cx), + ellipsis = ellipsis, + arrow = self.output.print(cx) + ) }) } @@ -1502,7 +1331,7 @@ impl clean::FnDecl { f: &mut fmt::Formatter<'_>, cx: &Context<'_>, ) -> fmt::Result { - let amp = if f.alternate() { "&" } else { "&" }; + let amp = "&"; write!(f, "(")?; if let Some(n) = line_wrapping_indent { @@ -1527,16 +1356,14 @@ impl clean::FnDecl { write!(f, "{}{}self", amp, mtbl.print_with_space())?; } clean::SelfExplicit(ref typ) => { - write!(f, "self: ")?; - fmt::Display::fmt(&typ.print(cx), f)?; + write!(f, "self: {}", typ.print(cx))?; } } } else { if input.is_const { write!(f, "const ")?; } - write!(f, "{}: ", input.name)?; - fmt::Display::fmt(&input.type_.print(cx), f)?; + write!(f, "{}: {}", input.name, input.type_.print(cx))?; } } @@ -1552,7 +1379,7 @@ impl clean::FnDecl { Some(n) => write!(f, "\n{})", Indent(n))?, }; - fmt::Display::fmt(&self.output.print(cx), f)?; + write!(f, "{}", self.output.print(cx))?; Ok(()) } } @@ -1738,26 +1565,14 @@ impl clean::TypeBinding { ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { f.write_str(self.assoc.name.as_str())?; - if f.alternate() { - write!(f, "{:#}", self.assoc.args.print(cx))?; - } else { - write!(f, "{}", self.assoc.args.print(cx))?; - } + write!(f, "{}", self.assoc.args.print(cx))?; match self.kind { clean::TypeBindingKind::Equality { ref term } => { - if f.alternate() { - write!(f, " = {:#}", term.print(cx))?; - } else { - write!(f, " = {}", term.print(cx))?; - } + write!(f, " = {}", term.print(cx))?; } clean::TypeBindingKind::Constraint { ref bounds } => { if !bounds.is_empty() { - if f.alternate() { - write!(f, ": {:#}", print_generic_bounds(bounds, cx))?; - } else { - write!(f, ": {}", print_generic_bounds(bounds, cx))?; - } + write!(f, ": {}", print_generic_bounds(bounds, cx))?; } } } @@ -1768,7 +1583,7 @@ impl clean::TypeBinding { pub(crate) fn print_abi_with_space(abi: Abi) -> impl fmt::Display { display_fn(move |f| { - let quot = if f.alternate() { "\"" } else { """ }; + let quot = """; match abi { Abi::Rust => Ok(()), abi => write!(f, "extern {0}{1}{0} ", quot, abi.name()), From 3819cf7f9b6cf95d64f14312f84b54736b862bbb Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Sun, 19 Mar 2023 11:52:48 -0700 Subject: [PATCH 4/5] rustdoc: templatize assoc_method and item_function --- src/librustdoc/html/format.rs | 33 +++------ src/librustdoc/html/render/mod.rs | 73 ++++++++++--------- src/librustdoc/html/render/print_item.rs | 53 ++++++-------- .../html/templates/function_header.html | 11 +++ .../rustdoc/array-links.link_box_generic.html | 2 +- tests/rustdoc/array-links.link_box_u32.html | 2 +- .../array-links.link_slice_generic.html | 2 +- tests/rustdoc/array-links.link_slice_u32.html | 2 +- tests/rustdoc/nested-modules.rs | 4 +- tests/rustdoc/reexports-priv.rs | 2 +- .../rustdoc/slice-links.link_box_generic.html | 2 +- tests/rustdoc/slice-links.link_box_u32.html | 2 +- .../slice-links.link_slice_generic.html | 2 +- tests/rustdoc/slice-links.link_slice_u32.html | 2 +- tests/rustdoc/tuples.link1_i32.html | 2 +- tests/rustdoc/tuples.link1_t.html | 2 +- tests/rustdoc/tuples.link2_i32.html | 2 +- tests/rustdoc/tuples.link2_t.html | 2 +- tests/rustdoc/tuples.link2_tu.html | 2 +- tests/rustdoc/tuples.link_unit.html | 2 +- 20 files changed, 102 insertions(+), 102 deletions(-) create mode 100644 src/librustdoc/html/templates/function_header.html diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 4174dbeed6db..4284f867a867 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -140,10 +140,6 @@ impl Buffer { self.for_html } - pub(crate) fn reserve(&mut self, additional: usize) { - self.buffer.reserve(additional) - } - pub(crate) fn len(&self) -> usize { self.buffer.len() } @@ -1268,7 +1264,7 @@ impl std::fmt::Write for WriteCounter { } } -// Implements Display by emitting the given number of spaces. +/// Implements Display by emitting the given number of spaces. struct Indent(usize); impl fmt::Display for Indent { @@ -1297,28 +1293,24 @@ impl clean::FnDecl { }) } - /// * `header_len`: The length of the function header and name. In other words, the number of - /// characters in the function declaration up to but not including the parentheses. - /// This is expected to go into a `
`/`code-header` block, so indentation and newlines
-    ///   are preserved.
+    /// * `header`: Prints the function header and name (everything before the params).
     /// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is
     ///   necessary.
     pub(crate) fn full_print<'a, 'tcx: 'a>(
         &'a self,
-        header_len: usize,
+        header: impl fmt::Display + 'a,
         indent: usize,
         cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| {
             // First, generate the text form of the declaration, with no line wrapping, and count the bytes.
+            let header = Plain(header);
+            let decl = Plain(display_fn(|f| self.inner_full_print(None, f, cx)));
             let mut counter = WriteCounter(0);
-            write!(&mut counter, "{:#}", display_fn(|f| { self.inner_full_print(None, f, cx) }))
-                .unwrap();
+            write!(&mut counter, "{header}{decl}")?;
             // 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.
+            let line_wrapping_indent = (counter.0 > 80).then_some(indent);
+            // Generate the final output.
             self.inner_full_print(line_wrapping_indent, f, cx)
         })
     }
@@ -1582,12 +1574,9 @@ impl clean::TypeBinding {
 }
 
 pub(crate) fn print_abi_with_space(abi: Abi) -> impl fmt::Display {
-    display_fn(move |f| {
-        let quot = """;
-        match abi {
-            Abi::Rust => Ok(()),
-            abi => write!(f, "extern {0}{1}{0} ", quot, abi.name()),
-        }
+    display_fn(move |f| match abi {
+        Abi::Rust => Ok(()),
+        abi => write!(f, "extern \"{}\" ", abi.name()),
     })
 }
 
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 8052eb06906c..08d23b953141 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -809,7 +809,7 @@ fn assoc_method(
 ) {
     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();
+    let name = meth.name.as_ref().unwrap().as_str();
     let vis = visibility_print_with_space(meth.visibility(tcx), meth.item_id, cx).to_string();
     // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
     // this condition.
@@ -825,22 +825,13 @@ fn assoc_method(
     let abi = print_abi_with_space(header.abi).to_string();
     let href = assoc_href_attr(meth, link, cx);
 
-    // 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()
-        + constness.len()
-        + asyncness.len()
-        + unsafety.len()
-        + defaultness.len()
-        + abi.len()
-        + name.as_str().len()
-        + generics_len;
-
-    let notable_traits = d.output.as_return().and_then(|output| notable_traits_button(output, cx));
+    let notable_traits = d
+        .output
+        .as_return()
+        .and_then(|output| notable_traits_button(output, cx))
+        .unwrap_or_default();
 
     let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
-        header_len += 4;
         let indent_str = "    ";
         render_attributes_in_pre(w, meth, indent_str);
         (4, indent_str, Ending::NoNewline)
@@ -848,25 +839,39 @@ fn assoc_method(
         render_attributes_in_code(w, meth);
         (0, "", Ending::Newline)
     };
-    w.reserve(header_len + "{".len() + "".len());
-    write!(
-        w,
-        "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn {name}\
-         {generics}{decl}{notable_traits}{where_clause}",
-        indent = indent_str,
-        vis = vis,
-        constness = constness,
-        asyncness = asyncness,
-        unsafety = unsafety,
-        defaultness = defaultness,
-        abi = abi,
-        href = href,
-        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),
-    );
+
+    let fn_header = FunctionHeader {
+        indent_str,
+        vis,
+        constness,
+        asyncness,
+        unsafety,
+        defaultness,
+        abi,
+        href,
+        name,
+        generics: g.print(cx).to_string(),
+    };
+
+    let decl = d.full_print(&fn_header, indent, cx);
+    let where_clause = print_where_clause(g, cx, indent, end_newline);
+
+    write!(w, "{fn_header}{decl}{notable_traits}{where_clause}");
+}
+
+#[derive(Template)]
+#[template(path = "function_header.html")]
+struct FunctionHeader<'a> {
+    indent_str: &'static str,
+    vis: String,
+    constness: &'a str,
+    asyncness: &'a str,
+    unsafety: &'a str,
+    defaultness: &'a str,
+    abi: String,
+    href: String,
+    name: &'a str,
+    generics: String,
 }
 
 /// Writes a span containing the versions at which an item became stable and/or const-stable. For
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 7eb9c0b7cf52..9f7b92201c9c 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -528,39 +528,34 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
     let abi = print_abi_with_space(header.abi).to_string();
     let asyncness = header.asyncness.print_with_space();
     let visibility = visibility_print_with_space(it.visibility(tcx), it.item_id, 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()
-        + unsafety.len()
-        + abi.len()
-        + name.as_str().len()
-        + generics_len;
+    let name = it.name.as_ref().unwrap().as_str();
+
+    let fn_header = super::FunctionHeader {
+        indent_str: "",
+        vis: visibility,
+        constness,
+        asyncness,
+        unsafety,
+        // standalone functions are never default, only associated functions.
+        defaultness: "",
+        abi,
+        href: "".to_string(),
+        name,
+        generics: f.generics.print(cx).to_string(),
+    };
 
-    let notable_traits =
-        f.decl.output.as_return().and_then(|output| notable_traits_button(output, cx));
+    let notable_traits = f
+        .decl
+        .output
+        .as_return()
+        .and_then(|output| notable_traits_button(output, cx))
+        .unwrap_or_default();
 
     wrap_item(w, |w| {
         render_attributes_in_pre(w, it, "");
-        w.reserve(header_len);
-        write!(
-            w,
-            "{vis}{constness}{asyncness}{unsafety}{abi}fn \
-                {name}{generics}{decl}{notable_traits}{where_clause}",
-            vis = visibility,
-            constness = constness,
-            asyncness = asyncness,
-            unsafety = unsafety,
-            abi = abi,
-            name = name,
-            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(),
-        );
+        let decl = f.decl.full_print(&fn_header, 0, cx);
+        let where_clause = print_where_clause(&f.generics, cx, 0, Ending::Newline);
+        write!(w, "{fn_header}{decl}{notable_traits}{where_clause}");
     });
     document(w, cx, it, None, HeadingOffset::H2);
 }
diff --git a/src/librustdoc/html/templates/function_header.html b/src/librustdoc/html/templates/function_header.html
new file mode 100644
index 000000000000..6d17dfa1efdf
--- /dev/null
+++ b/src/librustdoc/html/templates/function_header.html
@@ -0,0 +1,11 @@
+{# All of the parts of a function or method declaration that
+   come before the parameters and return value #}
+{{indent_str}}
+{{vis|safe}}
+{{constness}}
+{{asyncness}}
+{{unsafety}}
+{{defaultness}}
+{{abi|safe}}
+fn {{name}}
+{{generics|safe}}
diff --git a/tests/rustdoc/array-links.link_box_generic.html b/tests/rustdoc/array-links.link_box_generic.html
index 3481bb6a0254..1631f9988f7b 100644
--- a/tests/rustdoc/array-links.link_box_generic.html
+++ b/tests/rustdoc/array-links.link_box_generic.html
@@ -1 +1 @@
-pub fn delta<T>() -> MyBox<[T; 1]>
\ No newline at end of file
+pub fn delta<T>() -> MyBox<[T; 1]>
\ No newline at end of file
diff --git a/tests/rustdoc/array-links.link_box_u32.html b/tests/rustdoc/array-links.link_box_u32.html
index e864ae55c9f4..07715fe7fe15 100644
--- a/tests/rustdoc/array-links.link_box_u32.html
+++ b/tests/rustdoc/array-links.link_box_u32.html
@@ -1 +1 @@
-pub fn gamma() -> MyBox<[u32; 1]>
\ No newline at end of file
+pub fn gamma() -> MyBox<[u32; 1]>
\ No newline at end of file
diff --git a/tests/rustdoc/array-links.link_slice_generic.html b/tests/rustdoc/array-links.link_slice_generic.html
index f1ca2f59bd7c..6db4cf262d17 100644
--- a/tests/rustdoc/array-links.link_slice_generic.html
+++ b/tests/rustdoc/array-links.link_slice_generic.html
@@ -1 +1 @@
-pub fn beta<T>() -> &'static [T; 1]
\ No newline at end of file
+pub fn beta<T>() -> &'static [T; 1]
\ No newline at end of file
diff --git a/tests/rustdoc/array-links.link_slice_u32.html b/tests/rustdoc/array-links.link_slice_u32.html
index c3943e8d3212..c5b624ae3698 100644
--- a/tests/rustdoc/array-links.link_slice_u32.html
+++ b/tests/rustdoc/array-links.link_slice_u32.html
@@ -1 +1 @@
-pub fn alpha() -> &'static [u32; 1]
\ No newline at end of file
+pub fn alpha() -> &'static [u32; 1]
\ No newline at end of file
diff --git a/tests/rustdoc/nested-modules.rs b/tests/rustdoc/nested-modules.rs
index 12234d2cf7ef..4ed95cb04a8a 100644
--- a/tests/rustdoc/nested-modules.rs
+++ b/tests/rustdoc/nested-modules.rs
@@ -7,11 +7,11 @@ mod a_module {
 
     pub mod a_nested_module {
         // @has aCrate/a_nested_module/index.html '//a[@href="fn.a_nested_public_function.html"]' 'a_nested_public_function'
-        // @hasraw aCrate/a_nested_module/fn.a_nested_public_function.html 'pub fn a_nested_public_function()'
+        // @has aCrate/a_nested_module/fn.a_nested_public_function.html '//' 'pub fn a_nested_public_function()'
         pub fn a_nested_public_function() {}
 
         // @has aCrate/a_nested_module/index.html '//a[@href="fn.another_nested_public_function.html"]' 'another_nested_public_function'
-        // @hasraw aCrate/a_nested_module/fn.another_nested_public_function.html 'pub fn another_nested_public_function()'
+        // @has aCrate/a_nested_module/fn.another_nested_public_function.html '//' 'pub fn another_nested_public_function()'
         pub use a_nested_module::a_nested_public_function as another_nested_public_function;
     }
 
diff --git a/tests/rustdoc/reexports-priv.rs b/tests/rustdoc/reexports-priv.rs
index 571d7f06fdc6..84ea4ad2c9ef 100644
--- a/tests/rustdoc/reexports-priv.rs
+++ b/tests/rustdoc/reexports-priv.rs
@@ -98,7 +98,7 @@ pub mod outer {
         pub use reexports::foo;
         // @has 'foo/outer/inner/fn.foo_crate.html' '//pre[@class="rust item-decl"]' 'pub(crate) fn foo_crate()'
         pub(crate) use reexports::foo_crate;
-        // @has 'foo/outer/inner/fn.foo_super.html' '//pre[@class="rust item-decl"]' 'pub(in outer) fn foo_super( )'
+        // @has 'foo/outer/inner/fn.foo_super.html' '//pre[@class="rust item-decl"]' 'pub(in outer) fn foo_super()'
         pub(super) use::reexports::foo_super;
         // @!has 'foo/outer/inner/fn.foo_self.html'
         pub(self) use reexports::foo_self;
diff --git a/tests/rustdoc/slice-links.link_box_generic.html b/tests/rustdoc/slice-links.link_box_generic.html
index 38aaf20808cf..0ffbdadd4c62 100644
--- a/tests/rustdoc/slice-links.link_box_generic.html
+++ b/tests/rustdoc/slice-links.link_box_generic.html
@@ -1 +1 @@
-pub fn delta<T>() -> MyBox<[T]>
\ No newline at end of file
+pub fn delta<T>() -> MyBox<[T]>
\ No newline at end of file
diff --git a/tests/rustdoc/slice-links.link_box_u32.html b/tests/rustdoc/slice-links.link_box_u32.html
index 7bec7582df7c..5c0f2d86f16e 100644
--- a/tests/rustdoc/slice-links.link_box_u32.html
+++ b/tests/rustdoc/slice-links.link_box_u32.html
@@ -1 +1 @@
-pub fn gamma() -> MyBox<[u32]>
\ No newline at end of file
+pub fn gamma() -> MyBox<[u32]>
\ No newline at end of file
diff --git a/tests/rustdoc/slice-links.link_slice_generic.html b/tests/rustdoc/slice-links.link_slice_generic.html
index 1d0f2bf75a23..1c6e1fee2491 100644
--- a/tests/rustdoc/slice-links.link_slice_generic.html
+++ b/tests/rustdoc/slice-links.link_slice_generic.html
@@ -1 +1 @@
-pub fn beta<T>() -> &'static [T]
\ No newline at end of file
+pub fn beta<T>() -> &'static [T]
\ No newline at end of file
diff --git a/tests/rustdoc/slice-links.link_slice_u32.html b/tests/rustdoc/slice-links.link_slice_u32.html
index c86d38304261..461277da6520 100644
--- a/tests/rustdoc/slice-links.link_slice_u32.html
+++ b/tests/rustdoc/slice-links.link_slice_u32.html
@@ -1 +1 @@
-pub fn alpha() -> &'static [u32]
\ No newline at end of file
+pub fn alpha() -> &'static [u32]
\ No newline at end of file
diff --git a/tests/rustdoc/tuples.link1_i32.html b/tests/rustdoc/tuples.link1_i32.html
index 4efde28ed52e..6a7ae03d2df3 100644
--- a/tests/rustdoc/tuples.link1_i32.html
+++ b/tests/rustdoc/tuples.link1_i32.html
@@ -1 +1 @@
-pub fn tuple1(x: (i32,)) -> (i32,)
\ No newline at end of file
+pub fn tuple1(x: (i32,)) -> (i32,)
\ No newline at end of file
diff --git a/tests/rustdoc/tuples.link1_t.html b/tests/rustdoc/tuples.link1_t.html
index 1cbaec05733b..0a2b164c01ac 100644
--- a/tests/rustdoc/tuples.link1_t.html
+++ b/tests/rustdoc/tuples.link1_t.html
@@ -1 +1 @@
-pub fn tuple1_t<T>(x: (T,)) -> (T,)
\ No newline at end of file
+pub fn tuple1_t<T>(x: (T,)) -> (T,)
\ No newline at end of file
diff --git a/tests/rustdoc/tuples.link2_i32.html b/tests/rustdoc/tuples.link2_i32.html
index 77c8d81b842d..78bef8d617a1 100644
--- a/tests/rustdoc/tuples.link2_i32.html
+++ b/tests/rustdoc/tuples.link2_i32.html
@@ -1 +1 @@
-pub fn tuple2(x: (i32, i32)) -> (i32, i32)
\ No newline at end of file
+pub fn tuple2(x: (i32, i32)) -> (i32, i32)
\ No newline at end of file
diff --git a/tests/rustdoc/tuples.link2_t.html b/tests/rustdoc/tuples.link2_t.html
index 2477aa6be9d3..5779a24b1abc 100644
--- a/tests/rustdoc/tuples.link2_t.html
+++ b/tests/rustdoc/tuples.link2_t.html
@@ -1 +1 @@
-pub fn tuple2_t<T>(x: (T, T)) -> (T, T)
\ No newline at end of file
+pub fn tuple2_t<T>(x: (T, T)) -> (T, T)
\ No newline at end of file
diff --git a/tests/rustdoc/tuples.link2_tu.html b/tests/rustdoc/tuples.link2_tu.html
index b02f8dd8d653..68d586b568b4 100644
--- a/tests/rustdoc/tuples.link2_tu.html
+++ b/tests/rustdoc/tuples.link2_tu.html
@@ -1 +1 @@
-pub fn tuple2_tu<T, U>(x: (T, U)) -> (T, U)
\ No newline at end of file
+pub fn tuple2_tu<T, U>(x: (T, U)) -> (T, U)
\ No newline at end of file
diff --git a/tests/rustdoc/tuples.link_unit.html b/tests/rustdoc/tuples.link_unit.html
index 839990e1587c..37f19030aa1f 100644
--- a/tests/rustdoc/tuples.link_unit.html
+++ b/tests/rustdoc/tuples.link_unit.html
@@ -1 +1 @@
-pub fn tuple0(x: ())
\ No newline at end of file
+pub fn tuple0(x: ())
\ No newline at end of file

From 236c18ff0c2f19ba9c74ebdd8a60aa005e2ff754 Mon Sep 17 00:00:00 2001
From: Jacob Hoffman-Andrews 
Date: Fri, 24 Mar 2023 01:00:02 -0700
Subject: [PATCH 5/5] rustdoc: escape generics before writing

---
 src/librustdoc/html/format.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 4284f867a867..b6485c61bd2a 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -853,7 +853,7 @@ fn fmt_type<'cx>(
     trace!("fmt_type(t = {:?})", t);
 
     match *t {
-        clean::Generic(name) => write!(f, "{}", name),
+        clean::Generic(name) => write!(f, "{}", Escape(name.as_str())),
         clean::Type::Path { ref path } => {
             // Paths like `T::Output` and `Self::Output` should be rendered with all segments.
             let did = path.def_id();