about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-01-14 20:34:18 +0000
committerbors <bors@rust-lang.org>2022-01-14 20:34:18 +0000
commitb0ec3e09a996f2cb35be7710fd1003c3c38f1667 (patch)
treed16992ea23c034bc46d776ef9aec8fd04172bfdc
parentad46af24713115e7b9b258346e66b9b2d14eacfc (diff)
parentc7147e4e1acd49ea01e6d67a85c270946554783a (diff)
downloadrust-b0ec3e09a996f2cb35be7710fd1003c3c38f1667.tar.gz
rust-b0ec3e09a996f2cb35be7710fd1003c3c38f1667.zip
Auto merge of #91948 - nnethercote:rustdoc-more-Symbols, r=GuillaumeGomez
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.

r? `@GuillaumeGomez`
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--src/librustdoc/clean/inline.rs9
-rw-r--r--src/librustdoc/formats/cache.rs24
-rw-r--r--src/librustdoc/html/format.rs99
-rw-r--r--src/librustdoc/html/render/context.rs20
-rw-r--r--src/librustdoc/html/render/mod.rs7
-rw-r--r--src/librustdoc/html/render/print_item.rs112
-rw-r--r--src/librustdoc/html/render/search_index.rs10
-rw-r--r--src/librustdoc/html/render/write_shared.rs2
-rw-r--r--src/librustdoc/html/tests.rs48
-rw-r--r--src/librustdoc/html/url_parts_builder.rs67
-rw-r--r--src/librustdoc/html/url_parts_builder/tests.rs10
-rw-r--r--src/librustdoc/json/mod.rs4
-rw-r--r--src/librustdoc/visit_ast.rs12
14 files changed, 256 insertions, 169 deletions
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 84cf8878af8..5134d916320 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -567,6 +567,7 @@ symbols! {
         doc_spotlight,
         doctest,
         document_private_items,
+        dotdot: "..",
         dotdot_in_tuple_patterns,
         dotdoteq_in_patterns,
         dreg,
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index b91ba5523e0..a2e612955b3 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -9,7 +9,6 @@ use rustc_data_structures::thin_vec::ThinVec;
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::DefId;
-use rustc_hir::definitions::DefPathData;
 use rustc_hir::Mutability;
 use rustc_metadata::creader::{CStore, LoadedMacro};
 use rustc_middle::ty::{self, TyCtxt};
@@ -164,12 +163,10 @@ 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| {
-        // Filter out extern blocks
-        (elem.data != DefPathData::ForeignMod).then(|| elem.data.to_string())
-    });
+    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/formats/cache.rs b/src/librustdoc/formats/cache.rs
index 6b9ccd37cfb..a8fef4a3178 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -4,13 +4,14 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 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::{sym, Symbol};
 
 use crate::clean::{self, types::ExternalLocation, ExternalCrate, ItemId, PrimitiveType};
 use crate::core::DocContext;
 use crate::fold::DocFolder;
 use crate::formats::item_type::ItemType;
 use crate::formats::Impl;
+use crate::html::format::join_with_double_colon;
 use crate::html::markdown::short_markdown_summary;
 use crate::html::render::search_index::get_function_type_for_search;
 use crate::html::render::IndexItem;
@@ -39,11 +40,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<DefId, (Vec<String>, ItemType)>,
+    crate paths: FxHashMap<DefId, (Vec<Symbol>, ItemType)>,
 
     /// Similar to `paths`, but only holds external paths. This is only used for
     /// generating explicit hyperlinks to other crates.
-    crate external_paths: FxHashMap<DefId, (Vec<String>, ItemType)>,
+    crate external_paths: FxHashMap<DefId, (Vec<Symbol>, ItemType)>,
 
     /// Maps local `DefId`s of exported types to fully qualified paths.
     /// Unlike 'paths', this mapping ignores any renames that occur
@@ -55,7 +56,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<DefId, Vec<String>>,
+    crate exact_paths: FxHashMap<DefId, Vec<Symbol>>,
 
     /// This map contains information about all known traits of this crate.
     /// Implementations of a crate should inherit the documentation of the
@@ -92,7 +93,7 @@ crate struct Cache {
     crate masked_crates: FxHashSet<CrateNum>,
 
     // Private fields only used when initially crawling a crate to build a cache
-    stack: Vec<String>,
+    stack: Vec<Symbol>,
     parent_stack: Vec<DefId>,
     parent_is_trait_impl: bool,
     stripped_mod: bool,
@@ -155,7 +156,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)
@@ -164,10 +165,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);
@@ -299,7 +299,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_with_double_colon(path),
                             desc,
                             parent,
                             parent_idx: None,
@@ -320,7 +320,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 981eb9589e9..8571a6a137f 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -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::{
@@ -29,6 +30,7 @@ use crate::formats::item_type::ItemType;
 use crate::html::escape::Escape;
 use crate::html::render::Context;
 
+use super::url_parts_builder::estimate_item_path_byte_length;
 use super::url_parts_builder::UrlPartsBuilder;
 
 crate trait Print {
@@ -502,11 +504,22 @@ crate enum HrefError {
     NotInExternalCache,
 }
 
+// Panics if `syms` is empty.
+crate fn join_with_double_colon(syms: &[Symbol]) -> String {
+    let mut s = String::with_capacity(estimate_item_path_byte_length(syms.len()));
+    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 +531,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 +546,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 +561,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 +582,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 +652,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 +688,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 +751,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 +763,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 +981,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 +1291,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] {
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 4f3455a9208..865e14f694d 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -11,7 +11,7 @@ use rustc_middle::ty::TyCtxt;
 use rustc_session::Session;
 use rustc_span::edition::Edition;
 use rustc_span::source_map::FileName;
-use rustc_span::symbol::sym;
+use rustc_span::{sym, Symbol};
 
 use super::print_item::{full_path, item_path, print_item};
 use super::search_index::build_index;
@@ -29,7 +29,7 @@ use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
 use crate::formats::FormatRenderer;
 use crate::html::escape::Escape;
-use crate::html::format::Buffer;
+use crate::html::format::{join_with_double_colon, Buffer};
 use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
 use crate::html::{layout, sources};
 use crate::scrape_examples::AllCallLocations;
@@ -45,7 +45,7 @@ use crate::try_err;
 crate struct Context<'tcx> {
     /// Current hierarchy of components leading down to what's currently being
     /// rendered
-    pub(crate) current: Vec<String>,
+    pub(crate) current: Vec<Symbol>,
     /// The current destination folder of where HTML artifacts should be placed.
     /// This changes as the context descends into the module hierarchy.
     crate dst: PathBuf,
@@ -176,7 +176,7 @@ impl<'tcx> Context<'tcx> {
                 title.push_str(" in ");
             }
             // No need to include the namespace for primitive types and keywords
-            title.push_str(&self.current.join("::"));
+            title.push_str(&join_with_double_colon(&self.current));
         };
         title.push_str(" - Rust");
         let tyname = it.type_();
@@ -225,18 +225,18 @@ 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();
                         for name in &self.current {
-                            current_path.push_str(name);
+                            current_path.push_str(&name.as_str());
                             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)),
@@ -634,8 +634,8 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             self.render_redirect_pages = item.is_stripped();
         }
         let scx = &self.shared;
-        let item_name = item.name.as_ref().unwrap().to_string();
-        self.dst.push(&item_name);
+        let item_name = item.name.unwrap();
+        self.dst.push(&*item_name.as_str());
         self.current.push(item_name);
 
         info!("Recursing into {}", self.dst.display());
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index dda74904931..8ba9a6dccac 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -69,8 +69,9 @@ use crate::formats::item_type::ItemType;
 use crate::formats::{AssocItemRender, Impl, RenderMode};
 use crate::html::escape::Escape;
 use crate::html::format::{
-    href, print_abi_with_space, print_constness_with_space, print_default_space,
-    print_generic_bounds, print_where_clause, Buffer, HrefError, PrintWithSpace,
+    href, join_with_double_colon, print_abi_with_space, print_constness_with_space,
+    print_default_space, print_generic_bounds, print_where_clause, Buffer, HrefError,
+    PrintWithSpace,
 };
 use crate::html::highlight;
 use crate::html::markdown::{HeadingOffset, Markdown, MarkdownHtml, MarkdownSummaryLine};
@@ -2515,7 +2516,7 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> {
         let fqp = cache.exact_paths.get(&did).cloned().or_else(get_extern);
 
         if let Some(path) = fqp {
-            out.push(path.join("::"));
+            out.push(join_with_double_colon(&path));
         }
     };
 
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 5431bd8565b..eda637acfc5 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -26,11 +26,13 @@ use crate::formats::item_type::ItemType;
 use crate::formats::{AssocItemRender, Impl, RenderMode};
 use crate::html::escape::Escape;
 use crate::html::format::{
-    print_abi_with_space, print_constness_with_space, print_where_clause, Buffer, PrintWithSpace,
+    join_with_double_colon, print_abi_with_space, print_constness_with_space, print_where_clause,
+    Buffer, PrintWithSpace,
 };
 use crate::html::highlight;
 use crate::html::layout::Page;
 use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
+use crate::html::url_parts_builder::UrlPartsBuilder;
 
 use askama::Template;
 
@@ -40,9 +42,9 @@ const ITEM_TABLE_ROW_OPEN: &str = "<div class=\"item-row\">";
 const ITEM_TABLE_ROW_CLOSE: &str = "</div>";
 
 // A component in a `use` path, like `string` in std::string::ToString
-struct PathComponent<'a> {
+struct PathComponent {
     path: String,
-    name: &'a str,
+    name: Symbol,
 }
 
 #[derive(Template)]
@@ -53,7 +55,7 @@ struct ItemVars<'a> {
     typ: &'a str,
     name: &'a str,
     item_type: &'a str,
-    path_components: Vec<PathComponent<'a>>,
+    path_components: Vec<PathComponent>,
     stability_since_raw: &'a str,
     src_href: Option<&'a str>,
 }
@@ -121,7 +123,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer,
             .take(amt)
             .map(|(i, component)| PathComponent {
                 path: "../".repeat(cur.len() - i - 1),
-                name: component,
+                name: *component,
             })
             .collect()
     };
@@ -262,7 +264,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
     indices.dedup_by_key(|i| {
         (
             items[*i].def_id,
-            if items[*i].name.as_ref().is_some() { Some(full_path(cx, &items[*i])) } else { None },
+            if items[*i].name.is_some() { Some(full_path(cx, &items[*i])) } else { None },
             items[*i].type_(),
             if items[*i].is_import() { *i } else { 0 },
         )
@@ -304,22 +306,18 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
 
                 w.write_str(ITEM_TABLE_ROW_OPEN);
                 match *src {
-                    Some(ref src) => write!(
+                    Some(src) => write!(
                         w,
                         "<div class=\"item-left\"><code>{}extern crate {} as {};",
                         myitem.visibility.print_with_space(myitem.def_id, cx),
-                        anchor(myitem.def_id.expect_def_id(), src.as_str(), cx),
-                        myitem.name.as_ref().unwrap(),
+                        anchor(myitem.def_id.expect_def_id(), src, cx),
+                        myitem.name.unwrap(),
                     ),
                     None => write!(
                         w,
                         "<div class=\"item-left\"><code>{}extern crate {};",
                         myitem.visibility.print_with_space(myitem.def_id, cx),
-                        anchor(
-                            myitem.def_id.expect_def_id(),
-                            myitem.name.as_ref().unwrap().as_str(),
-                            cx
-                        ),
+                        anchor(myitem.def_id.expect_def_id(), myitem.name.unwrap(), cx),
                     ),
                 }
                 w.write_str("</code></div>");
@@ -391,7 +389,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
                              {stab_tags}\
                      </div>\
                      <div class=\"item-right docblock-short\">{docs}</div>",
-                    name = *myitem.name.as_ref().unwrap(),
+                    name = myitem.name.unwrap(),
                     stab_tags = extra_info_tags(myitem, item, cx.tcx()),
                     docs = MarkdownSummaryLine(&doc_value, &myitem.links(cx)).into_string(),
                     class = myitem.type_(),
@@ -463,7 +461,7 @@ fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean::
     let asyncness = f.header.asyncness.print_with_space();
     let unsafety = f.header.unsafety.print_with_space();
     let abi = print_abi_with_space(f.header.abi).to_string();
-    let name = it.name.as_ref().unwrap();
+    let name = it.name.unwrap();
 
     let generics_len = format!("{:#}", f.generics.print(cx)).len();
     let header_len = "fn ".len()
@@ -519,7 +517,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra
                 it.visibility.print_with_space(it.def_id, cx),
                 t.unsafety.print_with_space(),
                 if t.is_auto { "auto " } else { "" },
-                it.name.as_ref().unwrap(),
+                it.name.unwrap(),
                 t.generics.print(cx),
                 bounds
             );
@@ -660,7 +658,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra
     }
 
     fn trait_item(w: &mut Buffer, cx: &Context<'_>, m: &clean::Item, t: &clean::Item) {
-        let name = m.name.as_ref().unwrap();
+        let name = m.name.unwrap();
         info!("Documenting {} on {:?}", name, t.name);
         let item_type = m.type_();
         let id = cx.derive_id(format!("{}.{}", item_type, name));
@@ -857,20 +855,21 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra
         }
     }
 
+    let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..")
+        .take(cx.current.len())
+        .chain(std::iter::once("implementors"))
+        .collect();
+    if it.def_id.is_local() {
+        js_src_path.extend(cx.current.iter().copied());
+    } else {
+        let (ref path, _) = cache.external_paths[&it.def_id.expect_def_id()];
+        js_src_path.extend(path[..path.len() - 1].iter().copied());
+    }
+    js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), it.name.unwrap()));
     write!(
         w,
-        "<script type=\"text/javascript\" \
-                 src=\"{root_path}/implementors/{path}/{ty}.{name}.js\" async>\
-         </script>",
-        root_path = vec![".."; cx.current.len()].join("/"),
-        path = if it.def_id.is_local() {
-            cx.current.join("/")
-        } else {
-            let (ref path, _) = cache.external_paths[&it.def_id.expect_def_id()];
-            path[..path.len() - 1].join("/")
-        },
-        ty = it.type_(),
-        name = *it.name.as_ref().unwrap()
+        "<script type=\"text/javascript\" src=\"{src}\" async></script>",
+        src = js_src_path.finish(),
     );
 }
 
@@ -881,7 +880,7 @@ fn item_trait_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clea
             write!(
                 w,
                 "trait {}{}{} = {};",
-                it.name.as_ref().unwrap(),
+                it.name.unwrap(),
                 t.generics.print(cx),
                 print_where_clause(&t.generics, cx, 0, true),
                 bounds(&t.bounds, true, cx)
@@ -905,7 +904,7 @@ fn item_opaque_ty(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean:
             write!(
                 w,
                 "type {}{}{where_clause} = impl {bounds};",
-                it.name.as_ref().unwrap(),
+                it.name.unwrap(),
                 t.generics.print(cx),
                 where_clause = print_where_clause(&t.generics, cx, 0, true),
                 bounds = bounds(&t.bounds, false, cx),
@@ -944,7 +943,7 @@ fn item_typedef(
             write!(
                 w,
                 "type {}{}{where_clause} = {type_};",
-                it.name.as_ref().unwrap(),
+                it.name.unwrap(),
                 t.generics.print(cx),
                 where_clause = print_where_clause(&t.generics, cx, 0, true),
                 type_ = t.type_.print(cx),
@@ -997,7 +996,7 @@ fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
                    Fields<a href=\"#fields\" class=\"anchor\"></a></h2>"
         );
         for (field, ty) in fields {
-            let name = field.name.as_ref().expect("union field name");
+            let name = field.name.expect("union field name");
             let id = format!("{}.{}", ItemType::StructField, name);
             write!(
                 w,
@@ -1042,7 +1041,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum
                 w,
                 "{}enum {}{}{}",
                 it.visibility.print_with_space(it.def_id, cx),
-                it.name.as_ref().unwrap(),
+                it.name.unwrap(),
                 e.generics.print(cx),
                 print_where_clause(&e.generics, cx, 0, true),
             );
@@ -1057,7 +1056,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum
                 }
                 for v in &e.variants {
                     w.write_str("    ");
-                    let name = v.name.as_ref().unwrap();
+                    let name = v.name.unwrap();
                     match *v.kind {
                         clean::VariantItem(ref var) => match var {
                             clean::Variant::CLike => write!(w, "{}", name),
@@ -1106,15 +1105,14 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum
         );
         document_non_exhaustive(w, it);
         for variant in &e.variants {
-            let id =
-                cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.as_ref().unwrap()));
+            let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap()));
             write!(
                 w,
                 "<h3 id=\"{id}\" class=\"variant small-section-header\">\
                     <a href=\"#{id}\" class=\"anchor field\"></a>\
                     <code>{name}",
                 id = id,
-                name = variant.name.as_ref().unwrap()
+                name = variant.name.unwrap()
             );
             if let clean::VariantItem(clean::Variant::Tuple(ref s)) = *variant.kind {
                 w.write_str("(");
@@ -1140,11 +1138,8 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum
             };
 
             if let Some((heading, fields)) = heading_and_fields {
-                let variant_id = cx.derive_id(format!(
-                    "{}.{}.fields",
-                    ItemType::Variant,
-                    variant.name.as_ref().unwrap()
-                ));
+                let variant_id =
+                    cx.derive_id(format!("{}.{}.fields", ItemType::Variant, variant.name.unwrap()));
                 write!(w, "<div class=\"sub-variant\" id=\"{id}\">", id = variant_id);
                 write!(w, "<h4>{heading}</h4>", heading = heading);
                 document_non_exhaustive(w, variant);
@@ -1154,8 +1149,8 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum
                         clean::StructFieldItem(ref ty) => {
                             let id = cx.derive_id(format!(
                                 "variant.{}.field.{}",
-                                variant.name.as_ref().unwrap(),
-                                field.name.as_ref().unwrap()
+                                variant.name.unwrap(),
+                                field.name.unwrap()
                             ));
                             write!(
                                 w,
@@ -1165,7 +1160,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum
                                     <code>{f}:&nbsp;{t}</code>\
                                 </span>",
                                 id = id,
-                                f = field.name.as_ref().unwrap(),
+                                f = field.name.unwrap(),
                                 t = ty.print(cx)
                             );
                             document(w, cx, field, Some(variant), HeadingOffset::H5);
@@ -1204,7 +1199,7 @@ fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Mac
 
 fn item_proc_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, m: &clean::ProcMacro) {
     wrap_into_docblock(w, |w| {
-        let name = it.name.as_ref().expect("proc-macros always have names");
+        let name = it.name.expect("proc-macros always have names");
         match m.kind {
             MacroKind::Bang => {
                 wrap_item(w, "macro", |w| {
@@ -1248,7 +1243,7 @@ fn item_constant(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, c: &clean::
                 w,
                 "{vis}const {name}: {typ}",
                 vis = it.visibility.print_with_space(it.def_id, cx),
-                name = it.name.as_ref().unwrap(),
+                name = it.name.unwrap(),
                 typ = c.type_.print(cx),
             );
 
@@ -1341,7 +1336,7 @@ fn item_static(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::St
                 "{vis}static {mutability}{name}: {typ}",
                 vis = it.visibility.print_with_space(it.def_id, cx),
                 mutability = s.mutability.print_with_space(),
-                name = it.name.as_ref().unwrap(),
+                name = it.name.unwrap(),
                 typ = s.type_.print(cx)
             );
         });
@@ -1358,7 +1353,7 @@ fn item_foreign_type(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) {
                 w,
                 "    {}type {};\n}}",
                 it.visibility.print_with_space(it.def_id, cx),
-                it.name.as_ref().unwrap(),
+                it.name.unwrap(),
             );
         });
     });
@@ -1410,7 +1405,7 @@ crate fn compare_names(mut lhs: &str, mut rhs: &str) -> Ordering {
 }
 
 pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String {
-    let mut s = cx.current.join("::");
+    let mut s = join_with_double_colon(&cx.current);
     s.push_str("::");
     s.push_str(item.name.unwrap().as_str());
     s
@@ -1526,12 +1521,7 @@ fn render_union(
     tab: &str,
     cx: &Context<'_>,
 ) {
-    write!(
-        w,
-        "{}union {}",
-        it.visibility.print_with_space(it.def_id, cx),
-        it.name.as_ref().unwrap()
-    );
+    write!(w, "{}union {}", it.visibility.print_with_space(it.def_id, cx), it.name.unwrap());
     if let Some(g) = g {
         write!(w, "{}", g.print(cx));
         write!(w, "{}", print_where_clause(g, cx, 0, true));
@@ -1551,7 +1541,7 @@ fn render_union(
                 w,
                 "    {}{}: {},\n{}",
                 field.visibility.print_with_space(field.def_id, cx),
-                field.name.as_ref().unwrap(),
+                field.name.unwrap(),
                 ty.print(cx),
                 tab
             );
@@ -1582,7 +1572,7 @@ fn render_struct(
         "{}{}{}",
         it.visibility.print_with_space(it.def_id, cx),
         if structhead { "struct " } else { "" },
-        it.name.as_ref().unwrap()
+        it.name.unwrap()
     );
     if let Some(g) = g {
         write!(w, "{}", g.print(cx))
@@ -1607,7 +1597,7 @@ fn render_struct(
                         "\n{}    {}{}: {},",
                         tab,
                         field.visibility.print_with_space(field.def_id, cx),
-                        field.name.as_ref().unwrap(),
+                        field.name.unwrap(),
                         ty.print(cx),
                     );
                 }
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index 0fbe090f219..87138b9571c 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -10,6 +10,7 @@ use crate::clean;
 use crate::clean::types::{FnRetTy, Function, GenericBound, Generics, Type, WherePredicate};
 use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
+use crate::html::format::join_with_double_colon;
 use crate::html::markdown::short_markdown_summary;
 use crate::html::render::{IndexItem, IndexItemFunctionType, RenderType, TypeWithKind};
 
@@ -28,7 +29,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_with_double_colon(&fqp[..fqp.len() - 1]),
                 desc,
                 parent: Some(did),
                 parent_idx: None,
@@ -102,7 +103,7 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt<
     struct CrateData<'a> {
         doc: String,
         items: Vec<&'a IndexItem>,
-        paths: Vec<(ItemType, String)>,
+        paths: Vec<(ItemType, Symbol)>,
         // The String is alias name and the vec is the list of the elements with this alias.
         //
         // To be noted: the `usize` elements are indexes to `items`.
@@ -154,7 +155,10 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt<
                 "f",
                 &self.items.iter().map(|item| &item.search_type).collect::<Vec<_>>(),
             )?;
-            crate_data.serialize_field("p", &self.paths)?;
+            crate_data.serialize_field(
+                "p",
+                &self.paths.iter().map(|(it, s)| (it, s.to_string())).collect::<Vec<_>>(),
+            )?;
             if has_aliases {
                 crate_data.serialize_field("a", &self.aliases)?;
             }
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index d5e5af7bbf8..3fa16c6c3b8 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -562,7 +562,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.to_string());
         }
         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 dee9f5e5038..437d3995e29 100644
--- a/src/librustdoc/html/tests.rs
+++ b/src/librustdoc/html/tests.rs
@@ -1,44 +1,50 @@
 use crate::html::format::href_relative_parts;
+use rustc_span::{sym, Symbol};
 
-fn assert_relative_path(expected: &str, relative_to_fqp: &[&str], fqp: &[&str]) {
-    let relative_to_fqp: Vec<String> = relative_to_fqp.iter().copied().map(String::from).collect();
-    let fqp: Vec<String> = fqp.iter().copied().map(String::from).collect();
-    assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp).finish());
+fn assert_relative_path(expected: &[Symbol], relative_to_fqp: &[Symbol], fqp: &[Symbol]) {
+    // No `create_default_session_globals_then` call is needed here because all
+    // the symbols used are static, and no `Symbol::intern` calls occur.
+    assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp).collect::<Vec<_>>());
 }
 
 #[test]
 fn href_relative_parts_basic() {
-    let relative_to_fqp = &["std", "vec"];
-    let fqp = &["std", "iter"];
-    assert_relative_path("../iter", relative_to_fqp, fqp);
+    let relative_to_fqp = &[sym::std, sym::vec];
+    let fqp = &[sym::std, sym::iter];
+    assert_relative_path(&[sym::dotdot, sym::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);
+    let relative_to_fqp = &[sym::std, sym::vec];
+    let fqp = &[sym::std];
+    assert_relative_path(&[sym::dotdot], 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);
+    let relative_to_fqp = &[sym::std, sym::vec];
+    let fqp = &[sym::core, sym::iter];
+    assert_relative_path(&[sym::dotdot, sym::dotdot, sym::core, sym::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);
+    let relative_to_fqp = &[sym::std, sym::vec];
+    let fqp = &[sym::std, sym::vec];
+    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);
+    let relative_to_fqp = &[sym::std];
+    let fqp = &[sym::std, sym::vec];
+    assert_relative_path(&[sym::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);
+    let fqp = &[sym::std];
+    assert_relative_path(&[sym::std], relative_to_fqp, fqp);
 }
diff --git a/src/librustdoc/html/url_parts_builder.rs b/src/librustdoc/html/url_parts_builder.rs
index 918d5e6bd1b..2bb78aa7dc9 100644
--- a/src/librustdoc/html/url_parts_builder.rs
+++ b/src/librustdoc/html/url_parts_builder.rs
@@ -1,3 +1,7 @@
+use std::fmt::{self, Write};
+
+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`.
 ///
@@ -10,6 +14,7 @@ crate struct UrlPartsBuilder {
 
 impl UrlPartsBuilder {
     /// Create an empty buffer.
+    #[allow(dead_code)]
     crate fn new() -> Self {
         Self { buf: String::new() }
     }
@@ -62,6 +67,26 @@ impl UrlPartsBuilder {
         self.buf.push_str(part);
     }
 
+    /// Push a component onto the buffer, using [`format!`]'s formatting syntax.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage (equivalent to the example for [`UrlPartsBuilder::push`]):
+    ///
+    /// ```ignore (private-type)
+    /// let mut builder = UrlPartsBuilder::new();
+    /// builder.push("core");
+    /// builder.push("str");
+    /// builder.push_fmt(format_args!("{}.{}.html", "struct", "Bytes"));
+    /// assert_eq!(builder.finish(), "core/str/struct.Bytes.html");
+    /// ```
+    crate fn push_fmt(&mut self, args: fmt::Arguments<'_>) {
+        if !self.buf.is_empty() {
+            self.buf.push('/');
+        }
+        self.buf.write_fmt(args).unwrap()
+    }
+
     /// Push a component onto the front of the buffer.
     ///
     /// # Examples
@@ -93,10 +118,25 @@ impl UrlPartsBuilder {
 
 /// 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.
+/// and [`Extend`] impls, and for [estimating item path lengths].
 ///
-/// This is intentionally on the lower end to avoid overallocating.
-const AVG_PART_LENGTH: usize = 5;
+/// The value `8` was chosen for two main reasons:
+///
+/// * It seems like a good guess for the average part length.
+/// * jemalloc's size classes are all multiples of eight,
+///   which means that the amount of memory it allocates will often match
+///   the amount requested, avoiding wasted bytes.
+///
+/// [estimating item path lengths]: estimate_item_path_byte_length
+const AVG_PART_LENGTH: usize = 8;
+
+/// Estimate the number of bytes in an item's path, based on how many segments it has.
+///
+/// **Note:** This is only to be used with, e.g., [`String::with_capacity()`];
+/// the return value is just a rough estimate.
+crate const fn estimate_item_path_byte_length(segment_count: usize) -> usize {
+    AVG_PART_LENGTH * segment_count
+}
 
 impl<'a> FromIterator<&'a str> for UrlPartsBuilder {
     fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
@@ -115,5 +155,26 @@ impl<'a> Extend<&'a str> for UrlPartsBuilder {
     }
 }
 
+impl FromIterator<Symbol> for UrlPartsBuilder {
+    fn from_iter<T: IntoIterator<Item = Symbol>>(iter: T) -> Self {
+        // This code has to be duplicated from the `&str` impl because of
+        // `Symbol::as_str`'s lifetimes.
+        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<Symbol> for UrlPartsBuilder {
+    fn extend<T: IntoIterator<Item = Symbol>>(&mut self, iter: T) {
+        // This code has to be duplicated from the `&str` impl because of
+        // `Symbol::as_str`'s lifetimes.
+        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
index 43338c95010..636e1ab5527 100644
--- a/src/librustdoc/html/url_parts_builder/tests.rs
+++ b/src/librustdoc/html/url_parts_builder/tests.rs
@@ -41,6 +41,16 @@ fn push_front_non_empty() {
 }
 
 #[test]
+fn push_fmt() {
+    let mut builder = UrlPartsBuilder::new();
+    builder.push_fmt(format_args!("{}", "core"));
+    builder.push("str");
+    builder.push_front("nightly");
+    builder.push_fmt(format_args!("{}.{}.html", "struct", "Bytes"));
+    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");
diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs
index 88a5c0c5ca2..81fbfd9fdbd 100644
--- a/src/librustdoc/json/mod.rs
+++ b/src/librustdoc/json/mod.rs
@@ -120,7 +120,7 @@ impl<'tcx> 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,
@@ -230,7 +230,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 6f1736afc3b..90cb5d586c2 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -5,7 +5,6 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::DefId;
-use rustc_hir::definitions::DefPathData;
 use rustc_hir::Node;
 use rustc_hir::CRATE_HIR_ID;
 use rustc_middle::middle::privacy::AccessLevel;
@@ -43,12 +42,9 @@ impl Module<'_> {
 }
 
 // FIXME: Should this be replaced with tcx.def_path_str?
-fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<String> {
-    let crate_name = tcx.crate_name(did.krate).to_string();
-    let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| {
-        // Filter out extern blocks
-        (elem.data != DefPathData::ForeignMod).then(|| elem.data.to_string())
-    });
+fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<Symbol> {
+    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<DefId, Vec<String>>,
+    exact_paths: FxHashMap<DefId, Vec<Symbol>>,
 }
 
 impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {