diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 39544fa843de8..8534c7b6ba92f 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -162,13 +162,11 @@ crate fn load_attrs<'hir>(cx: &DocContext<'hir>, did: DefId) -> Attrs<'hir> { /// These names are used later on by HTML rendering to generate things like /// source links back to the original item. crate fn record_extern_fqn(cx: &mut DocContext<'_>, did: DefId, kind: ItemType) { - let crate_name = cx.tcx.crate_name(did.krate).to_string(); + let crate_name = cx.tcx.crate_name(did.krate); - let relative = cx.tcx.def_path(did).data.into_iter().filter_map(|elem| { - // extern blocks have an empty name - let s = elem.data.to_string(); - if !s.is_empty() { Some(s) } else { None } - }); + // FIXME: use def_id_to_path instead + let relative = + cx.tcx.def_path(did).data.into_iter().filter_map(|elem| elem.data.get_opt_name()); let fqn = if let ItemType::Macro = kind { // Check to see if it is a macro 2.0 or built-in macro if matches!( diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 19b309d6a30c8..b8e6ea551a101 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -536,3 +536,20 @@ pub(super) fn display_macro_source( } } } + +crate fn join_path_segments(segments: &[Symbol]) -> String { + join_path_segments_with_sep(segments, "::") +} + +crate fn join_path_segments_with_sep(segments: &[Symbol], sep: &str) -> String { + let mut out = String::new(); + // This can't use an iterator intersperse because then it would be borrowing from + // temporary SymbolStrs due to the lifetimes on SymbolStr's Deref impl. + for (i, s) in segments.iter().enumerate() { + if i > 0 { + out.push_str(sep); + } + out.push_str(&s.as_str()) + } + out +} diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index d3831450e1d87..3ad7bff9007ea 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -5,7 +5,9 @@ use rustc_hir::def_id::{CrateNum, DefId, CRATE_DEF_INDEX}; use rustc_middle::middle::privacy::AccessLevels; use rustc_middle::ty::TyCtxt; use rustc_span::symbol::sym; +use rustc_span::Symbol; +use crate::clean::utils::join_path_segments; use crate::clean::{self, ExternalCrate, ItemId, PrimitiveType}; use crate::core::DocContext; use crate::fold::DocFolder; @@ -39,11 +41,11 @@ crate struct Cache { /// URLs when a type is being linked to. External paths are not located in /// this map because the `External` type itself has all the information /// necessary. - crate paths: FxHashMap, ItemType)>, + crate paths: FxHashMap, ItemType)>, /// Similar to `paths`, but only holds external paths. This is only used for /// generating explicit hyperlinks to other crates. - crate external_paths: FxHashMap, ItemType)>, + crate external_paths: FxHashMap, ItemType)>, /// Maps local `DefId`s of exported types to fully qualified paths. /// Unlike 'paths', this mapping ignores any renames that occur @@ -55,7 +57,7 @@ crate struct Cache { /// to the path used if the corresponding type is inlined. By /// doing this, we can detect duplicate impls on a trait page, and only display /// the impl for the inlined type. - crate exact_paths: FxHashMap>, + crate exact_paths: FxHashMap>, /// This map contains information about all known traits of this crate. /// Implementations of a crate should inherit the documentation of the @@ -92,7 +94,7 @@ crate struct Cache { crate masked_crates: FxHashSet, // Private fields only used when initially crawling a crate to build a cache - stack: Vec, + stack: Vec, parent_stack: Vec, parent_is_trait_impl: bool, stripped_mod: bool, @@ -156,7 +158,7 @@ impl Cache { let dst = &render_options.output; let location = e.location(extern_url, extern_url_takes_precedence, dst, tcx); cx.cache.extern_locations.insert(e.crate_num, location); - cx.cache.external_paths.insert(e.def_id(), (vec![name.to_string()], ItemType::Module)); + cx.cache.external_paths.insert(e.def_id(), (vec![name], ItemType::Module)); } // FIXME: avoid this clone (requires implementing Default manually) @@ -165,10 +167,9 @@ impl Cache { let crate_name = tcx.crate_name(def_id.krate); // Recall that we only allow primitive modules to be at the root-level of the crate. // If that restriction is ever lifted, this will have to include the relative paths instead. - cx.cache.external_paths.insert( - def_id, - (vec![crate_name.to_string(), prim.as_sym().to_string()], ItemType::Primitive), - ); + cx.cache + .external_paths + .insert(def_id, (vec![crate_name, prim.as_sym()], ItemType::Primitive)); } krate = CacheBuilder { tcx, cache: &mut cx.cache }.fold_crate(krate); @@ -300,7 +301,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { self.cache.search_index.push(IndexItem { ty: item.type_(), name: s.to_string(), - path: path.join("::"), + path: join_path_segments(path), desc, parent, parent_idx: None, @@ -321,7 +322,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { // Keep track of the fully qualified path for this item. let pushed = match item.name { Some(n) if !n.is_empty() => { - self.cache.stack.push(n.to_string()); + self.cache.stack.push(n); true } _ => false, diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index a3cbb5756fefc..5b721a6e30719 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -19,14 +19,18 @@ 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::Symbol; use rustc_target::spec::abi::Abi; +use crate::clean::utils::join_path_segments; use crate::clean::{self, utils::find_nearest_parent_module, ExternalCrate, ItemId, PrimitiveType}; use crate::formats::item_type::ItemType; use crate::html::escape::Escape; use crate::html::render::cache::ExternalLocation; use crate::html::render::Context; +use super::url_parts_builder::UrlPartsBuilder; + crate trait Print { fn print(self, buffer: &mut Buffer); } @@ -503,7 +507,7 @@ crate fn href_with_root_path( did: DefId, cx: &Context<'_>, root_path: Option<&str>, -) -> Result<(String, ItemType, Vec), HrefError> { +) -> Result<(String, ItemType, Vec), HrefError> { let tcx = cx.tcx(); let def_kind = tcx.def_kind(did); let did = match def_kind { @@ -515,7 +519,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] } } @@ -544,9 +548,9 @@ crate fn href_with_root_path( ExternalLocation::Remote(ref s) => { is_remote = true; let s = s.trim_end_matches('/'); - let mut s = vec![s]; - s.extend(module_fqp[..].iter().map(String::as_str)); - s + let mut builder = UrlPartsBuilder::singleton(s); + builder.extend(module_fqp.iter().copied()); + builder } ExternalLocation::Local => href_relative_parts(module_fqp, relative_to), ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt), @@ -560,50 +564,52 @@ crate fn href_with_root_path( if !is_remote { if let Some(root_path) = root_path { let root = root_path.trim_end_matches('/'); - url_parts.insert(0, root); + url_parts.push_front(root); } } debug!(?url_parts); - let last = &fqp.last().unwrap()[..]; - let filename; + let last = &*fqp.last().unwrap().as_str(); match shortty { ItemType::Module => { url_parts.push("index.html"); } _ => { - filename = format!("{}.{}.html", shortty.as_str(), last); + let filename = format!("{}.{}.html", shortty.as_str(), last); url_parts.push(&filename); } } - Ok((url_parts.join("/"), shortty, fqp.to_vec())) + Ok((url_parts.finish(), shortty, fqp.to_vec())) } -crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec), HrefError> { +crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec), 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<'a>(fqp: &'a [String], relative_to_fqp: &'a [String]) -> Vec<&'a str> { +crate fn href_relative_parts(fqp: &[Symbol], relative_to_fqp: &[String]) -> UrlPartsBuilder { 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 { + if &*f.as_str() != 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()]; + let mut builder: UrlPartsBuilder = + iter::repeat("..").take(dissimilar_part_count).collect(); + builder.extend(fqp_module.iter().copied()); + return builder; } } // 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() + fqp[relative_to_fqp.len()..fqp.len()].iter().copied().collect() // 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() // linking to the same module } else { - Vec::new() + UrlPartsBuilder::new() } } @@ -630,8 +636,8 @@ fn resolved_path<'cx>( if let Ok((_, _, fqp)) = href(did, cx) { format!( "{}::{}", - fqp[..fqp.len() - 1].join("::"), - anchor(did, fqp.last().unwrap(), cx) + join_path_segments(&fqp[..fqp.len() - 1]), + anchor(did, &fqp.last().unwrap().as_str(), cx) ) } else { last.name.to_string() @@ -742,7 +748,7 @@ crate fn anchor<'a, 'cx: 'a>( short_ty, url, short_ty, - fqp.join("::"), + join_path_segments(&fqp), text ) } else { @@ -960,7 +966,7 @@ fn fmt_type<'cx>( url = url, shortty = ItemType::AssocType, name = name, - path = path.join("::") + path = join_path_segments(path), )?; } _ => write!(f, "{}", name)?, diff --git a/src/librustdoc/html/mod.rs b/src/librustdoc/html/mod.rs index 109b0a356db5f..e1bbc784fd1cd 100644 --- a/src/librustdoc/html/mod.rs +++ b/src/librustdoc/html/mod.rs @@ -9,6 +9,7 @@ crate mod render; crate mod sources; crate mod static_files; crate mod toc; +mod url_parts_builder; #[cfg(test)] mod tests; diff --git a/src/librustdoc/html/render/cache.rs b/src/librustdoc/html/render/cache.rs index 2f7214e958ea7..ee3412cd257d0 100644 --- a/src/librustdoc/html/render/cache.rs +++ b/src/librustdoc/html/render/cache.rs @@ -8,6 +8,7 @@ use serde::ser::{Serialize, SerializeStruct, Serializer}; use crate::clean; use crate::clean::types::{FnDecl, FnRetTy, GenericBound, Generics, Type, WherePredicate}; +use crate::clean::utils::join_path_segments; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::markdown::short_markdown_summary; @@ -38,7 +39,7 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt< cache.search_index.push(IndexItem { ty: item.type_(), name: item.name.unwrap().to_string(), - path: fqp[..fqp.len() - 1].join("::"), + path: join_path_segments(&fqp[..fqp.len() - 1]), desc, parent: Some(did), parent_idx: None, @@ -90,7 +91,7 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt< lastpathid += 1; if let Some(&(ref fqp, short)) = paths.get(&defid) { - crate_paths.push((short, fqp.last().unwrap().clone())); + crate_paths.push((short, fqp.last().unwrap().to_string())); Some(pathid) } else { None diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 365d959ad9f3b..0a0162668c744 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -237,10 +237,10 @@ impl<'tcx> Context<'tcx> { if let Some(&(ref names, ty)) = self.cache().paths.get(&it.def_id.expect_def_id()) { let mut path = String::new(); for name in &names[..names.len() - 1] { - path.push_str(name); + path.push_str(&name.as_str()); path.push('/'); } - path.push_str(&item_path(ty, names.last().unwrap())); + path.push_str(&item_path(ty, &names.last().unwrap().as_str())); match self.shared.redirections { Some(ref redirections) => { let mut current_path = String::new(); @@ -248,7 +248,7 @@ impl<'tcx> Context<'tcx> { current_path.push_str(name); current_path.push('/'); } - current_path.push_str(&item_path(ty, names.last().unwrap())); + current_path.push_str(&item_path(ty, &names.last().unwrap().as_str())); redirections.borrow_mut().insert(current_path, path); } None => return layout::redirect(&format!("{}{}", self.root_path(), path)), diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 166e084012724..67afc890b68e8 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -63,6 +63,7 @@ use rustc_span::{ use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; +use crate::clean::utils::join_path_segments; use crate::clean::{self, ItemId, RenderedLink, SelfTy}; use crate::error::Error; use crate::formats::cache::Cache; @@ -2524,7 +2525,7 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec { let fqp = cache.exact_paths.get(&did).cloned().or_else(get_extern); if let Some(path) = fqp { - out.push(path.join("::")); + out.push(join_path_segments(&path)); } }; diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 9943e23b9281c..af27e6196be1a 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -22,6 +22,7 @@ use super::{ ImplRenderingParameters, }; use crate::clean; +use crate::clean::utils::join_path_segments_with_sep; use crate::formats::item_type::ItemType; use crate::formats::{AssocItemRender, Impl, RenderMode}; use crate::html::escape::Escape; @@ -874,7 +875,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra cx.current.join("/") } else { let (ref path, _) = cache.external_paths[&it.def_id.expect_def_id()]; - path[..path.len() - 1].join("/") + join_path_segments_with_sep(&path[..path.len() - 1], "/") }, ty = it.type_(), name = *it.name.as_ref().unwrap() diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 0d5ba8e80d242..6808bcda00636 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -569,7 +569,7 @@ pub(super) fn write_shared( let mut mydst = dst.clone(); for part in &remote_path[..remote_path.len() - 1] { - mydst.push(part); + mydst.push(&*part.as_str()); } cx.shared.ensure_dir(&mydst)?; mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1])); diff --git a/src/librustdoc/html/tests.rs b/src/librustdoc/html/tests.rs index 5d537dabd0c07..dee9f5e5038a8 100644 --- a/src/librustdoc/html/tests.rs +++ b/src/librustdoc/html/tests.rs @@ -1,44 +1,44 @@ use crate::html::format::href_relative_parts; -fn assert_relative_path(expected: &[&str], relative_to_fqp: &[&str], fqp: &[&str]) { +fn assert_relative_path(expected: &str, relative_to_fqp: &[&str], fqp: &[&str]) { let relative_to_fqp: Vec = relative_to_fqp.iter().copied().map(String::from).collect(); let fqp: Vec = fqp.iter().copied().map(String::from).collect(); - assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp)); + assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp).finish()); } #[test] fn href_relative_parts_basic() { let relative_to_fqp = &["std", "vec"]; let fqp = &["std", "iter"]; - assert_relative_path(&["..", "iter"], relative_to_fqp, fqp); + assert_relative_path("../iter", relative_to_fqp, fqp); } #[test] fn href_relative_parts_parent_module() { let relative_to_fqp = &["std", "vec"]; let fqp = &["std"]; - assert_relative_path(&[".."], relative_to_fqp, fqp); + assert_relative_path("..", relative_to_fqp, fqp); } #[test] fn href_relative_parts_different_crate() { let relative_to_fqp = &["std", "vec"]; let fqp = &["core", "iter"]; - assert_relative_path(&["..", "..", "core", "iter"], relative_to_fqp, fqp); + assert_relative_path("../../core/iter", relative_to_fqp, fqp); } #[test] fn href_relative_parts_same_module() { let relative_to_fqp = &["std", "vec"]; let fqp = &["std", "vec"]; - assert_relative_path(&[], relative_to_fqp, fqp); + assert_relative_path("", relative_to_fqp, fqp); } #[test] fn href_relative_parts_child_module() { let relative_to_fqp = &["std"]; let fqp = &["std", "vec"]; - assert_relative_path(&["vec"], relative_to_fqp, fqp); + assert_relative_path("vec", relative_to_fqp, fqp); } #[test] fn href_relative_parts_root() { let relative_to_fqp = &[]; let fqp = &["std"]; - assert_relative_path(&["std"], relative_to_fqp, fqp); + assert_relative_path("std", relative_to_fqp, fqp); } diff --git a/src/librustdoc/html/url_parts_builder.rs b/src/librustdoc/html/url_parts_builder.rs new file mode 100644 index 0000000000000..42017a3b5985c --- /dev/null +++ b/src/librustdoc/html/url_parts_builder.rs @@ -0,0 +1,138 @@ +use rustc_span::Symbol; + +/// A builder that allows efficiently and easily constructing the part of a URL +/// after the domain: `nightly/core/str/struct.Bytes.html`. +/// +/// This type is a wrapper around the final `String` buffer, +/// but its API is like that of a `Vec` of URL components. +#[derive(Debug)] +crate struct UrlPartsBuilder { + buf: String, +} + +impl UrlPartsBuilder { + /// Create an empty buffer. + crate fn new() -> Self { + Self { buf: String::new() } + } + + /// Create an empty buffer with capacity for the specified number of bytes. + fn with_capacity_bytes(count: usize) -> Self { + Self { buf: String::with_capacity(count) } + } + + /// Create a buffer with one URL component. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ```ignore (private-type) + /// let builder = UrlPartsBuilder::singleton("core"); + /// assert_eq!(builder.finish(), "core"); + /// ``` + /// + /// Adding more components afterward. + /// + /// ```ignore (private-type) + /// let mut builder = UrlPartsBuilder::singleton("core"); + /// builder.push("str"); + /// builder.push_front("nightly"); + /// assert_eq!(builder.finish(), "nightly/core/str"); + /// ``` + crate fn singleton(part: &str) -> Self { + Self { buf: part.to_owned() } + } + + /// Push a component onto the buffer. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ```ignore (private-type) + /// let mut builder = UrlPartsBuilder::new(); + /// builder.push("core"); + /// builder.push("str"); + /// builder.push("struct.Bytes.html"); + /// assert_eq!(builder.finish(), "core/str/struct.Bytes.html"); + /// ``` + crate fn push(&mut self, part: &str) { + if !self.buf.is_empty() { + self.buf.push('/'); + } + self.buf.push_str(part); + } + + /// Push a component onto the front of the buffer. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ```ignore (private-type) + /// let mut builder = UrlPartsBuilder::new(); + /// builder.push("core"); + /// builder.push("str"); + /// builder.push_front("nightly"); + /// builder.push("struct.Bytes.html"); + /// assert_eq!(builder.finish(), "nightly/core/str/struct.Bytes.html"); + /// ``` + crate fn push_front(&mut self, part: &str) { + let is_empty = self.buf.is_empty(); + self.buf.reserve(part.len() + if !is_empty { 1 } else { 0 }); + self.buf.insert_str(0, part); + if !is_empty { + self.buf.insert(part.len(), '/'); + } + } + + /// Get the final `String` buffer. + crate fn finish(self) -> String { + self.buf + } +} + +/// This is just a guess at the average length of a URL part, +/// used for [`String::with_capacity`] calls in the [`FromIterator`] +/// and [`Extend`] impls. +/// +/// This is intentionally on the lower end to avoid overallocating. +const AVG_PART_LENGTH: usize = 5; + +impl<'a> FromIterator<&'a str> for UrlPartsBuilder { + fn from_iter>(iter: T) -> Self { + let iter = iter.into_iter(); + let mut builder = Self::with_capacity_bytes(AVG_PART_LENGTH * iter.size_hint().0); + iter.for_each(|part| builder.push(part)); + builder + } +} + +impl<'a> Extend<&'a str> for UrlPartsBuilder { + fn extend>(&mut self, iter: T) { + let iter = iter.into_iter(); + self.buf.reserve(AVG_PART_LENGTH * iter.size_hint().0); + iter.for_each(|part| self.push(part)); + } +} + +impl FromIterator for UrlPartsBuilder { + fn from_iter>(iter: T) -> Self { + let iter = iter.into_iter(); + let mut builder = Self::with_capacity_bytes(AVG_PART_LENGTH * iter.size_hint().0); + iter.for_each(|part| builder.push(&part.as_str())); + builder + } +} + +impl Extend for UrlPartsBuilder { + fn extend>(&mut self, iter: T) { + let iter = iter.into_iter(); + self.buf.reserve(AVG_PART_LENGTH * iter.size_hint().0); + iter.for_each(|part| self.push(&part.as_str())); + } +} + +#[cfg(test)] +mod tests; diff --git a/src/librustdoc/html/url_parts_builder/tests.rs b/src/librustdoc/html/url_parts_builder/tests.rs new file mode 100644 index 0000000000000..43338c95010a0 --- /dev/null +++ b/src/librustdoc/html/url_parts_builder/tests.rs @@ -0,0 +1,54 @@ +use super::*; + +fn t(builder: UrlPartsBuilder, expect: &str) { + assert_eq!(builder.finish(), expect); +} + +#[test] +fn empty() { + t(UrlPartsBuilder::new(), ""); +} + +#[test] +fn singleton() { + t(UrlPartsBuilder::singleton("index.html"), "index.html"); +} + +#[test] +fn push_several() { + let mut builder = UrlPartsBuilder::new(); + builder.push("core"); + builder.push("str"); + builder.push("struct.Bytes.html"); + t(builder, "core/str/struct.Bytes.html"); +} + +#[test] +fn push_front_empty() { + let mut builder = UrlPartsBuilder::new(); + builder.push_front("page.html"); + t(builder, "page.html"); +} + +#[test] +fn push_front_non_empty() { + let mut builder = UrlPartsBuilder::new(); + builder.push("core"); + builder.push("str"); + builder.push("struct.Bytes.html"); + builder.push_front("nightly"); + t(builder, "nightly/core/str/struct.Bytes.html"); +} + +#[test] +fn collect() { + t(["core", "str"].into_iter().collect(), "core/str"); + t(["core", "str", "struct.Bytes.html"].into_iter().collect(), "core/str/struct.Bytes.html"); +} + +#[test] +fn extend() { + let mut builder = UrlPartsBuilder::singleton("core"); + builder.extend(["str", "struct.Bytes.html"]); + t(builder, "core/str/struct.Bytes.html"); +} diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 0031e3915fa40..5f400621ccb6e 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -121,7 +121,7 @@ impl JsonRenderer<'tcx> { }) .0 .last() - .map(Clone::clone), + .map(|s| s.to_string()), visibility: types::Visibility::Public, inner: types::ItemEnum::Trait(trait_item.clone().into_tcx(self.tcx)), span: None, @@ -231,7 +231,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { from_item_id(k.into()), types::ItemSummary { crate_id: k.krate.as_u32(), - path, + path: path.iter().map(|s| s.to_string()).collect(), kind: kind.into_tcx(self.tcx), }, ) diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index a18bd48d72bc4..1175b43baf46c 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -42,13 +42,9 @@ impl Module<'hir> { } // FIXME: Should this be replaced with tcx.def_path_str? -fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec { - let crate_name = tcx.crate_name(did.krate).to_string(); - let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| { - // extern blocks have an empty name - let s = elem.data.to_string(); - if !s.is_empty() { Some(s) } else { None } - }); +fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec { + let crate_name = tcx.crate_name(did.krate); + let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| elem.data.get_opt_name()); std::iter::once(crate_name).chain(relative).collect() } @@ -71,7 +67,7 @@ crate struct RustdocVisitor<'a, 'tcx> { inlining: bool, /// Are the current module and all of its parents public? inside_public_path: bool, - exact_paths: FxHashMap>, + exact_paths: FxHashMap>, } impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {