From 57c373f179d7261bc28e4876d75be0c9e634fd85 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Thu, 6 Nov 2025 21:45:21 +0200 Subject: [PATCH] `DisplayFn` trait for print fns on `clean` rustdoc items --- src/librustdoc/display.rs | 15 + src/librustdoc/html/format.rs | 573 +++++++++++---------- src/librustdoc/html/render/context.rs | 3 +- src/librustdoc/html/render/mod.rs | 30 +- src/librustdoc/html/render/print_item.rs | 346 +++++++------ src/librustdoc/html/render/sidebar.rs | 8 +- src/librustdoc/html/render/write_shared.rs | 10 +- 7 files changed, 531 insertions(+), 454 deletions(-) diff --git a/src/librustdoc/display.rs b/src/librustdoc/display.rs index d62ea4c368804..4957c9659a23c 100644 --- a/src/librustdoc/display.rs +++ b/src/librustdoc/display.rs @@ -2,6 +2,8 @@ use std::fmt::{self, Display, Formatter, FormattingOptions}; +use crate::html::render::Context; + pub(crate) trait Joined: IntoIterator { /// Takes an iterator over elements that implement [`Display`], and format them into `f`, separated by `sep`. /// @@ -129,3 +131,16 @@ impl WithOpts { }) } } + +pub(crate) trait DisplayFn { + fn display_fn(&self, clean_item: &C, cx: &Context<'_>) -> impl Display; +} + +impl DisplayFn for F +where + F: Fn(&mut Formatter<'_>, &C, &Context<'_>) -> fmt::Result, +{ + fn display_fn(&self, clean_item: &C, cx: &Context<'_>) -> impl Display { + fmt::from_fn(move |f| self(f, clean_item, cx)) + } +} diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 337429a6248d9..fe4c9577ad365 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -8,7 +8,7 @@ //! not be used external to this module. use std::cmp::Ordering; -use std::fmt::{self, Display, Write}; +use std::fmt::{self, Display, Formatter, Write}; use std::iter::{self, once}; use std::slice; @@ -30,7 +30,7 @@ use super::url_parts_builder::UrlPartsBuilder; use crate::clean::types::ExternalLocation; use crate::clean::utils::find_nearest_parent_module; use crate::clean::{self, ExternalCrate, PrimitiveType}; -use crate::display::{Joined as _, MaybeDisplay as _, WithOpts, Wrapped}; +use crate::display::{DisplayFn as _, Joined as _, MaybeDisplay as _, WithOpts, Wrapped}; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::escape::{Escape, EscapeBodyText}; @@ -38,25 +38,25 @@ use crate::html::render::Context; use crate::passes::collect_intra_doc_links::UrlFragment; pub(crate) fn print_generic_bounds( + f: &mut Formatter<'_>, bounds: &[clean::GenericBound], cx: &Context<'_>, -) -> impl Display { - fmt::from_fn(move |f| { - let mut bounds_dup = FxHashSet::default(); +) -> fmt::Result { + let mut bounds_dup = FxHashSet::default(); - bounds - .iter() - .filter(move |b| bounds_dup.insert(*b)) - .map(|bound| print_generic_bound(bound, cx)) - .joined(" + ", f) - }) + bounds + .iter() + .filter(move |b| bounds_dup.insert(*b)) + .map(|bound| print_generic_bound.display_fn(bound, cx)) + .joined(" + ", f) } pub(crate) fn print_generic_param_def( + f: &mut Formatter<'_>, generic_param: &clean::GenericParamDef, cx: &Context<'_>, -) -> impl Display { - fmt::from_fn(move |f| match &generic_param.kind { +) -> fmt::Result { + match &generic_param.kind { clean::GenericParamDefKind::Lifetime { outlives } => { write!(f, "{}", generic_param.name)?; @@ -72,19 +72,19 @@ pub(crate) fn print_generic_param_def( if !bounds.is_empty() { f.write_str(": ")?; - print_generic_bounds(bounds, cx).fmt(f)?; + print_generic_bounds(f, bounds, cx)?; } if let Some(ty) = default { f.write_str(" = ")?; - print_type(ty, cx).fmt(f)?; + print_type(f, ty, cx)?; } Ok(()) } clean::GenericParamDefKind::Const { ty, default, .. } => { write!(f, "const {}: ", generic_param.name)?; - print_type(ty, cx).fmt(f)?; + print_type(f, ty, cx)?; if let Some(default) = default { f.write_str(" = ")?; @@ -97,19 +97,24 @@ pub(crate) fn print_generic_param_def( Ok(()) } - }) + } } -pub(crate) fn print_generics(generics: &clean::Generics, cx: &Context<'_>) -> impl Display { +pub(crate) fn print_generics( + f: &mut Formatter<'_>, + generics: &clean::Generics, + cx: &Context<'_>, +) -> fmt::Result { let mut real_params = generics.params.iter().filter(|p| !p.is_synthetic_param()).peekable(); if real_params.peek().is_none() { None } else { Some(Wrapped::with_angle_brackets().wrap_fn(move |f| { - real_params.clone().map(|g| print_generic_param_def(g, cx)).joined(", ", f) + real_params.clone().map(|g| print_generic_param_def.display_fn(g, cx)).joined(", ", f) })) } .maybe_display() + .fmt(f) } #[derive(Clone, Copy, PartialEq, Eq)] @@ -118,39 +123,41 @@ pub(crate) enum Ending { NoNewline, } -fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| { - match predicate { - clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => { - print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?; - print_type(ty, cx).fmt(f)?; - f.write_str(":")?; - if !bounds.is_empty() { - f.write_str(" ")?; - print_generic_bounds(bounds, cx).fmt(f)?; - } - Ok(()) - } - clean::WherePredicate::RegionPredicate { lifetime, bounds } => { - // We don't need to check `alternate` since we can be certain that neither - // the lifetime nor the bounds contain any characters which need escaping. - write!(f, "{}:", print_lifetime(lifetime))?; - if !bounds.is_empty() { - write!(f, " {}", print_generic_bounds(bounds, cx))?; - } - Ok(()) +fn print_where_predicate( + f: &mut Formatter<'_>, + predicate: &clean::WherePredicate, + cx: &Context<'_>, +) -> fmt::Result { + match predicate { + clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => { + print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?; + print_type(f, ty, cx)?; + f.write_str(":")?; + if !bounds.is_empty() { + f.write_str(" ")?; + print_generic_bounds(f, bounds, cx)?; } - clean::WherePredicate::EqPredicate { lhs, rhs } => { - let opts = WithOpts::from(f); - write!( - f, - "{} == {}", - opts.display(print_qpath_data(lhs, cx)), - opts.display(print_term(rhs, cx)), - ) + Ok(()) + } + clean::WherePredicate::RegionPredicate { lifetime, bounds } => { + // We don't need to check `alternate` since we can be certain that neither + // the lifetime nor the bounds contain any characters which need escaping. + write!(f, "{}:", print_lifetime(lifetime))?; + if !bounds.is_empty() { + write!(f, " {}", print_generic_bounds.display_fn(bounds, cx))?; } + Ok(()) } - }) + clean::WherePredicate::EqPredicate { lhs, rhs } => { + let opts = WithOpts::from(f); + write!( + f, + "{} == {}", + opts.display(print_qpath_data.display_fn(lhs, cx)), + opts.display(print_term.display_fn(rhs, cx)), + ) + } + } } /// * The Generics from which to emit a where-clause. @@ -177,7 +184,7 @@ pub(crate) fn print_where_clause( } else { f.write_str("\n")?; } - print_where_predicate(predicate, cx).fmt(f) + print_where_predicate(f, predicate, cx) }) }) .joined(",", f) @@ -238,29 +245,30 @@ pub(crate) fn print_lifetime(lt: &clean::Lifetime) -> &str { } pub(crate) fn print_constant_kind( + f: &mut Formatter<'_>, constant_kind: &clean::ConstantKind, tcx: TyCtxt<'_>, -) -> impl Display { +) -> fmt::Result { let expr = constant_kind.expr(tcx); - fmt::from_fn( - move |f| { - if f.alternate() { f.write_str(&expr) } else { write!(f, "{}", Escape(&expr)) } - }, - ) + + if f.alternate() { f.write_str(&expr) } else { write!(f, "{}", Escape(&expr)) } } -fn print_poly_trait(poly_trait: &clean::PolyTrait, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| { - print_higher_ranked_params_with_space(&poly_trait.generic_params, cx, "for").fmt(f)?; - print_path(&poly_trait.trait_, cx).fmt(f) - }) +fn print_poly_trait( + f: &mut Formatter<'_>, + poly_trait: &clean::PolyTrait, + cx: &Context<'_>, +) -> fmt::Result { + print_higher_ranked_params_with_space(&poly_trait.generic_params, cx, "for").fmt(f)?; + print_path(f, &poly_trait.trait_, cx) } pub(crate) fn print_generic_bound( + f: &mut Formatter<'_>, generic_bound: &clean::GenericBound, cx: &Context<'_>, -) -> impl Display { - fmt::from_fn(move |f| match generic_bound { +) -> fmt::Result { + match generic_bound { clean::GenericBound::Outlives(lt) => f.write_str(print_lifetime(lt)), clean::GenericBound::TraitBound(ty, modifiers) => { // `const` and `[const]` trait bounds are experimental; don't render them. @@ -270,7 +278,7 @@ pub(crate) fn print_generic_bound( hir::BoundPolarity::Maybe(_) => "?", hir::BoundPolarity::Negative(_) => "!", })?; - print_poly_trait(ty, cx).fmt(f) + print_poly_trait(f, ty, cx) } clean::GenericBound::Use(args) => { f.write_str("use")?; @@ -278,45 +286,49 @@ pub(crate) fn print_generic_bound( .wrap_fn(|f| args.iter().map(|arg| arg.name()).joined(", ", f)) .fmt(f) } - }) + } } -fn print_generic_args(generic_args: &clean::GenericArgs, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| { - match generic_args { - clean::GenericArgs::AngleBracketed { args, constraints } => { - if !args.is_empty() || !constraints.is_empty() { - Wrapped::with_angle_brackets() - .wrap_fn(|f| { - [Either::Left(args), Either::Right(constraints)] - .into_iter() - .flat_map(Either::factor_into_iter) - .map(|either| { - either.map_either( - |arg| print_generic_arg(arg, cx), - |constraint| print_assoc_item_constraint(constraint, cx), - ) - }) - .joined(", ", f) - }) - .fmt(f)?; - } - } - clean::GenericArgs::Parenthesized { inputs, output } => { - Wrapped::with_parens() - .wrap_fn(|f| inputs.iter().map(|ty| print_type(ty, cx)).joined(", ", f)) +fn print_generic_args( + f: &mut Formatter<'_>, + args: &clean::GenericArgs, + cx: &Context<'_>, +) -> fmt::Result { + match args { + clean::GenericArgs::AngleBracketed { args, constraints } => { + if !args.is_empty() || !constraints.is_empty() { + Wrapped::with_angle_brackets() + .wrap_fn(|f| { + [Either::Left(args), Either::Right(constraints)] + .into_iter() + .flat_map(Either::factor_into_iter) + .map(|either| { + either.map_either( + |arg| print_generic_arg.display_fn(&arg, cx), + |constraint| { + print_assoc_item_constraint.display_fn(&constraint, cx) + }, + ) + }) + .joined(", ", f) + }) .fmt(f)?; - if let Some(ref ty) = *output { - f.write_str(if f.alternate() { " -> " } else { " -> " })?; - print_type(ty, cx).fmt(f)?; - } } - clean::GenericArgs::ReturnTypeNotation => { - f.write_str("(..)")?; + } + clean::GenericArgs::Parenthesized { inputs, output } => { + Wrapped::with_parens() + .wrap_fn(|f| inputs.iter().map(|ty| print_type.display_fn(&ty, cx)).joined(", ", f)) + .fmt(f)?; + if let Some(ref ty) = *output { + f.write_str(if f.alternate() { " -> " } else { " -> " })?; + print_type(f, &ty, cx)?; } } - Ok(()) - }) + clean::GenericArgs::ReturnTypeNotation => { + f.write_str("(..)")?; + } + } + Ok(()) } // Possible errors when computing href link source for a `DefId` @@ -686,7 +698,7 @@ fn resolved_path( } } if w.alternate() { - write!(w, "{}{:#}", last.name, print_generic_args(&last.args, cx))?; + write!(w, "{}{:#}", last.name, print_generic_args.display_fn(&last.args, cx))?; } else { let path = fmt::from_fn(|f| { if use_absolute { @@ -704,7 +716,7 @@ fn resolved_path( write!(f, "{}", print_anchor(did, last.name, cx)) } }); - write!(w, "{path}{args}", args = print_generic_args(&last.args, cx))?; + write!(w, "{path}{args}", args = print_generic_args.display_fn(&last.args, cx))?; } Ok(()) } @@ -793,7 +805,7 @@ fn print_tybounds( cx: &Context<'_>, ) -> impl Display { fmt::from_fn(move |f| { - bounds.iter().map(|bound| print_poly_trait(bound, cx)).joined(" + ", f)?; + bounds.iter().map(|bound| print_poly_trait.display_fn(bound, cx)).joined(" + ", f)?; if let Some(lt) = lt { // We don't need to check `alternate` since we can be certain that // the lifetime doesn't contain any characters which need escaping. @@ -813,7 +825,10 @@ fn print_higher_ranked_params_with_space( f.write_str(keyword)?; Wrapped::with_angle_brackets() .wrap_fn(|f| { - params.iter().map(|lt| print_generic_param_def(lt, cx)).joined(", ", f) + params + .iter() + .map(|lt| print_generic_param_def.display_fn(lt, cx)) + .joined(", ", f) }) .fmt(f)?; f.write_char(' ')?; @@ -872,11 +887,11 @@ fn fmt_type( } else { primitive_link(f, PrimitiveType::Fn, format_args!("fn"), cx)?; } - print_fn_decl(&decl.decl, cx).fmt(f) + print_fn_decl(f, &decl.decl, cx) } clean::UnsafeBinder(binder) => { print_higher_ranked_params_with_space(&binder.generic_params, cx, "unsafe").fmt(f)?; - print_type(&binder.ty, cx).fmt(f) + print_type(f, &binder.ty, cx) } clean::Tuple(typs) => match &typs[..] { &[] => primitive_link(f, PrimitiveType::Unit, format_args!("()"), cx), @@ -885,7 +900,7 @@ fn fmt_type( primitive_link(f, PrimitiveType::Tuple, format_args!("({name},)"), cx) } else { write!(f, "(")?; - print_type(one, cx).fmt(f)?; + print_type(f, one, cx)?; write!(f, ",)") } } @@ -911,7 +926,9 @@ fn fmt_type( ) } else { Wrapped::with_parens() - .wrap_fn(|f| many.iter().map(|item| print_type(item, cx)).joined(", ", f)) + .wrap_fn(|f| { + many.iter().map(|item| print_type.display_fn(item, cx)).joined(", ", f) + }) .fmt(f) } } @@ -919,9 +936,11 @@ fn fmt_type( clean::Slice(box clean::Generic(name)) => { primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx) } - clean::Slice(t) => Wrapped::with_square_brackets().wrap(print_type(t, cx)).fmt(f), + clean::Slice(t) => { + Wrapped::with_square_brackets().wrap(print_type.display_fn(t, cx)).fmt(f) + } clean::Type::Pat(t, pat) => { - fmt::Display::fmt(&print_type(t, cx), f)?; + print_type(f, t, cx)?; write!(f, " is {pat}") } clean::Array(box clean::Generic(name), n) if !f.alternate() => primitive_link( @@ -932,7 +951,7 @@ fn fmt_type( ), clean::Array(t, n) => Wrapped::with_square_brackets() .wrap(fmt::from_fn(|f| { - print_type(t, cx).fmt(f)?; + print_type(f, t, cx)?; f.write_str("; ")?; if f.alternate() { f.write_str(n) @@ -948,12 +967,15 @@ fn fmt_type( primitive_link( f, clean::PrimitiveType::RawPointer, - format_args!("*{m} {ty}", ty = WithOpts::from(f).display(print_type(t, cx))), + format_args!( + "*{m} {ty}", + ty = WithOpts::from(f).display(print_type.display_fn(t, cx)) + ), cx, ) } else { primitive_link(f, clean::PrimitiveType::RawPointer, format_args!("*{m} "), cx)?; - print_type(t, cx).fmt(f) + print_type(f, t, cx) } } clean::BorrowedRef { lifetime: l, mutability, type_: ty } => { @@ -991,89 +1013,99 @@ fn fmt_type( } clean::ImplTrait(bounds) => { f.write_str("impl ")?; - print_generic_bounds(bounds, cx).fmt(f) + print_generic_bounds(f, bounds, cx) } - clean::QPath(qpath) => print_qpath_data(qpath, cx).fmt(f), + clean::QPath(qpath) => print_qpath_data(f, qpath, cx), } } -pub(crate) fn print_type(type_: &clean::Type, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| fmt_type(type_, f, false, cx)) +pub(crate) fn print_type( + f: &mut Formatter<'_>, + type_: &clean::Type, + cx: &Context<'_>, +) -> fmt::Result { + fmt_type(type_, f, false, cx) } -pub(crate) fn print_path(path: &clean::Path, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| resolved_path(f, path.def_id(), path, false, false, cx)) +pub(crate) fn print_path( + f: &mut Formatter<'_>, + path: &clean::Path, + cx: &Context<'_>, +) -> fmt::Result { + resolved_path(f, path.def_id(), path, false, false, cx) } -fn print_qpath_data(qpath_data: &clean::QPathData, cx: &Context<'_>) -> impl Display { +fn print_qpath_data( + f: &mut Formatter<'_>, + qpath_data: &clean::QPathData, + cx: &Context<'_>, +) -> fmt::Result { let clean::QPathData { ref assoc, ref self_type, should_fully_qualify, ref trait_ } = *qpath_data; - fmt::from_fn(move |f| { - // FIXME(inherent_associated_types): Once we support non-ADT self-types (#106719), - // we need to surround them with angle brackets in some cases (e.g. `::P`). - - if let Some(trait_) = trait_ - && should_fully_qualify - { - let opts = WithOpts::from(f); - Wrapped::with_angle_brackets() - .wrap(format_args!( - "{} as {}", - opts.display(print_type(self_type, cx)), - opts.display(print_path(trait_, cx)) - )) - .fmt(f)? - } else { - print_type(self_type, cx).fmt(f)?; - } - f.write_str("::")?; - // 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 - // `B` to disambiguate. - // - // FIXME: this is still a lossy conversion and there should probably - // be a better way of representing this in general? Most of - // the ugliness comes from inlining across crates where - // everything comes in as a fully resolved QPath (hard to - // look at). - if !f.alternate() { - // FIXME(inherent_associated_types): We always link to the very first associated - // type (in respect to source order) that bears the given name (`assoc.name`) and that is - // affiliated with the computed `DefId`. This is obviously incorrect when we have - // multiple impl blocks. Ideally, we would thread the `DefId` of the assoc ty itself - // through here and map it to the corresponding HTML ID that was generated by - // `render::Context::derive_id` when the impl blocks were rendered. - // There is no such mapping unfortunately. - // As a hack, we could badly imitate `derive_id` here by keeping *count* when looking - // for the assoc ty `DefId` in `tcx.associated_items(self_ty_did).in_definition_order()` - // considering privacy, `doc(hidden)`, etc. - // I don't feel like that right now :cold_sweat:. - - let parent_href = match trait_ { - Some(trait_) => href(trait_.def_id(), cx).ok(), - None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()), - }; + // FIXME(inherent_associated_types): Once we support non-ADT self-types (#106719), + // we need to surround them with angle brackets in some cases (e.g. `::P`). + + if let Some(trait_) = trait_ + && should_fully_qualify + { + let opts = WithOpts::from(f); + Wrapped::with_angle_brackets() + .wrap(format_args!( + "{} as {}", + opts.display(print_type.display_fn(self_type, cx)), + opts.display(print_path.display_fn(trait_, cx)) + )) + .fmt(f)? + } else { + print_type(f, self_type, cx)?; + } + f.write_str("::")?; + // 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 + // `B` to disambiguate. + // + // FIXME: this is still a lossy conversion and there should probably + // be a better way of representing this in general? Most of + // the ugliness comes from inlining across crates where + // everything comes in as a fully resolved QPath (hard to + // look at). + if !f.alternate() { + // FIXME(inherent_associated_types): We always link to the very first associated + // type (in respect to source order) that bears the given name (`assoc.name`) and that is + // affiliated with the computed `DefId`. This is obviously incorrect when we have + // multiple impl blocks. Ideally, we would thread the `DefId` of the assoc ty itself + // through here and map it to the corresponding HTML ID that was generated by + // `render::Context::derive_id` when the impl blocks were rendered. + // There is no such mapping unfortunately. + // As a hack, we could badly imitate `derive_id` here by keeping *count* when looking + // for the assoc ty `DefId` in `tcx.associated_items(self_ty_did).in_definition_order()` + // considering privacy, `doc(hidden)`, etc. + // I don't feel like that right now :cold_sweat:. + + let parent_href = match trait_ { + Some(trait_) => href(trait_.def_id(), cx).ok(), + None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()), + }; - if let Some((url, _, path)) = parent_href { - write!( - f, - "{name}", - shortty = ItemType::AssocType, - name = assoc.name, - path = join_path_syms(path), - ) - } else { - write!(f, "{}", assoc.name) - } + shortty = ItemType::AssocType, + name = assoc.name, + path = join_path_syms(path), + ) } else { write!(f, "{}", assoc.name) - }?; + } + } else { + write!(f, "{}", assoc.name) + }?; - print_generic_args(&assoc.args, cx).fmt(f) - }) + print_generic_args(f, &assoc.args, cx) } pub(crate) fn print_impl( @@ -1083,7 +1115,7 @@ pub(crate) fn print_impl( ) -> impl Display { fmt::from_fn(move |f| { f.write_str("impl")?; - print_generics(&impl_.generics, cx).fmt(f)?; + print_generics(f, &impl_.generics, cx)?; f.write_str(" ")?; if let Some(ref ty) = impl_.trait_ { @@ -1104,7 +1136,7 @@ pub(crate) fn print_impl( .wrap_fn(|f| impl_.print_type(inner_type, f, use_absolute, cx)) .fmt(f)?; } else { - print_path(ty, cx).fmt(f)?; + print_path(f, ty, cx)?; } f.write_str(" for ")?; } @@ -1186,20 +1218,22 @@ impl clean::Impl { } } -pub(crate) fn print_params(params: &[clean::Parameter], cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| { - params - .iter() - .map(|param| { - fmt::from_fn(|f| { - if let Some(name) = param.name { - write!(f, "{name}: ")?; - } - print_type(¶m.type_, cx).fmt(f) - }) +pub(crate) fn print_params( + f: &mut Formatter<'_>, + params: &[clean::Parameter], + cx: &Context<'_>, +) -> fmt::Result { + params + .iter() + .map(|param| { + fmt::from_fn(|f| { + if let Some(name) = param.name { + write!(f, "{name}: ")?; + } + print_type(f, ¶m.type_, cx) }) - .joined(", ", f) - }) + }) + .joined(", ", f) } // Implements Write but only counts the bytes "written". @@ -1225,46 +1259,46 @@ impl Display for Indent { } } -fn print_parameter(parameter: &clean::Parameter, cx: &Context<'_>) -> impl fmt::Display { - fmt::from_fn(move |f| { - if let Some(self_ty) = parameter.to_receiver() { - match self_ty { - clean::SelfTy => f.write_str("self"), - clean::BorrowedRef { lifetime, mutability, type_: box clean::SelfTy } => { - f.write_str(if f.alternate() { "&" } else { "&" })?; - if let Some(lt) = lifetime { - write!(f, "{lt} ", lt = print_lifetime(lt))?; - } - write!(f, "{mutability}self", mutability = mutability.print_with_space()) - } - _ => { - f.write_str("self: ")?; - print_type(self_ty, cx).fmt(f) +fn print_parameter( + f: &mut Formatter<'_>, + parameter: &clean::Parameter, + cx: &Context<'_>, +) -> fmt::Result { + if let Some(self_ty) = parameter.to_receiver() { + match self_ty { + clean::SelfTy => f.write_str("self"), + clean::BorrowedRef { lifetime, mutability, type_: box clean::SelfTy } => { + f.write_str(if f.alternate() { "&" } else { "&" })?; + if let Some(lt) = lifetime { + write!(f, "{lt} ", lt = print_lifetime(lt))?; } + write!(f, "{mutability}self", mutability = mutability.print_with_space()) } - } else { - if parameter.is_const { - write!(f, "const ")?; - } - if let Some(name) = parameter.name { - write!(f, "{name}: ")?; + _ => { + f.write_str("self: ")?; + print_type(f, self_ty, cx) } - print_type(¶meter.type_, cx).fmt(f) } - }) + } else { + if parameter.is_const { + write!(f, "const ")?; + } + if let Some(name) = parameter.name { + write!(f, "{name}: ")?; + } + print_type(f, ¶meter.type_, cx) + } } -fn print_fn_decl(fn_decl: &clean::FnDecl, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| { - let ellipsis = if fn_decl.c_variadic { ", ..." } else { "" }; - Wrapped::with_parens() - .wrap_fn(|f| { - print_params(&fn_decl.inputs, cx).fmt(f)?; - f.write_str(ellipsis) - }) - .fmt(f)?; - fn_decl.print_output(cx).fmt(f) - }) +fn print_fn_decl(f: &mut Formatter<'_>, fn_decl: &clean::FnDecl, cx: &Context<'_>) -> fmt::Result { + let ellipsis = if fn_decl.c_variadic { ", ..." } else { "" }; + Wrapped::with_parens() + .wrap_fn(|f| { + print_params(f, &fn_decl.inputs, cx)?; + f.write_str(ellipsis) + }) + .fmt(f)?; + fn_decl.print_output(cx).fmt(f) } /// * `header_len`: The length of the function header and name. In other words, the number of @@ -1317,7 +1351,10 @@ impl clean::FnDecl { } }); - self.inputs.iter().map(|param| print_parameter(param, cx)).joined(sep, f)?; + self.inputs + .iter() + .map(|param| print_parameter.display_fn(param, cx)) + .joined(sep, f)?; if line_wrapping_indent.is_some() { writeln!(f, ",")? @@ -1349,7 +1386,7 @@ impl clean::FnDecl { } f.write_str(if f.alternate() { " -> " } else { " -> " })?; - print_type(&self.output, cx).fmt(f) + print_type(f, &self.output, cx) }) } } @@ -1462,16 +1499,20 @@ pub(crate) fn print_constness_with_space( } } -pub(crate) fn print_import(import: &clean::Import, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| match import.kind { +pub(crate) fn print_import( + f: &mut Formatter<'_>, + import: &clean::Import, + cx: &Context<'_>, +) -> fmt::Result { + match import.kind { clean::ImportKind::Simple(name) => { if name == import.source.path.last() { - write!(f, "use {};", print_import_source(&import.source, cx)) + write!(f, "use {};", print_import_source.display_fn(&import.source, cx)) } else { write!( f, "use {source} as {name};", - source = print_import_source(&import.source, cx) + source = print_import_source.display_fn(&import.source, cx) ) } } @@ -1479,14 +1520,18 @@ pub(crate) fn print_import(import: &clean::Import, cx: &Context<'_>) -> impl Dis if import.source.path.segments.is_empty() { write!(f, "use *;") } else { - write!(f, "use {}::*;", print_import_source(&import.source, cx)) + write!(f, "use {}::*;", print_import_source.display_fn(&import.source, cx)) } } - }) + } } -fn print_import_source(import_source: &clean::ImportSource, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| match import_source.did { +fn print_import_source( + f: &mut Formatter<'_>, + import_source: &clean::ImportSource, + cx: &Context<'_>, +) -> fmt::Result { + match import_source.did { Some(did) => resolved_path(f, did, &import_source.path, true, false, cx), _ => { for seg in &import_source.path.segments[..import_source.path.segments.len() - 1] { @@ -1500,30 +1545,28 @@ fn print_import_source(import_source: &clean::ImportSource, cx: &Context<'_>) -> } Ok(()) } - }) + } } fn print_assoc_item_constraint( + f: &mut Formatter<'_>, assoc_item_constraint: &clean::AssocItemConstraint, cx: &Context<'_>, -) -> impl Display { - fmt::from_fn(move |f| { - f.write_str(assoc_item_constraint.assoc.name.as_str())?; - print_generic_args(&assoc_item_constraint.assoc.args, cx).fmt(f)?; - match assoc_item_constraint.kind { - clean::AssocItemConstraintKind::Equality { ref term } => { - f.write_str(" = ")?; - print_term(term, cx).fmt(f)?; - } - clean::AssocItemConstraintKind::Bound { ref bounds } => { - if !bounds.is_empty() { - f.write_str(": ")?; - print_generic_bounds(bounds, cx).fmt(f)?; - } +) -> fmt::Result { + f.write_str(assoc_item_constraint.assoc.name.as_str())?; + print_generic_args(f, &assoc_item_constraint.assoc.args, cx)?; + match assoc_item_constraint.kind { + clean::AssocItemConstraintKind::Equality { ref term } => { + f.write_str(" = ")?; + print_term(f, term, cx)?; + } + clean::AssocItemConstraintKind::Bound { ref bounds } => { + if !bounds.is_empty() { + write!(f, " {}", print_generic_bounds.display_fn(bounds, cx))?; } } - Ok(()) - }) + } + Ok(()) } pub(crate) fn print_abi_with_space(abi: ExternAbi) -> impl Display { @@ -1540,18 +1583,22 @@ pub(crate) fn print_default_space(v: bool) -> &'static str { if v { "default " } else { "" } } -fn print_generic_arg(generic_arg: &clean::GenericArg, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| match generic_arg { +fn print_generic_arg( + f: &mut Formatter<'_>, + generic_arg: &clean::GenericArg, + cx: &Context<'_>, +) -> fmt::Result { + match generic_arg { clean::GenericArg::Lifetime(lt) => f.write_str(print_lifetime(lt)), - clean::GenericArg::Type(ty) => print_type(ty, cx).fmt(f), - clean::GenericArg::Const(ct) => print_constant_kind(ct, cx.tcx()).fmt(f), + clean::GenericArg::Type(ty) => print_type(f, ty, cx), + clean::GenericArg::Const(ct) => print_constant_kind(f, ct, cx.tcx()), clean::GenericArg::Infer => f.write_char('_'), - }) + } } -fn print_term(term: &clean::Term, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| match term { - clean::Term::Type(ty) => print_type(ty, cx).fmt(f), - clean::Term::Constant(ct) => print_constant_kind(ct, cx.tcx()).fmt(f), - }) +fn print_term(f: &mut Formatter<'_>, term: &clean::Term, cx: &Context<'_>) -> fmt::Result { + match term { + clean::Term::Type(ty) => print_type(f, ty, cx), + clean::Term::Constant(ct) => print_constant_kind(f, ct, cx.tcx()), + } } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 4c06d0da47013..b5b00d5f65379 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -22,6 +22,7 @@ use crate::clean::types::ExternalLocation; use crate::clean::utils::has_doc_flag; use crate::clean::{self, ExternalCrate}; use crate::config::{ModuleSorting, RenderOptions, ShouldMerge}; +use crate::display::DisplayFn as _; use crate::docfs::{DocFS, PathError}; use crate::error::Error; use crate::formats::FormatRenderer; @@ -249,7 +250,7 @@ impl<'tcx> Context<'tcx> { }; if !render_redirect_pages { - let content = print_item(self, it); + let content = print_item.display_fn(it, self); let page = layout::Page { css_class: tyname_s, root_path: &self.root_path(), diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 36990332b2fc8..973e331be2675 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -67,7 +67,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::display::{DisplayFn as _, Joined as _, MaybeDisplay as _}; use crate::error::Error; use crate::formats::Impl; use crate::formats::cache::Cache; @@ -1045,8 +1045,8 @@ fn assoc_const( vis = visibility_print_with_space(it, cx), href = assoc_href_attr(it, link, cx).maybe_display(), name = it.name.as_ref().unwrap(), - generics = print_generics(generics, cx), - ty = print_type(ty, cx), + generics = print_generics.display_fn(generics, cx), + ty = print_type.display_fn(ty, cx), )?; if let AssocConstValue::TraitDefault(konst) | AssocConstValue::Impl(konst) = value { // FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the @@ -1084,14 +1084,14 @@ fn assoc_type( vis = visibility_print_with_space(it, cx), href = assoc_href_attr(it, link, cx).maybe_display(), name = it.name.as_ref().unwrap(), - generics = print_generics(generics, cx), + generics = print_generics.display_fn(generics, cx), )?; if !bounds.is_empty() { - write!(w, ": {}", print_generic_bounds(bounds, cx))?; + write!(w, ": {}", print_generic_bounds.display_fn(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, " = {}", print_type(default, cx))?; + write!(w, " = {}", print_type.display_fn(default, cx))?; } write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline).maybe_display()) }) @@ -1129,7 +1129,7 @@ fn assoc_method( 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!("{:#}", print_generics(g, cx)).len(); + let generics_len = format!("{:#}", print_generics.display_fn(g, cx)).len(); let mut header_len = "fn ".len() + vis.len() + defaultness.len() @@ -1156,7 +1156,7 @@ fn assoc_method( "{indent}{vis}{defaultness}{constness}{asyncness}{safety}{abi}fn \ {name}{generics}{decl}{notable_traits}{where_clause}", indent = indent_str, - generics = print_generics(g, cx), + generics = print_generics.display_fn(g, cx), decl = full_print_fn_decl(d, header_len, indent, cx), where_clause = print_where_clause(g, cx, indent, end_newline).maybe_display(), ) @@ -1444,7 +1444,7 @@ fn render_assoc_items_inner( AssocItemRender::DerefFor { trait_, type_, .. } => { let id = cx.derive_id(small_url_encode(format!( "deref-methods-{:#}", - print_type(type_, cx) + print_type.display_fn(type_, cx) ))); // the `impls.get` above only looks at the outermost type, // and the Deref impl may only be implemented for certain @@ -1469,8 +1469,8 @@ fn render_assoc_items_inner( fmt::from_fn(|f| write!( f, "Methods from {trait_}<Target = {type_}>", - trait_ = print_path(trait_, cx), - type_ = print_type(type_, cx), + trait_ = print_path.display_fn(trait_, cx), + type_ = print_type.display_fn(type_, cx), )), &id, ) @@ -1648,7 +1648,7 @@ fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Optionⓘ", - ty = Escape(&format!("{:#}", print_type(ty, cx))), + ty = Escape(&format!("{:#}", print_type.display_fn(ty, cx))), ) }) }) @@ -1686,7 +1686,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { f, "

Notable traits for {}

\
",
-                print_type(&impl_.for_, cx),
+                print_type.display_fn(&impl_.for_, cx),
             )?;
             true
         } else {
@@ -1727,7 +1727,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
     })
     .to_string();
 
-    (format!("{:#}", print_type(ty, cx)), out)
+    (format!("{:#}", print_type.display_fn(ty, cx)), out)
 }
 
 fn notable_traits_json<'a>(tys: impl Iterator, cx: &Context<'_>) -> String {
@@ -2427,7 +2427,7 @@ fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String
             // Alternative format produces no URLs,
             // so this parameter does nothing.
             Some((
-                format!("{:#}", print_type(&i.for_, cx)),
+                format!("{:#}", print_type.display_fn(&i.for_, cx)),
                 get_id_for_impl(cx.tcx(), item.item_id),
             ))
         }
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index fa7c9e75fdac4..f19abd2549a54 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -1,5 +1,5 @@
 use std::cmp::Ordering;
-use std::fmt::{self, Display, Write as _};
+use std::fmt::{self, Display, Formatter, Write as _};
 use std::iter;
 
 use askama::Template;
@@ -26,7 +26,7 @@ use super::{
 };
 use crate::clean;
 use crate::config::ModuleSorting;
-use crate::display::{Joined as _, MaybeDisplay as _};
+use crate::display::{DisplayFn as _, Joined as _, MaybeDisplay as _};
 use crate::formats::Impl;
 use crate::formats::item_type::ItemType;
 use crate::html::escape::{Escape, EscapeBodyTextWithWbr};
@@ -145,139 +145,141 @@ struct ItemVars<'a> {
     src_href: Option<&'a str>,
 }
 
-pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Display {
+pub(super) fn print_item(
+    f: &mut Formatter<'_>,
+    item: &clean::Item,
+    cx: &Context<'_>,
+) -> fmt::Result {
     debug_assert!(!item.is_stripped());
 
-    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::AttributeItem => "Attribute ",
-            clean::TraitAliasItem(..) => "Trait Alias ",
-            _ => {
-                // We don't generate pages for any other type.
-                unreachable!();
+    let typ = match item.kind {
+        clean::ModuleItem(_) => {
+            if item.is_crate() {
+                "Crate "
+            } else {
+                "Module "
             }
-        };
-        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_fake_item() {
-            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()
-        };
+        }
+        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::AttributeItem => "Attribute ",
+        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();
 
-        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(),
-        };
+    // 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_fake_item() {
+        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()
+    };
 
-        item_vars.render_into(buf).unwrap();
+    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(),
+    };
 
-        match &item.kind {
-            clean::ModuleItem(m) => {
-                write!(buf, "{}", item_module(cx, item, &m.items))
-            }
-            clean::FunctionItem(f) | clean::ForeignFunctionItem(f, _) => {
-                write!(buf, "{}", item_function(cx, item, f))
-            }
-            clean::TraitItem(t) => write!(buf, "{}", item_trait(cx, item, t)),
-            clean::StructItem(s) => {
-                write!(buf, "{}", item_struct(cx, item, s))
-            }
-            clean::UnionItem(s) => write!(buf, "{}", item_union(cx, item, s)),
-            clean::EnumItem(e) => write!(buf, "{}", item_enum(cx, item, e)),
-            clean::TypeAliasItem(t) => {
-                write!(buf, "{}", item_type_alias(cx, item, t))
-            }
-            clean::MacroItem(m) => write!(buf, "{}", item_macro(cx, item, m)),
-            clean::ProcMacroItem(m) => {
-                write!(buf, "{}", item_proc_macro(cx, item, m))
-            }
-            clean::PrimitiveItem(_) => write!(buf, "{}", item_primitive(cx, item)),
-            clean::StaticItem(i) => {
-                write!(buf, "{}", item_static(cx, item, i, None))
-            }
-            clean::ForeignStaticItem(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 | clean::AttributeItem => {
-                write!(buf, "{}", item_keyword_or_attribute(cx, item))
-            }
-            clean::TraitAliasItem(ta) => {
-                write!(buf, "{}", item_trait_alias(cx, item, ta))
-            }
-            _ => {
-                // We don't generate pages for any other type.
-                unreachable!();
-            }
-        }?;
+    item_vars.render_into(f).unwrap();
 
-        // 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();
+    match &item.kind {
+        clean::ModuleItem(m) => {
+            write!(f, "{}", item_module(cx, item, &m.items))
         }
-        Ok(())
-    })
+        clean::FunctionItem(func) | clean::ForeignFunctionItem(func, _) => {
+            write!(f, "{}", item_function(cx, item, func))
+        }
+        clean::TraitItem(t) => write!(f, "{}", item_trait(cx, item, t)),
+        clean::StructItem(s) => {
+            write!(f, "{}", item_struct(cx, item, s))
+        }
+        clean::UnionItem(s) => write!(f, "{}", item_union(cx, item, s)),
+        clean::EnumItem(e) => write!(f, "{}", item_enum(cx, item, e)),
+        clean::TypeAliasItem(t) => {
+            write!(f, "{}", item_type_alias(cx, item, t))
+        }
+        clean::MacroItem(m) => write!(f, "{}", item_macro(cx, item, m)),
+        clean::ProcMacroItem(m) => {
+            write!(f, "{}", item_proc_macro(cx, item, m))
+        }
+        clean::PrimitiveItem(_) => write!(f, "{}", item_primitive(cx, item)),
+        clean::StaticItem(i) => {
+            write!(f, "{}", item_static(cx, item, i, None))
+        }
+        clean::ForeignStaticItem(i, safety) => {
+            write!(f, "{}", item_static(cx, item, i, Some(*safety)))
+        }
+        clean::ConstantItem(ci) => {
+            write!(f, "{}", item_constant(cx, item, &ci.generics, &ci.type_, &ci.kind))
+        }
+        clean::ForeignTypeItem => {
+            write!(f, "{}", item_foreign_type(cx, item))
+        }
+        clean::KeywordItem | clean::AttributeItem => {
+            write!(f, "{}", item_keyword_or_attribute(cx, item))
+        }
+        clean::TraitAliasItem(ta) => {
+            write!(f, "{}", 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!(
+            f,
+            r#""#,
+            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
@@ -463,7 +465,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
                             "{vis}{imp}{stab_tags}\
                             ",
                             vis = visibility_print_with_space(myitem, cx),
-                            imp = print_import(import, cx),
+                            imp = print_import.display_fn(import, cx),
                         )?;
                     }
                     _ => {
@@ -612,7 +614,7 @@ fn item_function(cx: &Context<'_>, it: &clean::Item, f: &clean::Function) -> imp
         let visibility = visibility_print_with_space(it, cx).to_string();
         let name = it.name.unwrap();
 
-        let generics_len = format!("{:#}", print_generics(&f.generics, cx)).len();
+        let generics_len = format!("{:#}", print_generics.display_fn(&f.generics, cx)).len();
         let header_len = "fn ".len()
             + visibility.len()
             + constness.len()
@@ -636,7 +638,7 @@ fn item_function(cx: &Context<'_>, it: &clean::Item, f: &clean::Function) -> imp
                 safety = safety,
                 abi = abi,
                 name = name,
-                generics = print_generics(&f.generics, cx),
+                generics = print_generics.display_fn(&f.generics, cx),
                 where_clause =
                     print_where_clause(&f.generics, cx, 0, Ending::Newline).maybe_display(),
                 decl = full_print_fn_decl(&f.decl, header_len, 0, cx),
@@ -674,7 +676,7 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt:
                 safety = t.safety(tcx).print_with_space(),
                 is_auto = if t.is_auto(tcx) { "auto " } else { "" },
                 name = it.name.unwrap(),
-                generics = print_generics(&t.generics, cx),
+                generics = print_generics.display_fn(&t.generics, cx),
             )?;
 
             if !t.generics.where_predicates.is_empty() {
@@ -1245,7 +1247,7 @@ fn item_trait_alias(
                 w,
                 "trait {name}{generics} = {bounds}{where_clause};",
                 name = it.name.unwrap(),
-                generics = print_generics(&t.generics, cx),
+                generics = print_generics.display_fn(&t.generics, cx),
                 bounds = print_bounds(&t.bounds, true, cx),
                 where_clause =
                     print_where_clause(&t.generics, cx, 0, Ending::NoNewline).maybe_display(),
@@ -1274,10 +1276,10 @@ fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) ->
                 "{vis}type {name}{generics}{where_clause} = {type_};",
                 vis = visibility_print_with_space(it, cx),
                 name = it.name.unwrap(),
-                generics = print_generics(&t.generics, cx),
+                generics = print_generics.display_fn(&t.generics, cx),
                 where_clause =
                     print_where_clause(&t.generics, cx, 0, Ending::Newline).maybe_display(),
-                type_ = print_type(&t.type_, cx),
+                type_ = print_type.display_fn(&t.type_, cx),
             )
         })?;
 
@@ -1478,7 +1480,7 @@ impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
     }
 
     fn print_ty(&self, ty: &'a clean::Type) -> impl Display {
-        print_type(ty, self.cx)
+        print_type.display_fn(ty, self.cx)
     }
 
     // FIXME (GuillaumeGomez): When  is implemented,
@@ -1510,26 +1512,31 @@ fn item_union(cx: &Context<'_>, it: &clean::Item, s: &clean::Union) -> impl fmt:
     })
 }
 
-fn print_tuple_struct_fields(cx: &Context<'_>, s: &[clean::Item]) -> impl Display {
-    fmt::from_fn(|f| {
-        if !s.is_empty()
-            && s.iter().all(|field| {
-                matches!(field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))
-            })
-        {
-            return f.write_str("/* private fields */");
-        }
+fn print_tuple_struct_fields(
+    f: &mut Formatter<'_>,
+    fields: &[clean::Item],
+    cx: &Context<'_>,
+) -> fmt::Result {
+    if !fields.is_empty()
+        && fields
+            .iter()
+            .all(|field| matches!(field.kind, clean::StrippedItem(box clean::StructFieldItem(..))))
+    {
+        return f.write_str("/* private fields */");
+    }
 
-        s.iter()
-            .map(|ty| {
-                fmt::from_fn(|f| match ty.kind {
-                    clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_"),
-                    clean::StructFieldItem(ref ty) => write!(f, "{}", print_type(ty, cx)),
-                    _ => unreachable!(),
-                })
+    fields
+        .iter()
+        .map(|ty| {
+            fmt::from_fn(|f| match ty.kind {
+                clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_"),
+                clean::StructFieldItem(ref ty) => {
+                    write!(f, "{}", print_type.display_fn(ty, cx))
+                }
+                _ => unreachable!(),
             })
-            .joined(", ", f)
-    })
+        })
+        .joined(", ", f)
 }
 
 struct DisplayEnum<'clean> {
@@ -1563,7 +1570,7 @@ impl<'clean> DisplayEnum<'clean> {
                 "{}enum {}{}{}",
                 visibility_print_with_space(it, cx),
                 it.name.unwrap(),
-                print_generics(&self.generics, cx),
+                print_generics.display_fn(&self.generics, cx),
                 render_enum_fields(
                     cx,
                     Some(self.generics),
@@ -1714,7 +1721,12 @@ fn render_enum_fields(
                             )?;
                         }
                         clean::VariantKind::Tuple(ref s) => {
-                            write!(w, "{}({})", v.name.unwrap(), print_tuple_struct_fields(cx, s))?;
+                            write!(
+                                w,
+                                "{}({})",
+                                v.name.unwrap(),
+                                print_tuple_struct_fields.display_fn(s, cx)
+                            )?;
                         }
                         clean::VariantKind::Struct(ref s) => {
                             write!(
@@ -1802,7 +1814,7 @@ fn item_variants(
             let clean::VariantItem(variant_data) = &variant.kind else { unreachable!() };
 
             if let clean::VariantKind::Tuple(ref s) = variant_data.kind {
-                write!(w, "({})", print_tuple_struct_fields(cx, s))?;
+                write!(w, "({})", print_tuple_struct_fields.display_fn(s, cx))?;
             }
             w.write_str("")?;
 
@@ -1863,7 +1875,7 @@ fn item_variants(
                                     {doc}\
                                 ",
                                 f = field.name.unwrap(),
-                                t = print_type(ty, cx),
+                                t = print_type.display_fn(ty, cx),
                                 doc = document(cx, field, Some(variant), HeadingOffset::H5),
                             )?;
                         }
@@ -1957,8 +1969,8 @@ fn item_constant(
                 "{vis}const {name}{generics}: {typ}{where_clause}",
                 vis = visibility_print_with_space(it, cx),
                 name = it.name.unwrap(),
-                generics = print_generics(generics, cx),
-                typ = print_type(ty, cx),
+                generics = print_generics.display_fn(generics, cx),
+                typ = print_type.display_fn(ty, cx),
                 where_clause =
                     print_where_clause(generics, cx, 0, Ending::NoNewline).maybe_display(),
             )?;
@@ -2103,7 +2115,7 @@ fn item_fields(
                     "{field_name}: {ty}\
                     \
                     {doc}",
-                    ty = print_type(ty, cx),
+                    ty = print_type.display_fn(ty, cx),
                     doc = document(cx, field, Some(it), HeadingOffset::H3),
                 )?;
             }
@@ -2128,7 +2140,7 @@ fn item_static(
                 safe = safety.map(|safe| safe.prefix_str()).unwrap_or(""),
                 mutability = s.mutability.print_with_space(),
                 name = it.name.unwrap(),
-                typ = print_type(&s.type_, cx)
+                typ = print_type.display_fn(&s.type_, cx)
             )
         })?;
 
@@ -2287,7 +2299,7 @@ fn print_bounds(
                 }
             }
 
-            bounds.iter().map(|p| print_generic_bound(p, cx)).joined(inter_str, f)
+            bounds.iter().map(|p| print_generic_bound.display_fn(p, cx)).joined(inter_str, f)
         }))
         .maybe_display()
 }
@@ -2377,7 +2389,7 @@ fn render_union(
         write!(f, "{}union {}", visibility_print_with_space(it, cx), it.name.unwrap(),)?;
 
         let where_displayed = if let Some(generics) = g {
-            write!(f, "{}", print_generics(generics, cx))?;
+            write!(f, "{}", print_generics.display_fn(generics, cx))?;
             if let Some(where_clause) = print_where_clause(generics, cx, 0, Ending::Newline) {
                 write!(f, "{where_clause}")?;
                 true
@@ -2409,7 +2421,7 @@ fn render_union(
                     "    {}{}: {},",
                     visibility_print_with_space(field, cx),
                     field.name.unwrap(),
-                    print_type(ty, cx)
+                    print_type.display_fn(ty, cx)
                 )?;
             }
         }
@@ -2443,7 +2455,7 @@ fn render_struct(
             it.name.unwrap()
         )?;
         if let Some(g) = g {
-            write!(w, "{}", print_generics(g, cx))?;
+            write!(w, "{}", print_generics.display_fn(g, cx))?;
         }
         write!(
             w,
@@ -2506,7 +2518,7 @@ fn render_struct_fields(
                             "{tab}    {vis}{name}: {ty},",
                             vis = visibility_print_with_space(field, cx),
                             name = field.name.unwrap(),
-                            ty = print_type(ty, cx)
+                            ty = print_type.display_fn(ty, cx)
                         )?;
                     }
                 }
@@ -2549,7 +2561,7 @@ fn render_struct_fields(
                                     w,
                                     "{}{}",
                                     visibility_print_with_space(field, cx),
-                                    print_type(ty, cx),
+                                    print_type.display_fn(ty, cx),
                                 )?;
                             }
                             _ => unreachable!(),
diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs
index df9e8631bbdd6..4e74155b91343 100644
--- a/src/librustdoc/html/render/sidebar.rs
+++ b/src/librustdoc/html/render/sidebar.rs
@@ -11,6 +11,7 @@ use tracing::debug;
 
 use super::{Context, ItemSection, item_ty_to_section};
 use crate::clean;
+use crate::display::DisplayFn as _;
 use crate::formats::Impl;
 use crate::formats::item_type::ItemType;
 use crate::html::format::{print_path, print_type};
@@ -559,8 +560,8 @@ fn sidebar_deref_methods<'a>(
                 };
                 let title = format!(
                     "Methods from {:#}",
-                    print_path(impl_.inner_impl().trait_.as_ref().unwrap(), cx),
-                    print_type(real_target, cx),
+                    print_path.display_fn(impl_.inner_impl().trait_.as_ref().unwrap(), cx),
+                    print_type.display_fn(real_target, cx),
                 );
                 // We want links' order to be reproducible so we don't use unstable sort.
                 ret.sort();
@@ -691,7 +692,8 @@ fn sidebar_render_assoc_items(
                     ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "",
                     ty::ImplPolarity::Negative => "!",
                 };
-                let generated = Link::new(encoded, format!("{prefix}{:#}", print_path(trait_, cx)));
+                let generated =
+                    Link::new(encoded, format!("{prefix}{:#}", print_path.display_fn(trait_, cx)));
                 if links.insert(generated.clone()) { Some(generated) } else { None }
             })
             .collect::>>();
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index 6045b9a77ecae..d6f002d614e9b 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -40,6 +40,7 @@ use serde::{Deserialize, Serialize, Serializer};
 use super::{Context, RenderMode, collect_paths_for_type, ensure_trailing_slash};
 use crate::clean::{Crate, Item, ItemId, ItemKind};
 use crate::config::{EmitType, PathToParts, RenderOptions, ShouldMerge};
+use crate::display::DisplayFn as _;
 use crate::docfs::PathError;
 use crate::error::Error;
 use crate::formats::Impl;
@@ -602,11 +603,10 @@ impl TypeAliasPart {
                             )
                             .to_string();
                             // The alternate display prints it as plaintext instead of HTML.
-                            let trait_ = impl_
-                                .inner_impl()
-                                .trait_
-                                .as_ref()
-                                .map(|trait_| format!("{:#}", print_path(trait_, cx)));
+                            let trait_ =
+                                impl_.inner_impl().trait_.as_ref().map(|trait_| {
+                                    format!("{:#}", print_path.display_fn(trait_, cx))
+                                });
                             ret = Some(AliasSerializableImpl {
                                 text,
                                 trait_,