Skip to content

Commit a804f1e

Browse files
committed
Auto merge of rust-lang#136828 - yotamofek:pr/rustdoc/more-laziness, r=<try>
Do more lazy-formatting in `librustdoc` 🦥 Modify some formatting to be lazy, i.e. to not allocate interim strings that are later formatted into different strings. Commits are small and stand on their own, and should mostly compile separately. (The first one doesn't compile due to `dead_code` because all it does is introduce a helper used in later commits) Really excited about this one, local perf results are really good. I'd love a perf run to see how this looks on CI. This is the comparison of `instructions:u` count between master and this PR, on my computer: # Summary | | Range | Mean | Count | |:---:|:---:|:---:|:---:| | Regressions | - | 0.00% | 0 | | Improvements | -8.03%, -0.40% | -2.93% | 5 | | All | -8.03%, -0.40% | -2.93% | 5 | # Primary benchmarks | Benchmark | Profile | Scenario | % Change | Significance Factor | |:---:|:---:|:---:|:---:|:---:| | typenum-1.17.0 | doc | full | -8.03% | 40.16x | | nalgebra-0.33.0 | doc | full | -4.19% | 20.97x | | stm32f4-0.14.0 | doc | full | -1.35% | 6.73x | | libc-0.2.124 | doc | full | -0.67% | 3.33x | | cranelift-codegen-0.82.1 | doc | full | -0.40% | 1.99x |
2 parents 8c04e39 + 984a2e4 commit a804f1e

File tree

9 files changed

+162
-94
lines changed

9 files changed

+162
-94
lines changed

src/librustdoc/clean/cfg.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use rustc_session::parse::ParseSess;
1313
use rustc_span::Span;
1414
use rustc_span::symbol::{Symbol, sym};
1515

16+
use crate::display::Joined as _;
1617
use crate::html::escape::Escape;
17-
use crate::joined::Joined as _;
1818

1919
#[cfg(test)]
2020
mod tests;
File renamed without changes.

src/librustdoc/display/maybe.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use std::fmt::{self, Display};
2+
3+
pub(crate) trait MaybeDisplay {
4+
/// For a given `Option<T: Display>`, returns a `Display` implementation that will display `t` if `Some(t)`, or nothing if `None`.
5+
fn maybe_display(self) -> impl Display;
6+
}
7+
8+
impl<T: Display> MaybeDisplay for Option<T> {
9+
fn maybe_display(self) -> impl Display {
10+
fmt::from_fn(move |f| {
11+
if let Some(t) = self.as_ref() {
12+
t.fmt(f)?;
13+
}
14+
Ok(())
15+
})
16+
}
17+
}

src/librustdoc/display/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! Various utilities for working with [`fmt::Display`](std::fmt::Display) implementations.
2+
3+
mod joined;
4+
mod maybe;
5+
6+
pub(crate) use self::joined::Joined;
7+
pub(crate) use self::maybe::MaybeDisplay;

src/librustdoc/html/format.rs

+27-20
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ use super::url_parts_builder::{UrlPartsBuilder, estimate_item_path_byte_length};
3030
use crate::clean::types::ExternalLocation;
3131
use crate::clean::utils::find_nearest_parent_module;
3232
use crate::clean::{self, ExternalCrate, PrimitiveType};
33+
use crate::display::Joined as _;
3334
use crate::formats::cache::Cache;
3435
use crate::formats::item_type::ItemType;
3536
use crate::html::escape::{Escape, EscapeBodyText};
3637
use crate::html::render::Context;
37-
use crate::joined::Joined as _;
3838
use crate::passes::collect_intra_doc_links::UrlFragment;
3939

4040
pub(crate) trait Print {
@@ -814,19 +814,22 @@ fn resolved_path(
814814
if w.alternate() {
815815
write!(w, "{}{:#}", last.name, last.args.print(cx))?;
816816
} else {
817-
let path = if use_absolute {
818-
if let Ok((_, _, fqp)) = href(did, cx) {
819-
format!(
820-
"{path}::{anchor}",
821-
path = join_with_double_colon(&fqp[..fqp.len() - 1]),
822-
anchor = anchor(did, *fqp.last().unwrap(), cx)
823-
)
817+
let path = fmt::from_fn(|f| {
818+
if use_absolute {
819+
if let Ok((_, _, fqp)) = href(did, cx) {
820+
write!(
821+
f,
822+
"{path}::{anchor}",
823+
path = join_with_double_colon(&fqp[..fqp.len() - 1]),
824+
anchor = anchor(did, *fqp.last().unwrap(), cx)
825+
)
826+
} else {
827+
write!(f, "{}", last.name)
828+
}
824829
} else {
825-
last.name.to_string()
830+
write!(f, "{}", anchor(did, last.name, cx))
826831
}
827-
} else {
828-
anchor(did, last.name, cx).to_string()
829-
};
832+
});
830833
write!(w, "{path}{args}", args = last.args.print(cx))?;
831834
}
832835
Ok(())
@@ -854,16 +857,20 @@ fn primitive_link_fragment(
854857
match m.primitive_locations.get(&prim) {
855858
Some(&def_id) if def_id.is_local() => {
856859
let len = cx.current.len();
857-
let path = if len == 0 {
858-
let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
859-
format!("{cname_sym}/")
860-
} else {
861-
"../".repeat(len - 1)
862-
};
860+
let path = fmt::from_fn(|f| {
861+
if len == 0 {
862+
let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
863+
write!(f, "{cname_sym}/")?;
864+
} else {
865+
for _ in 0..(len - 1) {
866+
f.write_str("../")?;
867+
}
868+
}
869+
Ok(())
870+
});
863871
write!(
864872
f,
865-
"<a class=\"primitive\" href=\"{}primitive.{}.html{fragment}\">",
866-
path,
873+
"<a class=\"primitive\" href=\"{path}primitive.{}.html{fragment}\">",
867874
prim.as_sym()
868875
)?;
869876
needs_termination = true;

src/librustdoc/html/render/context.rs

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::cell::RefCell;
22
use std::collections::BTreeMap;
3+
use std::fmt::{self, Write as _};
34
use std::io;
45
use std::path::{Path, PathBuf};
56
use std::sync::mpsc::{Receiver, channel};
@@ -262,21 +263,25 @@ impl<'tcx> Context<'tcx> {
262263
// preventing an infinite redirection loop in the generated
263264
// documentation.
264265

265-
let mut path = String::new();
266-
for name in &names[..names.len() - 1] {
267-
path.push_str(name.as_str());
268-
path.push('/');
269-
}
270-
path.push_str(&item_path(ty, names.last().unwrap().as_str()));
266+
let path = fmt::from_fn(|f| {
267+
for name in &names[..names.len() - 1] {
268+
write!(f, "{name}/")?;
269+
}
270+
write!(f, "{}", item_path(ty, names.last().unwrap().as_str()))
271+
});
271272
match self.shared.redirections {
272273
Some(ref redirections) => {
273274
let mut current_path = String::new();
274275
for name in &self.current {
275276
current_path.push_str(name.as_str());
276277
current_path.push('/');
277278
}
278-
current_path.push_str(&item_path(ty, names.last().unwrap().as_str()));
279-
redirections.borrow_mut().insert(current_path, path);
279+
let _ = write!(
280+
current_path,
281+
"{}",
282+
item_path(ty, names.last().unwrap().as_str())
283+
);
284+
redirections.borrow_mut().insert(current_path, path.to_string());
280285
}
281286
None => {
282287
return layout::redirect(&format!(
@@ -851,9 +856,9 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
851856
if !buf.is_empty() {
852857
let name = item.name.as_ref().unwrap();
853858
let item_type = item.type_();
854-
let file_name = &item_path(item_type, name.as_str());
859+
let file_name = item_path(item_type, name.as_str()).to_string();
855860
self.shared.ensure_dir(&self.dst)?;
856-
let joint_dst = self.dst.join(file_name);
861+
let joint_dst = self.dst.join(&file_name);
857862
self.shared.fs.write(joint_dst, buf)?;
858863

859864
if !self.info.render_redirect_pages {
@@ -870,7 +875,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
870875
format!("{crate_name}/{file_name}"),
871876
);
872877
} else {
873-
let v = layout::redirect(file_name);
878+
let v = layout::redirect(&file_name);
874879
let redir_dst = self.dst.join(redir_name);
875880
self.shared.fs.write(redir_dst, v)?;
876881
}

src/librustdoc/html/render/mod.rs

+65-31
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub(crate) use self::context::*;
6363
pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources};
6464
pub(crate) use self::write_shared::*;
6565
use crate::clean::{self, ItemId, RenderedLink};
66+
use crate::display::{Joined as _, MaybeDisplay as _};
6667
use crate::error::Error;
6768
use crate::formats::Impl;
6869
use crate::formats::cache::Cache;
@@ -566,17 +567,27 @@ fn document_short<'a, 'cx: 'a>(
566567
let (mut summary_html, has_more_content) =
567568
MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content();
568569

569-
if has_more_content {
570-
let link = format!(" <a{}>Read more</a>", assoc_href_attr(item, link, cx));
570+
let link = if has_more_content {
571+
let link = fmt::from_fn(|f| {
572+
write!(
573+
f,
574+
" <a{}>Read more</a>",
575+
assoc_href_attr(item, link, cx).maybe_display()
576+
)
577+
});
571578

572579
if let Some(idx) = summary_html.rfind("</p>") {
573-
summary_html.insert_str(idx, &link);
580+
summary_html.insert_str(idx, &link.to_string());
581+
None
574582
} else {
575-
summary_html.push_str(&link);
583+
Some(link)
576584
}
585+
} else {
586+
None
577587
}
588+
.maybe_display();
578589

579-
write!(f, "<div class='docblock'>{summary_html}</div>")?;
590+
write!(f, "<div class='docblock'>{summary_html}{link}</div>")?;
580591
}
581592
Ok(())
582593
})
@@ -786,13 +797,23 @@ pub(crate) fn render_impls(
786797
}
787798

788799
/// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
789-
fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String {
800+
fn assoc_href_attr<'a, 'tcx>(
801+
it: &clean::Item,
802+
link: AssocItemLink<'a>,
803+
cx: &Context<'tcx>,
804+
) -> Option<impl fmt::Display + 'a + Captures<'tcx>> {
790805
let name = it.name.unwrap();
791806
let item_type = it.type_();
792807

808+
enum Href<'a> {
809+
AnchorId(&'a str),
810+
Anchor(ItemType),
811+
Url(String, ItemType),
812+
}
813+
793814
let href = match link {
794-
AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{id}")),
795-
AssocItemLink::Anchor(None) => Some(format!("#{item_type}.{name}")),
815+
AssocItemLink::Anchor(Some(ref id)) => Href::AnchorId(id),
816+
AssocItemLink::Anchor(None) => Href::Anchor(item_type),
796817
AssocItemLink::GotoSource(did, provided_methods) => {
797818
// We're creating a link from the implementation of an associated item to its
798819
// declaration in the trait declaration.
@@ -812,7 +833,7 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>)
812833
};
813834

814835
match href(did.expect_def_id(), cx) {
815-
Ok((url, ..)) => Some(format!("{url}#{item_type}.{name}")),
836+
Ok((url, ..)) => Href::Url(url, item_type),
816837
// The link is broken since it points to an external crate that wasn't documented.
817838
// Do not create any link in such case. This is better than falling back to a
818839
// dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item
@@ -824,15 +845,25 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>)
824845
// those two items are distinct!
825846
// In this scenario, the actual `id` of this impl item would be
826847
// `#{item_type}.{name}-{n}` for some number `n` (a disambiguator).
827-
Err(HrefError::DocumentationNotBuilt) => None,
828-
Err(_) => Some(format!("#{item_type}.{name}")),
848+
Err(HrefError::DocumentationNotBuilt) => return None,
849+
Err(_) => Href::Anchor(item_type),
829850
}
830851
}
831852
};
832853

854+
let href = fmt::from_fn(move |f| match &href {
855+
Href::AnchorId(id) => write!(f, "#{id}"),
856+
Href::Url(url, item_type) => {
857+
write!(f, "{url}#{item_type}.{name}")
858+
}
859+
Href::Anchor(item_type) => {
860+
write!(f, "#{item_type}.{name}")
861+
}
862+
});
863+
833864
// If there is no `href` for the reason explained above, simply do not render it which is valid:
834865
// https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements
835-
href.map(|href| format!(" href=\"{href}\"")).unwrap_or_default()
866+
Some(fmt::from_fn(move |f| write!(f, " href=\"{href}\"")))
836867
}
837868

838869
#[derive(Debug)]
@@ -862,7 +893,7 @@ fn assoc_const(
862893
"{indent}{vis}const <a{href} class=\"constant\">{name}</a>{generics}: {ty}",
863894
indent = " ".repeat(indent),
864895
vis = visibility_print_with_space(it, cx),
865-
href = assoc_href_attr(it, link, cx),
896+
href = assoc_href_attr(it, link, cx).maybe_display(),
866897
name = it.name.as_ref().unwrap(),
867898
generics = generics.print(cx),
868899
ty = ty.print(cx),
@@ -900,7 +931,7 @@ fn assoc_type(
900931
"{indent}{vis}type <a{href} class=\"associatedtype\">{name}</a>{generics}",
901932
indent = " ".repeat(indent),
902933
vis = visibility_print_with_space(it, cx),
903-
href = assoc_href_attr(it, link, cx),
934+
href = assoc_href_attr(it, link, cx).maybe_display(),
904935
name = it.name.as_ref().unwrap(),
905936
generics = generics.print(cx),
906937
);
@@ -942,7 +973,7 @@ fn assoc_method(
942973
let asyncness = header.asyncness.print_with_space();
943974
let safety = header.safety.print_with_space();
944975
let abi = print_abi_with_space(header.abi).to_string();
945-
let href = assoc_href_attr(meth, link, cx);
976+
let href = assoc_href_attr(meth, link, cx).maybe_display();
946977

947978
// NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
948979
let generics_len = format!("{:#}", g.print(cx)).len();
@@ -956,7 +987,7 @@ fn assoc_method(
956987
+ name.as_str().len()
957988
+ generics_len;
958989

959-
let notable_traits = notable_traits_button(&d.output, cx);
990+
let notable_traits = notable_traits_button(&d.output, cx).maybe_display();
960991

961992
let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
962993
header_len += 4;
@@ -983,7 +1014,6 @@ fn assoc_method(
9831014
name = name,
9841015
generics = g.print(cx),
9851016
decl = d.full_print(header_len, indent, cx),
986-
notable_traits = notable_traits.unwrap_or_default(),
9871017
where_clause = print_where_clause(g, cx, indent, end_newline),
9881018
);
9891019
}
@@ -1431,7 +1461,10 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) ->
14311461
}
14321462
}
14331463

1434-
pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option<String> {
1464+
pub(crate) fn notable_traits_button<'a, 'tcx>(
1465+
ty: &'a clean::Type,
1466+
cx: &'a Context<'tcx>,
1467+
) -> Option<impl fmt::Display + 'a + Captures<'tcx>> {
14351468
let mut has_notable_trait = false;
14361469

14371470
if ty.is_unit() {
@@ -1473,15 +1506,16 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Optio
14731506
}
14741507
}
14751508

1476-
if has_notable_trait {
1509+
has_notable_trait.then(|| {
14771510
cx.types_with_notable_traits.borrow_mut().insert(ty.clone());
1478-
Some(format!(
1479-
" <a href=\"#\" class=\"tooltip\" data-notable-ty=\"{ty}\">ⓘ</a>",
1480-
ty = Escape(&format!("{:#}", ty.print(cx))),
1481-
))
1482-
} else {
1483-
None
1484-
}
1511+
fmt::from_fn(|f| {
1512+
write!(
1513+
f,
1514+
" <a href=\"#\" class=\"tooltip\" data-notable-ty=\"{ty}\">ⓘ</a>",
1515+
ty = Escape(&format!("{:#}", ty.print(cx))),
1516+
)
1517+
})
1518+
})
14851519
}
14861520

14871521
fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
@@ -2073,11 +2107,11 @@ pub(crate) fn render_impl_summary(
20732107
) {
20742108
let inner_impl = i.inner_impl();
20752109
let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id));
2076-
let aliases = if aliases.is_empty() {
2077-
String::new()
2078-
} else {
2079-
format!(" data-aliases=\"{}\"", aliases.join(","))
2080-
};
2110+
let aliases = (!aliases.is_empty())
2111+
.then_some(fmt::from_fn(|f| {
2112+
write!(f, " data-aliases=\"{}\"", fmt::from_fn(|f| aliases.iter().joined(",", f)))
2113+
}))
2114+
.maybe_display();
20812115
write!(w, "<section id=\"{id}\" class=\"impl\"{aliases}>");
20822116
render_rightside(w, cx, &i.impl_item, RenderMode::Normal);
20832117
write!(

0 commit comments

Comments
 (0)