rustdoc: avoid many Symbol
to String
conversions.
Particularly when constructing file paths and fully qualified paths. This avoids a lot of allocations, speeding things up on almost all examples.
This commit is contained in:
parent
02c9e73e6c
commit
10b1c9aa8b
13 changed files with 198 additions and 120 deletions
|
@ -19,6 +19,7 @@ use rustc_middle::ty;
|
|||
use rustc_middle::ty::DefIdTree;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_span::def_id::CRATE_DEF_INDEX;
|
||||
use rustc_span::{sym, Symbol};
|
||||
use rustc_target::spec::abi::Abi;
|
||||
|
||||
use crate::clean::{
|
||||
|
@ -502,11 +503,45 @@ crate enum HrefError {
|
|||
NotInExternalCache,
|
||||
}
|
||||
|
||||
// This mostly works with sequences of symbols, but sometimes the first item
|
||||
// comes from a string, and in that case we want to trim any trailing `/`.
|
||||
// `syms` can be empty.
|
||||
crate fn join_with_slash(first: Option<&str>, syms: &[Symbol]) -> String {
|
||||
// 64 bytes covers 99.9%+ of cases.
|
||||
let mut s = String::with_capacity(64);
|
||||
if let Some(first) = first {
|
||||
s.push_str(first.trim_end_matches('/'));
|
||||
if !syms.is_empty() {
|
||||
s.push('/');
|
||||
}
|
||||
}
|
||||
if !syms.is_empty() {
|
||||
s.push_str(&syms[0].as_str());
|
||||
for sym in &syms[1..] {
|
||||
s.push('/');
|
||||
s.push_str(&sym.as_str());
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
// Panics if `syms` is empty.
|
||||
crate fn join_with_double_colon(syms: &[Symbol]) -> String {
|
||||
// 64 bytes covers 99.9%+ of cases.
|
||||
let mut s = String::with_capacity(64);
|
||||
s.push_str(&syms[0].as_str());
|
||||
for sym in &syms[1..] {
|
||||
s.push_str("::");
|
||||
s.push_str(&sym.as_str());
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
crate fn href_with_root_path(
|
||||
did: DefId,
|
||||
cx: &Context<'_>,
|
||||
root_path: Option<&str>,
|
||||
) -> Result<(String, ItemType, Vec<String>), HrefError> {
|
||||
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
|
||||
let tcx = cx.tcx();
|
||||
let def_kind = tcx.def_kind(did);
|
||||
let did = match def_kind {
|
||||
|
@ -518,7 +553,7 @@ crate fn href_with_root_path(
|
|||
};
|
||||
let cache = cx.cache();
|
||||
let relative_to = &cx.current;
|
||||
fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
|
||||
fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
|
||||
if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
|
||||
}
|
||||
|
||||
|
@ -533,9 +568,9 @@ crate fn href_with_root_path(
|
|||
let mut is_remote = false;
|
||||
let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
|
||||
Some(&(ref fqp, shortty)) => (fqp, shortty, {
|
||||
let module_fqp = to_module_fqp(shortty, fqp);
|
||||
let module_fqp = to_module_fqp(shortty, fqp.as_slice());
|
||||
debug!(?fqp, ?shortty, ?module_fqp);
|
||||
href_relative_parts(module_fqp, relative_to)
|
||||
href_relative_parts(module_fqp, relative_to).collect()
|
||||
}),
|
||||
None => {
|
||||
if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&did) {
|
||||
|
@ -548,10 +583,12 @@ crate fn href_with_root_path(
|
|||
is_remote = true;
|
||||
let s = s.trim_end_matches('/');
|
||||
let mut builder = UrlPartsBuilder::singleton(s);
|
||||
builder.extend(module_fqp.iter().map(String::as_str));
|
||||
builder.extend(module_fqp.iter().copied());
|
||||
builder
|
||||
}
|
||||
ExternalLocation::Local => href_relative_parts(module_fqp, relative_to),
|
||||
ExternalLocation::Local => {
|
||||
href_relative_parts(module_fqp, relative_to).collect()
|
||||
}
|
||||
ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
|
||||
},
|
||||
)
|
||||
|
@ -567,45 +604,50 @@ crate fn href_with_root_path(
|
|||
}
|
||||
}
|
||||
debug!(?url_parts);
|
||||
let last = &fqp.last().unwrap()[..];
|
||||
match shortty {
|
||||
ItemType::Module => {
|
||||
url_parts.push("index.html");
|
||||
}
|
||||
_ => {
|
||||
let filename = format!("{}.{}.html", shortty.as_str(), last);
|
||||
url_parts.push(&filename);
|
||||
let prefix = shortty.as_str();
|
||||
let last = fqp.last().unwrap();
|
||||
url_parts.push_fmt(format_args!("{}.{}.html", prefix, last));
|
||||
}
|
||||
}
|
||||
Ok((url_parts.finish(), shortty, fqp.to_vec()))
|
||||
}
|
||||
|
||||
crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<String>), HrefError> {
|
||||
crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
|
||||
href_with_root_path(did, cx, None)
|
||||
}
|
||||
|
||||
/// Both paths should only be modules.
|
||||
/// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will
|
||||
/// both need `../iter/trait.Iterator.html` to get at the iterator trait.
|
||||
crate fn href_relative_parts(fqp: &[String], relative_to_fqp: &[String]) -> UrlPartsBuilder {
|
||||
crate fn href_relative_parts<'fqp>(
|
||||
fqp: &'fqp [Symbol],
|
||||
relative_to_fqp: &[Symbol],
|
||||
) -> Box<dyn Iterator<Item = Symbol> + 'fqp> {
|
||||
for (i, (f, r)) in fqp.iter().zip(relative_to_fqp.iter()).enumerate() {
|
||||
// e.g. linking to std::iter from std::vec (`dissimilar_part_count` will be 1)
|
||||
if f != r {
|
||||
let dissimilar_part_count = relative_to_fqp.len() - i;
|
||||
let fqp_module = fqp[i..fqp.len()].iter().map(String::as_str);
|
||||
return iter::repeat("..").take(dissimilar_part_count).chain(fqp_module).collect();
|
||||
let fqp_module = &fqp[i..fqp.len()];
|
||||
return box iter::repeat(sym::dotdot)
|
||||
.take(dissimilar_part_count)
|
||||
.chain(fqp_module.iter().copied());
|
||||
}
|
||||
}
|
||||
// e.g. linking to std::sync::atomic from std::sync
|
||||
if relative_to_fqp.len() < fqp.len() {
|
||||
fqp[relative_to_fqp.len()..fqp.len()].iter().map(String::as_str).collect()
|
||||
box fqp[relative_to_fqp.len()..fqp.len()].iter().copied()
|
||||
// e.g. linking to std::sync from std::sync::atomic
|
||||
} else if fqp.len() < relative_to_fqp.len() {
|
||||
let dissimilar_part_count = relative_to_fqp.len() - fqp.len();
|
||||
iter::repeat("..").take(dissimilar_part_count).collect()
|
||||
box iter::repeat(sym::dotdot).take(dissimilar_part_count)
|
||||
// linking to the same module
|
||||
} else {
|
||||
UrlPartsBuilder::new()
|
||||
box iter::empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -632,14 +674,14 @@ fn resolved_path<'cx>(
|
|||
if let Ok((_, _, fqp)) = href(did, cx) {
|
||||
format!(
|
||||
"{}::{}",
|
||||
fqp[..fqp.len() - 1].join("::"),
|
||||
anchor(did, fqp.last().unwrap(), cx)
|
||||
join_with_double_colon(&fqp[..fqp.len() - 1]),
|
||||
anchor(did, *fqp.last().unwrap(), cx)
|
||||
)
|
||||
} else {
|
||||
last.name.to_string()
|
||||
}
|
||||
} else {
|
||||
anchor(did, last.name.as_str(), cx).to_string()
|
||||
anchor(did, last.name, cx).to_string()
|
||||
};
|
||||
write!(w, "{}{}", path, last.args.print(cx))?;
|
||||
}
|
||||
|
@ -668,30 +710,31 @@ fn primitive_link(
|
|||
needs_termination = true;
|
||||
}
|
||||
Some(&def_id) => {
|
||||
let cname_sym;
|
||||
let loc = match m.extern_locations[&def_id.krate] {
|
||||
ExternalLocation::Remote(ref s) => {
|
||||
cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
|
||||
Some(vec![s.trim_end_matches('/'), cname_sym.as_str()])
|
||||
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 => {
|
||||
cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
|
||||
Some(if cx.current.first().map(|x| &x[..]) == Some(cname_sym.as_str()) {
|
||||
iter::repeat("..").take(cx.current.len() - 1).collect()
|
||||
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 {
|
||||
let cname = iter::once(cname_sym.as_str());
|
||||
iter::repeat("..").take(cx.current.len()).chain(cname).collect()
|
||||
iter::repeat(sym::dotdot)
|
||||
.take(cx.current.len())
|
||||
.chain(iter::once(cname_sym))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
ExternalLocation::Unknown => None,
|
||||
};
|
||||
if let Some(loc) = loc {
|
||||
write!(
|
||||
f,
|
||||
"<a class=\"primitive\" href=\"{}/primitive.{}.html\">",
|
||||
loc.join("/"),
|
||||
prim.as_sym()
|
||||
)?;
|
||||
if let Some(mut loc) = loc {
|
||||
loc.push_fmt(format_args!("primitive.{}.html", prim.as_sym()));
|
||||
write!(f, "<a class=\"primitive\" href=\"{}\">", loc.finish())?;
|
||||
needs_termination = true;
|
||||
}
|
||||
}
|
||||
|
@ -730,7 +773,7 @@ fn tybounds<'a, 'tcx: 'a>(
|
|||
|
||||
crate fn anchor<'a, 'cx: 'a>(
|
||||
did: DefId,
|
||||
text: &'a str,
|
||||
text: Symbol,
|
||||
cx: &'cx Context<'_>,
|
||||
) -> impl fmt::Display + 'a {
|
||||
let parts = href(did, cx);
|
||||
|
@ -742,8 +785,8 @@ crate fn anchor<'a, 'cx: 'a>(
|
|||
short_ty,
|
||||
url,
|
||||
short_ty,
|
||||
fqp.join("::"),
|
||||
text
|
||||
join_with_double_colon(&fqp),
|
||||
&*text.as_str()
|
||||
)
|
||||
} else {
|
||||
write!(f, "{}", text)
|
||||
|
@ -960,7 +1003,7 @@ fn fmt_type<'cx>(
|
|||
url = url,
|
||||
shortty = ItemType::AssocType,
|
||||
name = name,
|
||||
path = path.join("::")
|
||||
path = join_with_double_colon(path),
|
||||
)?;
|
||||
}
|
||||
_ => write!(f, "{}", name)?,
|
||||
|
@ -1270,7 +1313,7 @@ impl clean::Visibility {
|
|||
debug!("path={:?}", path);
|
||||
// modified from `resolved_path()` to work with `DefPathData`
|
||||
let last_name = path.data.last().unwrap().data.get_opt_name().unwrap();
|
||||
let anchor = anchor(vis_did, last_name.as_str(), cx).to_string();
|
||||
let anchor = anchor(vis_did, last_name, cx).to_string();
|
||||
|
||||
let mut s = "pub(in ".to_owned();
|
||||
for seg in &path.data[..path.data.len() - 1] {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue