about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--library/std/src/net/ip.rs3
-rw-r--r--src/librustdoc/clean/types.rs33
-rw-r--r--src/librustdoc/html/format.rs457
-rw-r--r--src/librustdoc/html/layout.rs3
-rw-r--r--src/librustdoc/html/mod.rs3
-rw-r--r--src/librustdoc/html/render/context.rs8
-rw-r--r--src/librustdoc/html/render/mod.rs145
-rw-r--r--src/librustdoc/html/render/print_item.rs137
-rw-r--r--src/librustdoc/html/render/write_shared.rs6
-rw-r--r--src/librustdoc/html/static/main.js1842
-rw-r--r--src/librustdoc/html/static/search.js1512
-rw-r--r--src/librustdoc/html/static_files.rs3
-rw-r--r--src/librustdoc/html/tests.rs44
-rw-r--r--src/librustdoc/lib.rs8
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs24
-rw-r--r--src/test/rustdoc-ui/intra-doc/anchors.stderr20
-rw-r--r--src/test/rustdoc-ui/intra-doc/double-anchor.stderr4
-rw-r--r--src/test/rustdoc/assoc-types.rs8
-rw-r--r--src/test/rustdoc/auxiliary/primitive-doc.rs6
-rw-r--r--src/test/rustdoc/check-styled-link.rs2
-rw-r--r--src/test/rustdoc/cross-crate-primitive-doc.rs9
-rw-r--r--src/test/rustdoc/default-trait-method-link.rs4
-rw-r--r--src/test/rustdoc/intra-doc-crate/self.rs4
-rw-r--r--src/test/rustdoc/intra-doc/anchors.rs2
-rw-r--r--src/test/rustdoc/intra-doc/associated-defaults.rs8
-rw-r--r--src/test/rustdoc/intra-doc/associated-items.rs10
-rw-r--r--src/test/rustdoc/intra-doc/basic.rs44
-rw-r--r--src/test/rustdoc/intra-doc/cross-crate/additional_doc.rs2
-rw-r--r--src/test/rustdoc/intra-doc/cross-crate/hidden.rs2
-rw-r--r--src/test/rustdoc/intra-doc/cross-crate/submodule-outer.rs4
-rw-r--r--src/test/rustdoc/intra-doc/disambiguators-removed.rs26
-rw-r--r--src/test/rustdoc/intra-doc/enum-struct-field.rs2
-rw-r--r--src/test/rustdoc/intra-doc/extern-type.rs2
-rw-r--r--src/test/rustdoc/intra-doc/issue-82209.rs2
-rw-r--r--src/test/rustdoc/intra-doc/mod-ambiguity.rs4
-rw-r--r--src/test/rustdoc/intra-doc/prim-precedence.rs4
-rw-r--r--src/test/rustdoc/intra-doc/private.rs6
-rw-r--r--src/test/rustdoc/intra-doc/proc-macro.rs10
-rw-r--r--src/test/rustdoc/intra-doc/pub-use.rs2
-rw-r--r--src/test/rustdoc/intra-doc/raw-ident-self.rs2
-rw-r--r--src/test/rustdoc/intra-doc/reexport-additional-docs.rs8
-rw-r--r--src/test/rustdoc/intra-doc/self.rs28
-rw-r--r--src/test/rustdoc/intra-doc/trait-impl.rs6
-rw-r--r--src/test/rustdoc/intra-doc/trait-item.rs2
-rw-r--r--src/test/rustdoc/intra-link-self-cache.rs4
-rw-r--r--src/test/rustdoc/issue-28478.rs8
-rw-r--r--src/test/rustdoc/issue-55364.rs28
-rw-r--r--src/test/rustdoc/issue-72340.rs2
-rw-r--r--src/test/rustdoc/link-assoc-const.rs4
-rw-r--r--src/test/rustdoc/proc-macro.rs6
-rw-r--r--src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs6
-rw-r--r--src/test/rustdoc/struct-field.rs6
-rw-r--r--src/test/rustdoc/trait-impl-items-links-and-anchors.rs10
-rw-r--r--src/test/rustdoc/trait-self-link.rs2
-rw-r--r--src/tools/rustdoc-js/tester.js12
55 files changed, 2295 insertions, 2254 deletions
diff --git a/library/std/src/net/ip.rs b/library/std/src/net/ip.rs
index da2415e3610..9b629e19be5 100644
--- a/library/std/src/net/ip.rs
+++ b/library/std/src/net/ip.rs
@@ -334,6 +334,8 @@ impl Ipv4Addr {
 
     /// An IPv4 address representing an unspecified address: 0.0.0.0
     ///
+    /// This corresponds to the constant `INADDR_ANY` in other languages.
+    ///
     /// # Examples
     ///
     /// ```
@@ -342,6 +344,7 @@ impl Ipv4Addr {
     /// let addr = Ipv4Addr::UNSPECIFIED;
     /// assert_eq!(addr, Ipv4Addr::new(0, 0, 0, 0));
     /// ```
+    #[doc(alias = "INADDR_ANY")]
     #[stable(feature = "ip_constructors", since = "1.30.0")]
     pub const UNSPECIFIED: Self = Ipv4Addr::new(0, 0, 0, 0);
 
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index f3c9b987eb0..2b25c6a26bc 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -41,6 +41,7 @@ use crate::core::DocContext;
 use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
 use crate::html::render::cache::ExternalLocation;
+use crate::html::render::Context;
 
 use self::FnRetTy::*;
 use self::ItemKind::*;
@@ -193,19 +194,18 @@ impl Item {
         self.attrs.collapsed_doc_value()
     }
 
-    crate fn links(&self, cache: &Cache) -> Vec<RenderedLink> {
+    crate fn links(&self, cx: &Context<'_>) -> Vec<RenderedLink> {
         use crate::html::format::href;
-        use crate::html::render::CURRENT_DEPTH;
 
-        cache
+        cx.cache()
             .intra_doc_links
             .get(&self.def_id)
             .map_or(&[][..], |v| v.as_slice())
             .iter()
-            .filter_map(|ItemLink { link: s, link_text, did, fragment }| {
+            .filter_map(|ItemLink { link: s, link_text, did, ref fragment }| {
                 match *did {
                     Some(did) => {
-                        if let Some((mut href, ..)) = href(did, cache) {
+                        if let Some((mut href, ..)) = href(did, cx) {
                             if let Some(ref fragment) = *fragment {
                                 href.push('#');
                                 href.push_str(fragment);
@@ -219,16 +219,26 @@ impl Item {
                             None
                         }
                     }
+                    // FIXME(83083): using fragments as a side-channel for
+                    // primitive names is very unfortunate
                     None => {
+                        let relative_to = &cx.current;
                         if let Some(ref fragment) = *fragment {
-                            let url = match cache.extern_locations.get(&self.def_id.krate) {
+                            let url = match cx.cache().extern_locations.get(&self.def_id.krate) {
                                 Some(&(_, _, ExternalLocation::Local)) => {
-                                    let depth = CURRENT_DEPTH.with(|l| l.get());
-                                    "../".repeat(depth)
+                                    if relative_to[0] == "std" {
+                                        let depth = relative_to.len() - 1;
+                                        "../".repeat(depth)
+                                    } else {
+                                        let depth = relative_to.len();
+                                        format!("{}std/", "../".repeat(depth))
+                                    }
+                                }
+                                Some(&(_, _, ExternalLocation::Remote(ref s))) => {
+                                    format!("{}/std/", s.trim_end_matches('/'))
                                 }
-                                Some(&(_, _, ExternalLocation::Remote(ref s))) => s.to_string(),
                                 Some(&(_, _, ExternalLocation::Unknown)) | None => format!(
-                                    "https://doc.rust-lang.org/{}",
+                                    "https://doc.rust-lang.org/{}/std/",
                                     crate::doc_rust_lang_org_channel(),
                                 ),
                             };
@@ -238,9 +248,8 @@ impl Item {
                                 original_text: s.clone(),
                                 new_text: link_text.clone(),
                                 href: format!(
-                                    "{}{}std/primitive.{}.html{}",
+                                    "{}primitive.{}.html{}",
                                     url,
-                                    if !url.ends_with('/') { "/" } else { "" },
                                     &fragment[..tail],
                                     &fragment[tail..]
                                 ),
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 29d468e3d23..ca364b9f103 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -20,6 +20,7 @@ use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
 use crate::html::escape::Escape;
 use crate::html::render::cache::ExternalLocation;
+use crate::html::render::Context;
 use crate::html::render::CURRENT_DEPTH;
 
 crate trait Print {
@@ -125,19 +126,18 @@ fn comma_sep<T: fmt::Display>(items: impl Iterator<Item = T>) -> impl fmt::Displ
 
 crate fn print_generic_bounds<'a, 'tcx: 'a>(
     bounds: &'a [clean::GenericBound],
-    cache: &'a Cache,
-    tcx: TyCtxt<'tcx>,
+    cx: &'a Context<'tcx>,
 ) -> impl fmt::Display + 'a + Captures<'tcx> {
     display_fn(move |f| {
         let mut bounds_dup = FxHashSet::default();
 
         for (i, bound) in
-            bounds.iter().filter(|b| bounds_dup.insert(b.print(cache, tcx).to_string())).enumerate()
+            bounds.iter().filter(|b| bounds_dup.insert(b.print(cx).to_string())).enumerate()
         {
             if i > 0 {
                 f.write_str(" + ")?;
             }
-            fmt::Display::fmt(&bound.print(cache, tcx), f)?;
+            fmt::Display::fmt(&bound.print(cx), f)?;
         }
         Ok(())
     })
@@ -146,8 +146,7 @@ crate fn print_generic_bounds<'a, 'tcx: 'a>(
 impl clean::GenericParamDef {
     crate fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'a Cache,
-        tcx: TyCtxt<'tcx>,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| match self.kind {
             clean::GenericParamDefKind::Lifetime => write!(f, "{}", self.name),
@@ -156,17 +155,17 @@ impl clean::GenericParamDef {
 
                 if !bounds.is_empty() {
                     if f.alternate() {
-                        write!(f, ": {:#}", print_generic_bounds(bounds, cache, tcx))?;
+                        write!(f, ": {:#}", print_generic_bounds(bounds, cx))?;
                     } else {
-                        write!(f, ":&nbsp;{}", print_generic_bounds(bounds, cache, tcx))?;
+                        write!(f, ":&nbsp;{}", print_generic_bounds(bounds, cx))?;
                     }
                 }
 
                 if let Some(ref ty) = default {
                     if f.alternate() {
-                        write!(f, " = {:#}", ty.print(cache, tcx))?;
+                        write!(f, " = {:#}", ty.print(cx))?;
                     } else {
-                        write!(f, "&nbsp;=&nbsp;{}", ty.print(cache, tcx))?;
+                        write!(f, "&nbsp;=&nbsp;{}", ty.print(cx))?;
                     }
                 }
 
@@ -174,9 +173,9 @@ impl clean::GenericParamDef {
             }
             clean::GenericParamDefKind::Const { ref ty, .. } => {
                 if f.alternate() {
-                    write!(f, "const {}: {:#}", self.name, ty.print(cache, tcx))
+                    write!(f, "const {}: {:#}", self.name, ty.print(cx))
                 } else {
-                    write!(f, "const {}:&nbsp;{}", self.name, ty.print(cache, tcx))
+                    write!(f, "const {}:&nbsp;{}", self.name, ty.print(cx))
                 }
             }
         })
@@ -186,8 +185,7 @@ impl clean::GenericParamDef {
 impl clean::Generics {
     crate fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'a Cache,
-        tcx: TyCtxt<'tcx>,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| {
             let real_params =
@@ -196,9 +194,9 @@ impl clean::Generics {
                 return Ok(());
             }
             if f.alternate() {
-                write!(f, "<{:#}>", comma_sep(real_params.iter().map(|g| g.print(cache, tcx))))
+                write!(f, "<{:#}>", comma_sep(real_params.iter().map(|g| g.print(cx))))
             } else {
-                write!(f, "&lt;{}&gt;", comma_sep(real_params.iter().map(|g| g.print(cache, tcx))))
+                write!(f, "&lt;{}&gt;", comma_sep(real_params.iter().map(|g| g.print(cx))))
             }
         })
     }
@@ -209,8 +207,7 @@ impl clean::Generics {
 /// * Whether the where-clause needs to add a comma and newline after the last bound.
 crate fn print_where_clause<'a, 'tcx: 'a>(
     gens: &'a clean::Generics,
-    cache: &'a Cache,
-    tcx: TyCtxt<'tcx>,
+    cx: &'a Context<'tcx>,
     indent: usize,
     end_newline: bool,
 ) -> impl fmt::Display + 'a + Captures<'tcx> {
@@ -241,14 +238,14 @@ crate fn print_where_clause<'a, 'tcx: 'a>(
                     if f.alternate() {
                         clause.push_str(&format!(
                             "{:#}: {:#}",
-                            ty.print(cache, tcx),
-                            print_generic_bounds(bounds, cache, tcx)
+                            ty.print(cx),
+                            print_generic_bounds(bounds, cx)
                         ));
                     } else {
                         clause.push_str(&format!(
                             "{}: {}",
-                            ty.print(cache, tcx),
-                            print_generic_bounds(bounds, cache, tcx)
+                            ty.print(cx),
+                            print_generic_bounds(bounds, cx)
                         ));
                     }
                 }
@@ -258,24 +255,16 @@ crate fn print_where_clause<'a, 'tcx: 'a>(
                         lifetime.print(),
                         bounds
                             .iter()
-                            .map(|b| b.print(cache, tcx).to_string())
+                            .map(|b| b.print(cx).to_string())
                             .collect::<Vec<_>>()
                             .join(" + ")
                     ));
                 }
                 clean::WherePredicate::EqPredicate { lhs, rhs } => {
                     if f.alternate() {
-                        clause.push_str(&format!(
-                            "{:#} == {:#}",
-                            lhs.print(cache, tcx),
-                            rhs.print(cache, tcx),
-                        ));
+                        clause.push_str(&format!("{:#} == {:#}", lhs.print(cx), rhs.print(cx),));
                     } else {
-                        clause.push_str(&format!(
-                            "{} == {}",
-                            lhs.print(cache, tcx),
-                            rhs.print(cache, tcx),
-                        ));
+                        clause.push_str(&format!("{} == {}", lhs.print(cx), rhs.print(cx),));
                     }
                 }
             }
@@ -327,8 +316,7 @@ impl clean::Constant {
 impl clean::PolyTrait {
     fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'a Cache,
-        tcx: TyCtxt<'tcx>,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| {
             if !self.generic_params.is_empty() {
@@ -336,20 +324,20 @@ impl clean::PolyTrait {
                     write!(
                         f,
                         "for<{:#}> ",
-                        comma_sep(self.generic_params.iter().map(|g| g.print(cache, tcx)))
+                        comma_sep(self.generic_params.iter().map(|g| g.print(cx)))
                     )?;
                 } else {
                     write!(
                         f,
                         "for&lt;{}&gt; ",
-                        comma_sep(self.generic_params.iter().map(|g| g.print(cache, tcx)))
+                        comma_sep(self.generic_params.iter().map(|g| g.print(cx)))
                     )?;
                 }
             }
             if f.alternate() {
-                write!(f, "{:#}", self.trait_.print(cache, tcx))
+                write!(f, "{:#}", self.trait_.print(cx))
             } else {
-                write!(f, "{}", self.trait_.print(cache, tcx))
+                write!(f, "{}", self.trait_.print(cx))
             }
         })
     }
@@ -358,8 +346,7 @@ impl clean::PolyTrait {
 impl clean::GenericBound {
     crate fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'a Cache,
-        tcx: TyCtxt<'tcx>,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| match self {
             clean::GenericBound::Outlives(lt) => write!(f, "{}", lt.print()),
@@ -370,9 +357,9 @@ impl clean::GenericBound {
                     hir::TraitBoundModifier::MaybeConst => "?const",
                 };
                 if f.alternate() {
-                    write!(f, "{}{:#}", modifier_str, ty.print(cache, tcx))
+                    write!(f, "{}{:#}", modifier_str, ty.print(cx))
                 } else {
-                    write!(f, "{}{}", modifier_str, ty.print(cache, tcx))
+                    write!(f, "{}{}", modifier_str, ty.print(cx))
                 }
             }
         })
@@ -382,8 +369,7 @@ impl clean::GenericBound {
 impl clean::GenericArgs {
     fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'a Cache,
-        tcx: TyCtxt<'tcx>,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| {
             match self {
@@ -401,9 +387,9 @@ impl clean::GenericArgs {
                             }
                             comma = true;
                             if f.alternate() {
-                                write!(f, "{:#}", arg.print(cache, tcx))?;
+                                write!(f, "{:#}", arg.print(cx))?;
                             } else {
-                                write!(f, "{}", arg.print(cache, tcx))?;
+                                write!(f, "{}", arg.print(cx))?;
                             }
                         }
                         for binding in bindings {
@@ -412,9 +398,9 @@ impl clean::GenericArgs {
                             }
                             comma = true;
                             if f.alternate() {
-                                write!(f, "{:#}", binding.print(cache, tcx))?;
+                                write!(f, "{:#}", binding.print(cx))?;
                             } else {
-                                write!(f, "{}", binding.print(cache, tcx))?;
+                                write!(f, "{}", binding.print(cx))?;
                             }
                         }
                         if f.alternate() {
@@ -433,17 +419,17 @@ impl clean::GenericArgs {
                         }
                         comma = true;
                         if f.alternate() {
-                            write!(f, "{:#}", ty.print(cache, tcx))?;
+                            write!(f, "{:#}", ty.print(cx))?;
                         } else {
-                            write!(f, "{}", ty.print(cache, tcx))?;
+                            write!(f, "{}", ty.print(cx))?;
                         }
                     }
                     f.write_str(")")?;
                     if let Some(ref ty) = *output {
                         if f.alternate() {
-                            write!(f, " -> {:#}", ty.print(cache, tcx))?;
+                            write!(f, " -> {:#}", ty.print(cx))?;
                         } else {
-                            write!(f, " -&gt; {}", ty.print(cache, tcx))?;
+                            write!(f, " -&gt; {}", ty.print(cx))?;
                         }
                     }
                 }
@@ -453,56 +439,89 @@ impl clean::GenericArgs {
     }
 }
 
-crate fn href(did: DefId, cache: &Cache) -> Option<(String, ItemType, Vec<String>)> {
+crate fn href(did: DefId, cx: &Context<'_>) -> Option<(String, ItemType, Vec<String>)> {
+    let cache = &cx.cache();
+    let relative_to = &cx.current;
+    fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
+        if shortty == ItemType::Module { &fqp[..] } else { &fqp[..fqp.len() - 1] }
+    }
+
     if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private {
         return None;
     }
 
-    let depth = CURRENT_DEPTH.with(|l| l.get());
-    let (fqp, shortty, mut url) = match cache.paths.get(&did) {
-        Some(&(ref fqp, shortty)) => (fqp, shortty, "../".repeat(depth)),
+    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);
+            href_relative_parts(module_fqp, relative_to)
+        }),
         None => {
             let &(ref fqp, shortty) = cache.external_paths.get(&did)?;
+            let module_fqp = to_module_fqp(shortty, fqp);
             (
                 fqp,
                 shortty,
                 match cache.extern_locations[&did.krate] {
-                    (.., ExternalLocation::Remote(ref s)) => s.to_string(),
-                    (.., ExternalLocation::Local) => "../".repeat(depth),
+                    (.., ExternalLocation::Remote(ref s)) => {
+                        let s = s.trim_end_matches('/');
+                        let mut s = vec![&s[..]];
+                        s.extend(module_fqp[..].iter().map(String::as_str));
+                        s
+                    }
+                    (.., ExternalLocation::Local) => href_relative_parts(module_fqp, relative_to),
                     (.., ExternalLocation::Unknown) => return None,
                 },
             )
         }
     };
-    for component in &fqp[..fqp.len() - 1] {
-        url.push_str(component);
-        url.push('/');
-    }
+    let last = &fqp.last().unwrap()[..];
+    let filename;
     match shortty {
         ItemType::Module => {
-            url.push_str(fqp.last().unwrap());
-            url.push_str("/index.html");
+            url_parts.push("index.html");
         }
         _ => {
-            url.push_str(shortty.as_str());
-            url.push('.');
-            url.push_str(fqp.last().unwrap());
-            url.push_str(".html");
+            filename = format!("{}.{}.html", shortty.as_str(), last);
+            url_parts.push(&filename);
         }
     }
-    Some((url, shortty, fqp.to_vec()))
+    Some((url_parts.join("/"), shortty, fqp.to_vec()))
+}
+
+/// 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> {
+    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 std::iter::repeat("..").take(dissimilar_part_count).chain(fqp_module).collect();
+        }
+    }
+    // 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()
+    // 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();
+        std::iter::repeat("..").take(dissimilar_part_count).collect()
+    // linking to the same module
+    } else {
+        Vec::new()
+    }
 }
 
 /// Used when rendering a `ResolvedPath` structure. This invokes the `path`
 /// rendering function with the necessary arguments for linking to a local path.
-fn resolved_path<'a, 'tcx: 'a>(
+fn resolved_path<'a, 'cx: 'a>(
     w: &mut fmt::Formatter<'_>,
     did: DefId,
     path: &clean::Path,
     print_all: bool,
     use_absolute: bool,
-    cache: &Cache,
-    tcx: TyCtxt<'tcx>,
+    cx: &'cx Context<'_>,
 ) -> fmt::Result {
     let last = path.segments.last().unwrap();
 
@@ -512,22 +531,22 @@ fn resolved_path<'a, 'tcx: 'a>(
         }
     }
     if w.alternate() {
-        write!(w, "{}{:#}", &last.name, last.args.print(cache, tcx))?;
+        write!(w, "{}{:#}", &last.name, last.args.print(cx))?;
     } else {
         let path = if use_absolute {
-            if let Some((_, _, fqp)) = href(did, cache) {
+            if let Some((_, _, fqp)) = href(did, cx) {
                 format!(
                     "{}::{}",
                     fqp[..fqp.len() - 1].join("::"),
-                    anchor(did, fqp.last().unwrap(), cache)
+                    anchor(did, fqp.last().unwrap(), cx)
                 )
             } else {
                 last.name.to_string()
             }
         } else {
-            anchor(did, &*last.name.as_str(), cache).to_string()
+            anchor(did, &*last.name.as_str(), cx).to_string()
         };
-        write!(w, "{}{}", path, last.args.print(cache, tcx))?;
+        write!(w, "{}{}", path, last.args.print(cx))?;
     }
     Ok(())
 }
@@ -585,14 +604,13 @@ fn primitive_link(
 /// Helper to render type parameters
 fn tybounds<'a, 'tcx: 'a>(
     param_names: &'a Option<Vec<clean::GenericBound>>,
-    cache: &'a Cache,
-    tcx: TyCtxt<'tcx>,
+    cx: &'a Context<'tcx>,
 ) -> impl fmt::Display + 'a + Captures<'tcx> {
     display_fn(move |f| match *param_names {
         Some(ref params) => {
             for param in params {
                 write!(f, " + ")?;
-                fmt::Display::fmt(&param.print(cache, tcx), f)?;
+                fmt::Display::fmt(&param.print(cx), f)?;
             }
             Ok(())
         }
@@ -600,9 +618,14 @@ fn tybounds<'a, 'tcx: 'a>(
     })
 }
 
-crate fn anchor<'a>(did: DefId, text: &'a str, cache: &'a Cache) -> impl fmt::Display + 'a {
+crate fn anchor<'a, 'cx: 'a>(
+    did: DefId,
+    text: &'a str,
+    cx: &'cx Context<'_>,
+) -> impl fmt::Display + 'a {
+    let parts = href(did, cx);
     display_fn(move |f| {
-        if let Some((url, short_ty, fqp)) = href(did, cache) {
+        if let Some((url, short_ty, fqp)) = parts {
             write!(
                 f,
                 r#"<a class="{}" href="{}" title="{} {}">{}</a>"#,
@@ -618,12 +641,11 @@ crate fn anchor<'a>(did: DefId, text: &'a str, cache: &'a Cache) -> impl fmt::Di
     })
 }
 
-fn fmt_type(
+fn fmt_type<'cx>(
     t: &clean::Type,
     f: &mut fmt::Formatter<'_>,
     use_absolute: bool,
-    cache: &Cache,
-    tcx: TyCtxt<'_>,
+    cx: &'cx Context<'_>,
 ) -> fmt::Result {
     debug!("fmt_type(t = {:?})", t);
 
@@ -634,69 +656,69 @@ fn fmt_type(
                 f.write_str("dyn ")?;
             }
             // Paths like `T::Output` and `Self::Output` should be rendered with all segments.
-            resolved_path(f, did, path, is_generic, use_absolute, cache, tcx)?;
-            fmt::Display::fmt(&tybounds(param_names, cache, tcx), f)
+            resolved_path(f, did, path, is_generic, use_absolute, cx)?;
+            fmt::Display::fmt(&tybounds(param_names, cx), f)
         }
         clean::Infer => write!(f, "_"),
-        clean::Primitive(prim) => primitive_link(f, prim, prim.as_str(), cache),
+        clean::Primitive(prim) => primitive_link(f, prim, prim.as_str(), &cx.cache()),
         clean::BareFunction(ref decl) => {
             if f.alternate() {
                 write!(
                     f,
                     "{:#}{}{:#}fn{:#}",
-                    decl.print_hrtb_with_space(cache, tcx),
+                    decl.print_hrtb_with_space(cx),
                     decl.unsafety.print_with_space(),
                     print_abi_with_space(decl.abi),
-                    decl.decl.print(cache, tcx),
+                    decl.decl.print(cx),
                 )
             } else {
                 write!(
                     f,
                     "{}{}{}",
-                    decl.print_hrtb_with_space(cache, tcx),
+                    decl.print_hrtb_with_space(cx),
                     decl.unsafety.print_with_space(),
                     print_abi_with_space(decl.abi)
                 )?;
-                primitive_link(f, PrimitiveType::Fn, "fn", cache)?;
-                write!(f, "{}", decl.decl.print(cache, tcx))
+                primitive_link(f, PrimitiveType::Fn, "fn", &cx.cache())?;
+                write!(f, "{}", decl.decl.print(cx))
             }
         }
         clean::Tuple(ref typs) => {
             match &typs[..] {
-                &[] => primitive_link(f, PrimitiveType::Unit, "()", cache),
+                &[] => primitive_link(f, PrimitiveType::Unit, "()", &cx.cache()),
                 &[ref one] => {
-                    primitive_link(f, PrimitiveType::Tuple, "(", cache)?;
+                    primitive_link(f, PrimitiveType::Tuple, "(", &cx.cache())?;
                     // Carry `f.alternate()` into this display w/o branching manually.
-                    fmt::Display::fmt(&one.print(cache, tcx), f)?;
-                    primitive_link(f, PrimitiveType::Tuple, ",)", cache)
+                    fmt::Display::fmt(&one.print(cx), f)?;
+                    primitive_link(f, PrimitiveType::Tuple, ",)", &cx.cache())
                 }
                 many => {
-                    primitive_link(f, PrimitiveType::Tuple, "(", cache)?;
+                    primitive_link(f, PrimitiveType::Tuple, "(", &cx.cache())?;
                     for (i, item) in many.iter().enumerate() {
                         if i != 0 {
                             write!(f, ", ")?;
                         }
-                        fmt::Display::fmt(&item.print(cache, tcx), f)?;
+                        fmt::Display::fmt(&item.print(cx), f)?;
                     }
-                    primitive_link(f, PrimitiveType::Tuple, ")", cache)
+                    primitive_link(f, PrimitiveType::Tuple, ")", &cx.cache())
                 }
             }
         }
         clean::Slice(ref t) => {
-            primitive_link(f, PrimitiveType::Slice, "[", cache)?;
-            fmt::Display::fmt(&t.print(cache, tcx), f)?;
-            primitive_link(f, PrimitiveType::Slice, "]", cache)
+            primitive_link(f, PrimitiveType::Slice, "[", &cx.cache())?;
+            fmt::Display::fmt(&t.print(cx), f)?;
+            primitive_link(f, PrimitiveType::Slice, "]", &cx.cache())
         }
         clean::Array(ref t, ref n) => {
-            primitive_link(f, PrimitiveType::Array, "[", cache)?;
-            fmt::Display::fmt(&t.print(cache, tcx), f)?;
+            primitive_link(f, PrimitiveType::Array, "[", &cx.cache())?;
+            fmt::Display::fmt(&t.print(cx), f)?;
             if f.alternate() {
-                primitive_link(f, PrimitiveType::Array, &format!("; {}]", n), cache)
+                primitive_link(f, PrimitiveType::Array, &format!("; {}]", n), &cx.cache())
             } else {
-                primitive_link(f, PrimitiveType::Array, &format!("; {}]", Escape(n)), cache)
+                primitive_link(f, PrimitiveType::Array, &format!("; {}]", Escape(n)), &cx.cache())
             }
         }
-        clean::Never => primitive_link(f, PrimitiveType::Never, "!", cache),
+        clean::Never => primitive_link(f, PrimitiveType::Never, "!", &cx.cache()),
         clean::RawPointer(m, ref t) => {
             let m = match m {
                 hir::Mutability::Mut => "mut",
@@ -708,15 +730,15 @@ fn fmt_type(
                         primitive_link(
                             f,
                             clean::PrimitiveType::RawPointer,
-                            &format!("*{} {:#}", m, t.print(cache, tcx)),
-                            cache,
+                            &format!("*{} {:#}", m, t.print(cx)),
+                            &cx.cache(),
                         )
                     } else {
                         primitive_link(
                             f,
                             clean::PrimitiveType::RawPointer,
-                            &format!("*{} {}", m, t.print(cache, tcx)),
-                            cache,
+                            &format!("*{} {}", m, t.print(cx)),
+                            &cx.cache(),
                         )
                     }
                 }
@@ -725,9 +747,9 @@ fn fmt_type(
                         f,
                         clean::PrimitiveType::RawPointer,
                         &format!("*{} ", m),
-                        cache,
+                        &cx.cache(),
                     )?;
-                    fmt::Display::fmt(&t.print(cache, tcx), f)
+                    fmt::Display::fmt(&t.print(cx), f)
                 }
             }
         }
@@ -747,15 +769,15 @@ fn fmt_type(
                                 primitive_link(
                                     f,
                                     PrimitiveType::Slice,
-                                    &format!("{}{}{}[{:#}]", amp, lt, m, bt.print(cache, tcx)),
-                                    cache,
+                                    &format!("{}{}{}[{:#}]", amp, lt, m, bt.print(cx)),
+                                    &cx.cache(),
                                 )
                             } else {
                                 primitive_link(
                                     f,
                                     PrimitiveType::Slice,
-                                    &format!("{}{}{}[{}]", amp, lt, m, bt.print(cache, tcx)),
-                                    cache,
+                                    &format!("{}{}{}[{}]", amp, lt, m, bt.print(cx)),
+                                    &cx.cache(),
                                 )
                             }
                         }
@@ -764,20 +786,20 @@ fn fmt_type(
                                 f,
                                 PrimitiveType::Slice,
                                 &format!("{}{}{}[", amp, lt, m),
-                                cache,
+                                &cx.cache(),
                             )?;
                             if f.alternate() {
-                                write!(f, "{:#}", bt.print(cache, tcx))?;
+                                write!(f, "{:#}", bt.print(cx))?;
                             } else {
-                                write!(f, "{}", bt.print(cache, tcx))?;
+                                write!(f, "{}", bt.print(cx))?;
                             }
-                            primitive_link(f, PrimitiveType::Slice, "]", cache)
+                            primitive_link(f, PrimitiveType::Slice, "]", &cx.cache())
                         }
                     }
                 }
                 clean::ResolvedPath { param_names: Some(ref v), .. } if !v.is_empty() => {
                     write!(f, "{}{}{}(", amp, lt, m)?;
-                    fmt_type(&ty, f, use_absolute, cache, tcx)?;
+                    fmt_type(&ty, f, use_absolute, cx)?;
                     write!(f, ")")
                 }
                 clean::Generic(..) => {
@@ -785,21 +807,21 @@ fn fmt_type(
                         f,
                         PrimitiveType::Reference,
                         &format!("{}{}{}", amp, lt, m),
-                        cache,
+                        &cx.cache(),
                     )?;
-                    fmt_type(&ty, f, use_absolute, cache, tcx)
+                    fmt_type(&ty, f, use_absolute, cx)
                 }
                 _ => {
                     write!(f, "{}{}{}", amp, lt, m)?;
-                    fmt_type(&ty, f, use_absolute, cache, tcx)
+                    fmt_type(&ty, f, use_absolute, cx)
                 }
             }
         }
         clean::ImplTrait(ref bounds) => {
             if f.alternate() {
-                write!(f, "impl {:#}", print_generic_bounds(bounds, cache, tcx))
+                write!(f, "impl {:#}", print_generic_bounds(bounds, cx))
             } else {
-                write!(f, "impl {}", print_generic_bounds(bounds, cache, tcx))
+                write!(f, "impl {}", print_generic_bounds(bounds, cx))
             }
         }
         clean::QPath { ref name, ref self_type, ref trait_ } => {
@@ -811,25 +833,15 @@ fn fmt_type(
             };
             if f.alternate() {
                 if should_show_cast {
-                    write!(
-                        f,
-                        "<{:#} as {:#}>::",
-                        self_type.print(cache, tcx),
-                        trait_.print(cache, tcx)
-                    )?
+                    write!(f, "<{:#} as {:#}>::", self_type.print(cx), trait_.print(cx))?
                 } else {
-                    write!(f, "{:#}::", self_type.print(cache, tcx))?
+                    write!(f, "{:#}::", self_type.print(cx))?
                 }
             } else {
                 if should_show_cast {
-                    write!(
-                        f,
-                        "&lt;{} as {}&gt;::",
-                        self_type.print(cache, tcx),
-                        trait_.print(cache, tcx)
-                    )?
+                    write!(f, "&lt;{} as {}&gt;::", self_type.print(cx), trait_.print(cx))?
                 } else {
-                    write!(f, "{}::", self_type.print(cache, tcx))?
+                    write!(f, "{}::", self_type.print(cx))?
                 }
             };
             match *trait_ {
@@ -844,7 +856,7 @@ fn fmt_type(
                 //        everything comes in as a fully resolved QPath (hard to
                 //        look at).
                 box clean::ResolvedPath { did, ref param_names, .. } => {
-                    match href(did, cache) {
+                    match href(did, cx) {
                         Some((ref url, _, ref path)) if !f.alternate() => {
                             write!(
                                 f,
@@ -872,42 +884,40 @@ fn fmt_type(
 impl clean::Type {
     crate fn print<'b, 'a: 'b, 'tcx: 'a>(
         &'a self,
-        cache: &'b Cache,
-        tcx: TyCtxt<'tcx>,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'b + Captures<'tcx> {
-        display_fn(move |f| fmt_type(self, f, false, cache, tcx))
+        display_fn(move |f| fmt_type(self, f, false, cx))
     }
 }
 
 impl clean::Impl {
     crate fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'a Cache,
         use_absolute: bool,
-        tcx: TyCtxt<'tcx>,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| {
             if f.alternate() {
-                write!(f, "impl{:#} ", self.generics.print(cache, tcx))?;
+                write!(f, "impl{:#} ", self.generics.print(cx))?;
             } else {
-                write!(f, "impl{} ", self.generics.print(cache, tcx))?;
+                write!(f, "impl{} ", self.generics.print(cx))?;
             }
 
             if let Some(ref ty) = self.trait_ {
                 if self.negative_polarity {
                     write!(f, "!")?;
                 }
-                fmt::Display::fmt(&ty.print(cache, tcx), f)?;
+                fmt::Display::fmt(&ty.print(cx), f)?;
                 write!(f, " for ")?;
             }
 
             if let Some(ref ty) = self.blanket_impl {
-                fmt_type(ty, f, use_absolute, cache, tcx)?;
+                fmt_type(ty, f, use_absolute, cx)?;
             } else {
-                fmt_type(&self.for_, f, use_absolute, cache, tcx)?;
+                fmt_type(&self.for_, f, use_absolute, cx)?;
             }
 
-            fmt::Display::fmt(&print_where_clause(&self.generics, cache, tcx, 0, true), f)?;
+            fmt::Display::fmt(&print_where_clause(&self.generics, cx, 0, true), f)?;
             Ok(())
         })
     }
@@ -916,8 +926,7 @@ impl clean::Impl {
 impl clean::Arguments {
     crate fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'a Cache,
-        tcx: TyCtxt<'tcx>,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| {
             for (i, input) in self.values.iter().enumerate() {
@@ -925,9 +934,9 @@ impl clean::Arguments {
                     write!(f, "{}: ", input.name)?;
                 }
                 if f.alternate() {
-                    write!(f, "{:#}", input.type_.print(cache, tcx))?;
+                    write!(f, "{:#}", input.type_.print(cx))?;
                 } else {
-                    write!(f, "{}", input.type_.print(cache, tcx))?;
+                    write!(f, "{}", input.type_.print(cx))?;
                 }
                 if i + 1 < self.values.len() {
                     write!(f, ", ")?;
@@ -941,13 +950,14 @@ impl clean::Arguments {
 impl clean::FnRetTy {
     crate fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'a Cache,
-        tcx: TyCtxt<'tcx>,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| match self {
             clean::Return(clean::Tuple(tys)) if tys.is_empty() => Ok(()),
-            clean::Return(ty) if f.alternate() => write!(f, " -> {:#}", ty.print(cache, tcx)),
-            clean::Return(ty) => write!(f, " -&gt; {}", ty.print(cache, tcx)),
+            clean::Return(ty) if f.alternate() => {
+                write!(f, " -> {:#}", ty.print(cx))
+            }
+            clean::Return(ty) => write!(f, " -&gt; {}", ty.print(cx)),
             clean::DefaultReturn => Ok(()),
         })
     }
@@ -956,16 +966,11 @@ impl clean::FnRetTy {
 impl clean::BareFunctionDecl {
     fn print_hrtb_with_space<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'a Cache,
-        tcx: TyCtxt<'tcx>,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| {
             if !self.generic_params.is_empty() {
-                write!(
-                    f,
-                    "for<{}> ",
-                    comma_sep(self.generic_params.iter().map(|g| g.print(cache, tcx)))
-                )
+                write!(f, "for<{}> ", comma_sep(self.generic_params.iter().map(|g| g.print(cx))))
             } else {
                 Ok(())
             }
@@ -976,8 +981,7 @@ impl clean::BareFunctionDecl {
 impl clean::FnDecl {
     crate fn print<'b, 'a: 'b, 'tcx: 'a>(
         &'a self,
-        cache: &'b Cache,
-        tcx: TyCtxt<'tcx>,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'b + Captures<'tcx> {
         display_fn(move |f| {
             let ellipsis = if self.c_variadic { ", ..." } else { "" };
@@ -985,17 +989,17 @@ impl clean::FnDecl {
                 write!(
                     f,
                     "({args:#}{ellipsis}){arrow:#}",
-                    args = self.inputs.print(cache, tcx),
+                    args = self.inputs.print(cx),
                     ellipsis = ellipsis,
-                    arrow = self.output.print(cache, tcx)
+                    arrow = self.output.print(cx)
                 )
             } else {
                 write!(
                     f,
                     "({args}{ellipsis}){arrow}",
-                    args = self.inputs.print(cache, tcx),
+                    args = self.inputs.print(cx),
                     ellipsis = ellipsis,
-                    arrow = self.output.print(cache, tcx)
+                    arrow = self.output.print(cx)
                 )
             }
         })
@@ -1007,25 +1011,23 @@ impl clean::FnDecl {
     /// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is
     ///   necessary.
     /// * `asyncness`: Whether the function is async or not.
-    crate fn full_print<'b, 'a: 'b, 'tcx: 'a>(
+    crate fn full_print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'b Cache,
-        tcx: TyCtxt<'tcx>,
         header_len: usize,
         indent: usize,
         asyncness: hir::IsAsync,
-    ) -> impl fmt::Display + 'b + Captures<'tcx> {
-        display_fn(move |f| self.inner_full_print(cache, tcx, header_len, indent, asyncness, f))
+        cx: &'a Context<'tcx>,
+    ) -> impl fmt::Display + 'a + Captures<'tcx> {
+        display_fn(move |f| self.inner_full_print(header_len, indent, asyncness, f, cx))
     }
 
     fn inner_full_print(
         &self,
-        cache: &Cache,
-        tcx: TyCtxt<'_>,
         header_len: usize,
         indent: usize,
         asyncness: hir::IsAsync,
         f: &mut fmt::Formatter<'_>,
+        cx: &Context<'_>,
     ) -> fmt::Result {
         let amp = if f.alternate() { "&" } else { "&amp;" };
         let mut args = String::new();
@@ -1060,11 +1062,11 @@ impl clean::FnDecl {
                     }
                     clean::SelfExplicit(ref typ) => {
                         if f.alternate() {
-                            args.push_str(&format!("self: {:#}", typ.print(cache, tcx)));
+                            args.push_str(&format!("self: {:#}", typ.print(cx)));
                         } else {
-                            args.push_str(&format!("self: {}", typ.print(cache, tcx)));
+                            args.push_str(&format!("self: {}", typ.print(cx)));
                         }
-                        args_plain.push_str(&format!("self: {:#}", typ.print(cache, tcx)));
+                        args_plain.push_str(&format!("self: {:#}", typ.print(cx)));
                     }
                 }
             } else {
@@ -1078,11 +1080,11 @@ impl clean::FnDecl {
                 }
 
                 if f.alternate() {
-                    args.push_str(&format!("{:#}", input.type_.print(cache, tcx)));
+                    args.push_str(&format!("{:#}", input.type_.print(cx)));
                 } else {
-                    args.push_str(&input.type_.print(cache, tcx).to_string());
+                    args.push_str(&input.type_.print(cx).to_string());
                 }
-                args_plain.push_str(&format!("{:#}", input.type_.print(cache, tcx)));
+                args_plain.push_str(&format!("{:#}", input.type_.print(cx)));
             }
             if i + 1 < self.inputs.values.len() {
                 args.push(',');
@@ -1100,19 +1102,11 @@ impl clean::FnDecl {
         let arrow_plain;
         let arrow = if let hir::IsAsync::Async = asyncness {
             let output = self.sugared_async_return_type();
-            arrow_plain = format!("{:#}", output.print(cache, tcx));
-            if f.alternate() {
-                arrow_plain.clone()
-            } else {
-                format!("{}", output.print(cache, tcx))
-            }
+            arrow_plain = format!("{:#}", output.print(cx));
+            if f.alternate() { arrow_plain.clone() } else { format!("{}", output.print(cx)) }
         } else {
-            arrow_plain = format!("{:#}", self.output.print(cache, tcx));
-            if f.alternate() {
-                arrow_plain.clone()
-            } else {
-                format!("{}", self.output.print(cache, tcx))
-            }
+            arrow_plain = format!("{:#}", self.output.print(cx));
+            if f.alternate() { arrow_plain.clone() } else { format!("{}", self.output.print(cx)) }
         };
 
         let declaration_len = header_len + args_plain.len() + arrow_plain.len();
@@ -1140,9 +1134,8 @@ impl clean::FnDecl {
 impl clean::Visibility {
     crate fn print_with_space<'a, 'tcx: 'a>(
         self,
-        tcx: TyCtxt<'tcx>,
         item_did: DefId,
-        cache: &'a Cache,
+        cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         let to_print = match self {
             clean::Public => "pub ".to_owned(),
@@ -1151,7 +1144,7 @@ impl clean::Visibility {
                 // FIXME(camelid): This may not work correctly if `item_did` is a module.
                 //                 However, rustdoc currently never displays a module's
                 //                 visibility, so it shouldn't matter.
-                let parent_module = find_nearest_parent_module(tcx, item_did);
+                let parent_module = find_nearest_parent_module(cx.tcx(), item_did);
 
                 if vis_did.index == CRATE_DEF_INDEX {
                     "pub(crate) ".to_owned()
@@ -1160,17 +1153,17 @@ impl clean::Visibility {
                     // is the same as no visibility modifier
                     String::new()
                 } else if parent_module
-                    .map(|parent| find_nearest_parent_module(tcx, parent))
+                    .map(|parent| find_nearest_parent_module(cx.tcx(), parent))
                     .flatten()
                     == Some(vis_did)
                 {
                     "pub(super) ".to_owned()
                 } else {
-                    let path = tcx.def_path(vis_did);
+                    let path = cx.tcx().def_path(vis_did);
                     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(), cache).to_string();
+                    let anchor = anchor(vis_did, &last_name.as_str(), cx).to_string();
 
                     let mut s = "pub(in ".to_owned();
                     for seg in &path.data[..path.data.len() - 1] {
@@ -1263,24 +1256,23 @@ impl PrintWithSpace for hir::Mutability {
 }
 
 impl clean::Import {
-    crate fn print<'b, 'a: 'b, 'tcx: 'a>(
+    crate fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'b Cache,
-        tcx: TyCtxt<'tcx>,
-    ) -> impl fmt::Display + 'b + Captures<'tcx> {
+        cx: &'a Context<'tcx>,
+    ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| match self.kind {
             clean::ImportKind::Simple(name) => {
                 if name == self.source.path.last() {
-                    write!(f, "use {};", self.source.print(cache, tcx))
+                    write!(f, "use {};", self.source.print(cx))
                 } else {
-                    write!(f, "use {} as {};", self.source.print(cache, tcx), name)
+                    write!(f, "use {} as {};", self.source.print(cx), name)
                 }
             }
             clean::ImportKind::Glob => {
                 if self.source.path.segments.is_empty() {
                     write!(f, "use *;")
                 } else {
-                    write!(f, "use {}::*;", self.source.print(cache, tcx))
+                    write!(f, "use {}::*;", self.source.print(cx))
                 }
             }
         })
@@ -1288,20 +1280,19 @@ impl clean::Import {
 }
 
 impl clean::ImportSource {
-    crate fn print<'b, 'a: 'b, 'tcx: 'a>(
+    crate fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'b Cache,
-        tcx: TyCtxt<'tcx>,
-    ) -> impl fmt::Display + 'b + Captures<'tcx> {
+        cx: &'a Context<'tcx>,
+    ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| match self.did {
-            Some(did) => resolved_path(f, did, &self.path, true, false, cache, tcx),
+            Some(did) => resolved_path(f, did, &self.path, true, false, cx),
             _ => {
                 for seg in &self.path.segments[..self.path.segments.len() - 1] {
                     write!(f, "{}::", seg.name)?;
                 }
                 let name = self.path.last_name();
                 if let hir::def::Res::PrimTy(p) = self.path.res {
-                    primitive_link(f, PrimitiveType::from(p), &*name, cache)?;
+                    primitive_link(f, PrimitiveType::from(p), &*name, &cx.cache())?;
                 } else {
                     write!(f, "{}", name)?;
                 }
@@ -1312,27 +1303,26 @@ impl clean::ImportSource {
 }
 
 impl clean::TypeBinding {
-    crate fn print<'b, 'a: 'b, 'tcx: 'a>(
+    crate fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'b Cache,
-        tcx: TyCtxt<'tcx>,
-    ) -> impl fmt::Display + 'b + Captures<'tcx> {
+        cx: &'a Context<'tcx>,
+    ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| {
             f.write_str(&*self.name.as_str())?;
             match self.kind {
                 clean::TypeBindingKind::Equality { ref ty } => {
                     if f.alternate() {
-                        write!(f, " = {:#}", ty.print(cache, tcx))?;
+                        write!(f, " = {:#}", ty.print(cx))?;
                     } else {
-                        write!(f, " = {}", ty.print(cache, tcx))?;
+                        write!(f, " = {}", ty.print(cx))?;
                     }
                 }
                 clean::TypeBindingKind::Constraint { ref bounds } => {
                     if !bounds.is_empty() {
                         if f.alternate() {
-                            write!(f, ": {:#}", print_generic_bounds(bounds, cache, tcx))?;
+                            write!(f, ": {:#}", print_generic_bounds(bounds, cx))?;
                         } else {
-                            write!(f, ":&nbsp;{}", print_generic_bounds(bounds, cache, tcx))?;
+                            write!(f, ":&nbsp;{}", print_generic_bounds(bounds, cx))?;
                         }
                     }
                 }
@@ -1357,15 +1347,14 @@ crate fn print_default_space<'a>(v: bool) -> &'a str {
 }
 
 impl clean::GenericArg {
-    crate fn print<'b, 'a: 'b, 'tcx: 'a>(
+    crate fn print<'a, 'tcx: 'a>(
         &'a self,
-        cache: &'b Cache,
-        tcx: TyCtxt<'tcx>,
-    ) -> impl fmt::Display + 'b + Captures<'tcx> {
+        cx: &'a Context<'tcx>,
+    ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| match self {
             clean::GenericArg::Lifetime(lt) => fmt::Display::fmt(&lt.print(), f),
-            clean::GenericArg::Type(ty) => fmt::Display::fmt(&ty.print(cache, tcx), f),
-            clean::GenericArg::Const(ct) => fmt::Display::fmt(&ct.print(tcx), f),
+            clean::GenericArg::Type(ty) => fmt::Display::fmt(&ty.print(cx), f),
+            clean::GenericArg::Const(ct) => fmt::Display::fmt(&ct.print(cx.tcx()), f),
         })
     }
 }
diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs
index 68d70f27c8c..dc67a63d333 100644
--- a/src/librustdoc/html/layout.rs
+++ b/src/librustdoc/html/layout.rs
@@ -113,7 +113,8 @@ crate fn render<T: Print, S: Print>(
     <section class=\"footer\"></section>\
     {after_content}\
     <div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\" \
-       data-search-js=\"{root_path}search-index{suffix}.js\"></div>
+       data-search-index-js=\"{root_path}search-index{suffix}.js\" \
+       data-search-js=\"{static_root_path}search{suffix}.js\"></div>
     <script src=\"{static_root_path}main{suffix}.js\"></script>\
     {extra_scripts}\
 </body>\
diff --git a/src/librustdoc/html/mod.rs b/src/librustdoc/html/mod.rs
index 4318be898ce..60ebdf5690d 100644
--- a/src/librustdoc/html/mod.rs
+++ b/src/librustdoc/html/mod.rs
@@ -8,3 +8,6 @@ crate mod render;
 crate mod sources;
 crate mod static_files;
 crate mod toc;
+
+#[cfg(test)]
+mod tests;
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index df5ff6e106d..d866cf4f4cf 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -40,7 +40,7 @@ use crate::html::{layout, sources};
 crate struct Context<'tcx> {
     /// Current hierarchy of components leading down to what's currently being
     /// rendered
-    pub(super) current: Vec<String>,
+    pub(crate) current: Vec<String>,
     /// The current destination folder of where HTML artifacts should be placed.
     /// This changes as the context descends into the module hierarchy.
     pub(super) dst: PathBuf,
@@ -144,10 +144,14 @@ impl SharedContext<'_> {
 }
 
 impl<'tcx> Context<'tcx> {
-    pub(super) fn tcx(&self) -> TyCtxt<'tcx> {
+    pub(crate) fn tcx(&self) -> TyCtxt<'tcx> {
         self.shared.tcx
     }
 
+    pub(crate) fn cache(&self) -> &Cache {
+        &self.cache
+    }
+
     fn sess(&self) -> &'tcx Session {
         &self.shared.tcx.sess
     }
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 9886b5ceeed..d10b612a737 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -51,7 +51,6 @@ use rustc_hir::def::CtorKind;
 use rustc_hir::def_id::DefId;
 use rustc_hir::Mutability;
 use rustc_middle::middle::stability;
-use rustc_middle::ty::TyCtxt;
 use rustc_span::symbol::{kw, sym, Symbol};
 use serde::ser::SerializeSeq;
 use serde::{Serialize, Serializer};
@@ -61,7 +60,7 @@ use crate::docfs::PathError;
 use crate::error::Error;
 use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
-use crate::formats::{AssocItemRender, FormatRenderer, Impl, RenderMode};
+use crate::formats::{AssocItemRender, Impl, RenderMode};
 use crate::html::escape::Escape;
 use crate::html::format::{
     href, print_abi_with_space, print_default_space, print_generic_bounds, print_where_clause,
@@ -560,11 +559,10 @@ fn document_short(
         return;
     }
     if let Some(s) = item.doc_value() {
-        let mut summary_html = MarkdownSummaryLine(&s, &item.links(&cx.cache)).into_string();
+        let mut summary_html = MarkdownSummaryLine(&s, &item.links(cx)).into_string();
 
         if s.contains('\n') {
-            let link =
-                format!(r#" <a href="{}">Read more</a>"#, naive_assoc_href(item, link, cx.cache()));
+            let link = format!(r#" <a href="{}">Read more</a>"#, naive_assoc_href(item, link, cx));
 
             if let Some(idx) = summary_html.rfind("</p>") {
                 summary_html.insert_str(idx, &link);
@@ -599,7 +597,7 @@ fn document_full(
 ) {
     if let Some(s) = cx.shared.maybe_collapsed_doc_value(item) {
         debug!("Doc block: =====\n{}\n=====", s);
-        render_markdown(w, cx, &*s, item.links(&cx.cache), prefix, is_hidden);
+        render_markdown(w, cx, &*s, item.links(cx), prefix, is_hidden);
     } else if !prefix.is_empty() {
         if is_hidden {
             w.write_str("<div class=\"docblock hidden\">");
@@ -785,7 +783,7 @@ fn render_impls(
     w.write_str(&impls.join(""));
 }
 
-fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>, cache: &Cache) -> String {
+fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String {
     use crate::formats::item_type::ItemType::*;
 
     let name = it.name.as_ref().unwrap();
@@ -799,7 +797,7 @@ fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>, cache: &Cache) ->
         AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id),
         AssocItemLink::Anchor(None) => anchor,
         AssocItemLink::GotoSource(did, _) => {
-            href(did, cache).map(|p| format!("{}{}", p.0, anchor)).unwrap_or(anchor)
+            href(did, cx).map(|p| format!("{}{}", p.0, anchor)).unwrap_or(anchor)
         }
     }
 }
@@ -813,16 +811,14 @@ fn assoc_const(
     extra: &str,
     cx: &Context<'_>,
 ) {
-    let cache = cx.cache();
-    let tcx = cx.tcx();
     write!(
         w,
         "{}{}const <a href=\"{}\" class=\"constant\"><b>{}</b></a>: {}",
         extra,
-        it.visibility.print_with_space(tcx, it.def_id, cache),
-        naive_assoc_href(it, link, cache),
+        it.visibility.print_with_space(it.def_id, cx),
+        naive_assoc_href(it, link, cx),
         it.name.as_ref().unwrap(),
-        ty.print(cache, tcx)
+        ty.print(cx)
     );
 }
 
@@ -833,21 +829,20 @@ fn assoc_type(
     default: Option<&clean::Type>,
     link: AssocItemLink<'_>,
     extra: &str,
-    cache: &Cache,
-    tcx: TyCtxt<'_>,
+    cx: &Context<'_>,
 ) {
     write!(
         w,
         "{}type <a href=\"{}\" class=\"type\">{}</a>",
         extra,
-        naive_assoc_href(it, link, cache),
+        naive_assoc_href(it, link, cx),
         it.name.as_ref().unwrap()
     );
     if !bounds.is_empty() {
-        write!(w, ": {}", print_generic_bounds(bounds, cache, tcx))
+        write!(w, ": {}", print_generic_bounds(bounds, cx))
     }
     if let Some(default) = default {
-        write!(w, " = {}", default.print(cache, tcx))
+        write!(w, " = {}", default.print(cx))
     }
 }
 
@@ -897,8 +892,6 @@ fn render_assoc_item(
         parent: ItemType,
         cx: &Context<'_>,
     ) {
-        let cache = cx.cache();
-        let tcx = cx.tcx();
         let name = meth.name.as_ref().unwrap();
         let href = match link {
             AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id),
@@ -912,19 +905,19 @@ fn render_assoc_item(
                     ItemType::TyMethod
                 };
 
-                href(did, cache)
+                href(did, cx)
                     .map(|p| format!("{}#{}.{}", p.0, ty, name))
                     .unwrap_or_else(|| format!("#{}.{}", ty, name))
             }
         };
-        let vis = meth.visibility.print_with_space(tcx, meth.def_id, cache).to_string();
+        let vis = meth.visibility.print_with_space(meth.def_id, cx).to_string();
         let constness = header.constness.print_with_space();
         let asyncness = header.asyncness.print_with_space();
         let unsafety = header.unsafety.print_with_space();
         let defaultness = print_default_space(meth.is_default());
         let abi = print_abi_with_space(header.abi).to_string();
         // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
-        let generics_len = format!("{:#}", g.print(cache, tcx)).len();
+        let generics_len = format!("{:#}", g.print(cx)).len();
         let mut header_len = "fn ".len()
             + vis.len()
             + constness.len()
@@ -958,10 +951,10 @@ fn render_assoc_item(
             abi,
             href = href,
             name = name,
-            generics = g.print(cache, tcx),
-            decl = d.full_print(cache, tcx, header_len, indent, header.asyncness),
-            notable_traits = notable_traits_decl(&d, cache, tcx),
-            where_clause = print_where_clause(g, cache, tcx, indent, end_newline),
+            generics = g.print(cx),
+            decl = d.full_print(header_len, indent, header.asyncness, cx),
+            notable_traits = notable_traits_decl(&d, cx),
+            where_clause = print_where_clause(g, cx, indent, end_newline),
         )
     }
     match *item.kind {
@@ -988,8 +981,7 @@ fn render_assoc_item(
             default.as_ref(),
             link,
             if parent == ItemType::Trait { "    " } else { "" },
-            cx.cache(),
-            cx.tcx(),
+            cx,
         ),
         _ => panic!("render_assoc_item called on non-associated-item"),
     }
@@ -1076,11 +1068,9 @@ fn render_assoc_items(
                 RenderMode::Normal
             }
             AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => {
-                let id = cx.derive_id(small_url_encode(format!(
-                    "deref-methods-{:#}",
-                    type_.print(cache, tcx)
-                )));
-                debug!("Adding {} to deref id map", type_.print(cache, tcx));
+                let id =
+                    cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx))));
+                debug!("Adding {} to deref id map", type_.print(cx));
                 cx.deref_id_map.borrow_mut().insert(type_.def_id_full(cache).unwrap(), id.clone());
                 write!(
                     w,
@@ -1089,8 +1079,8 @@ fn render_assoc_items(
                          <a href=\"#{id}\" class=\"anchor\"></a>\
                      </h2>",
                     id = id,
-                    trait_ = trait_.print(cache, tcx),
-                    type_ = type_.print(cache, tcx),
+                    trait_ = trait_.print(cx),
+                    type_ = type_.print(cx),
                 );
                 RenderMode::ForDeref { mut_: deref_mut_ }
             }
@@ -1242,36 +1232,34 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, cache: &Cache) -> bo
     }
 }
 
-fn notable_traits_decl(decl: &clean::FnDecl, cache: &Cache, tcx: TyCtxt<'_>) -> String {
+fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String {
     let mut out = Buffer::html();
     let mut trait_ = String::new();
 
-    if let Some(did) = decl.output.def_id_full(cache) {
-        if let Some(impls) = cache.impls.get(&did) {
+    if let Some(did) = decl.output.def_id_full(cx.cache()) {
+        if let Some(impls) = cx.cache().impls.get(&did) {
             for i in impls {
                 let impl_ = i.inner_impl();
-                if impl_
-                    .trait_
-                    .def_id()
-                    .map_or(false, |d| cache.traits.get(&d).map(|t| t.is_notable).unwrap_or(false))
-                {
+                if impl_.trait_.def_id().map_or(false, |d| {
+                    cx.cache().traits.get(&d).map(|t| t.is_notable).unwrap_or(false)
+                }) {
                     if out.is_empty() {
                         write!(
                             &mut out,
                             "<h3 class=\"notable\">Notable traits for {}</h3>\
                              <code class=\"content\">",
-                            impl_.for_.print(cache, tcx)
+                            impl_.for_.print(cx)
                         );
-                        trait_.push_str(&impl_.for_.print(cache, tcx).to_string());
+                        trait_.push_str(&impl_.for_.print(cx).to_string());
                     }
 
                     //use the "where" class here to make it small
                     write!(
                         &mut out,
                         "<span class=\"where fmt-newline\">{}</span>",
-                        impl_.print(cache, false, tcx)
+                        impl_.print(false, cx)
                     );
-                    let t_did = impl_.trait_.def_id_full(cache).unwrap();
+                    let t_did = impl_.trait_.def_id_full(cx.cache()).unwrap();
                     for it in &impl_.items {
                         if let clean::TypedefItem(ref tydef, _) = *it.kind {
                             out.push_str("<span class=\"where fmt-newline\">    ");
@@ -1282,8 +1270,7 @@ fn notable_traits_decl(decl: &clean::FnDecl, cache: &Cache, tcx: TyCtxt<'_>) ->
                                 Some(&tydef.type_),
                                 AssocItemLink::GotoSource(t_did, &FxHashSet::default()),
                                 "",
-                                cache,
-                                tcx,
+                                cx,
                             );
                             out.push_str(";</span>");
                         }
@@ -1322,18 +1309,18 @@ fn render_impl(
     // in documentation pages for trait with automatic implementations like "Send" and "Sync".
     aliases: &[String],
 ) {
-    let traits = &cx.cache.traits;
     let tcx = cx.tcx();
     let cache = cx.cache();
+    let traits = &cache.traits;
     let trait_ = i.trait_did_full(cache).map(|did| &traits[&did]);
 
     if render_mode == RenderMode::Normal {
         let id = cx.derive_id(match i.inner_impl().trait_ {
             Some(ref t) => {
                 if is_on_foreign_type {
-                    get_id_for_impl_on_foreign_type(&i.inner_impl().for_, t, cache, tcx)
+                    get_id_for_impl_on_foreign_type(&i.inner_impl().for_, t, cx)
                 } else {
-                    format!("impl-{}", small_url_encode(format!("{:#}", t.print(cache, tcx))))
+                    format!("impl-{}", small_url_encode(format!("{:#}", t.print(cx))))
                 }
             }
             None => "impl".to_string(),
@@ -1345,7 +1332,7 @@ fn render_impl(
         };
         if let Some(use_absolute) = use_absolute {
             write!(w, "<h3 id=\"{}\" class=\"impl\"{}><code class=\"in-band\">", id, aliases);
-            write!(w, "{}", i.inner_impl().print(cache, use_absolute, tcx));
+            write!(w, "{}", i.inner_impl().print(use_absolute, cx));
             if show_def_docs {
                 for it in &i.inner_impl().items {
                     if let clean::TypedefItem(ref tydef, _) = *it.kind {
@@ -1357,8 +1344,7 @@ fn render_impl(
                             Some(&tydef.type_),
                             AssocItemLink::Anchor(None),
                             "",
-                            cache,
-                            tcx,
+                            cx,
                         );
                         w.write_str(";</span>");
                     }
@@ -1371,7 +1357,7 @@ fn render_impl(
                 "<h3 id=\"{}\" class=\"impl\"{}><code class=\"in-band\">{}</code>",
                 id,
                 aliases,
-                i.inner_impl().print(cache, false, tcx)
+                i.inner_impl().print(false, cx)
             );
         }
         write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
@@ -1398,7 +1384,7 @@ fn render_impl(
                 "<div class=\"docblock\">{}</div>",
                 Markdown(
                     &*dox,
-                    &i.impl_item.links(&cx.cache),
+                    &i.impl_item.links(cx),
                     &mut ids,
                     cx.shared.codes,
                     cx.shared.edition,
@@ -1495,8 +1481,7 @@ fn render_impl(
                     Some(&tydef.type_),
                     link.anchor(if trait_.is_some() { &source_id } else { &id }),
                     "",
-                    cx.cache(),
-                    tcx,
+                    cx,
                 );
                 w.write_str("</code>");
                 write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
@@ -1546,8 +1531,7 @@ fn render_impl(
                     default.as_ref(),
                     link.anchor(if trait_.is_some() { &source_id } else { &id }),
                     "",
-                    cx.cache(),
-                    tcx,
+                    cx,
                 );
                 w.write_str("</code>");
                 write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
@@ -1853,7 +1837,6 @@ fn small_url_encode(s: String) -> String {
 fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) {
     if let Some(v) = cx.cache.impls.get(&it.def_id) {
         let mut used_links = FxHashSet::default();
-        let tcx = cx.tcx();
         let cache = cx.cache();
 
         {
@@ -1888,9 +1871,9 @@ fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) {
                     .iter()
                     .filter_map(|it| {
                         if let Some(ref i) = it.inner_impl().trait_ {
-                            let i_display = format!("{:#}", i.print(cache, tcx));
+                            let i_display = format!("{:#}", i.print(cx));
                             let out = Escape(&i_display);
-                            let encoded = small_url_encode(format!("{:#}", i.print(cache, tcx)));
+                            let encoded = small_url_encode(format!("{:#}", i.print(cx)));
                             let generated = format!(
                                 "<a href=\"#impl-{}\">{}{}</a>",
                                 encoded,
@@ -1962,7 +1945,6 @@ fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) {
 
 fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &Vec<Impl>) {
     let c = cx.cache();
-    let tcx = cx.tcx();
 
     debug!("found Deref: {:?}", impl_);
     if let Some((target, real_target)) =
@@ -2011,11 +1993,8 @@ fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &V
                     out,
                     "<a class=\"sidebar-title\" href=\"#{}\">Methods from {}&lt;Target={}&gt;</a>",
                     id,
-                    Escape(&format!(
-                        "{:#}",
-                        impl_.inner_impl().trait_.as_ref().unwrap().print(c, tcx)
-                    )),
-                    Escape(&format!("{:#}", real_target.print(c, tcx))),
+                    Escape(&format!("{:#}", impl_.inner_impl().trait_.as_ref().unwrap().print(cx))),
+                    Escape(&format!("{:#}", real_target.print(cx))),
                 );
                 // We want links' order to be reproducible so we don't use unstable sort.
                 ret.sort();
@@ -2071,27 +2050,20 @@ fn sidebar_struct(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, s: &clea
 fn get_id_for_impl_on_foreign_type(
     for_: &clean::Type,
     trait_: &clean::Type,
-    cache: &Cache,
-    tcx: TyCtxt<'_>,
+    cx: &Context<'_>,
 ) -> String {
-    small_url_encode(format!(
-        "impl-{:#}-for-{:#}",
-        trait_.print(cache, tcx),
-        for_.print(cache, tcx)
-    ))
+    small_url_encode(format!("impl-{:#}-for-{:#}", trait_.print(cx), for_.print(cx),))
 }
 
-fn extract_for_impl_name(
-    item: &clean::Item,
-    cache: &Cache,
-    tcx: TyCtxt<'_>,
-) -> Option<(String, String)> {
+fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> {
     match *item.kind {
         clean::ItemKind::ImplItem(ref i) => {
             if let Some(ref trait_) = i.trait_ {
+                // Alternative format produces no URLs,
+                // so this parameter does nothing.
                 Some((
-                    format!("{:#}", i.for_.print(cache, tcx)),
-                    get_id_for_impl_on_foreign_type(&i.for_, trait_, cache, tcx),
+                    format!("{:#}", i.for_.print(cx)),
+                    get_id_for_impl_on_foreign_type(&i.for_, trait_, cx),
                 ))
             } else {
                 None
@@ -2172,7 +2144,6 @@ fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean
 
     if let Some(implementors) = cx.cache.implementors.get(&it.def_id) {
         let cache = cx.cache();
-        let tcx = cx.tcx();
         let mut res = implementors
             .iter()
             .filter(|i| {
@@ -2181,7 +2152,7 @@ fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean
                     .def_id_full(cache)
                     .map_or(false, |d| !cx.cache.paths.contains_key(&d))
             })
-            .filter_map(|i| extract_for_impl_name(&i.impl_item, cache, tcx))
+            .filter_map(|i| extract_for_impl_name(&i.impl_item, cx))
             .collect::<Vec<_>>();
 
         if !res.is_empty() {
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index ff9e4d031da..42b79503017 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -15,9 +15,8 @@ use super::{
     render_impl, render_stability_since_raw, write_srclink, AssocItemLink, Context,
 };
 use crate::clean::{self, GetDefId};
-use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
-use crate::formats::{AssocItemRender, FormatRenderer, Impl, RenderMode};
+use crate::formats::{AssocItemRender, Impl, RenderMode};
 use crate::html::escape::Escape;
 use crate::html::format::{print_abi_with_space, print_where_clause, Buffer, PrintWithSpace};
 use crate::html::highlight;
@@ -268,15 +267,15 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
                     Some(ref src) => write!(
                         w,
                         "<tr><td><code>{}extern crate {} as {};",
-                        myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()),
-                        anchor(myitem.def_id, &*src.as_str(), cx.cache()),
+                        myitem.visibility.print_with_space(myitem.def_id, cx),
+                        anchor(myitem.def_id, &*src.as_str(), cx),
                         myitem.name.as_ref().unwrap(),
                     ),
                     None => write!(
                         w,
                         "<tr><td><code>{}extern crate {};",
-                        myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()),
-                        anchor(myitem.def_id, &*myitem.name.as_ref().unwrap().as_str(), cx.cache()),
+                        myitem.visibility.print_with_space(myitem.def_id, cx),
+                        anchor(myitem.def_id, &*myitem.name.as_ref().unwrap().as_str(), cx),
                     ),
                 }
                 w.write_str("</code></td></tr>");
@@ -286,8 +285,8 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
                 write!(
                     w,
                     "<tr><td><code>{}{}</code></td></tr>",
-                    myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()),
-                    import.print(cx.cache(), cx.tcx()),
+                    myitem.visibility.print_with_space(myitem.def_id, cx),
+                    import.print(cx),
                 );
             }
 
@@ -318,7 +317,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
                      </tr>",
                     name = *myitem.name.as_ref().unwrap(),
                     stab_tags = extra_info_tags(myitem, item, cx.tcx()),
-                    docs = MarkdownSummaryLine(&doc_value, &myitem.links(&cx.cache)).into_string(),
+                    docs = MarkdownSummaryLine(&doc_value, &myitem.links(cx)).into_string(),
                     class = myitem.type_(),
                     add = add,
                     stab = stab.unwrap_or_else(String::new),
@@ -387,13 +386,13 @@ fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) ->
 fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean::Function) {
     let header_len = format!(
         "{}{}{}{}{:#}fn {}{:#}",
-        it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        it.visibility.print_with_space(it.def_id, cx),
         f.header.constness.print_with_space(),
         f.header.asyncness.print_with_space(),
         f.header.unsafety.print_with_space(),
         print_abi_with_space(f.header.abi),
         it.name.as_ref().unwrap(),
-        f.generics.print(cx.cache(), cx.tcx())
+        f.generics.print(cx),
     )
     .len();
     w.write_str("<pre class=\"rust fn\">");
@@ -402,22 +401,22 @@ fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean::
         w,
         "{vis}{constness}{asyncness}{unsafety}{abi}fn \
          {name}{generics}{decl}{notable_traits}{where_clause}</pre>",
-        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        vis = it.visibility.print_with_space(it.def_id, cx),
         constness = f.header.constness.print_with_space(),
         asyncness = f.header.asyncness.print_with_space(),
         unsafety = f.header.unsafety.print_with_space(),
         abi = print_abi_with_space(f.header.abi),
         name = it.name.as_ref().unwrap(),
-        generics = f.generics.print(cx.cache(), cx.tcx()),
-        where_clause = print_where_clause(&f.generics, cx.cache(), cx.tcx(), 0, true),
-        decl = f.decl.full_print(cx.cache(), cx.tcx(), header_len, 0, f.header.asyncness),
-        notable_traits = notable_traits_decl(&f.decl, cx.cache(), cx.tcx()),
+        generics = f.generics.print(cx),
+        where_clause = print_where_clause(&f.generics, cx, 0, true),
+        decl = f.decl.full_print(header_len, 0, f.header.asyncness, cx),
+        notable_traits = notable_traits_decl(&f.decl, cx),
     );
     document(w, cx, it, None)
 }
 
 fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) {
-    let bounds = bounds(&t.bounds, false, cx.cache(), cx.tcx());
+    let bounds = bounds(&t.bounds, false, cx);
     let types = t.items.iter().filter(|m| m.is_associated_type()).collect::<Vec<_>>();
     let consts = t.items.iter().filter(|m| m.is_associated_const()).collect::<Vec<_>>();
     let required = t.items.iter().filter(|m| m.is_ty_method()).collect::<Vec<_>>();
@@ -430,16 +429,16 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra
         write!(
             w,
             "{}{}{}trait {}{}{}",
-            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+            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(),
-            t.generics.print(cx.cache(), cx.tcx()),
+            t.generics.print(cx),
             bounds
         );
 
         if !t.generics.where_predicates.is_empty() {
-            write!(w, "{}", print_where_clause(&t.generics, cx.cache(), cx.tcx(), 0, true));
+            write!(w, "{}", print_where_clause(&t.generics, cx, 0, true));
         } else {
             w.write_str(" ");
         }
@@ -634,8 +633,8 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra
         let (mut synthetic, mut concrete): (Vec<&&Impl>, Vec<&&Impl>) =
             local.iter().partition(|i| i.inner_impl().synthetic);
 
-        synthetic.sort_by(|a, b| compare_impl(a, b, cx.cache(), cx.tcx()));
-        concrete.sort_by(|a, b| compare_impl(a, b, cx.cache(), cx.tcx()));
+        synthetic.sort_by(|a, b| compare_impl(a, b, cx));
+        concrete.sort_by(|a, b| compare_impl(a, b, cx));
 
         if !foreign.is_empty() {
             write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", "");
@@ -740,9 +739,9 @@ fn item_trait_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clea
         w,
         "trait {}{}{} = {};</pre>",
         it.name.as_ref().unwrap(),
-        t.generics.print(cx.cache(), cx.tcx()),
-        print_where_clause(&t.generics, cx.cache(), cx.tcx(), 0, true),
-        bounds(&t.bounds, true, cx.cache(), cx.tcx())
+        t.generics.print(cx),
+        print_where_clause(&t.generics, cx, 0, true),
+        bounds(&t.bounds, true, cx)
     );
 
     document(w, cx, it, None);
@@ -761,9 +760,9 @@ fn item_opaque_ty(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean:
         w,
         "type {}{}{where_clause} = impl {bounds};</pre>",
         it.name.as_ref().unwrap(),
-        t.generics.print(cx.cache(), cx.tcx()),
-        where_clause = print_where_clause(&t.generics, cx.cache(), cx.tcx(), 0, true),
-        bounds = bounds(&t.bounds, false, cx.cache(), cx.tcx()),
+        t.generics.print(cx),
+        where_clause = print_where_clause(&t.generics, cx, 0, true),
+        bounds = bounds(&t.bounds, false, cx),
     );
 
     document(w, cx, it, None);
@@ -782,9 +781,9 @@ fn item_typedef(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::T
         w,
         "type {}{}{where_clause} = {type_};</pre>",
         it.name.as_ref().unwrap(),
-        t.generics.print(cx.cache(), cx.tcx()),
-        where_clause = print_where_clause(&t.generics, cx.cache(), cx.tcx(), 0, true),
-        type_ = t.type_.print(cx.cache(), cx.tcx()),
+        t.generics.print(cx),
+        where_clause = print_where_clause(&t.generics, cx, 0, true),
+        type_ = t.type_.print(cx),
     );
 
     document(w, cx, it, None);
@@ -831,7 +830,7 @@ fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
                 id = id,
                 name = name,
                 shortty = ItemType::StructField,
-                ty = ty.print(cx.cache(), cx.tcx()),
+                ty = ty.print(cx),
             );
             if let Some(stability_class) = field.stability_class(cx.tcx()) {
                 write!(w, "<span class=\"stab {stab}\"></span>", stab = stability_class);
@@ -849,10 +848,10 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum
         write!(
             w,
             "{}enum {}{}{}",
-            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+            it.visibility.print_with_space(it.def_id, cx),
             it.name.as_ref().unwrap(),
-            e.generics.print(cx.cache(), cx.tcx()),
-            print_where_clause(&e.generics, cx.cache(), cx.tcx(), 0, true),
+            e.generics.print(cx),
+            print_where_clause(&e.generics, cx, 0, true),
         );
         if e.variants.is_empty() && !e.variants_stripped {
             w.write_str(" {}");
@@ -874,7 +873,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum
                                 if i > 0 {
                                     w.write_str(",&nbsp;")
                                 }
-                                write!(w, "{}", ty.print(cx.cache(), cx.tcx()));
+                                write!(w, "{}", ty.print(cx));
                             }
                             w.write_str(")");
                         }
@@ -924,7 +923,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum
                     if i > 0 {
                         w.write_str(",&nbsp;");
                     }
-                    write!(w, "{}", ty.print(cx.cache(), cx.tcx()));
+                    write!(w, "{}", ty.print(cx));
                 }
                 w.write_str(")");
             }
@@ -961,7 +960,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum
                              </span>",
                             id = id,
                             f = field.name.as_ref().unwrap(),
-                            t = ty.print(cx.cache(), cx.tcx())
+                            t = ty.print(cx)
                         );
                         document(w, cx, field, Some(variant));
                     }
@@ -1030,9 +1029,9 @@ fn item_constant(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, c: &clean::
     write!(
         w,
         "{vis}const {name}: {typ}",
-        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        vis = it.visibility.print_with_space(it.def_id, cx),
         name = it.name.as_ref().unwrap(),
-        typ = c.type_.print(cx.cache(), cx.tcx()),
+        typ = c.type_.print(cx),
     );
 
     let value = c.value(cx.tcx());
@@ -1102,7 +1101,7 @@ fn item_struct(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::St
                     item_type = ItemType::StructField,
                     id = id,
                     name = field.name.as_ref().unwrap(),
-                    ty = ty.print(cx.cache(), cx.tcx())
+                    ty = ty.print(cx)
                 );
                 document(w, cx, field, Some(it));
             }
@@ -1117,10 +1116,10 @@ fn item_static(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::St
     write!(
         w,
         "{vis}static {mutability}{name}: {typ}</pre>",
-        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        vis = it.visibility.print_with_space(it.def_id, cx),
         mutability = s.mutability.print_with_space(),
         name = it.name.as_ref().unwrap(),
-        typ = s.type_.print(cx.cache(), cx.tcx())
+        typ = s.type_.print(cx)
     );
     document(w, cx, it, None)
 }
@@ -1131,7 +1130,7 @@ fn item_foreign_type(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) {
     write!(
         w,
         "    {}type {};\n}}</pre>",
-        it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        it.visibility.print_with_space(it.def_id, cx),
         it.name.as_ref().unwrap(),
     );
 
@@ -1195,12 +1194,7 @@ pub(super) fn item_path(ty: ItemType, name: &str) -> String {
     }
 }
 
-fn bounds(
-    t_bounds: &[clean::GenericBound],
-    trait_alias: bool,
-    cache: &Cache,
-    tcx: TyCtxt<'_>,
-) -> String {
+fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>) -> String {
     let mut bounds = String::new();
     if !t_bounds.is_empty() {
         if !trait_alias {
@@ -1210,7 +1204,7 @@ fn bounds(
             if i > 0 {
                 bounds.push_str(" + ");
             }
-            bounds.push_str(&p.print(cache, tcx).to_string());
+            bounds.push_str(&p.print(cx).to_string());
         }
     }
     bounds
@@ -1240,17 +1234,12 @@ fn render_stability_since(
     )
 }
 
-fn compare_impl<'a, 'b>(
-    lhs: &'a &&Impl,
-    rhs: &'b &&Impl,
-    cache: &Cache,
-    tcx: TyCtxt<'_>,
-) -> Ordering {
-    let lhs = format!("{}", lhs.inner_impl().print(cache, false, tcx));
-    let rhs = format!("{}", rhs.inner_impl().print(cache, false, tcx));
+fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl, cx: &Context<'_>) -> Ordering {
+    let lhss = format!("{}", lhs.inner_impl().print(false, cx));
+    let rhss = format!("{}", rhs.inner_impl().print(false, cx));
 
     // lhs and rhs are formatted as HTML, which may be unnecessary
-    compare_names(&lhs, &rhs)
+    compare_names(&lhss, &rhss)
 }
 
 fn render_implementor(
@@ -1300,13 +1289,13 @@ fn render_union(
     write!(
         w,
         "{}{}{}",
-        it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        it.visibility.print_with_space(it.def_id, cx),
         if structhead { "union " } else { "" },
         it.name.as_ref().unwrap()
     );
     if let Some(g) = g {
-        write!(w, "{}", g.print(cx.cache(), cx.tcx()));
-        write!(w, "{}", print_where_clause(&g, cx.cache(), cx.tcx(), 0, true));
+        write!(w, "{}", g.print(cx));
+        write!(w, "{}", print_where_clause(&g, cx, 0, true));
     }
 
     write!(w, " {{\n{}", tab);
@@ -1322,9 +1311,9 @@ fn render_union(
             write!(
                 w,
                 "    {}{}: {},\n{}",
-                field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()),
+                field.visibility.print_with_space(field.def_id, cx),
                 field.name.as_ref().unwrap(),
-                ty.print(cx.cache(), cx.tcx()),
+                ty.print(cx),
                 tab
             );
         }
@@ -1352,17 +1341,17 @@ fn render_struct(
     write!(
         w,
         "{}{}{}",
-        it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        it.visibility.print_with_space(it.def_id, cx),
         if structhead { "struct " } else { "" },
         it.name.as_ref().unwrap()
     );
     if let Some(g) = g {
-        write!(w, "{}", g.print(cx.cache(), cx.tcx()))
+        write!(w, "{}", g.print(cx))
     }
     match ty {
         CtorKind::Fictive => {
             if let Some(g) = g {
-                write!(w, "{}", print_where_clause(g, cx.cache(), cx.tcx(), 0, true),)
+                write!(w, "{}", print_where_clause(g, cx, 0, true),)
             }
             w.write_str(" {");
             let count_fields =
@@ -1378,9 +1367,9 @@ fn render_struct(
                         w,
                         "\n{}    {}{}: {},",
                         tab,
-                        field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()),
+                        field.visibility.print_with_space(field.def_id, cx),
                         field.name.as_ref().unwrap(),
-                        ty.print(cx.cache(), cx.tcx()),
+                        ty.print(cx),
                     );
                 }
             }
@@ -1412,8 +1401,8 @@ fn render_struct(
                         write!(
                             w,
                             "{}{}",
-                            field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()),
-                            ty.print(cx.cache(), cx.tcx()),
+                            field.visibility.print_with_space(field.def_id, cx),
+                            ty.print(cx),
                         )
                     }
                     _ => unreachable!(),
@@ -1421,14 +1410,14 @@ fn render_struct(
             }
             w.write_str(")");
             if let Some(g) = g {
-                write!(w, "{}", print_where_clause(g, cx.cache(), cx.tcx(), 0, false),)
+                write!(w, "{}", print_where_clause(g, cx, 0, false),)
             }
             w.write_str(";");
         }
         CtorKind::Const => {
             // Needed for PhantomData.
             if let Some(g) = g {
-                write!(w, "{}", print_where_clause(g, cx.cache(), cx.tcx(), 0, false),)
+                write!(w, "{}", print_where_clause(g, cx, 0, false),)
             }
             w.write_str(";");
         }
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index 8fb6d68f3c6..78bcd40af75 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -16,7 +16,6 @@ use crate::clean::Crate;
 use crate::config::{EmitType, RenderOptions};
 use crate::docfs::PathError;
 use crate::error::Error;
-use crate::formats::FormatRenderer;
 use crate::html::{layout, static_files};
 
 crate static FILES_UNVERSIONED: Lazy<FxHashMap<&str, &[u8]>> = Lazy::new(|| {
@@ -223,6 +222,7 @@ pub(super) fn write_shared(
             &format!(" = {}", serde_json::to_string(&themes).unwrap()),
         ),
     )?;
+    write_minify("search.js", static_files::SEARCH_JS)?;
     write_minify("settings.js", static_files::SETTINGS_JS)?;
     if cx.shared.include_sources {
         write_minify("source-script.js", static_files::sidebar::SOURCE_SCRIPT)?;
@@ -410,7 +410,7 @@ pub(super) fn write_shared(
     write_crate("search-index.js", &|| {
         let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
         v.push_str(&all_indexes.join(",\\\n"));
-        v.push_str("\\\n}');\ninitSearch(searchIndex);");
+        v.push_str("\\\n}');\nif (window.initSearch) {window.initSearch(searchIndex)};");
         Ok(v.into_bytes())
     })?;
 
@@ -500,7 +500,7 @@ pub(super) fn write_shared(
                     None
                 } else {
                     Some(Implementor {
-                        text: imp.inner_impl().print(cx.cache(), false, cx.tcx()).to_string(),
+                        text: imp.inner_impl().print(false, cx).to_string(),
                         synthetic: imp.inner_impl().synthetic,
                         types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()),
                     })
diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js
index d71cc15a457..f017fd846b1 100644
--- a/src/librustdoc/html/static/main.js
+++ b/src/librustdoc/html/static/main.js
@@ -1,4 +1,3 @@
-// ignore-tidy-filelength
 // Local js definitions:
 /* global addClass, getSettingValue, hasClass */
 /* global onEach, onEachLazy, hasOwnProperty, removeClass, updateLocalStorage */
@@ -44,6 +43,7 @@ if (!DOMTokenList.prototype.remove) {
         window.rootPath = rustdocVars.attributes["data-root-path"].value;
         window.currentCrate = rustdocVars.attributes["data-current-crate"].value;
         window.searchJS = rustdocVars.attributes["data-search-js"].value;
+        window.searchIndexJS = rustdocVars.attributes["data-search-index-js"].value;
     }
     var sidebarVars = document.getElementById("sidebar-vars");
     if (sidebarVars) {
@@ -77,14 +77,6 @@ function getVirtualKey(ev) {
     return String.fromCharCode(c);
 }
 
-function getSearchInput() {
-    return document.getElementsByClassName("search-input")[0];
-}
-
-function getSearchElement() {
-    return document.getElementById("search");
-}
-
 var THEME_PICKER_ELEMENT_ID = "theme-picker";
 var THEMES_ELEMENT_ID = "theme-choices";
 
@@ -101,16 +93,6 @@ function getNakedUrl() {
     return window.location.href.split("?")[0].split("#")[0];
 }
 
-// Sets the focus on the search bar at the top of the page
-function focusSearchBar() {
-    getSearchInput().focus();
-}
-
-// Removes the focus from the search bar.
-function defocusSearchBar() {
-    getSearchInput().blur();
-}
-
 function showThemeButtonState() {
     var themePicker = getThemePickerElement();
     var themeChoices = getThemesElement();
@@ -173,67 +155,154 @@ function hideThemeButtonState() {
 (function() {
     "use strict";
 
-    // This mapping table should match the discriminants of
-    // `rustdoc::html::item_type::ItemType` type in Rust.
-    var itemTypes = ["mod",
-                     "externcrate",
-                     "import",
-                     "struct",
-                     "enum",
-                     "fn",
-                     "type",
-                     "static",
-                     "trait",
-                     "impl",
-                     "tymethod",
-                     "method",
-                     "structfield",
-                     "variant",
-                     "macro",
-                     "primitive",
-                     "associatedtype",
-                     "constant",
-                     "associatedconstant",
-                     "union",
-                     "foreigntype",
-                     "keyword",
-                     "existential",
-                     "attr",
-                     "derive",
-                     "traitalias"];
+    window.searchState = {
+      loadingText: "Loading search results...",
+      input: document.getElementsByClassName("search-input")[0],
+      outputElement: function() {
+        return document.getElementById("search");
+      },
+      title: null,
+      titleBeforeSearch: document.title,
+      timeout: null,
+      // On the search screen, so you remain on the last tab you opened.
+      //
+      // 0 for "In Names"
+      // 1 for "In Parameters"
+      // 2 for "In Return Types"
+      currentTab: 0,
+      mouseMovedAfterSearch: true,
+      clearInputTimeout: function() {
+        if (searchState.timeout !== null) {
+            clearTimeout(searchState.timeout);
+            searchState.timeout = null;
+        }
+      },
+      // Sets the focus on the search bar at the top of the page
+      focus: function() {
+          searchState.input.focus();
+      },
+      // Removes the focus from the search bar.
+      defocus: function() {
+          searchState.input.blur();
+      },
+      showResults: function(search) {
+        if (search === null || typeof search === 'undefined') {
+            search = searchState.outputElement();
+        }
+        addClass(main, "hidden");
+        removeClass(search, "hidden");
+        searchState.mouseMovedAfterSearch = false;
+        document.title = searchState.title;
+      },
+      hideResults: function(search) {
+        if (search === null || typeof search === 'undefined') {
+            search = searchState.outputElement();
+        }
+        addClass(search, "hidden");
+        removeClass(main, "hidden");
+        document.title = searchState.titleBeforeSearch;
+        // We also remove the query parameter from the URL.
+        if (searchState.browserSupportsHistoryApi()) {
+            history.replaceState("", window.currentCrate + " - Rust",
+                getNakedUrl() + window.location.hash);
+        }
+      },
+      getQueryStringParams: function() {
+        var params = {};
+        window.location.search.substring(1).split("&").
+            map(function(s) {
+                var pair = s.split("=");
+                params[decodeURIComponent(pair[0])] =
+                    typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]);
+            });
+        return params;
+      },
+      putBackSearch: function(search_input) {
+        var search = searchState.outputElement();
+        if (search_input.value !== "" && hasClass(search, "hidden")) {
+            searchState.showResults(search);
+            if (searchState.browserSupportsHistoryApi()) {
+                var extra = "?search=" + encodeURIComponent(search_input.value);
+                history.replaceState(search_input.value, "",
+                    getNakedUrl() + extra + window.location.hash);
+            }
+            document.title = searchState.title;
+        }
+      },
+      browserSupportsHistoryApi: function() {
+          return window.history && typeof window.history.pushState === "function";
+      },
+      setup: function() {
+        var search_input = searchState.input;
+        if (!searchState.input) {
+            return;
+        }
+        function loadScript(url) {
+            var script = document.createElement('script');
+            script.src = url;
+            document.head.append(script);
+        }
 
-    var disableShortcuts = getSettingValue("disable-shortcuts") === "true";
-    var search_input = getSearchInput();
-    var searchTimeout = null;
-    var toggleAllDocsId = "toggle-all-docs";
+        var searchLoaded = false;
+        function loadSearch() {
+            if (!searchLoaded) {
+                searchLoaded = true;
+                loadScript(window.searchJS);
+                loadScript(window.searchIndexJS);
+            }
+        }
 
-    // On the search screen, so you remain on the last tab you opened.
-    //
-    // 0 for "In Names"
-    // 1 for "In Parameters"
-    // 2 for "In Return Types"
-    var currentTab = 0;
+        search_input.addEventListener("focus", function() {
+            searchState.putBackSearch(this);
+            search_input.origPlaceholder = searchState.input.placeholder;
+            search_input.placeholder = "Type your search here.";
+            loadSearch();
+        });
+        search_input.addEventListener("blur", function() {
+            search_input.placeholder = searchState.input.origPlaceholder;
+        });
 
-    var mouseMovedAfterSearch = true;
+        document.addEventListener("mousemove", function() {
+          searchState.mouseMovedAfterSearch = true;
+        });
 
-    var titleBeforeSearch = document.title;
-    var searchTitle = null;
+        search_input.removeAttribute('disabled');
 
-    function removeEmptyStringsFromArray(x) {
-        for (var i = 0, len = x.length; i < len; ++i) {
-            if (x[i] === "") {
-                x.splice(i, 1);
-                i -= 1;
-            }
+        // `crates{version}.js` should always be loaded before this script, so we can use it safely.
+        searchState.addCrateDropdown(window.ALL_CRATES);
+        var params = searchState.getQueryStringParams();
+        if (params.search !== undefined) {
+            var search = searchState.outputElement();
+            search.innerHTML = "<h3 style=\"text-align: center;\">" +
+               searchState.loadingText + "</h3>";
+            searchState.showResults(search);
+            loadSearch();
         }
-    }
+      },
+      addCrateDropdown: function(crates) {
+        var elem = document.getElementById("crate-search");
 
-    function clearInputTimeout() {
-        if (searchTimeout !== null) {
-            clearTimeout(searchTimeout);
-            searchTimeout = null;
+        if (!elem) {
+            return;
         }
-    }
+        var savedCrate = getSettingValue("saved-filter-crate");
+        for (var i = 0, len = crates.length; i < len; ++i) {
+            var option = document.createElement("option");
+            option.value = crates[i];
+            option.innerText = crates[i];
+            elem.appendChild(option);
+            // Set the crate filter from saved storage, if the current page has the saved crate
+            // filter.
+            //
+            // If not, ignore the crate filter -- we want to support filtering for crates on sites
+            // like doc.rust-lang.org where the crates may differ from page to page while on the
+            // same domain.
+            if (crates[i] === savedCrate) {
+                elem.value = savedCrate;
+            }
+        }
+      },
+    };
 
     function getPageId() {
         if (window.location.hash) {
@@ -276,65 +345,23 @@ function hideThemeButtonState() {
         document.getElementsByTagName("body")[0].style.marginTop = "";
     }
 
-    function showSearchResults(search) {
-        if (search === null || typeof search === 'undefined') {
-            search = getSearchElement();
-        }
-        addClass(main, "hidden");
-        removeClass(search, "hidden");
-        mouseMovedAfterSearch = false;
-        document.title = searchTitle;
-    }
-
-    function hideSearchResults(search) {
-        if (search === null || typeof search === 'undefined') {
-            search = getSearchElement();
-        }
-        addClass(search, "hidden");
-        removeClass(main, "hidden");
-        document.title = titleBeforeSearch;
-        // We also remove the query parameter from the URL.
-        if (browserSupportsHistoryApi()) {
-            history.replaceState("", window.currentCrate + " - Rust",
-                getNakedUrl() + window.location.hash);
-        }
-    }
-
-    // used for special search precedence
-    var TY_PRIMITIVE = itemTypes.indexOf("primitive");
-    var TY_KEYWORD = itemTypes.indexOf("keyword");
-
-    function getQueryStringParams() {
-        var params = {};
-        window.location.search.substring(1).split("&").
-            map(function(s) {
-                var pair = s.split("=");
-                params[decodeURIComponent(pair[0])] =
-                    typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]);
-            });
-        return params;
-    }
-
-    function browserSupportsHistoryApi() {
-        return window.history && typeof window.history.pushState === "function";
-    }
-
     function isHidden(elem) {
         return elem.offsetHeight === 0;
     }
 
+    var toggleAllDocsId = "toggle-all-docs";
     var main = document.getElementById("main");
     var savedHash = "";
 
     function handleHashes(ev) {
         var elem;
-        var search = getSearchElement();
+        var search = searchState.outputElement();
         if (ev !== null && search && !hasClass(search, "hidden") && ev.newURL) {
             // This block occurs when clicking on an element in the navbar while
             // in a search.
-            hideSearchResults(search);
+            searchState.hideResults(search);
             var hash = ev.newURL.slice(ev.newURL.indexOf("#") + 1);
-            if (browserSupportsHistoryApi()) {
+            if (searchState.browserSupportsHistoryApi()) {
                 // `window.location.search`` contains all the query parameters, not just `search`.
                 history.replaceState(hash, "",
                     getNakedUrl() + window.location.search + "#" + hash);
@@ -475,18 +502,19 @@ function hideThemeButtonState() {
 
     function handleEscape(ev) {
         var help = getHelpElement(false);
-        var search = getSearchElement();
+        var search = searchState.outputElement();
         if (hasClass(help, "hidden") === false) {
             displayHelp(false, ev, help);
         } else if (hasClass(search, "hidden") === false) {
-            clearInputTimeout();
+            searchState.clearInputTimeout();
             ev.preventDefault();
-            hideSearchResults(search);
+            searchState.hideResults(search);
         }
-        defocusSearchBar();
+        searchState.defocus();
         hideThemeButtonState();
     }
 
+    var disableShortcuts = getSettingValue("disable-shortcuts") === "true";
     function handleShortcut(ev) {
         // Don't interfere with browser shortcuts
         if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts === true) {
@@ -509,7 +537,7 @@ function hideThemeButtonState() {
             case "S":
                 displayHelp(false, ev);
                 ev.preventDefault();
-                focusSearchBar();
+                searchState.focus();
                 break;
 
             case "+":
@@ -596,15 +624,13 @@ function hideThemeButtonState() {
     document.addEventListener("keypress", handleShortcut);
     document.addEventListener("keydown", handleShortcut);
 
-    document.addEventListener("mousemove", function() { mouseMovedAfterSearch = true; });
-
     var handleSourceHighlight = (function() {
         var prev_line_id = 0;
 
         var set_fragment = function(name) {
             var x = window.scrollX,
                 y = window.scrollY;
-            if (browserSupportsHistoryApi()) {
+            if (searchState.browserSupportsHistoryApi()) {
                 history.replaceState(null, null, "#" + name);
                 highlightSourceLines();
             } else {
@@ -686,1444 +712,6 @@ function hideThemeButtonState() {
         }
     }());
 
-    /**
-     * A function to compute the Levenshtein distance between two strings
-     * Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported
-     * Full License can be found at http://creativecommons.org/licenses/by-sa/3.0/legalcode
-     * This code is an unmodified version of the code written by Marco de Wit
-     * and was found at http://stackoverflow.com/a/18514751/745719
-     */
-    var levenshtein_row2 = [];
-    function levenshtein(s1, s2) {
-        if (s1 === s2) {
-            return 0;
-        }
-        var s1_len = s1.length, s2_len = s2.length;
-        if (s1_len && s2_len) {
-            var i1 = 0, i2 = 0, a, b, c, c2, row = levenshtein_row2;
-            while (i1 < s1_len) {
-                row[i1] = ++i1;
-            }
-            while (i2 < s2_len) {
-                c2 = s2.charCodeAt(i2);
-                a = i2;
-                ++i2;
-                b = i2;
-                for (i1 = 0; i1 < s1_len; ++i1) {
-                    c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
-                    a = row[i1];
-                    b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
-                    row[i1] = b;
-                }
-            }
-            return b;
-        }
-        return s1_len + s2_len;
-    }
-
-    window.initSearch = function(rawSearchIndex) {
-        var MAX_LEV_DISTANCE = 3;
-        var MAX_RESULTS = 200;
-        var GENERICS_DATA = 1;
-        var NAME = 0;
-        var INPUTS_DATA = 0;
-        var OUTPUT_DATA = 1;
-        var NO_TYPE_FILTER = -1;
-        var currentResults, index, searchIndex;
-        var ALIASES = {};
-        var params = getQueryStringParams();
-
-        // Populate search bar with query string search term when provided,
-        // but only if the input bar is empty. This avoid the obnoxious issue
-        // where you start trying to do a search, and the index loads, and
-        // suddenly your search is gone!
-        if (search_input.value === "") {
-            search_input.value = params.search || "";
-        }
-
-        /**
-         * Executes the query and builds an index of results
-         * @param  {[Object]} query      [The user query]
-         * @param  {[type]} searchWords  [The list of search words to query
-         *                                against]
-         * @param  {[type]} filterCrates [Crate to search in if defined]
-         * @return {[type]}              [A search index of results]
-         */
-        function execQuery(query, searchWords, filterCrates) {
-            function itemTypeFromName(typename) {
-                for (var i = 0, len = itemTypes.length; i < len; ++i) {
-                    if (itemTypes[i] === typename) {
-                        return i;
-                    }
-                }
-                return NO_TYPE_FILTER;
-            }
-
-            var valLower = query.query.toLowerCase(),
-                val = valLower,
-                typeFilter = itemTypeFromName(query.type),
-                results = {}, results_in_args = {}, results_returned = {},
-                split = valLower.split("::");
-
-            removeEmptyStringsFromArray(split);
-
-            function transformResults(results, isType) {
-                var out = [];
-                for (var i = 0, len = results.length; i < len; ++i) {
-                    if (results[i].id > -1) {
-                        var obj = searchIndex[results[i].id];
-                        obj.lev = results[i].lev;
-                        if (isType !== true || obj.type) {
-                            var res = buildHrefAndPath(obj);
-                            obj.displayPath = pathSplitter(res[0]);
-                            obj.fullPath = obj.displayPath + obj.name;
-                            // To be sure than it some items aren't considered as duplicate.
-                            obj.fullPath += "|" + obj.ty;
-                            obj.href = res[1];
-                            out.push(obj);
-                            if (out.length >= MAX_RESULTS) {
-                                break;
-                            }
-                        }
-                    }
-                }
-                return out;
-            }
-
-            function sortResults(results, isType) {
-                var ar = [];
-                for (var entry in results) {
-                    if (hasOwnProperty(results, entry)) {
-                        ar.push(results[entry]);
-                    }
-                }
-                results = ar;
-                var i, len, result;
-                for (i = 0, len = results.length; i < len; ++i) {
-                    result = results[i];
-                    result.word = searchWords[result.id];
-                    result.item = searchIndex[result.id] || {};
-                }
-                // if there are no results then return to default and fail
-                if (results.length === 0) {
-                    return [];
-                }
-
-                results.sort(function(aaa, bbb) {
-                    var a, b;
-
-                    // sort by exact match with regard to the last word (mismatch goes later)
-                    a = (aaa.word !== val);
-                    b = (bbb.word !== val);
-                    if (a !== b) { return a - b; }
-
-                    // Sort by non levenshtein results and then levenshtein results by the distance
-                    // (less changes required to match means higher rankings)
-                    a = (aaa.lev);
-                    b = (bbb.lev);
-                    if (a !== b) { return a - b; }
-
-                    // sort by crate (non-current crate goes later)
-                    a = (aaa.item.crate !== window.currentCrate);
-                    b = (bbb.item.crate !== window.currentCrate);
-                    if (a !== b) { return a - b; }
-
-                    // sort by item name length (longer goes later)
-                    a = aaa.word.length;
-                    b = bbb.word.length;
-                    if (a !== b) { return a - b; }
-
-                    // sort by item name (lexicographically larger goes later)
-                    a = aaa.word;
-                    b = bbb.word;
-                    if (a !== b) { return (a > b ? +1 : -1); }
-
-                    // sort by index of keyword in item name (no literal occurrence goes later)
-                    a = (aaa.index < 0);
-                    b = (bbb.index < 0);
-                    if (a !== b) { return a - b; }
-                    // (later literal occurrence, if any, goes later)
-                    a = aaa.index;
-                    b = bbb.index;
-                    if (a !== b) { return a - b; }
-
-                    // special precedence for primitive and keyword pages
-                    if ((aaa.item.ty === TY_PRIMITIVE && bbb.item.ty !== TY_KEYWORD) ||
-                        (aaa.item.ty === TY_KEYWORD && bbb.item.ty !== TY_PRIMITIVE)) {
-                        return -1;
-                    }
-                    if ((bbb.item.ty === TY_PRIMITIVE && aaa.item.ty !== TY_PRIMITIVE) ||
-                        (bbb.item.ty === TY_KEYWORD && aaa.item.ty !== TY_KEYWORD)) {
-                        return 1;
-                    }
-
-                    // sort by description (no description goes later)
-                    a = (aaa.item.desc === "");
-                    b = (bbb.item.desc === "");
-                    if (a !== b) { return a - b; }
-
-                    // sort by type (later occurrence in `itemTypes` goes later)
-                    a = aaa.item.ty;
-                    b = bbb.item.ty;
-                    if (a !== b) { return a - b; }
-
-                    // sort by path (lexicographically larger goes later)
-                    a = aaa.item.path;
-                    b = bbb.item.path;
-                    if (a !== b) { return (a > b ? +1 : -1); }
-
-                    // que sera, sera
-                    return 0;
-                });
-
-                for (i = 0, len = results.length; i < len; ++i) {
-                    var result = results[i];
-
-                    // this validation does not make sense when searching by types
-                    if (result.dontValidate) {
-                        continue;
-                    }
-                    var name = result.item.name.toLowerCase(),
-                        path = result.item.path.toLowerCase(),
-                        parent = result.item.parent;
-
-                    if (isType !== true &&
-                        validateResult(name, path, split, parent) === false)
-                    {
-                        result.id = -1;
-                    }
-                }
-                return transformResults(results);
-            }
-
-            function extractGenerics(val) {
-                val = val.toLowerCase();
-                if (val.indexOf("<") !== -1) {
-                    var values = val.substring(val.indexOf("<") + 1, val.lastIndexOf(">"));
-                    return {
-                        name: val.substring(0, val.indexOf("<")),
-                        generics: values.split(/\s*,\s*/),
-                    };
-                }
-                return {
-                    name: val,
-                    generics: [],
-                };
-            }
-
-            function getObjectNameFromId(id) {
-                if (typeof id === "number") {
-                    return searchIndex[id].name;
-                }
-                return id;
-            }
-
-            function checkGenerics(obj, val) {
-                // The names match, but we need to be sure that all generics kinda
-                // match as well.
-                var tmp_lev, elem_name;
-                if (val.generics.length > 0) {
-                    if (obj.length > GENERICS_DATA &&
-                          obj[GENERICS_DATA].length >= val.generics.length) {
-                        var elems = Object.create(null);
-                        var elength = object[GENERICS_DATA].length;
-                        for (var x = 0; x < elength; ++x) {
-                            elems[getObjectNameFromId(obj[GENERICS_DATA][x])] += 1;
-                        }
-                        var total = 0;
-                        var done = 0;
-                        // We need to find the type that matches the most to remove it in order
-                        // to move forward.
-                        var vlength = val.generics.length;
-                        for (x = 0; x < vlength; ++x) {
-                            var lev = MAX_LEV_DISTANCE + 1;
-                            var firstGeneric = getObjectNameFromId(val.generics[x]);
-                            var match = null;
-                            if (elems[firstGeneric]) {
-                                match = firstGeneric;
-                                lev = 0;
-                            } else {
-                                for (elem_name in elems) {
-                                    tmp_lev = levenshtein(elem_name, firstGeneric);
-                                    if (tmp_lev < lev) {
-                                        lev = tmp_lev;
-                                        match = elem_name;
-                                    }
-                                }
-                            }
-                            if (match !== null) {
-                                elems[match] -= 1;
-                                if (elems[match] == 0) {
-                                    delete elems[match];
-                                }
-                                total += lev;
-                                done += 1;
-                            } else {
-                                return MAX_LEV_DISTANCE + 1;
-                            }
-                        }
-                        return Math.ceil(total / done);
-                    }
-                }
-                return MAX_LEV_DISTANCE + 1;
-            }
-
-            // Check for type name and type generics (if any).
-            function checkType(obj, val, literalSearch) {
-                var lev_distance = MAX_LEV_DISTANCE + 1;
-                var len, x, firstGeneric;
-                if (obj[NAME] === val.name) {
-                    if (literalSearch === true) {
-                        if (val.generics && val.generics.length !== 0) {
-                            if (obj.length > GENERICS_DATA &&
-                                  obj[GENERICS_DATA].length >= val.generics.length) {
-                                var elems = Object.create(null);
-                                len = obj[GENERICS_DATA].length;
-                                for (x = 0; x < len; ++x) {
-                                    elems[getObjectNameFromId(obj[GENERICS_DATA][x])] += 1;
-                                }
-
-                                var allFound = true;
-                                len = val.generics.length;
-                                for (x = 0; x < len; ++x) {
-                                    firstGeneric = getObjectNameFromId(val.generics[x]);
-                                    if (elems[firstGeneric]) {
-                                        elems[firstGeneric] -= 1;
-                                    } else {
-                                        allFound = false;
-                                        break;
-                                    }
-                                }
-                                if (allFound === true) {
-                                    return true;
-                                }
-                            } else {
-                                return false;
-                            }
-                        }
-                        return true;
-                    }
-                    // If the type has generics but don't match, then it won't return at this point.
-                    // Otherwise, `checkGenerics` will return 0 and it'll return.
-                    if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length !== 0) {
-                        var tmp_lev = checkGenerics(obj, val);
-                        if (tmp_lev <= MAX_LEV_DISTANCE) {
-                            return tmp_lev;
-                        }
-                    } else {
-                        return 0;
-                    }
-                }
-                // Names didn't match so let's check if one of the generic types could.
-                if (literalSearch === true) {
-                     if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length > 0) {
-                        return obj[GENERICS_DATA].some(
-                            function(name) {
-                                return name === val.name;
-                            });
-                    }
-                    return false;
-                }
-                lev_distance = Math.min(levenshtein(obj[NAME], val.name), lev_distance);
-                if (lev_distance <= MAX_LEV_DISTANCE) {
-                    // The generics didn't match but the name kinda did so we give it
-                    // a levenshtein distance value that isn't *this* good so it goes
-                    // into the search results but not too high.
-                    lev_distance = Math.ceil((checkGenerics(obj, val) + lev_distance) / 2);
-                } else if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length > 0) {
-                    // We can check if the type we're looking for is inside the generics!
-                    var olength = obj[GENERICS_DATA].length;
-                    for (x = 0; x < olength; ++x) {
-                        lev_distance = Math.min(levenshtein(obj[GENERICS_DATA][x], val.name),
-                                                lev_distance);
-                    }
-                }
-                // Now whatever happens, the returned distance is "less good" so we should mark it
-                // as such, and so we add 1 to the distance to make it "less good".
-                return lev_distance + 1;
-            }
-
-            function findArg(obj, val, literalSearch, typeFilter) {
-                var lev_distance = MAX_LEV_DISTANCE + 1;
-
-                if (obj && obj.type && obj.type[INPUTS_DATA] && obj.type[INPUTS_DATA].length > 0) {
-                    var length = obj.type[INPUTS_DATA].length;
-                    for (var i = 0; i < length; i++) {
-                        var tmp = obj.type[INPUTS_DATA][i];
-                        if (typePassesFilter(typeFilter, tmp[1]) === false) {
-                            continue;
-                        }
-                        tmp = checkType(tmp, val, literalSearch);
-                        if (literalSearch === true) {
-                            if (tmp === true) {
-                                return true;
-                            }
-                            continue;
-                        }
-                        lev_distance = Math.min(tmp, lev_distance);
-                        if (lev_distance === 0) {
-                            return 0;
-                        }
-                    }
-                }
-                return literalSearch === true ? false : lev_distance;
-            }
-
-            function checkReturned(obj, val, literalSearch, typeFilter) {
-                var lev_distance = MAX_LEV_DISTANCE + 1;
-
-                if (obj && obj.type && obj.type.length > OUTPUT_DATA) {
-                    var ret = obj.type[OUTPUT_DATA];
-                    if (typeof ret[0] === "string") {
-                        ret = [ret];
-                    }
-                    for (var x = 0, len = ret.length; x < len; ++x) {
-                        var tmp = ret[x];
-                        if (typePassesFilter(typeFilter, tmp[1]) === false) {
-                            continue;
-                        }
-                        tmp = checkType(tmp, val, literalSearch);
-                        if (literalSearch === true) {
-                            if (tmp === true) {
-                                return true;
-                            }
-                            continue;
-                        }
-                        lev_distance = Math.min(tmp, lev_distance);
-                        if (lev_distance === 0) {
-                            return 0;
-                        }
-                    }
-                }
-                return literalSearch === true ? false : lev_distance;
-            }
-
-            function checkPath(contains, lastElem, ty) {
-                if (contains.length === 0) {
-                    return 0;
-                }
-                var ret_lev = MAX_LEV_DISTANCE + 1;
-                var path = ty.path.split("::");
-
-                if (ty.parent && ty.parent.name) {
-                    path.push(ty.parent.name.toLowerCase());
-                }
-
-                var length = path.length;
-                var clength = contains.length;
-                if (clength > length) {
-                    return MAX_LEV_DISTANCE + 1;
-                }
-                for (var i = 0; i < length; ++i) {
-                    if (i + clength > length) {
-                        break;
-                    }
-                    var lev_total = 0;
-                    var aborted = false;
-                    for (var x = 0; x < clength; ++x) {
-                        var lev = levenshtein(path[i + x], contains[x]);
-                        if (lev > MAX_LEV_DISTANCE) {
-                            aborted = true;
-                            break;
-                        }
-                        lev_total += lev;
-                    }
-                    if (aborted === false) {
-                        ret_lev = Math.min(ret_lev, Math.round(lev_total / clength));
-                    }
-                }
-                return ret_lev;
-            }
-
-            function typePassesFilter(filter, type) {
-                // No filter
-                if (filter <= NO_TYPE_FILTER) return true;
-
-                // Exact match
-                if (filter === type) return true;
-
-                // Match related items
-                var name = itemTypes[type];
-                switch (itemTypes[filter]) {
-                    case "constant":
-                        return name === "associatedconstant";
-                    case "fn":
-                        return name === "method" || name === "tymethod";
-                    case "type":
-                        return name === "primitive" || name === "associatedtype";
-                    case "trait":
-                        return name === "traitalias";
-                }
-
-                // No match
-                return false;
-            }
-
-            function createAliasFromItem(item) {
-                return {
-                    crate: item.crate,
-                    name: item.name,
-                    path: item.path,
-                    desc: item.desc,
-                    ty: item.ty,
-                    parent: item.parent,
-                    type: item.type,
-                    is_alias: true,
-                };
-            }
-
-            function handleAliases(ret, query, filterCrates) {
-                // We separate aliases and crate aliases because we want to have current crate
-                // aliases to be before the others in the displayed results.
-                var aliases = [];
-                var crateAliases = [];
-                if (filterCrates !== undefined) {
-                    if (ALIASES[filterCrates] && ALIASES[filterCrates][query.search]) {
-                        var query_aliases = ALIASES[filterCrates][query.search];
-                        var len = query_aliases.length;
-                        for (var i = 0; i < len; ++i) {
-                            aliases.push(createAliasFromItem(searchIndex[query_aliases[i]]));
-                        }
-                    }
-                } else {
-                    Object.keys(ALIASES).forEach(function(crate) {
-                        if (ALIASES[crate][query.search]) {
-                            var pushTo = crate === window.currentCrate ? crateAliases : aliases;
-                            var query_aliases = ALIASES[crate][query.search];
-                            var len = query_aliases.length;
-                            for (var i = 0; i < len; ++i) {
-                                pushTo.push(createAliasFromItem(searchIndex[query_aliases[i]]));
-                            }
-                        }
-                    });
-                }
-
-                var sortFunc = function(aaa, bbb) {
-                    if (aaa.path < bbb.path) {
-                        return 1;
-                    } else if (aaa.path === bbb.path) {
-                        return 0;
-                    }
-                    return -1;
-                };
-                crateAliases.sort(sortFunc);
-                aliases.sort(sortFunc);
-
-                var pushFunc = function(alias) {
-                    alias.alias = query.raw;
-                    var res = buildHrefAndPath(alias);
-                    alias.displayPath = pathSplitter(res[0]);
-                    alias.fullPath = alias.displayPath + alias.name;
-                    alias.href = res[1];
-
-                    ret.others.unshift(alias);
-                    if (ret.others.length > MAX_RESULTS) {
-                        ret.others.pop();
-                    }
-                };
-                onEach(aliases, pushFunc);
-                onEach(crateAliases, pushFunc);
-            }
-
-            // quoted values mean literal search
-            var nSearchWords = searchWords.length;
-            var i, it;
-            var ty;
-            var fullId;
-            var returned;
-            var in_args;
-            var len;
-            if ((val.charAt(0) === "\"" || val.charAt(0) === "'") &&
-                val.charAt(val.length - 1) === val.charAt(0))
-            {
-                val = extractGenerics(val.substr(1, val.length - 2));
-                for (i = 0; i < nSearchWords; ++i) {
-                    if (filterCrates !== undefined && searchIndex[i].crate !== filterCrates) {
-                        continue;
-                    }
-                    in_args = findArg(searchIndex[i], val, true, typeFilter);
-                    returned = checkReturned(searchIndex[i], val, true, typeFilter);
-                    ty = searchIndex[i];
-                    fullId = ty.id;
-
-                    if (searchWords[i] === val.name
-                        && typePassesFilter(typeFilter, searchIndex[i].ty)
-                        && results[fullId] === undefined) {
-                        results[fullId] = {
-                            id: i,
-                            index: -1,
-                            dontValidate: true,
-                        };
-                    }
-                    if (in_args === true && results_in_args[fullId] === undefined) {
-                        results_in_args[fullId] = {
-                            id: i,
-                            index: -1,
-                            dontValidate: true,
-                        };
-                    }
-                    if (returned === true && results_returned[fullId] === undefined) {
-                        results_returned[fullId] = {
-                            id: i,
-                            index: -1,
-                            dontValidate: true,
-                        };
-                    }
-                }
-                query.inputs = [val];
-                query.output = val;
-                query.search = val;
-            // searching by type
-            } else if (val.search("->") > -1) {
-                var trimmer = function(s) { return s.trim(); };
-                var parts = val.split("->").map(trimmer);
-                var input = parts[0];
-                // sort inputs so that order does not matter
-                var inputs = input.split(",").map(trimmer).sort();
-                for (i = 0, len = inputs.length; i < len; ++i) {
-                    inputs[i] = extractGenerics(inputs[i]);
-                }
-                var output = extractGenerics(parts[1]);
-
-                for (i = 0; i < nSearchWords; ++i) {
-                    if (filterCrates !== undefined && searchIndex[i].crate !== filterCrates) {
-                        continue;
-                    }
-                    var type = searchIndex[i].type;
-                    ty = searchIndex[i];
-                    if (!type) {
-                        continue;
-                    }
-                    fullId = ty.id;
-
-                    returned = checkReturned(ty, output, true, NO_TYPE_FILTER);
-                    if (output.name === "*" || returned === true) {
-                        in_args = false;
-                        var is_module = false;
-
-                        if (input === "*") {
-                            is_module = true;
-                        } else {
-                            var allFound = true;
-                            for (it = 0, len = inputs.length; allFound === true && it < len; it++) {
-                                allFound = checkType(type, inputs[it], true);
-                            }
-                            in_args = allFound;
-                        }
-                        if (in_args === true) {
-                            results_in_args[fullId] = {
-                                id: i,
-                                index: -1,
-                                dontValidate: true,
-                            };
-                        }
-                        if (returned === true) {
-                            results_returned[fullId] = {
-                                id: i,
-                                index: -1,
-                                dontValidate: true,
-                            };
-                        }
-                        if (is_module === true) {
-                            results[fullId] = {
-                                id: i,
-                                index: -1,
-                                dontValidate: true,
-                            };
-                        }
-                    }
-                }
-                query.inputs = inputs.map(function(input) {
-                    return input.name;
-                });
-                query.output = output.name;
-            } else {
-                query.inputs = [val];
-                query.output = val;
-                query.search = val;
-                // gather matching search results up to a certain maximum
-                val = val.replace(/\_/g, "");
-
-                var valGenerics = extractGenerics(val);
-
-                var paths = valLower.split("::");
-                removeEmptyStringsFromArray(paths);
-                val = paths[paths.length - 1];
-                var contains = paths.slice(0, paths.length > 1 ? paths.length - 1 : 1);
-
-                var lev, j;
-                for (j = 0; j < nSearchWords; ++j) {
-                    ty = searchIndex[j];
-                    if (!ty || (filterCrates !== undefined && ty.crate !== filterCrates)) {
-                        continue;
-                    }
-                    var lev_add = 0;
-                    if (paths.length > 1) {
-                        lev = checkPath(contains, paths[paths.length - 1], ty);
-                        if (lev > MAX_LEV_DISTANCE) {
-                            continue;
-                        } else if (lev > 0) {
-                            lev_add = lev / 10;
-                        }
-                    }
-
-                    returned = MAX_LEV_DISTANCE + 1;
-                    in_args = MAX_LEV_DISTANCE + 1;
-                    var index = -1;
-                    // we want lev results to go lower than others
-                    lev = MAX_LEV_DISTANCE + 1;
-                    fullId = ty.id;
-
-                    if (searchWords[j].indexOf(split[i]) > -1 ||
-                        searchWords[j].indexOf(val) > -1 ||
-                        ty.normalizedName.indexOf(val) > -1)
-                    {
-                        // filter type: ... queries
-                        if (typePassesFilter(typeFilter, ty.ty) && results[fullId] === undefined) {
-                            index = ty.normalizedName.indexOf(val);
-                        }
-                    }
-                    if ((lev = levenshtein(searchWords[j], val)) <= MAX_LEV_DISTANCE) {
-                        if (typePassesFilter(typeFilter, ty.ty) === false) {
-                            lev = MAX_LEV_DISTANCE + 1;
-                        } else {
-                            lev += 1;
-                        }
-                    }
-                    in_args = findArg(ty, valGenerics, false, typeFilter);
-                    returned = checkReturned(ty, valGenerics, false, typeFilter);
-
-                    lev += lev_add;
-                    if (lev > 0 && val.length > 3 && searchWords[j].indexOf(val) > -1) {
-                        if (val.length < 6) {
-                            lev -= 1;
-                        } else {
-                            lev = 0;
-                        }
-                    }
-                    if (in_args <= MAX_LEV_DISTANCE) {
-                        if (results_in_args[fullId] === undefined) {
-                            results_in_args[fullId] = {
-                                id: j,
-                                index: index,
-                                lev: in_args,
-                            };
-                        }
-                        results_in_args[fullId].lev =
-                            Math.min(results_in_args[fullId].lev, in_args);
-                    }
-                    if (returned <= MAX_LEV_DISTANCE) {
-                        if (results_returned[fullId] === undefined) {
-                            results_returned[fullId] = {
-                                id: j,
-                                index: index,
-                                lev: returned,
-                            };
-                        }
-                        results_returned[fullId].lev =
-                            Math.min(results_returned[fullId].lev, returned);
-                    }
-                    if (index !== -1 || lev <= MAX_LEV_DISTANCE) {
-                        if (index !== -1 && paths.length < 2) {
-                            lev = 0;
-                        }
-                        if (results[fullId] === undefined) {
-                            results[fullId] = {
-                                id: j,
-                                index: index,
-                                lev: lev,
-                            };
-                        }
-                        results[fullId].lev = Math.min(results[fullId].lev, lev);
-                    }
-                }
-            }
-
-            var ret = {
-                "in_args": sortResults(results_in_args, true),
-                "returned": sortResults(results_returned, true),
-                "others": sortResults(results),
-            };
-            handleAliases(ret, query, filterCrates);
-            return ret;
-        }
-
-        /**
-         * Validate performs the following boolean logic. For example:
-         * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
-         * exists in (name || path || parent) OR => ("file" && "open") exists in
-         * (name || path )
-         *
-         * This could be written functionally, but I wanted to minimise
-         * functions on stack.
-         *
-         * @param  {[string]} name   [The name of the result]
-         * @param  {[string]} path   [The path of the result]
-         * @param  {[string]} keys   [The keys to be used (["file", "open"])]
-         * @param  {[object]} parent [The parent of the result]
-         * @return {[boolean]}       [Whether the result is valid or not]
-         */
-        function validateResult(name, path, keys, parent) {
-            for (var i = 0, len = keys.length; i < len; ++i) {
-                // each check is for validation so we negate the conditions and invalidate
-                if (!(
-                    // check for an exact name match
-                    name.indexOf(keys[i]) > -1 ||
-                    // then an exact path match
-                    path.indexOf(keys[i]) > -1 ||
-                    // next if there is a parent, check for exact parent match
-                    (parent !== undefined && parent.name !== undefined &&
-                        parent.name.toLowerCase().indexOf(keys[i]) > -1) ||
-                    // lastly check to see if the name was a levenshtein match
-                    levenshtein(name, keys[i]) <= MAX_LEV_DISTANCE)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        function getQuery(raw) {
-            var matches, type, query;
-            query = raw;
-
-            matches = query.match(/^(fn|mod|struct|enum|trait|type|const|macro)\s*:\s*/i);
-            if (matches) {
-                type = matches[1].replace(/^const$/, "constant");
-                query = query.substring(matches[0].length);
-            }
-
-            return {
-                raw: raw,
-                query: query,
-                type: type,
-                id: query + type
-            };
-        }
-
-        function initSearchNav() {
-            var hoverTimeout;
-
-            var click_func = function(e) {
-                var el = e.target;
-                // to retrieve the real "owner" of the event.
-                while (el.tagName !== "TR") {
-                    el = el.parentNode;
-                }
-                var dst = e.target.getElementsByTagName("a");
-                if (dst.length < 1) {
-                    return;
-                }
-                dst = dst[0];
-                if (window.location.pathname === dst.pathname) {
-                    hideSearchResults();
-                    document.location.href = dst.href;
-                }
-            };
-            var mouseover_func = function(e) {
-                if (mouseMovedAfterSearch) {
-                    var el = e.target;
-                    // to retrieve the real "owner" of the event.
-                    while (el.tagName !== "TR") {
-                        el = el.parentNode;
-                    }
-                    clearTimeout(hoverTimeout);
-                    hoverTimeout = setTimeout(function() {
-                        onEachLazy(document.getElementsByClassName("search-results"), function(e) {
-                            onEachLazy(e.getElementsByClassName("result"), function(i_e) {
-                                removeClass(i_e, "highlighted");
-                            });
-                        });
-                        addClass(el, "highlighted");
-                    }, 20);
-                }
-            };
-            onEachLazy(document.getElementsByClassName("search-results"), function(e) {
-                onEachLazy(e.getElementsByClassName("result"), function(i_e) {
-                    i_e.onclick = click_func;
-                    i_e.onmouseover = mouseover_func;
-                });
-            });
-
-            search_input.onkeydown = function(e) {
-                // "actives" references the currently highlighted item in each search tab.
-                // Each array in "actives" represents a tab.
-                var actives = [[], [], []];
-                // "current" is used to know which tab we're looking into.
-                var current = 0;
-                onEachLazy(document.getElementById("results").childNodes, function(e) {
-                    onEachLazy(e.getElementsByClassName("highlighted"), function(h_e) {
-                        actives[current].push(h_e);
-                    });
-                    current += 1;
-                });
-
-                if (e.which === 38) { // up
-                    if (e.ctrlKey) { // Going through result tabs.
-                        printTab(currentTab > 0 ? currentTab - 1 : 2);
-                    } else {
-                        if (!actives[currentTab].length ||
-                            !actives[currentTab][0].previousElementSibling) {
-                            return;
-                        }
-                        addClass(actives[currentTab][0].previousElementSibling, "highlighted");
-                        removeClass(actives[currentTab][0], "highlighted");
-                    }
-                    e.preventDefault();
-                } else if (e.which === 40) { // down
-                    if (e.ctrlKey) { // Going through result tabs.
-                        printTab(currentTab > 1 ? 0 : currentTab + 1);
-                    } else if (!actives[currentTab].length) {
-                        var results = document.getElementById("results").childNodes;
-                        if (results.length > 0) {
-                            var res = results[currentTab].getElementsByClassName("result");
-                            if (res.length > 0) {
-                                addClass(res[0], "highlighted");
-                            }
-                        }
-                    } else if (actives[currentTab][0].nextElementSibling) {
-                        addClass(actives[currentTab][0].nextElementSibling, "highlighted");
-                        removeClass(actives[currentTab][0], "highlighted");
-                    }
-                    e.preventDefault();
-                } else if (e.which === 13) { // return
-                    if (actives[currentTab].length) {
-                        document.location.href =
-                            actives[currentTab][0].getElementsByTagName("a")[0].href;
-                    }
-                } else if (e.which === 16) { // shift
-                    // Does nothing, it's just to avoid losing "focus" on the highlighted element.
-                } else if (actives[currentTab].length > 0) {
-                    removeClass(actives[currentTab][0], "highlighted");
-                }
-            };
-        }
-
-        function buildHrefAndPath(item) {
-            var displayPath;
-            var href;
-            var type = itemTypes[item.ty];
-            var name = item.name;
-            var path = item.path;
-
-            if (type === "mod") {
-                displayPath = path + "::";
-                href = window.rootPath + path.replace(/::/g, "/") + "/" +
-                       name + "/index.html";
-            } else if (type === "primitive" || type === "keyword") {
-                displayPath = "";
-                href = window.rootPath + path.replace(/::/g, "/") +
-                       "/" + type + "." + name + ".html";
-            } else if (type === "externcrate") {
-                displayPath = "";
-                href = window.rootPath + name + "/index.html";
-            } else if (item.parent !== undefined) {
-                var myparent = item.parent;
-                var anchor = "#" + type + "." + name;
-                var parentType = itemTypes[myparent.ty];
-                var pageType = parentType;
-                var pageName = myparent.name;
-
-                if (parentType === "primitive") {
-                    displayPath = myparent.name + "::";
-                } else if (type === "structfield" && parentType === "variant") {
-                    // Structfields belonging to variants are special: the
-                    // final path element is the enum name.
-                    var enumNameIdx = item.path.lastIndexOf("::");
-                    var enumName = item.path.substr(enumNameIdx + 2);
-                    path = item.path.substr(0, enumNameIdx);
-                    displayPath = path + "::" + enumName + "::" + myparent.name + "::";
-                    anchor = "#variant." + myparent.name + ".field." + name;
-                    pageType = "enum";
-                    pageName = enumName;
-                } else {
-                    displayPath = path + "::" + myparent.name + "::";
-                }
-                href = window.rootPath + path.replace(/::/g, "/") +
-                       "/" + pageType +
-                       "." + pageName +
-                       ".html" + anchor;
-            } else {
-                displayPath = item.path + "::";
-                href = window.rootPath + item.path.replace(/::/g, "/") +
-                       "/" + type + "." + name + ".html";
-            }
-            return [displayPath, href];
-        }
-
-        function escape(content) {
-            var h1 = document.createElement("h1");
-            h1.textContent = content;
-            return h1.innerHTML;
-        }
-
-        function pathSplitter(path) {
-            var tmp = "<span>" + path.replace(/::/g, "::</span><span>");
-            if (tmp.endsWith("<span>")) {
-                return tmp.slice(0, tmp.length - 6);
-            }
-            return tmp;
-        }
-
-        function addTab(array, query, display) {
-            var extraStyle = "";
-            if (display === false) {
-                extraStyle = " style=\"display: none;\"";
-            }
-
-            var output = "";
-            var duplicates = {};
-            var length = 0;
-            if (array.length > 0) {
-                output = "<table class=\"search-results\"" + extraStyle + ">";
-
-                array.forEach(function(item) {
-                    var name, type;
-
-                    name = item.name;
-                    type = itemTypes[item.ty];
-
-                    if (item.is_alias !== true) {
-                        if (duplicates[item.fullPath]) {
-                            return;
-                        }
-                        duplicates[item.fullPath] = true;
-                    }
-                    length += 1;
-
-                    output += "<tr class=\"" + type + " result\"><td>" +
-                              "<a href=\"" + item.href + "\">" +
-                              (item.is_alias === true ?
-                               ("<span class=\"alias\"><b>" + item.alias + " </b></span><span " +
-                                  "class=\"grey\"><i>&nbsp;- see&nbsp;</i></span>") : "") +
-                              item.displayPath + "<span class=\"" + type + "\">" +
-                              name + "</span></a></td><td>" +
-                              "<a href=\"" + item.href + "\">" +
-                              "<span class=\"desc\">" + item.desc +
-                              "&nbsp;</span></a></td></tr>";
-                });
-                output += "</table>";
-            } else {
-                output = "<div class=\"search-failed\"" + extraStyle + ">No results :(<br/>" +
-                    "Try on <a href=\"https://duckduckgo.com/?q=" +
-                    encodeURIComponent("rust " + query.query) +
-                    "\">DuckDuckGo</a>?<br/><br/>" +
-                    "Or try looking in one of these:<ul><li>The <a " +
-                    "href=\"https://doc.rust-lang.org/reference/index.html\">Rust Reference</a> " +
-                    " for technical details about the language.</li><li><a " +
-                    "href=\"https://doc.rust-lang.org/rust-by-example/index.html\">Rust By " +
-                    "Example</a> for expository code examples.</a></li><li>The <a " +
-                    "href=\"https://doc.rust-lang.org/book/index.html\">Rust Book</a> for " +
-                    "introductions to language features and the language itself.</li><li><a " +
-                    "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
-                    " <a href=\"https://crates.io/\">crates.io</a>.</li></ul></div>";
-            }
-            return [output, length];
-        }
-
-        function makeTabHeader(tabNb, text, nbElems) {
-            if (currentTab === tabNb) {
-                return "<button class=\"selected\">" + text +
-                       " <div class=\"count\">(" + nbElems + ")</div></button>";
-            }
-            return "<button>" + text + " <div class=\"count\">(" + nbElems + ")</div></button>";
-        }
-
-        function showResults(results) {
-            var search = getSearchElement();
-            if (results.others.length === 1
-                && getSettingValue("go-to-only-result") === "true"
-                // By default, the search DOM element is "empty" (meaning it has no children not
-                // text content). Once a search has been run, it won't be empty, even if you press
-                // ESC or empty the search input (which also "cancels" the search).
-                && (!search.firstChild || search.firstChild.innerText !== getSearchLoadingText()))
-            {
-                var elem = document.createElement("a");
-                elem.href = results.others[0].href;
-                elem.style.display = "none";
-                // For firefox, we need the element to be in the DOM so it can be clicked.
-                document.body.appendChild(elem);
-                elem.click();
-                return;
-            }
-            var query = getQuery(search_input.value);
-
-            currentResults = query.id;
-
-            var ret_others = addTab(results.others, query);
-            var ret_in_args = addTab(results.in_args, query, false);
-            var ret_returned = addTab(results.returned, query, false);
-
-            // Navigate to the relevant tab if the current tab is empty, like in case users search
-            // for "-> String". If they had selected another tab previously, they have to click on
-            // it again.
-            if ((currentTab === 0 && ret_others[1] === 0) ||
-                    (currentTab === 1 && ret_in_args[1] === 0) ||
-                    (currentTab === 2 && ret_returned[1] === 0)) {
-                if (ret_others[1] !== 0) {
-                    currentTab = 0;
-                } else if (ret_in_args[1] !== 0) {
-                    currentTab = 1;
-                } else if (ret_returned[1] !== 0) {
-                    currentTab = 2;
-                }
-            }
-
-            var output = "<h1>Results for " + escape(query.query) +
-                (query.type ? " (type: " + escape(query.type) + ")" : "") + "</h1>" +
-                "<div id=\"titles\">" +
-                makeTabHeader(0, "In Names", ret_others[1]) +
-                makeTabHeader(1, "In Parameters", ret_in_args[1]) +
-                makeTabHeader(2, "In Return Types", ret_returned[1]) +
-                "</div><div id=\"results\">" +
-                ret_others[0] + ret_in_args[0] + ret_returned[0] + "</div>";
-
-            search.innerHTML = output;
-            showSearchResults(search);
-            initSearchNav();
-            var elems = document.getElementById("titles").childNodes;
-            elems[0].onclick = function() { printTab(0); };
-            elems[1].onclick = function() { printTab(1); };
-            elems[2].onclick = function() { printTab(2); };
-            printTab(currentTab);
-        }
-
-        function execSearch(query, searchWords, filterCrates) {
-            function getSmallest(arrays, positions, notDuplicates) {
-                var start = null;
-
-                for (var it = 0, len = positions.length; it < len; ++it) {
-                    if (arrays[it].length > positions[it] &&
-                        (start === null || start > arrays[it][positions[it]].lev) &&
-                        !notDuplicates[arrays[it][positions[it]].fullPath]) {
-                        start = arrays[it][positions[it]].lev;
-                    }
-                }
-                return start;
-            }
-
-            function mergeArrays(arrays) {
-                var ret = [];
-                var positions = [];
-                var notDuplicates = {};
-
-                for (var x = 0, arrays_len = arrays.length; x < arrays_len; ++x) {
-                    positions.push(0);
-                }
-                while (ret.length < MAX_RESULTS) {
-                    var smallest = getSmallest(arrays, positions, notDuplicates);
-
-                    if (smallest === null) {
-                        break;
-                    }
-                    for (x = 0; x < arrays_len && ret.length < MAX_RESULTS; ++x) {
-                        if (arrays[x].length > positions[x] &&
-                                arrays[x][positions[x]].lev === smallest &&
-                                !notDuplicates[arrays[x][positions[x]].fullPath]) {
-                            ret.push(arrays[x][positions[x]]);
-                            notDuplicates[arrays[x][positions[x]].fullPath] = true;
-                            positions[x] += 1;
-                        }
-                    }
-                }
-                return ret;
-            }
-
-            var queries = query.raw.split(",");
-            var results = {
-                "in_args": [],
-                "returned": [],
-                "others": [],
-            };
-
-            for (var i = 0, len = queries.length; i < len; ++i) {
-                query = queries[i].trim();
-                if (query.length !== 0) {
-                    var tmp = execQuery(getQuery(query), searchWords, filterCrates);
-
-                    results.in_args.push(tmp.in_args);
-                    results.returned.push(tmp.returned);
-                    results.others.push(tmp.others);
-                }
-            }
-            if (queries.length > 1) {
-                return {
-                    "in_args": mergeArrays(results.in_args),
-                    "returned": mergeArrays(results.returned),
-                    "others": mergeArrays(results.others),
-                };
-            }
-            return {
-                "in_args": results.in_args[0],
-                "returned": results.returned[0],
-                "others": results.others[0],
-            };
-        }
-
-        function getFilterCrates() {
-            var elem = document.getElementById("crate-search");
-
-            if (elem && elem.value !== "All crates" && hasOwnProperty(rawSearchIndex, elem.value)) {
-                return elem.value;
-            }
-            return undefined;
-        }
-
-        function search(e, forced) {
-            var params = getQueryStringParams();
-            var query = getQuery(search_input.value.trim());
-
-            if (e) {
-                e.preventDefault();
-            }
-
-            if (query.query.length === 0) {
-                return;
-            }
-            if (forced !== true && query.id === currentResults) {
-                if (query.query.length > 0) {
-                    putBackSearch(search_input);
-                }
-                return;
-            }
-
-            // Update document title to maintain a meaningful browser history
-            searchTitle = "Results for " + query.query + " - Rust";
-
-            // Because searching is incremental by character, only the most
-            // recent search query is added to the browser history.
-            if (browserSupportsHistoryApi()) {
-                var newURL = getNakedUrl() + "?search=" + encodeURIComponent(query.raw) +
-                    window.location.hash;
-                if (!history.state && !params.search) {
-                    history.pushState(query, "", newURL);
-                } else {
-                    history.replaceState(query, "", newURL);
-                }
-            }
-
-            var filterCrates = getFilterCrates();
-            showResults(execSearch(query, index, filterCrates));
-        }
-
-        function buildIndex(rawSearchIndex) {
-            searchIndex = [];
-            var searchWords = [];
-            var i, word;
-            var currentIndex = 0;
-            var id = 0;
-
-            for (var crate in rawSearchIndex) {
-                if (!hasOwnProperty(rawSearchIndex, crate)) { continue; }
-
-                var crateSize = 0;
-
-                searchWords.push(crate);
-                var normalizedName = crate.indexOf("_") === -1
-                    ? crate
-                    : crate.replace(/_/g, "");
-                // This object should have exactly the same set of fields as the "row"
-                // object defined below. Your JavaScript runtime will thank you.
-                // https://mathiasbynens.be/notes/shapes-ics
-                var crateRow = {
-                    crate: crate,
-                    ty: 1, // == ExternCrate
-                    name: crate,
-                    path: "",
-                    desc: rawSearchIndex[crate].doc,
-                    parent: undefined,
-                    type: null,
-                    id: id,
-                    normalizedName: normalizedName,
-                };
-                id += 1;
-                searchIndex.push(crateRow);
-                currentIndex += 1;
-
-                // an array of (Number) item types
-                var itemTypes = rawSearchIndex[crate].t;
-                // an array of (String) item names
-                var itemNames = rawSearchIndex[crate].n;
-                // an array of (String) full paths (or empty string for previous path)
-                var itemPaths = rawSearchIndex[crate].q;
-                // an array of (String) descriptions
-                var itemDescs = rawSearchIndex[crate].d;
-                // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
-                var itemParentIdxs = rawSearchIndex[crate].i;
-                // an array of (Object | null) the type of the function, if any
-                var itemFunctionSearchTypes = rawSearchIndex[crate].f;
-                // an array of [(Number) item type,
-                //              (String) name]
-                var paths = rawSearchIndex[crate].p;
-                // a array of [(String) alias name
-                //             [Number] index to items]
-                var aliases = rawSearchIndex[crate].a;
-
-                // convert `rawPaths` entries into object form
-                var len = paths.length;
-                for (i = 0; i < len; ++i) {
-                    paths[i] = {ty: paths[i][0], name: paths[i][1]};
-                }
-
-                // convert `item*` into an object form, and construct word indices.
-                //
-                // before any analysis is performed lets gather the search terms to
-                // search against apart from the rest of the data.  This is a quick
-                // operation that is cached for the life of the page state so that
-                // all other search operations have access to this cached data for
-                // faster analysis operations
-                len = itemTypes.length;
-                var lastPath = "";
-                for (i = 0; i < len; ++i) {
-                    // This object should have exactly the same set of fields as the "crateRow"
-                    // object defined above.
-                    if (typeof itemNames[i] === "string") {
-                        word = itemNames[i].toLowerCase();
-                        searchWords.push(word);
-                    } else {
-                        word = "";
-                        searchWords.push("");
-                    }
-                    var normalizedName = word.indexOf("_") === -1
-                        ? word
-                        : word.replace(/_/g, "");
-                    var row = {
-                        crate: crate,
-                        ty: itemTypes[i],
-                        name: itemNames[i],
-                        path: itemPaths[i] ? itemPaths[i] : lastPath,
-                        desc: itemDescs[i],
-                        parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
-                        type: itemFunctionSearchTypes[i],
-                        id: id,
-                        normalizedName: normalizedName,
-                    };
-                    id += 1;
-                    searchIndex.push(row);
-                    lastPath = row.path;
-                    crateSize += 1;
-                }
-
-                if (aliases) {
-                    ALIASES[crate] = {};
-                    var j, local_aliases;
-                    for (var alias_name in aliases) {
-                        if (!aliases.hasOwnProperty(alias_name)) { continue; }
-
-                        if (!ALIASES[crate].hasOwnProperty(alias_name)) {
-                            ALIASES[crate][alias_name] = [];
-                        }
-                        local_aliases = aliases[alias_name];
-                        for (j = 0, len = local_aliases.length; j < len; ++j) {
-                            ALIASES[crate][alias_name].push(local_aliases[j] + currentIndex);
-                        }
-                    }
-                }
-                currentIndex += crateSize;
-            }
-            return searchWords;
-        }
-
-        function registerSearchEvents() {
-            var searchAfter500ms = function() {
-                clearInputTimeout();
-                if (search_input.value.length === 0) {
-                    if (browserSupportsHistoryApi()) {
-                        history.replaceState("", window.currentCrate + " - Rust",
-                            getNakedUrl() + window.location.hash);
-                    }
-                    hideSearchResults();
-                } else {
-                    searchTimeout = setTimeout(search, 500);
-                }
-            };
-            search_input.onkeyup = searchAfter500ms;
-            search_input.oninput = searchAfter500ms;
-            document.getElementsByClassName("search-form")[0].onsubmit = function(e) {
-                e.preventDefault();
-                clearInputTimeout();
-                search();
-            };
-            search_input.onchange = function(e) {
-                if (e.target !== document.activeElement) {
-                    // To prevent doing anything when it's from a blur event.
-                    return;
-                }
-                // Do NOT e.preventDefault() here. It will prevent pasting.
-                clearInputTimeout();
-                // zero-timeout necessary here because at the time of event handler execution the
-                // pasted content is not in the input field yet. Shouldn’t make any difference for
-                // change, though.
-                setTimeout(search, 0);
-            };
-            search_input.onpaste = search_input.onchange;
-
-            var selectCrate = document.getElementById("crate-search");
-            if (selectCrate) {
-                selectCrate.onchange = function() {
-                    updateLocalStorage("rustdoc-saved-filter-crate", selectCrate.value);
-                    search(undefined, true);
-                };
-            }
-
-            // Push and pop states are used to add search results to the browser
-            // history.
-            if (browserSupportsHistoryApi()) {
-                // Store the previous <title> so we can revert back to it later.
-                var previousTitle = document.title;
-
-                window.addEventListener("popstate", function(e) {
-                    var params = getQueryStringParams();
-                    // Revert to the previous title manually since the History
-                    // API ignores the title parameter.
-                    document.title = previousTitle;
-                    // When browsing forward to search results the previous
-                    // search will be repeated, so the currentResults are
-                    // cleared to ensure the search is successful.
-                    currentResults = null;
-                    // Synchronize search bar with query string state and
-                    // perform the search. This will empty the bar if there's
-                    // nothing there, which lets you really go back to a
-                    // previous state with nothing in the bar.
-                    if (params.search && params.search.length > 0) {
-                        search_input.value = params.search;
-                        // Some browsers fire "onpopstate" for every page load
-                        // (Chrome), while others fire the event only when actually
-                        // popping a state (Firefox), which is why search() is
-                        // called both here and at the end of the startSearch()
-                        // function.
-                        search(e);
-                    } else {
-                        search_input.value = "";
-                        // When browsing back from search results the main page
-                        // visibility must be reset.
-                        hideSearchResults();
-                    }
-                });
-            }
-
-            // This is required in firefox to avoid this problem: Navigating to a search result
-            // with the keyboard, hitting enter, and then hitting back would take you back to
-            // the doc page, rather than the search that should overlay it.
-            // This was an interaction between the back-forward cache and our handlers
-            // that try to sync state between the URL and the search input. To work around it,
-            // do a small amount of re-init on page show.
-            window.onpageshow = function(){
-                var qSearch = getQueryStringParams().search;
-                if (search_input.value === "" && qSearch) {
-                    search_input.value = qSearch;
-                }
-                search();
-            };
-        }
-
-        index = buildIndex(rawSearchIndex);
-        registerSearchEvents();
-        // If there's a search term in the URL, execute the search now.
-        if (getQueryStringParams().search) {
-            search();
-        }
-    };
-
     function addSidebarCrates(crates) {
         // Draw a convenient sidebar of known crates if we have a listing
         if (window.rootPath === "../" || window.rootPath === "./") {
@@ -2216,6 +804,9 @@ function hideThemeButtonState() {
         block("foreigntype", "Foreign Types");
         block("keyword", "Keywords");
         block("traitalias", "Trait Aliases");
+
+        // `crates{version}.js` should always be loaded before this script, so we can use it safely.
+        addSidebarCrates(window.ALL_CRATES);
     };
 
     window.register_implementors = function(imp) {
@@ -2813,60 +1404,6 @@ function hideThemeButtonState() {
         };
     });
 
-    // In the search display, allows to switch between tabs.
-    function printTab(nb) {
-        if (nb === 0 || nb === 1 || nb === 2) {
-            currentTab = nb;
-        }
-        var nb_copy = nb;
-        onEachLazy(document.getElementById("titles").childNodes, function(elem) {
-            if (nb_copy === 0) {
-                addClass(elem, "selected");
-            } else {
-                removeClass(elem, "selected");
-            }
-            nb_copy -= 1;
-        });
-        onEachLazy(document.getElementById("results").childNodes, function(elem) {
-            if (nb === 0) {
-                elem.style.display = "";
-            } else {
-                elem.style.display = "none";
-            }
-            nb -= 1;
-        });
-    }
-
-    function putBackSearch(search_input) {
-        var search = getSearchElement();
-        if (search_input.value !== "" && hasClass(search, "hidden")) {
-            showSearchResults(search);
-            if (browserSupportsHistoryApi()) {
-                var extra = "?search=" + encodeURIComponent(search_input.value);
-                history.replaceState(search_input.value, "",
-                    getNakedUrl() + extra + window.location.hash);
-            }
-            document.title = searchTitle;
-        }
-    }
-
-    function getSearchLoadingText() {
-        return "Loading search results...";
-    }
-
-    if (search_input) {
-        search_input.onfocus = function() {
-            putBackSearch(this);
-        };
-    }
-
-    var params = getQueryStringParams();
-    if (params && params.search) {
-        var search = getSearchElement();
-        search.innerHTML = "<h3 style=\"text-align: center;\">" + getSearchLoadingText() + "</h3>";
-        showSearchResults(search);
-    }
-
     var sidebar_menu = document.getElementsByClassName("sidebar-menu")[0];
     if (sidebar_menu) {
         sidebar_menu.onclick = function() {
@@ -2899,30 +1436,6 @@ function hideThemeButtonState() {
         });
     }
 
-    function addSearchOptions(crates) {
-        var elem = document.getElementById("crate-search");
-
-        if (!elem) {
-            return;
-        }
-        var savedCrate = getSettingValue("saved-filter-crate");
-        for (var i = 0, len = crates.length; i < len; ++i) {
-            var option = document.createElement("option");
-            option.value = crates[i];
-            option.innerText = crates[i];
-            elem.appendChild(option);
-            // Set the crate filter from saved storage, if the current page has the saved crate
-            // filter.
-            //
-            // If not, ignore the crate filter -- we want to support filtering for crates on sites
-            // like doc.rust-lang.org where the crates may differ from page to page while on the
-            // same domain.
-            if (crates[i] === savedCrate) {
-                elem.value = savedCrate;
-            }
-        }
-    };
-
     function buildHelperPopup() {
         var popup = document.createElement("aside");
         addClass(popup, "hidden");
@@ -2980,55 +1493,14 @@ function hideThemeButtonState() {
         container.appendChild(div_infos);
 
         popup.appendChild(container);
-        insertAfter(popup, getSearchElement());
+        insertAfter(popup, searchState.outputElement());
         // So that it's only built once and then it'll do nothing when called!
         buildHelperPopup = function() {};
     }
 
-    function loadScript(url) {
-        var script = document.createElement('script');
-        script.src = url;
-        document.head.append(script);
-    }
-
-    function setupSearchLoader() {
-        var searchLoaded = false;
-        function loadSearch() {
-            if (!searchLoaded) {
-                searchLoaded = true;
-                loadScript(window.searchJS);
-            }
-        }
-
-        // `crates{version}.js` should always be loaded before this script, so we can use it safely.
-        addSearchOptions(window.ALL_CRATES);
-        addSidebarCrates(window.ALL_CRATES);
-
-        search_input.addEventListener("focus", function() {
-            search_input.origPlaceholder = search_input.placeholder;
-            search_input.placeholder = "Type your search here.";
-            loadSearch();
-        });
-        search_input.addEventListener("blur", function() {
-            search_input.placeholder = search_input.origPlaceholder;
-        });
-        search_input.removeAttribute('disabled');
-
-        var crateSearchDropDown = document.getElementById("crate-search");
-        // `crateSearchDropDown` can be null in case there is only crate because in that case, the
-        // crate filter dropdown is removed.
-        if (crateSearchDropDown) {
-            crateSearchDropDown.addEventListener("focus", loadSearch);
-        }
-        var params = getQueryStringParams();
-        if (params.search !== undefined) {
-            loadSearch();
-        }
-    }
-
     onHashChange(null);
     window.onhashchange = onHashChange;
-    setupSearchLoader();
+    searchState.setup();
 }());
 
 function copy_path(but) {
diff --git a/src/librustdoc/html/static/search.js b/src/librustdoc/html/static/search.js
new file mode 100644
index 00000000000..538c811c710
--- /dev/null
+++ b/src/librustdoc/html/static/search.js
@@ -0,0 +1,1512 @@
+(function() {
+// This mapping table should match the discriminants of
+// `rustdoc::html::item_type::ItemType` type in Rust.
+var itemTypes = ["mod",
+                    "externcrate",
+                    "import",
+                    "struct",
+                    "enum",
+                    "fn",
+                    "type",
+                    "static",
+                    "trait",
+                    "impl",
+                    "tymethod",
+                    "method",
+                    "structfield",
+                    "variant",
+                    "macro",
+                    "primitive",
+                    "associatedtype",
+                    "constant",
+                    "associatedconstant",
+                    "union",
+                    "foreigntype",
+                    "keyword",
+                    "existential",
+                    "attr",
+                    "derive",
+                    "traitalias"];
+
+// used for special search precedence
+var TY_PRIMITIVE = itemTypes.indexOf("primitive");
+var TY_KEYWORD = itemTypes.indexOf("keyword");
+
+// In the search display, allows to switch between tabs.
+function printTab(nb) {
+    if (nb === 0 || nb === 1 || nb === 2) {
+        searchState.currentTab = nb;
+    }
+    var nb_copy = nb;
+    onEachLazy(document.getElementById("titles").childNodes, function(elem) {
+        if (nb_copy === 0) {
+            addClass(elem, "selected");
+        } else {
+            removeClass(elem, "selected");
+        }
+        nb_copy -= 1;
+    });
+    onEachLazy(document.getElementById("results").childNodes, function(elem) {
+        if (nb === 0) {
+            elem.style.display = "";
+        } else {
+            elem.style.display = "none";
+        }
+        nb -= 1;
+    });
+}
+
+function removeEmptyStringsFromArray(x) {
+    for (var i = 0, len = x.length; i < len; ++i) {
+        if (x[i] === "") {
+            x.splice(i, 1);
+            i -= 1;
+        }
+    }
+}
+
+/**
+ * A function to compute the Levenshtein distance between two strings
+ * Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported
+ * Full License can be found at http://creativecommons.org/licenses/by-sa/3.0/legalcode
+ * This code is an unmodified version of the code written by Marco de Wit
+ * and was found at http://stackoverflow.com/a/18514751/745719
+ */
+var levenshtein_row2 = [];
+function levenshtein(s1, s2) {
+    if (s1 === s2) {
+        return 0;
+    }
+    var s1_len = s1.length, s2_len = s2.length;
+    if (s1_len && s2_len) {
+        var i1 = 0, i2 = 0, a, b, c, c2, row = levenshtein_row2;
+        while (i1 < s1_len) {
+            row[i1] = ++i1;
+        }
+        while (i2 < s2_len) {
+            c2 = s2.charCodeAt(i2);
+            a = i2;
+            ++i2;
+            b = i2;
+            for (i1 = 0; i1 < s1_len; ++i1) {
+                c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
+                a = row[i1];
+                b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
+                row[i1] = b;
+            }
+        }
+        return b;
+    }
+    return s1_len + s2_len;
+}
+
+window.initSearch = function(rawSearchIndex) {
+    var MAX_LEV_DISTANCE = 3;
+    var MAX_RESULTS = 200;
+    var GENERICS_DATA = 1;
+    var NAME = 0;
+    var INPUTS_DATA = 0;
+    var OUTPUT_DATA = 1;
+    var NO_TYPE_FILTER = -1;
+    var currentResults, index, searchIndex;
+    var ALIASES = {};
+    var params = searchState.getQueryStringParams();
+
+    // Populate search bar with query string search term when provided,
+    // but only if the input bar is empty. This avoid the obnoxious issue
+    // where you start trying to do a search, and the index loads, and
+    // suddenly your search is gone!
+    if (searchState.input.value === "") {
+        searchState.input.value = params.search || "";
+    }
+
+    /**
+     * Executes the query and builds an index of results
+     * @param  {[Object]} query      [The user query]
+     * @param  {[type]} searchWords  [The list of search words to query
+     *                                against]
+     * @param  {[type]} filterCrates [Crate to search in if defined]
+     * @return {[type]}              [A search index of results]
+     */
+    function execQuery(query, searchWords, filterCrates) {
+        function itemTypeFromName(typename) {
+            for (var i = 0, len = itemTypes.length; i < len; ++i) {
+                if (itemTypes[i] === typename) {
+                    return i;
+                }
+            }
+            return NO_TYPE_FILTER;
+        }
+
+        var valLower = query.query.toLowerCase(),
+            val = valLower,
+            typeFilter = itemTypeFromName(query.type),
+            results = {}, results_in_args = {}, results_returned = {},
+            split = valLower.split("::");
+
+        removeEmptyStringsFromArray(split);
+
+        function transformResults(results, isType) {
+            var out = [];
+            for (var i = 0, len = results.length; i < len; ++i) {
+                if (results[i].id > -1) {
+                    var obj = searchIndex[results[i].id];
+                    obj.lev = results[i].lev;
+                    if (isType !== true || obj.type) {
+                        var res = buildHrefAndPath(obj);
+                        obj.displayPath = pathSplitter(res[0]);
+                        obj.fullPath = obj.displayPath + obj.name;
+                        // To be sure than it some items aren't considered as duplicate.
+                        obj.fullPath += "|" + obj.ty;
+                        obj.href = res[1];
+                        out.push(obj);
+                        if (out.length >= MAX_RESULTS) {
+                            break;
+                        }
+                    }
+                }
+            }
+            return out;
+        }
+
+        function sortResults(results, isType) {
+            var ar = [];
+            for (var entry in results) {
+                if (hasOwnProperty(results, entry)) {
+                    ar.push(results[entry]);
+                }
+            }
+            results = ar;
+            var i, len, result;
+            for (i = 0, len = results.length; i < len; ++i) {
+                result = results[i];
+                result.word = searchWords[result.id];
+                result.item = searchIndex[result.id] || {};
+            }
+            // if there are no results then return to default and fail
+            if (results.length === 0) {
+                return [];
+            }
+
+            results.sort(function(aaa, bbb) {
+                var a, b;
+
+                // sort by exact match with regard to the last word (mismatch goes later)
+                a = (aaa.word !== val);
+                b = (bbb.word !== val);
+                if (a !== b) { return a - b; }
+
+                // Sort by non levenshtein results and then levenshtein results by the distance
+                // (less changes required to match means higher rankings)
+                a = (aaa.lev);
+                b = (bbb.lev);
+                if (a !== b) { return a - b; }
+
+                // sort by crate (non-current crate goes later)
+                a = (aaa.item.crate !== window.currentCrate);
+                b = (bbb.item.crate !== window.currentCrate);
+                if (a !== b) { return a - b; }
+
+                // sort by item name length (longer goes later)
+                a = aaa.word.length;
+                b = bbb.word.length;
+                if (a !== b) { return a - b; }
+
+                // sort by item name (lexicographically larger goes later)
+                a = aaa.word;
+                b = bbb.word;
+                if (a !== b) { return (a > b ? +1 : -1); }
+
+                // sort by index of keyword in item name (no literal occurrence goes later)
+                a = (aaa.index < 0);
+                b = (bbb.index < 0);
+                if (a !== b) { return a - b; }
+                // (later literal occurrence, if any, goes later)
+                a = aaa.index;
+                b = bbb.index;
+                if (a !== b) { return a - b; }
+
+                // special precedence for primitive and keyword pages
+                if ((aaa.item.ty === TY_PRIMITIVE && bbb.item.ty !== TY_KEYWORD) ||
+                    (aaa.item.ty === TY_KEYWORD && bbb.item.ty !== TY_PRIMITIVE)) {
+                    return -1;
+                }
+                if ((bbb.item.ty === TY_PRIMITIVE && aaa.item.ty !== TY_PRIMITIVE) ||
+                    (bbb.item.ty === TY_KEYWORD && aaa.item.ty !== TY_KEYWORD)) {
+                    return 1;
+                }
+
+                // sort by description (no description goes later)
+                a = (aaa.item.desc === "");
+                b = (bbb.item.desc === "");
+                if (a !== b) { return a - b; }
+
+                // sort by type (later occurrence in `itemTypes` goes later)
+                a = aaa.item.ty;
+                b = bbb.item.ty;
+                if (a !== b) { return a - b; }
+
+                // sort by path (lexicographically larger goes later)
+                a = aaa.item.path;
+                b = bbb.item.path;
+                if (a !== b) { return (a > b ? +1 : -1); }
+
+                // que sera, sera
+                return 0;
+            });
+
+            for (i = 0, len = results.length; i < len; ++i) {
+                var result = results[i];
+
+                // this validation does not make sense when searching by types
+                if (result.dontValidate) {
+                    continue;
+                }
+                var name = result.item.name.toLowerCase(),
+                    path = result.item.path.toLowerCase(),
+                    parent = result.item.parent;
+
+                if (isType !== true &&
+                    validateResult(name, path, split, parent) === false)
+                {
+                    result.id = -1;
+                }
+            }
+            return transformResults(results);
+        }
+
+        function extractGenerics(val) {
+            val = val.toLowerCase();
+            if (val.indexOf("<") !== -1) {
+                var values = val.substring(val.indexOf("<") + 1, val.lastIndexOf(">"));
+                return {
+                    name: val.substring(0, val.indexOf("<")),
+                    generics: values.split(/\s*,\s*/),
+                };
+            }
+            return {
+                name: val,
+                generics: [],
+            };
+        }
+
+        function getObjectNameFromId(id) {
+            if (typeof id === "number") {
+                return searchIndex[id].name;
+            }
+            return id;
+        }
+
+        function checkGenerics(obj, val) {
+            // The names match, but we need to be sure that all generics kinda
+            // match as well.
+            var tmp_lev, elem_name;
+            if (val.generics.length > 0) {
+                if (obj.length > GENERICS_DATA &&
+                      obj[GENERICS_DATA].length >= val.generics.length) {
+                    var elems = Object.create(null);
+                    var elength = object[GENERICS_DATA].length;
+                    for (var x = 0; x < elength; ++x) {
+                        elems[getObjectNameFromId(obj[GENERICS_DATA][x])] += 1;
+                    }
+                    var total = 0;
+                    var done = 0;
+                    // We need to find the type that matches the most to remove it in order
+                    // to move forward.
+                    var vlength = val.generics.length;
+                    for (x = 0; x < vlength; ++x) {
+                        var lev = MAX_LEV_DISTANCE + 1;
+                        var firstGeneric = getObjectNameFromId(val.generics[x]);
+                        var match = null;
+                        if (elems[firstGeneric]) {
+                            match = firstGeneric;
+                            lev = 0;
+                        } else {
+                            for (elem_name in elems) {
+                                tmp_lev = levenshtein(elem_name, firstGeneric);
+                                if (tmp_lev < lev) {
+                                    lev = tmp_lev;
+                                    match = elem_name;
+                                }
+                            }
+                        }
+                        if (match !== null) {
+                            elems[match] -= 1;
+                            if (elems[match] == 0) {
+                                delete elems[match];
+                            }
+                            total += lev;
+                            done += 1;
+                        } else {
+                            return MAX_LEV_DISTANCE + 1;
+                        }
+                    }
+                    return Math.ceil(total / done);
+                }
+            }
+            return MAX_LEV_DISTANCE + 1;
+        }
+
+        // Check for type name and type generics (if any).
+        function checkType(obj, val, literalSearch) {
+            var lev_distance = MAX_LEV_DISTANCE + 1;
+            var len, x, firstGeneric;
+            if (obj[NAME] === val.name) {
+                if (literalSearch === true) {
+                    if (val.generics && val.generics.length !== 0) {
+                        if (obj.length > GENERICS_DATA &&
+                              obj[GENERICS_DATA].length >= val.generics.length) {
+                            var elems = Object.create(null);
+                            len = obj[GENERICS_DATA].length;
+                            for (x = 0; x < len; ++x) {
+                                elems[getObjectNameFromId(obj[GENERICS_DATA][x])] += 1;
+                            }
+
+                            var allFound = true;
+                            len = val.generics.length;
+                            for (x = 0; x < len; ++x) {
+                                firstGeneric = getObjectNameFromId(val.generics[x]);
+                                if (elems[firstGeneric]) {
+                                    elems[firstGeneric] -= 1;
+                                } else {
+                                    allFound = false;
+                                    break;
+                                }
+                            }
+                            if (allFound === true) {
+                                return true;
+                            }
+                        } else {
+                            return false;
+                        }
+                    }
+                    return true;
+                }
+                // If the type has generics but don't match, then it won't return at this point.
+                // Otherwise, `checkGenerics` will return 0 and it'll return.
+                if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length !== 0) {
+                    var tmp_lev = checkGenerics(obj, val);
+                    if (tmp_lev <= MAX_LEV_DISTANCE) {
+                        return tmp_lev;
+                    }
+                } else {
+                    return 0;
+                }
+            }
+            // Names didn't match so let's check if one of the generic types could.
+            if (literalSearch === true) {
+                 if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length > 0) {
+                    return obj[GENERICS_DATA].some(
+                        function(name) {
+                            return name === val.name;
+                        });
+                }
+                return false;
+            }
+            lev_distance = Math.min(levenshtein(obj[NAME], val.name), lev_distance);
+            if (lev_distance <= MAX_LEV_DISTANCE) {
+                // The generics didn't match but the name kinda did so we give it
+                // a levenshtein distance value that isn't *this* good so it goes
+                // into the search results but not too high.
+                lev_distance = Math.ceil((checkGenerics(obj, val) + lev_distance) / 2);
+            } else if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length > 0) {
+                // We can check if the type we're looking for is inside the generics!
+                var olength = obj[GENERICS_DATA].length;
+                for (x = 0; x < olength; ++x) {
+                    lev_distance = Math.min(levenshtein(obj[GENERICS_DATA][x], val.name),
+                                            lev_distance);
+                }
+            }
+            // Now whatever happens, the returned distance is "less good" so we should mark it
+            // as such, and so we add 1 to the distance to make it "less good".
+            return lev_distance + 1;
+        }
+
+        function findArg(obj, val, literalSearch, typeFilter) {
+            var lev_distance = MAX_LEV_DISTANCE + 1;
+
+            if (obj && obj.type && obj.type[INPUTS_DATA] && obj.type[INPUTS_DATA].length > 0) {
+                var length = obj.type[INPUTS_DATA].length;
+                for (var i = 0; i < length; i++) {
+                    var tmp = obj.type[INPUTS_DATA][i];
+                    if (typePassesFilter(typeFilter, tmp[1]) === false) {
+                        continue;
+                    }
+                    tmp = checkType(tmp, val, literalSearch);
+                    if (literalSearch === true) {
+                        if (tmp === true) {
+                            return true;
+                        }
+                        continue;
+                    }
+                    lev_distance = Math.min(tmp, lev_distance);
+                    if (lev_distance === 0) {
+                        return 0;
+                    }
+                }
+            }
+            return literalSearch === true ? false : lev_distance;
+        }
+
+        function checkReturned(obj, val, literalSearch, typeFilter) {
+            var lev_distance = MAX_LEV_DISTANCE + 1;
+
+            if (obj && obj.type && obj.type.length > OUTPUT_DATA) {
+                var ret = obj.type[OUTPUT_DATA];
+                if (typeof ret[0] === "string") {
+                    ret = [ret];
+                }
+                for (var x = 0, len = ret.length; x < len; ++x) {
+                    var tmp = ret[x];
+                    if (typePassesFilter(typeFilter, tmp[1]) === false) {
+                        continue;
+                    }
+                    tmp = checkType(tmp, val, literalSearch);
+                    if (literalSearch === true) {
+                        if (tmp === true) {
+                            return true;
+                        }
+                        continue;
+                    }
+                    lev_distance = Math.min(tmp, lev_distance);
+                    if (lev_distance === 0) {
+                        return 0;
+                    }
+                }
+            }
+            return literalSearch === true ? false : lev_distance;
+        }
+
+        function checkPath(contains, lastElem, ty) {
+            if (contains.length === 0) {
+                return 0;
+            }
+            var ret_lev = MAX_LEV_DISTANCE + 1;
+            var path = ty.path.split("::");
+
+            if (ty.parent && ty.parent.name) {
+                path.push(ty.parent.name.toLowerCase());
+            }
+
+            var length = path.length;
+            var clength = contains.length;
+            if (clength > length) {
+                return MAX_LEV_DISTANCE + 1;
+            }
+            for (var i = 0; i < length; ++i) {
+                if (i + clength > length) {
+                    break;
+                }
+                var lev_total = 0;
+                var aborted = false;
+                for (var x = 0; x < clength; ++x) {
+                    var lev = levenshtein(path[i + x], contains[x]);
+                    if (lev > MAX_LEV_DISTANCE) {
+                        aborted = true;
+                        break;
+                    }
+                    lev_total += lev;
+                }
+                if (aborted === false) {
+                    ret_lev = Math.min(ret_lev, Math.round(lev_total / clength));
+                }
+            }
+            return ret_lev;
+        }
+
+        function typePassesFilter(filter, type) {
+            // No filter
+            if (filter <= NO_TYPE_FILTER) return true;
+
+            // Exact match
+            if (filter === type) return true;
+
+            // Match related items
+            var name = itemTypes[type];
+            switch (itemTypes[filter]) {
+                case "constant":
+                    return name === "associatedconstant";
+                case "fn":
+                    return name === "method" || name === "tymethod";
+                case "type":
+                    return name === "primitive" || name === "associatedtype";
+                case "trait":
+                    return name === "traitalias";
+            }
+
+            // No match
+            return false;
+        }
+
+        function createAliasFromItem(item) {
+            return {
+                crate: item.crate,
+                name: item.name,
+                path: item.path,
+                desc: item.desc,
+                ty: item.ty,
+                parent: item.parent,
+                type: item.type,
+                is_alias: true,
+            };
+        }
+
+        function handleAliases(ret, query, filterCrates) {
+            // We separate aliases and crate aliases because we want to have current crate
+            // aliases to be before the others in the displayed results.
+            var aliases = [];
+            var crateAliases = [];
+            if (filterCrates !== undefined) {
+                if (ALIASES[filterCrates] && ALIASES[filterCrates][query.search]) {
+                    var query_aliases = ALIASES[filterCrates][query.search];
+                    var len = query_aliases.length;
+                    for (var i = 0; i < len; ++i) {
+                        aliases.push(createAliasFromItem(searchIndex[query_aliases[i]]));
+                    }
+                }
+            } else {
+                Object.keys(ALIASES).forEach(function(crate) {
+                    if (ALIASES[crate][query.search]) {
+                        var pushTo = crate === window.currentCrate ? crateAliases : aliases;
+                        var query_aliases = ALIASES[crate][query.search];
+                        var len = query_aliases.length;
+                        for (var i = 0; i < len; ++i) {
+                            pushTo.push(createAliasFromItem(searchIndex[query_aliases[i]]));
+                        }
+                    }
+                });
+            }
+
+            var sortFunc = function(aaa, bbb) {
+                if (aaa.path < bbb.path) {
+                    return 1;
+                } else if (aaa.path === bbb.path) {
+                    return 0;
+                }
+                return -1;
+            };
+            crateAliases.sort(sortFunc);
+            aliases.sort(sortFunc);
+
+            var pushFunc = function(alias) {
+                alias.alias = query.raw;
+                var res = buildHrefAndPath(alias);
+                alias.displayPath = pathSplitter(res[0]);
+                alias.fullPath = alias.displayPath + alias.name;
+                alias.href = res[1];
+
+                ret.others.unshift(alias);
+                if (ret.others.length > MAX_RESULTS) {
+                    ret.others.pop();
+                }
+            };
+            onEach(aliases, pushFunc);
+            onEach(crateAliases, pushFunc);
+        }
+
+        // quoted values mean literal search
+        var nSearchWords = searchWords.length;
+        var i, it;
+        var ty;
+        var fullId;
+        var returned;
+        var in_args;
+        var len;
+        if ((val.charAt(0) === "\"" || val.charAt(0) === "'") &&
+            val.charAt(val.length - 1) === val.charAt(0))
+        {
+            val = extractGenerics(val.substr(1, val.length - 2));
+            for (i = 0; i < nSearchWords; ++i) {
+                if (filterCrates !== undefined && searchIndex[i].crate !== filterCrates) {
+                    continue;
+                }
+                in_args = findArg(searchIndex[i], val, true, typeFilter);
+                returned = checkReturned(searchIndex[i], val, true, typeFilter);
+                ty = searchIndex[i];
+                fullId = ty.id;
+
+                if (searchWords[i] === val.name
+                    && typePassesFilter(typeFilter, searchIndex[i].ty)
+                    && results[fullId] === undefined) {
+                    results[fullId] = {
+                        id: i,
+                        index: -1,
+                        dontValidate: true,
+                    };
+                }
+                if (in_args === true && results_in_args[fullId] === undefined) {
+                    results_in_args[fullId] = {
+                        id: i,
+                        index: -1,
+                        dontValidate: true,
+                    };
+                }
+                if (returned === true && results_returned[fullId] === undefined) {
+                    results_returned[fullId] = {
+                        id: i,
+                        index: -1,
+                        dontValidate: true,
+                    };
+                }
+            }
+            query.inputs = [val];
+            query.output = val;
+            query.search = val;
+        // searching by type
+        } else if (val.search("->") > -1) {
+            var trimmer = function(s) { return s.trim(); };
+            var parts = val.split("->").map(trimmer);
+            var input = parts[0];
+            // sort inputs so that order does not matter
+            var inputs = input.split(",").map(trimmer).sort();
+            for (i = 0, len = inputs.length; i < len; ++i) {
+                inputs[i] = extractGenerics(inputs[i]);
+            }
+            var output = extractGenerics(parts[1]);
+
+            for (i = 0; i < nSearchWords; ++i) {
+                if (filterCrates !== undefined && searchIndex[i].crate !== filterCrates) {
+                    continue;
+                }
+                var type = searchIndex[i].type;
+                ty = searchIndex[i];
+                if (!type) {
+                    continue;
+                }
+                fullId = ty.id;
+
+                returned = checkReturned(ty, output, true, NO_TYPE_FILTER);
+                if (output.name === "*" || returned === true) {
+                    in_args = false;
+                    var is_module = false;
+
+                    if (input === "*") {
+                        is_module = true;
+                    } else {
+                        var allFound = true;
+                        for (it = 0, len = inputs.length; allFound === true && it < len; it++) {
+                            allFound = checkType(type, inputs[it], true);
+                        }
+                        in_args = allFound;
+                    }
+                    if (in_args === true) {
+                        results_in_args[fullId] = {
+                            id: i,
+                            index: -1,
+                            dontValidate: true,
+                        };
+                    }
+                    if (returned === true) {
+                        results_returned[fullId] = {
+                            id: i,
+                            index: -1,
+                            dontValidate: true,
+                        };
+                    }
+                    if (is_module === true) {
+                        results[fullId] = {
+                            id: i,
+                            index: -1,
+                            dontValidate: true,
+                        };
+                    }
+                }
+            }
+            query.inputs = inputs.map(function(input) {
+                return input.name;
+            });
+            query.output = output.name;
+        } else {
+            query.inputs = [val];
+            query.output = val;
+            query.search = val;
+            // gather matching search results up to a certain maximum
+            val = val.replace(/\_/g, "");
+
+            var valGenerics = extractGenerics(val);
+
+            var paths = valLower.split("::");
+            removeEmptyStringsFromArray(paths);
+            val = paths[paths.length - 1];
+            var contains = paths.slice(0, paths.length > 1 ? paths.length - 1 : 1);
+
+            var lev, j;
+            for (j = 0; j < nSearchWords; ++j) {
+                ty = searchIndex[j];
+                if (!ty || (filterCrates !== undefined && ty.crate !== filterCrates)) {
+                    continue;
+                }
+                var lev_add = 0;
+                if (paths.length > 1) {
+                    lev = checkPath(contains, paths[paths.length - 1], ty);
+                    if (lev > MAX_LEV_DISTANCE) {
+                        continue;
+                    } else if (lev > 0) {
+                        lev_add = lev / 10;
+                    }
+                }
+
+                returned = MAX_LEV_DISTANCE + 1;
+                in_args = MAX_LEV_DISTANCE + 1;
+                var index = -1;
+                // we want lev results to go lower than others
+                lev = MAX_LEV_DISTANCE + 1;
+                fullId = ty.id;
+
+                if (searchWords[j].indexOf(split[i]) > -1 ||
+                    searchWords[j].indexOf(val) > -1 ||
+                    ty.normalizedName.indexOf(val) > -1)
+                {
+                    // filter type: ... queries
+                    if (typePassesFilter(typeFilter, ty.ty) && results[fullId] === undefined) {
+                        index = ty.normalizedName.indexOf(val);
+                    }
+                }
+                if ((lev = levenshtein(searchWords[j], val)) <= MAX_LEV_DISTANCE) {
+                    if (typePassesFilter(typeFilter, ty.ty) === false) {
+                        lev = MAX_LEV_DISTANCE + 1;
+                    } else {
+                        lev += 1;
+                    }
+                }
+                in_args = findArg(ty, valGenerics, false, typeFilter);
+                returned = checkReturned(ty, valGenerics, false, typeFilter);
+
+                lev += lev_add;
+                if (lev > 0 && val.length > 3 && searchWords[j].indexOf(val) > -1) {
+                    if (val.length < 6) {
+                        lev -= 1;
+                    } else {
+                        lev = 0;
+                    }
+                }
+                if (in_args <= MAX_LEV_DISTANCE) {
+                    if (results_in_args[fullId] === undefined) {
+                        results_in_args[fullId] = {
+                            id: j,
+                            index: index,
+                            lev: in_args,
+                        };
+                    }
+                    results_in_args[fullId].lev =
+                        Math.min(results_in_args[fullId].lev, in_args);
+                }
+                if (returned <= MAX_LEV_DISTANCE) {
+                    if (results_returned[fullId] === undefined) {
+                        results_returned[fullId] = {
+                            id: j,
+                            index: index,
+                            lev: returned,
+                        };
+                    }
+                    results_returned[fullId].lev =
+                        Math.min(results_returned[fullId].lev, returned);
+                }
+                if (index !== -1 || lev <= MAX_LEV_DISTANCE) {
+                    if (index !== -1 && paths.length < 2) {
+                        lev = 0;
+                    }
+                    if (results[fullId] === undefined) {
+                        results[fullId] = {
+                            id: j,
+                            index: index,
+                            lev: lev,
+                        };
+                    }
+                    results[fullId].lev = Math.min(results[fullId].lev, lev);
+                }
+            }
+        }
+
+        var ret = {
+            "in_args": sortResults(results_in_args, true),
+            "returned": sortResults(results_returned, true),
+            "others": sortResults(results),
+        };
+        handleAliases(ret, query, filterCrates);
+        return ret;
+    }
+
+    /**
+     * Validate performs the following boolean logic. For example:
+     * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
+     * exists in (name || path || parent) OR => ("file" && "open") exists in
+     * (name || path )
+     *
+     * This could be written functionally, but I wanted to minimise
+     * functions on stack.
+     *
+     * @param  {[string]} name   [The name of the result]
+     * @param  {[string]} path   [The path of the result]
+     * @param  {[string]} keys   [The keys to be used (["file", "open"])]
+     * @param  {[object]} parent [The parent of the result]
+     * @return {[boolean]}       [Whether the result is valid or not]
+     */
+    function validateResult(name, path, keys, parent) {
+        for (var i = 0, len = keys.length; i < len; ++i) {
+            // each check is for validation so we negate the conditions and invalidate
+            if (!(
+                // check for an exact name match
+                name.indexOf(keys[i]) > -1 ||
+                // then an exact path match
+                path.indexOf(keys[i]) > -1 ||
+                // next if there is a parent, check for exact parent match
+                (parent !== undefined && parent.name !== undefined &&
+                    parent.name.toLowerCase().indexOf(keys[i]) > -1) ||
+                // lastly check to see if the name was a levenshtein match
+                levenshtein(name, keys[i]) <= MAX_LEV_DISTANCE)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    function getQuery(raw) {
+        var matches, type, query;
+        query = raw;
+
+        matches = query.match(/^(fn|mod|struct|enum|trait|type|const|macro)\s*:\s*/i);
+        if (matches) {
+            type = matches[1].replace(/^const$/, "constant");
+            query = query.substring(matches[0].length);
+        }
+
+        return {
+            raw: raw,
+            query: query,
+            type: type,
+            id: query + type
+        };
+    }
+
+    function initSearchNav() {
+        var hoverTimeout;
+
+        var click_func = function(e) {
+            var el = e.target;
+            // to retrieve the real "owner" of the event.
+            while (el.tagName !== "TR") {
+                el = el.parentNode;
+            }
+            var dst = e.target.getElementsByTagName("a");
+            if (dst.length < 1) {
+                return;
+            }
+            dst = dst[0];
+            if (window.location.pathname === dst.pathname) {
+                searchState.hideResults();
+                document.location.href = dst.href;
+            }
+        };
+        var mouseover_func = function(e) {
+            if (searchState.mouseMovedAfterSearch) {
+                var el = e.target;
+                // to retrieve the real "owner" of the event.
+                while (el.tagName !== "TR") {
+                    el = el.parentNode;
+                }
+                clearTimeout(hoverTimeout);
+                hoverTimeout = setTimeout(function() {
+                    onEachLazy(document.getElementsByClassName("search-results"), function(e) {
+                        onEachLazy(e.getElementsByClassName("result"), function(i_e) {
+                            removeClass(i_e, "highlighted");
+                        });
+                    });
+                    addClass(el, "highlighted");
+                }, 20);
+            }
+        };
+        onEachLazy(document.getElementsByClassName("search-results"), function(e) {
+            onEachLazy(e.getElementsByClassName("result"), function(i_e) {
+                i_e.onclick = click_func;
+                i_e.onmouseover = mouseover_func;
+            });
+        });
+
+        searchState.input.onkeydown = function(e) {
+            // "actives" references the currently highlighted item in each search tab.
+            // Each array in "actives" represents a tab.
+            var actives = [[], [], []];
+            // "current" is used to know which tab we're looking into.
+            var current = 0;
+            onEachLazy(document.getElementById("results").childNodes, function(e) {
+                onEachLazy(e.getElementsByClassName("highlighted"), function(h_e) {
+                    actives[current].push(h_e);
+                });
+                current += 1;
+            });
+
+            var currentTab = searchState.currentTab;
+            if (e.which === 38) { // up
+                if (e.ctrlKey) { // Going through result tabs.
+                    printTab(currentTab > 0 ? currentTab - 1 : 2);
+                } else {
+                    if (!actives[currentTab].length ||
+                        !actives[currentTab][0].previousElementSibling) {
+                        return;
+                    }
+                    addClass(actives[currentTab][0].previousElementSibling, "highlighted");
+                    removeClass(actives[currentTab][0], "highlighted");
+                }
+                e.preventDefault();
+            } else if (e.which === 40) { // down
+                if (e.ctrlKey) { // Going through result tabs.
+                    printTab(currentTab > 1 ? 0 : currentTab + 1);
+                } else if (!actives[currentTab].length) {
+                    var results = document.getElementById("results").childNodes;
+                    if (results.length > 0) {
+                        var res = results[currentTab].getElementsByClassName("result");
+                        if (res.length > 0) {
+                            addClass(res[0], "highlighted");
+                        }
+                    }
+                } else if (actives[currentTab][0].nextElementSibling) {
+                    addClass(actives[currentTab][0].nextElementSibling, "highlighted");
+                    removeClass(actives[currentTab][0], "highlighted");
+                }
+                e.preventDefault();
+            } else if (e.which === 13) { // return
+                if (actives[currentTab].length) {
+                    document.location.href =
+                        actives[currentTab][0].getElementsByTagName("a")[0].href;
+                }
+            } else if (e.which === 16) { // shift
+                // Does nothing, it's just to avoid losing "focus" on the highlighted element.
+            } else if (actives[currentTab].length > 0) {
+                removeClass(actives[currentTab][0], "highlighted");
+            }
+        };
+    }
+
+    function buildHrefAndPath(item) {
+        var displayPath;
+        var href;
+        var type = itemTypes[item.ty];
+        var name = item.name;
+        var path = item.path;
+
+        if (type === "mod") {
+            displayPath = path + "::";
+            href = window.rootPath + path.replace(/::/g, "/") + "/" +
+                   name + "/index.html";
+        } else if (type === "primitive" || type === "keyword") {
+            displayPath = "";
+            href = window.rootPath + path.replace(/::/g, "/") +
+                   "/" + type + "." + name + ".html";
+        } else if (type === "externcrate") {
+            displayPath = "";
+            href = window.rootPath + name + "/index.html";
+        } else if (item.parent !== undefined) {
+            var myparent = item.parent;
+            var anchor = "#" + type + "." + name;
+            var parentType = itemTypes[myparent.ty];
+            var pageType = parentType;
+            var pageName = myparent.name;
+
+            if (parentType === "primitive") {
+                displayPath = myparent.name + "::";
+            } else if (type === "structfield" && parentType === "variant") {
+                // Structfields belonging to variants are special: the
+                // final path element is the enum name.
+                var enumNameIdx = item.path.lastIndexOf("::");
+                var enumName = item.path.substr(enumNameIdx + 2);
+                path = item.path.substr(0, enumNameIdx);
+                displayPath = path + "::" + enumName + "::" + myparent.name + "::";
+                anchor = "#variant." + myparent.name + ".field." + name;
+                pageType = "enum";
+                pageName = enumName;
+            } else {
+                displayPath = path + "::" + myparent.name + "::";
+            }
+            href = window.rootPath + path.replace(/::/g, "/") +
+                   "/" + pageType +
+                   "." + pageName +
+                   ".html" + anchor;
+        } else {
+            displayPath = item.path + "::";
+            href = window.rootPath + item.path.replace(/::/g, "/") +
+                   "/" + type + "." + name + ".html";
+        }
+        return [displayPath, href];
+    }
+
+    function escape(content) {
+        var h1 = document.createElement("h1");
+        h1.textContent = content;
+        return h1.innerHTML;
+    }
+
+    function pathSplitter(path) {
+        var tmp = "<span>" + path.replace(/::/g, "::</span><span>");
+        if (tmp.endsWith("<span>")) {
+            return tmp.slice(0, tmp.length - 6);
+        }
+        return tmp;
+    }
+
+    function addTab(array, query, display) {
+        var extraStyle = "";
+        if (display === false) {
+            extraStyle = " style=\"display: none;\"";
+        }
+
+        var output = "";
+        var duplicates = {};
+        var length = 0;
+        if (array.length > 0) {
+            output = "<table class=\"search-results\"" + extraStyle + ">";
+
+            array.forEach(function(item) {
+                var name, type;
+
+                name = item.name;
+                type = itemTypes[item.ty];
+
+                if (item.is_alias !== true) {
+                    if (duplicates[item.fullPath]) {
+                        return;
+                    }
+                    duplicates[item.fullPath] = true;
+                }
+                length += 1;
+
+                output += "<tr class=\"" + type + " result\"><td>" +
+                          "<a href=\"" + item.href + "\">" +
+                          (item.is_alias === true ?
+                           ("<span class=\"alias\"><b>" + item.alias + " </b></span><span " +
+                              "class=\"grey\"><i>&nbsp;- see&nbsp;</i></span>") : "") +
+                          item.displayPath + "<span class=\"" + type + "\">" +
+                          name + "</span></a></td><td>" +
+                          "<a href=\"" + item.href + "\">" +
+                          "<span class=\"desc\">" + item.desc +
+                          "&nbsp;</span></a></td></tr>";
+            });
+            output += "</table>";
+        } else {
+            output = "<div class=\"search-failed\"" + extraStyle + ">No results :(<br/>" +
+                "Try on <a href=\"https://duckduckgo.com/?q=" +
+                encodeURIComponent("rust " + query.query) +
+                "\">DuckDuckGo</a>?<br/><br/>" +
+                "Or try looking in one of these:<ul><li>The <a " +
+                "href=\"https://doc.rust-lang.org/reference/index.html\">Rust Reference</a> " +
+                " for technical details about the language.</li><li><a " +
+                "href=\"https://doc.rust-lang.org/rust-by-example/index.html\">Rust By " +
+                "Example</a> for expository code examples.</a></li><li>The <a " +
+                "href=\"https://doc.rust-lang.org/book/index.html\">Rust Book</a> for " +
+                "introductions to language features and the language itself.</li><li><a " +
+                "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
+                " <a href=\"https://crates.io/\">crates.io</a>.</li></ul></div>";
+        }
+        return [output, length];
+    }
+
+    function makeTabHeader(tabNb, text, nbElems) {
+        if (searchState.currentTab === tabNb) {
+            return "<button class=\"selected\">" + text +
+                   " <div class=\"count\">(" + nbElems + ")</div></button>";
+        }
+        return "<button>" + text + " <div class=\"count\">(" + nbElems + ")</div></button>";
+    }
+
+    function showResults(results) {
+        var search = searchState.outputElement();
+        if (results.others.length === 1
+            && getSettingValue("go-to-only-result") === "true"
+            // By default, the search DOM element is "empty" (meaning it has no children not
+            // text content). Once a search has been run, it won't be empty, even if you press
+            // ESC or empty the search input (which also "cancels" the search).
+            && (!search.firstChild || search.firstChild.innerText !== searchState.loadingText))
+        {
+            var elem = document.createElement("a");
+            elem.href = results.others[0].href;
+            elem.style.display = "none";
+            // For firefox, we need the element to be in the DOM so it can be clicked.
+            document.body.appendChild(elem);
+            elem.click();
+            return;
+        }
+        var query = getQuery(searchState.input.value);
+
+        currentResults = query.id;
+
+        var ret_others = addTab(results.others, query);
+        var ret_in_args = addTab(results.in_args, query, false);
+        var ret_returned = addTab(results.returned, query, false);
+
+        // Navigate to the relevant tab if the current tab is empty, like in case users search
+        // for "-> String". If they had selected another tab previously, they have to click on
+        // it again.
+        var currentTab = searchState.currentTab;
+        if ((currentTab === 0 && ret_others[1] === 0) ||
+                (currentTab === 1 && ret_in_args[1] === 0) ||
+                (currentTab === 2 && ret_returned[1] === 0)) {
+            if (ret_others[1] !== 0) {
+                currentTab = 0;
+            } else if (ret_in_args[1] !== 0) {
+                currentTab = 1;
+            } else if (ret_returned[1] !== 0) {
+                currentTab = 2;
+            }
+        }
+
+        var output = "<h1>Results for " + escape(query.query) +
+            (query.type ? " (type: " + escape(query.type) + ")" : "") + "</h1>" +
+            "<div id=\"titles\">" +
+            makeTabHeader(0, "In Names", ret_others[1]) +
+            makeTabHeader(1, "In Parameters", ret_in_args[1]) +
+            makeTabHeader(2, "In Return Types", ret_returned[1]) +
+            "</div><div id=\"results\">" +
+            ret_others[0] + ret_in_args[0] + ret_returned[0] + "</div>";
+
+        search.innerHTML = output;
+        searchState.showResults(search);
+        initSearchNav();
+        var elems = document.getElementById("titles").childNodes;
+        elems[0].onclick = function() { printTab(0); };
+        elems[1].onclick = function() { printTab(1); };
+        elems[2].onclick = function() { printTab(2); };
+        printTab(currentTab);
+    }
+
+    function execSearch(query, searchWords, filterCrates) {
+        function getSmallest(arrays, positions, notDuplicates) {
+            var start = null;
+
+            for (var it = 0, len = positions.length; it < len; ++it) {
+                if (arrays[it].length > positions[it] &&
+                    (start === null || start > arrays[it][positions[it]].lev) &&
+                    !notDuplicates[arrays[it][positions[it]].fullPath]) {
+                    start = arrays[it][positions[it]].lev;
+                }
+            }
+            return start;
+        }
+
+        function mergeArrays(arrays) {
+            var ret = [];
+            var positions = [];
+            var notDuplicates = {};
+
+            for (var x = 0, arrays_len = arrays.length; x < arrays_len; ++x) {
+                positions.push(0);
+            }
+            while (ret.length < MAX_RESULTS) {
+                var smallest = getSmallest(arrays, positions, notDuplicates);
+
+                if (smallest === null) {
+                    break;
+                }
+                for (x = 0; x < arrays_len && ret.length < MAX_RESULTS; ++x) {
+                    if (arrays[x].length > positions[x] &&
+                            arrays[x][positions[x]].lev === smallest &&
+                            !notDuplicates[arrays[x][positions[x]].fullPath]) {
+                        ret.push(arrays[x][positions[x]]);
+                        notDuplicates[arrays[x][positions[x]].fullPath] = true;
+                        positions[x] += 1;
+                    }
+                }
+            }
+            return ret;
+        }
+
+        var queries = query.raw.split(",");
+        var results = {
+            "in_args": [],
+            "returned": [],
+            "others": [],
+        };
+
+        for (var i = 0, len = queries.length; i < len; ++i) {
+            query = queries[i].trim();
+            if (query.length !== 0) {
+                var tmp = execQuery(getQuery(query), searchWords, filterCrates);
+
+                results.in_args.push(tmp.in_args);
+                results.returned.push(tmp.returned);
+                results.others.push(tmp.others);
+            }
+        }
+        if (queries.length > 1) {
+            return {
+                "in_args": mergeArrays(results.in_args),
+                "returned": mergeArrays(results.returned),
+                "others": mergeArrays(results.others),
+            };
+        }
+        return {
+            "in_args": results.in_args[0],
+            "returned": results.returned[0],
+            "others": results.others[0],
+        };
+    }
+
+    function getFilterCrates() {
+        var elem = document.getElementById("crate-search");
+
+        if (elem && elem.value !== "All crates" && hasOwnProperty(rawSearchIndex, elem.value)) {
+            return elem.value;
+        }
+        return undefined;
+    }
+
+    function search(e, forced) {
+        var params = searchState.getQueryStringParams();
+        var query = getQuery(searchState.input.value.trim());
+
+        if (e) {
+            e.preventDefault();
+        }
+
+        if (query.query.length === 0) {
+            return;
+        }
+        if (forced !== true && query.id === currentResults) {
+            if (query.query.length > 0) {
+                searchState.putBackSearch(searchState.input);
+            }
+            return;
+        }
+
+        // Update document title to maintain a meaningful browser history
+        searchState.title = "Results for " + query.query + " - Rust";
+
+        // Because searching is incremental by character, only the most
+        // recent search query is added to the browser history.
+        if (searchState.browserSupportsHistoryApi()) {
+            var newURL = getNakedUrl() + "?search=" + encodeURIComponent(query.raw) +
+                window.location.hash;
+            if (!history.state && !params.search) {
+                history.pushState(query, "", newURL);
+            } else {
+                history.replaceState(query, "", newURL);
+            }
+        }
+
+        var filterCrates = getFilterCrates();
+        showResults(execSearch(query, index, filterCrates));
+    }
+
+    function buildIndex(rawSearchIndex) {
+        searchIndex = [];
+        var searchWords = [];
+        var i, word;
+        var currentIndex = 0;
+        var id = 0;
+
+        for (var crate in rawSearchIndex) {
+            if (!hasOwnProperty(rawSearchIndex, crate)) { continue; }
+
+            var crateSize = 0;
+
+            searchWords.push(crate);
+            var normalizedName = crate.indexOf("_") === -1
+                ? crate
+                : crate.replace(/_/g, "");
+            // This object should have exactly the same set of fields as the "row"
+            // object defined below. Your JavaScript runtime will thank you.
+            // https://mathiasbynens.be/notes/shapes-ics
+            var crateRow = {
+                crate: crate,
+                ty: 1, // == ExternCrate
+                name: crate,
+                path: "",
+                desc: rawSearchIndex[crate].doc,
+                parent: undefined,
+                type: null,
+                id: id,
+                normalizedName: normalizedName,
+            };
+            id += 1;
+            searchIndex.push(crateRow);
+            currentIndex += 1;
+
+            // an array of (Number) item types
+            var itemTypes = rawSearchIndex[crate].t;
+            // an array of (String) item names
+            var itemNames = rawSearchIndex[crate].n;
+            // an array of (String) full paths (or empty string for previous path)
+            var itemPaths = rawSearchIndex[crate].q;
+            // an array of (String) descriptions
+            var itemDescs = rawSearchIndex[crate].d;
+            // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
+            var itemParentIdxs = rawSearchIndex[crate].i;
+            // an array of (Object | null) the type of the function, if any
+            var itemFunctionSearchTypes = rawSearchIndex[crate].f;
+            // an array of [(Number) item type,
+            //              (String) name]
+            var paths = rawSearchIndex[crate].p;
+            // a array of [(String) alias name
+            //             [Number] index to items]
+            var aliases = rawSearchIndex[crate].a;
+
+            // convert `rawPaths` entries into object form
+            var len = paths.length;
+            for (i = 0; i < len; ++i) {
+                paths[i] = {ty: paths[i][0], name: paths[i][1]};
+            }
+
+            // convert `item*` into an object form, and construct word indices.
+            //
+            // before any analysis is performed lets gather the search terms to
+            // search against apart from the rest of the data.  This is a quick
+            // operation that is cached for the life of the page state so that
+            // all other search operations have access to this cached data for
+            // faster analysis operations
+            len = itemTypes.length;
+            var lastPath = "";
+            for (i = 0; i < len; ++i) {
+                // This object should have exactly the same set of fields as the "crateRow"
+                // object defined above.
+                if (typeof itemNames[i] === "string") {
+                    word = itemNames[i].toLowerCase();
+                    searchWords.push(word);
+                } else {
+                    word = "";
+                    searchWords.push("");
+                }
+                var normalizedName = word.indexOf("_") === -1
+                    ? word
+                    : word.replace(/_/g, "");
+                var row = {
+                    crate: crate,
+                    ty: itemTypes[i],
+                    name: itemNames[i],
+                    path: itemPaths[i] ? itemPaths[i] : lastPath,
+                    desc: itemDescs[i],
+                    parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
+                    type: itemFunctionSearchTypes[i],
+                    id: id,
+                    normalizedName: normalizedName,
+                };
+                id += 1;
+                searchIndex.push(row);
+                lastPath = row.path;
+                crateSize += 1;
+            }
+
+            if (aliases) {
+                ALIASES[crate] = {};
+                var j, local_aliases;
+                for (var alias_name in aliases) {
+                    if (!aliases.hasOwnProperty(alias_name)) { continue; }
+
+                    if (!ALIASES[crate].hasOwnProperty(alias_name)) {
+                        ALIASES[crate][alias_name] = [];
+                    }
+                    local_aliases = aliases[alias_name];
+                    for (j = 0, len = local_aliases.length; j < len; ++j) {
+                        ALIASES[crate][alias_name].push(local_aliases[j] + currentIndex);
+                    }
+                }
+            }
+            currentIndex += crateSize;
+        }
+        return searchWords;
+    }
+
+    function registerSearchEvents() {
+        var searchAfter500ms = function() {
+            searchState.clearInputTimeout();
+            if (searchState.input.value.length === 0) {
+                if (searchState.browserSupportsHistoryApi()) {
+                    history.replaceState("", window.currentCrate + " - Rust",
+                        getNakedUrl() + window.location.hash);
+                }
+                searchState.hideResults();
+            } else {
+                searchState.timeout = setTimeout(search, 500);
+            }
+        };
+        searchState.input.onkeyup = searchAfter500ms;
+        searchState.input.oninput = searchAfter500ms;
+        document.getElementsByClassName("search-form")[0].onsubmit = function(e) {
+            e.preventDefault();
+            searchState.clearInputTimeout();
+            search();
+        };
+        searchState.input.onchange = function(e) {
+            if (e.target !== document.activeElement) {
+                // To prevent doing anything when it's from a blur event.
+                return;
+            }
+            // Do NOT e.preventDefault() here. It will prevent pasting.
+            searchState.clearInputTimeout();
+            // zero-timeout necessary here because at the time of event handler execution the
+            // pasted content is not in the input field yet. Shouldn’t make any difference for
+            // change, though.
+            setTimeout(search, 0);
+        };
+        searchState.input.onpaste = searchState.input.onchange;
+
+        var selectCrate = document.getElementById("crate-search");
+        if (selectCrate) {
+            selectCrate.onchange = function() {
+                updateLocalStorage("rustdoc-saved-filter-crate", selectCrate.value);
+                search(undefined, true);
+            };
+        }
+
+        // Push and pop states are used to add search results to the browser
+        // history.
+        if (searchState.browserSupportsHistoryApi()) {
+            // Store the previous <title> so we can revert back to it later.
+            var previousTitle = document.title;
+
+            window.addEventListener("popstate", function(e) {
+                var params = searchState.getQueryStringParams();
+                // Revert to the previous title manually since the History
+                // API ignores the title parameter.
+                document.title = previousTitle;
+                // When browsing forward to search results the previous
+                // search will be repeated, so the currentResults are
+                // cleared to ensure the search is successful.
+                currentResults = null;
+                // Synchronize search bar with query string state and
+                // perform the search. This will empty the bar if there's
+                // nothing there, which lets you really go back to a
+                // previous state with nothing in the bar.
+                if (params.search && params.search.length > 0) {
+                    searchState.input.value = params.search;
+                    // Some browsers fire "onpopstate" for every page load
+                    // (Chrome), while others fire the event only when actually
+                    // popping a state (Firefox), which is why search() is
+                    // called both here and at the end of the startSearch()
+                    // function.
+                    search(e);
+                } else {
+                    searchState.input.value = "";
+                    // When browsing back from search results the main page
+                    // visibility must be reset.
+                    searchState.hideResults();
+                }
+            });
+        }
+
+        // This is required in firefox to avoid this problem: Navigating to a search result
+        // with the keyboard, hitting enter, and then hitting back would take you back to
+        // the doc page, rather than the search that should overlay it.
+        // This was an interaction between the back-forward cache and our handlers
+        // that try to sync state between the URL and the search input. To work around it,
+        // do a small amount of re-init on page show.
+        window.onpageshow = function(){
+            var qSearch = searchState.getQueryStringParams().search;
+            if (searchState.input.value === "" && qSearch) {
+                searchState.input.value = qSearch;
+            }
+            search();
+        };
+    }
+
+    index = buildIndex(rawSearchIndex);
+    registerSearchEvents();
+    // If there's a search term in the URL, execute the search now.
+    if (searchState.getQueryStringParams().search) {
+        search();
+    }
+};
+
+if (window.searchIndex !== undefined) {
+  initSearch(window.searchIndex);
+}
+
+})();
diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs
index b3ac865d55e..2b73bd5d52e 100644
--- a/src/librustdoc/html/static_files.rs
+++ b/src/librustdoc/html/static_files.rs
@@ -24,6 +24,9 @@ crate static NORMALIZE_CSS: &str = include_str!("static/normalize.css");
 /// including search behavior and docblock folding, among others.
 crate static MAIN_JS: &str = include_str!("static/main.js");
 
+/// The file contents of `search.js`, which contains the search behavior.
+crate static SEARCH_JS: &str = include_str!("static/search.js");
+
 /// The file contents of `settings.js`, which contains the JavaScript used to handle the settings
 /// page.
 crate static SETTINGS_JS: &str = include_str!("static/settings.js");
diff --git a/src/librustdoc/html/tests.rs b/src/librustdoc/html/tests.rs
new file mode 100644
index 00000000000..5d537dabd0c
--- /dev/null
+++ b/src/librustdoc/html/tests.rs
@@ -0,0 +1,44 @@
+use crate::html::format::href_relative_parts;
+
+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));
+}
+
+#[test]
+fn href_relative_parts_basic() {
+    let relative_to_fqp = &["std", "vec"];
+    let fqp = &["std", "iter"];
+    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);
+}
+#[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);
+}
+#[test]
+fn href_relative_parts_same_module() {
+    let relative_to_fqp = &["std", "vec"];
+    let fqp = &["std", "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);
+}
+#[test]
+fn href_relative_parts_root() {
+    let relative_to_fqp = &[];
+    let fqp = &["std"];
+    assert_relative_path(&["std"], relative_to_fqp, fqp);
+}
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index b9c4bbdceb2..2a51d78f64a 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -289,7 +289,13 @@ fn opts() -> Vec<RustcOptGroup> {
         stable("cfg", |o| o.optmulti("", "cfg", "pass a --cfg to rustc", "")),
         stable("extern", |o| o.optmulti("", "extern", "pass an --extern to rustc", "NAME[=PATH]")),
         unstable("extern-html-root-url", |o| {
-            o.optmulti("", "extern-html-root-url", "base URL to use for dependencies", "NAME=URL")
+            o.optmulti(
+                "",
+                "extern-html-root-url",
+                "base URL to use for dependencies; for example, \
+                 \"std=/doc\" links std::vec::Vec to /doc/std/vec/struct.Vec.html",
+                "NAME=URL",
+            )
         }),
         stable("plugin-path", |o| o.optmulti("", "plugin-path", "removed", "DIR")),
         stable("C", |o| {
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 4ce7c70d4b5..837cd034b65 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -1957,20 +1957,28 @@ fn resolution_failure(
 
 /// Report an anchor failure.
 fn anchor_failure(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, failure: AnchorFailure) {
-    let msg = match failure {
+    let (msg, anchor_idx) = match failure {
         AnchorFailure::MultipleAnchors => {
-            format!("`{}` contains multiple anchors", diag_info.ori_link)
+            (format!("`{}` contains multiple anchors", diag_info.ori_link), 1)
         }
-        AnchorFailure::RustdocAnchorConflict(res) => format!(
-            "`{}` contains an anchor, but links to {kind}s are already anchored",
-            diag_info.ori_link,
-            kind = res.descr(),
+        AnchorFailure::RustdocAnchorConflict(res) => (
+            format!(
+                "`{}` contains an anchor, but links to {kind}s are already anchored",
+                diag_info.ori_link,
+                kind = res.descr(),
+            ),
+            0,
         ),
     };
 
     report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, &diag_info, |diag, sp| {
-        if let Some(sp) = sp {
-            diag.span_label(sp, "contains invalid anchor");
+        if let Some(mut sp) = sp {
+            if let Some((fragment_offset, _)) =
+                diag_info.ori_link.char_indices().filter(|(_, x)| *x == '#').nth(anchor_idx)
+            {
+                sp = sp.with_lo(sp.lo() + rustc_span::BytePos(fragment_offset as _));
+            }
+            diag.span_label(sp, "invalid anchor");
         }
         if let AnchorFailure::RustdocAnchorConflict(Res::Primitive(_)) = failure {
             diag.note("this restriction may be lifted in a future release");
diff --git a/src/test/rustdoc-ui/intra-doc/anchors.stderr b/src/test/rustdoc-ui/intra-doc/anchors.stderr
index 42a8832185a..d63e1ee60b3 100644
--- a/src/test/rustdoc-ui/intra-doc/anchors.stderr
+++ b/src/test/rustdoc-ui/intra-doc/anchors.stderr
@@ -2,7 +2,9 @@ error: `prim@usize#x` contains an anchor, but links to builtin types are already
   --> $DIR/anchors.rs:47:6
    |
 LL | /// [prim@usize#x]
-   |      ^^^^^^^^^^^^ contains invalid anchor
+   |      ^^^^^^^^^^--
+   |                |
+   |                invalid anchor
    |
 note: the lint level is defined here
   --> $DIR/anchors.rs:1:9
@@ -16,25 +18,33 @@ error: `Foo::f#hola` contains an anchor, but links to fields are already anchore
   --> $DIR/anchors.rs:25:15
    |
 LL | /// Or maybe [Foo::f#hola].
-   |               ^^^^^^^^^^^ contains invalid anchor
+   |               ^^^^^^-----
+   |                     |
+   |                     invalid anchor
 
 error: `hello#people#!` contains multiple anchors
   --> $DIR/anchors.rs:31:28
    |
 LL | /// Another anchor error: [hello#people#!].
-   |                            ^^^^^^^^^^^^^^ contains invalid anchor
+   |                            ^^^^^^^^^^^^--
+   |                                        |
+   |                                        invalid anchor
 
 error: `Enum::A#whatever` contains an anchor, but links to variants are already anchored
   --> $DIR/anchors.rs:37:28
    |
 LL | /// Damn enum's variants: [Enum::A#whatever].
-   |                            ^^^^^^^^^^^^^^^^ contains invalid anchor
+   |                            ^^^^^^^---------
+   |                                   |
+   |                                   invalid anchor
 
 error: `u32#hello` contains an anchor, but links to builtin types are already anchored
   --> $DIR/anchors.rs:43:6
    |
 LL | /// [u32#hello]
-   |      ^^^^^^^^^ contains invalid anchor
+   |      ^^^------
+   |         |
+   |         invalid anchor
    |
    = note: this restriction may be lifted in a future release
    = note: see https://github.com/rust-lang/rust/issues/83083 for more information
diff --git a/src/test/rustdoc-ui/intra-doc/double-anchor.stderr b/src/test/rustdoc-ui/intra-doc/double-anchor.stderr
index c0241b98b78..6addb010e07 100644
--- a/src/test/rustdoc-ui/intra-doc/double-anchor.stderr
+++ b/src/test/rustdoc-ui/intra-doc/double-anchor.stderr
@@ -2,7 +2,9 @@ warning: `with#anchor#error` contains multiple anchors
   --> $DIR/double-anchor.rs:5:18
    |
 LL | /// docs [label][with#anchor#error]
-   |                  ^^^^^^^^^^^^^^^^^ contains invalid anchor
+   |                  ^^^^^^^^^^^------
+   |                             |
+   |                             invalid anchor
    |
    = note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default
 
diff --git a/src/test/rustdoc/assoc-types.rs b/src/test/rustdoc/assoc-types.rs
index 82fa7cf9e60..8fda171002b 100644
--- a/src/test/rustdoc/assoc-types.rs
+++ b/src/test/rustdoc/assoc-types.rs
@@ -6,14 +6,14 @@ pub trait Index<I: ?Sized> {
     type Output: ?Sized;
     // @has - '//*[@id="tymethod.index"]//code' \
     //      "fn index<'a>(&'a self, index: I) -> &'a Self::Output"
-    // @has - '//*[@id="tymethod.index"]//code//a[@href="../assoc_types/trait.Index.html#associatedtype.Output"]' \
+    // @has - '//*[@id="tymethod.index"]//code//a[@href="trait.Index.html#associatedtype.Output"]' \
     //      "Output"
     fn index<'a>(&'a self, index: I) -> &'a Self::Output;
 }
 
 // @has assoc_types/fn.use_output.html
 // @has - '//*[@class="rust fn"]' '-> &T::Output'
-// @has - '//*[@class="rust fn"]//a[@href="../assoc_types/trait.Index.html#associatedtype.Output"]' 'Output'
+// @has - '//*[@class="rust fn"]//a[@href="trait.Index.html#associatedtype.Output"]' 'Output'
 pub fn use_output<T: Index<usize>>(obj: &T, index: usize) -> &T::Output {
     obj.index(index)
 }
@@ -24,12 +24,12 @@ pub trait Feed {
 
 // @has assoc_types/fn.use_input.html
 // @has - '//*[@class="rust fn"]' 'T::Input'
-// @has - '//*[@class="rust fn"]//a[@href="../assoc_types/trait.Feed.html#associatedtype.Input"]' 'Input'
+// @has - '//*[@class="rust fn"]//a[@href="trait.Feed.html#associatedtype.Input"]' 'Input'
 pub fn use_input<T: Feed>(_feed: &T, _element: T::Input) { }
 
 // @has assoc_types/fn.cmp_input.html
 // @has - '//*[@class="rust fn"]' 'where T::Input: PartialEq<U::Input>'
-// @has - '//*[@class="rust fn"]//a[@href="../assoc_types/trait.Feed.html#associatedtype.Input"]' 'Input'
+// @has - '//*[@class="rust fn"]//a[@href="trait.Feed.html#associatedtype.Input"]' 'Input'
 pub fn cmp_input<T: Feed, U: Feed>(a: &T::Input, b: &U::Input) -> bool
     where T::Input: PartialEq<U::Input>
 {
diff --git a/src/test/rustdoc/auxiliary/primitive-doc.rs b/src/test/rustdoc/auxiliary/primitive-doc.rs
new file mode 100644
index 00000000000..a5b69740dd4
--- /dev/null
+++ b/src/test/rustdoc/auxiliary/primitive-doc.rs
@@ -0,0 +1,6 @@
+// compile-flags: --crate-type lib --edition 2018
+
+#[doc(primitive = "usize")]
+/// This is the built-in type `usize`.
+mod usize {
+}
diff --git a/src/test/rustdoc/check-styled-link.rs b/src/test/rustdoc/check-styled-link.rs
index b479820dfb2..ed4a5ea2137 100644
--- a/src/test/rustdoc/check-styled-link.rs
+++ b/src/test/rustdoc/check-styled-link.rs
@@ -2,7 +2,7 @@
 
 pub struct Foo;
 
-// @has foo/struct.Bar.html '//a[@href="../foo/struct.Foo.html"]' 'Foo'
+// @has foo/struct.Bar.html '//a[@href="struct.Foo.html"]' 'Foo'
 
 /// Code-styled reference to [`Foo`].
 pub struct Bar;
diff --git a/src/test/rustdoc/cross-crate-primitive-doc.rs b/src/test/rustdoc/cross-crate-primitive-doc.rs
new file mode 100644
index 00000000000..05376e4680e
--- /dev/null
+++ b/src/test/rustdoc/cross-crate-primitive-doc.rs
@@ -0,0 +1,9 @@
+// aux-build:primitive-doc.rs
+// compile-flags: --extern-html-root-url=primitive_doc=../ -Z unstable-options
+
+#![no_std]
+
+extern crate primitive_doc;
+
+// @has 'cross_crate_primitive_doc/fn.foo.html' '//a[@href="../primitive_doc/primitive.usize.html"]' 'usize'
+pub fn foo() -> usize { 0 }
diff --git a/src/test/rustdoc/default-trait-method-link.rs b/src/test/rustdoc/default-trait-method-link.rs
index e4f0bdab162..7bcd2a3c149 100644
--- a/src/test/rustdoc/default-trait-method-link.rs
+++ b/src/test/rustdoc/default-trait-method-link.rs
@@ -1,7 +1,7 @@
 #![crate_name = "foo"]
 
-// @has foo/trait.Foo.html '//a[@href="../foo/trait.Foo.html#tymethod.req"]' 'req'
-// @has foo/trait.Foo.html '//a[@href="../foo/trait.Foo.html#method.prov"]' 'prov'
+// @has foo/trait.Foo.html '//a[@href="trait.Foo.html#tymethod.req"]' 'req'
+// @has foo/trait.Foo.html '//a[@href="trait.Foo.html#method.prov"]' 'prov'
 
 /// Always make sure to implement [`req`], but you don't have to implement [`prov`].
 ///
diff --git a/src/test/rustdoc/intra-doc-crate/self.rs b/src/test/rustdoc/intra-doc-crate/self.rs
index 4db63b12b6b..8c36a7fa002 100644
--- a/src/test/rustdoc/intra-doc-crate/self.rs
+++ b/src/test/rustdoc/intra-doc-crate/self.rs
@@ -3,7 +3,7 @@
 
 extern crate cross_crate_self;
 
-// @has self/struct.S.html '//a[@href="../self/struct.S.html#method.f"]' "Self::f"
-// @has self/struct.S.html '//a[@href="../self/struct.S.html"]' "Self"
+// @has self/struct.S.html '//a[@href="struct.S.html#method.f"]' "Self::f"
+// @has self/struct.S.html '//a[@href="struct.S.html"]' "Self"
 // @has self/struct.S.html '//a[@href="../cross_crate_self/index.html"]' "crate"
 pub use cross_crate_self::S;
diff --git a/src/test/rustdoc/intra-doc/anchors.rs b/src/test/rustdoc/intra-doc/anchors.rs
index e4f0c737bdd..8ec1a7b4f90 100644
--- a/src/test/rustdoc/intra-doc/anchors.rs
+++ b/src/test/rustdoc/intra-doc/anchors.rs
@@ -4,7 +4,7 @@
 pub struct Something;
 
 // @has anchors/struct.SomeOtherType.html
-// @has - '//a/@href' '../anchors/struct.Something.html#Anchor!'
+// @has - '//a/@href' 'struct.Something.html#Anchor!'
 
 /// I want...
 ///
diff --git a/src/test/rustdoc/intra-doc/associated-defaults.rs b/src/test/rustdoc/intra-doc/associated-defaults.rs
index 28dc7073a3e..68647127fe8 100644
--- a/src/test/rustdoc/intra-doc/associated-defaults.rs
+++ b/src/test/rustdoc/intra-doc/associated-defaults.rs
@@ -9,14 +9,14 @@ pub trait TraitWithDefault {
 }
 
 /// Link to [UsesDefaults::T] and [UsesDefaults::f]
-// @has 'associated_defaults/struct.UsesDefaults.html' '//a[@href="../associated_defaults/struct.UsesDefaults.html#associatedtype.T"]' 'UsesDefaults::T'
-// @has 'associated_defaults/struct.UsesDefaults.html' '//a[@href="../associated_defaults/struct.UsesDefaults.html#method.f"]' 'UsesDefaults::f'
+// @has 'associated_defaults/struct.UsesDefaults.html' '//a[@href="struct.UsesDefaults.html#associatedtype.T"]' 'UsesDefaults::T'
+// @has 'associated_defaults/struct.UsesDefaults.html' '//a[@href="struct.UsesDefaults.html#method.f"]' 'UsesDefaults::f'
 pub struct UsesDefaults;
 impl TraitWithDefault for UsesDefaults {}
 
 /// Link to [OverridesDefaults::T] and [OverridesDefaults::f]
-// @has 'associated_defaults/struct.OverridesDefaults.html' '//a[@href="../associated_defaults/struct.OverridesDefaults.html#associatedtype.T"]' 'OverridesDefaults::T'
-// @has 'associated_defaults/struct.OverridesDefaults.html' '//a[@href="../associated_defaults/struct.OverridesDefaults.html#method.f"]' 'OverridesDefaults::f'
+// @has 'associated_defaults/struct.OverridesDefaults.html' '//a[@href="struct.OverridesDefaults.html#associatedtype.T"]' 'OverridesDefaults::T'
+// @has 'associated_defaults/struct.OverridesDefaults.html' '//a[@href="struct.OverridesDefaults.html#method.f"]' 'OverridesDefaults::f'
 pub struct OverridesDefaults;
 impl TraitWithDefault for OverridesDefaults {
     type T = bool;
diff --git a/src/test/rustdoc/intra-doc/associated-items.rs b/src/test/rustdoc/intra-doc/associated-items.rs
index 43a43a79738..2757418bc64 100644
--- a/src/test/rustdoc/intra-doc/associated-items.rs
+++ b/src/test/rustdoc/intra-doc/associated-items.rs
@@ -9,10 +9,10 @@
 pub fn foo() {}
 
 /// Link to [MyStruct], [link from struct][MyStruct::method], [MyStruct::clone], [MyStruct::Input]
-// @has 'associated_items/struct.MyStruct.html' '//a[@href="../associated_items/struct.MyStruct.html"]' 'MyStruct'
-// @has 'associated_items/struct.MyStruct.html' '//a[@href="../associated_items/struct.MyStruct.html#method.method"]' 'link from struct'
-// @has 'associated_items/struct.MyStruct.html' '//a[@href="../associated_items/struct.MyStruct.html#method.clone"]' 'MyStruct::clone'
-// @has 'associated_items/struct.MyStruct.html' '//a[@href="../associated_items/struct.MyStruct.html#associatedtype.Input"]' 'MyStruct::Input'
+// @has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html"]' 'MyStruct'
+// @has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.method"]' 'link from struct'
+// @has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.clone"]' 'MyStruct::clone'
+// @has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#associatedtype.Input"]' 'MyStruct::Input'
 pub struct MyStruct { foo: () }
 
 impl Clone for MyStruct {
@@ -30,7 +30,7 @@ impl T for MyStruct {
     type Input = usize;
 
     /// [link from method][MyStruct::method] on method
-    // @has 'associated_items/struct.MyStruct.html' '//a[@href="../associated_items/struct.MyStruct.html#method.method"]' 'link from method'
+    // @has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.method"]' 'link from method'
     fn method(i: usize) {
     }
 }
diff --git a/src/test/rustdoc/intra-doc/basic.rs b/src/test/rustdoc/intra-doc/basic.rs
index 7760546e1fa..39f5c298bc4 100644
--- a/src/test/rustdoc/intra-doc/basic.rs
+++ b/src/test/rustdoc/intra-doc/basic.rs
@@ -1,21 +1,21 @@
 // @has basic/index.html
-// @has - '//a/@href' '../basic/struct.ThisType.html'
-// @has - '//a/@href' '../basic/struct.ThisType.html#method.this_method'
-// @has - '//a/@href' '../basic/enum.ThisEnum.html'
-// @has - '//a/@href' '../basic/enum.ThisEnum.html#variant.ThisVariant'
-// @has - '//a/@href' '../basic/trait.ThisTrait.html'
-// @has - '//a/@href' '../basic/trait.ThisTrait.html#tymethod.this_associated_method'
-// @has - '//a/@href' '../basic/trait.ThisTrait.html#associatedtype.ThisAssociatedType'
-// @has - '//a/@href' '../basic/trait.ThisTrait.html#associatedconstant.THIS_ASSOCIATED_CONST'
-// @has - '//a/@href' '../basic/trait.ThisTrait.html'
-// @has - '//a/@href' '../basic/type.ThisAlias.html'
-// @has - '//a/@href' '../basic/union.ThisUnion.html'
-// @has - '//a/@href' '../basic/fn.this_function.html'
-// @has - '//a/@href' '../basic/constant.THIS_CONST.html'
-// @has - '//a/@href' '../basic/static.THIS_STATIC.html'
-// @has - '//a/@href' '../basic/macro.this_macro.html'
-// @has - '//a/@href' '../basic/trait.SoAmbiguous.html'
-// @has - '//a/@href' '../basic/fn.SoAmbiguous.html'
+// @has - '//a/@href' 'struct.ThisType.html'
+// @has - '//a/@href' 'struct.ThisType.html#method.this_method'
+// @has - '//a/@href' 'enum.ThisEnum.html'
+// @has - '//a/@href' 'enum.ThisEnum.html#variant.ThisVariant'
+// @has - '//a/@href' 'trait.ThisTrait.html'
+// @has - '//a/@href' 'trait.ThisTrait.html#tymethod.this_associated_method'
+// @has - '//a/@href' 'trait.ThisTrait.html#associatedtype.ThisAssociatedType'
+// @has - '//a/@href' 'trait.ThisTrait.html#associatedconstant.THIS_ASSOCIATED_CONST'
+// @has - '//a/@href' 'trait.ThisTrait.html'
+// @has - '//a/@href' 'type.ThisAlias.html'
+// @has - '//a/@href' 'union.ThisUnion.html'
+// @has - '//a/@href' 'fn.this_function.html'
+// @has - '//a/@href' 'constant.THIS_CONST.html'
+// @has - '//a/@href' 'static.THIS_STATIC.html'
+// @has - '//a/@href' 'macro.this_macro.html'
+// @has - '//a/@href' 'trait.SoAmbiguous.html'
+// @has - '//a/@href' 'fn.SoAmbiguous.html'
 //! In this crate we would like to link to:
 //!
 //! * [`ThisType`](ThisType)
@@ -46,7 +46,7 @@ macro_rules! this_macro {
     () => {};
 }
 
-// @has basic/struct.ThisType.html '//a/@href' '../basic/macro.this_macro.html'
+// @has basic/struct.ThisType.html '//a/@href' 'macro.this_macro.html'
 /// another link to [`this_macro!()`]
 pub struct ThisType;
 
@@ -72,10 +72,10 @@ pub trait SoAmbiguous {}
 pub fn SoAmbiguous() {}
 
 
-// @has basic/struct.SomeOtherType.html '//a/@href' '../basic/struct.ThisType.html'
-// @has - '//a/@href' '../basic/struct.ThisType.html#method.this_method'
-// @has - '//a/@href' '../basic/enum.ThisEnum.html'
-// @has - '//a/@href' '../basic/enum.ThisEnum.html#variant.ThisVariant'
+// @has basic/struct.SomeOtherType.html '//a/@href' 'struct.ThisType.html'
+// @has - '//a/@href' 'struct.ThisType.html#method.this_method'
+// @has - '//a/@href' 'enum.ThisEnum.html'
+// @has - '//a/@href' 'enum.ThisEnum.html#variant.ThisVariant'
 /// Shortcut links for:
 /// * [`ThisType`]
 /// * [`ThisType::this_method`]
diff --git a/src/test/rustdoc/intra-doc/cross-crate/additional_doc.rs b/src/test/rustdoc/intra-doc/cross-crate/additional_doc.rs
index 837390b3c71..85c5866ca7e 100644
--- a/src/test/rustdoc/intra-doc/cross-crate/additional_doc.rs
+++ b/src/test/rustdoc/intra-doc/cross-crate/additional_doc.rs
@@ -4,7 +4,7 @@
 
 extern crate my_rand;
 
-// @has 'additional_doc/trait.Rng.html' '//a[@href="../additional_doc/trait.Rng.html"]' 'Rng'
+// @has 'additional_doc/trait.Rng.html' '//a[@href="trait.Rng.html"]' 'Rng'
 // @has 'additional_doc/trait.Rng.html' '//a[@href="../my_rand/trait.RngCore.html"]' 'RngCore'
 /// This is an [`Rng`].
 pub use my_rand::Rng;
diff --git a/src/test/rustdoc/intra-doc/cross-crate/hidden.rs b/src/test/rustdoc/intra-doc/cross-crate/hidden.rs
index 9c9d4c64945..31337f20f18 100644
--- a/src/test/rustdoc/intra-doc/cross-crate/hidden.rs
+++ b/src/test/rustdoc/intra-doc/cross-crate/hidden.rs
@@ -6,5 +6,5 @@
 
 extern crate hidden_dep;
 
-// @has 'hidden/struct.Ready.html' '//a/@href' '../hidden/fn.ready.html'
+// @has 'hidden/struct.Ready.html' '//a/@href' 'fn.ready.html'
 pub use hidden_dep::future::{ready, Ready};
diff --git a/src/test/rustdoc/intra-doc/cross-crate/submodule-outer.rs b/src/test/rustdoc/intra-doc/cross-crate/submodule-outer.rs
index 45f561328f2..db7952b5ace 100644
--- a/src/test/rustdoc/intra-doc/cross-crate/submodule-outer.rs
+++ b/src/test/rustdoc/intra-doc/cross-crate/submodule-outer.rs
@@ -11,6 +11,6 @@ pub mod bar {
 
 // NOTE: we re-exported both `Foo` and `Bar` here,
 // NOTE: so they are inlined and therefore we link to the current module.
-// @has 'submodule_outer/trait.Foo.html' '//a[@href="../submodule_outer/bar/trait.Bar.html"]' 'Bar'
-// @has 'submodule_outer/trait.Foo.html' '//a[@href="../submodule_outer/trait.Baz.html"]' 'Baz'
+// @has 'submodule_outer/trait.Foo.html' '//a[@href="bar/trait.Bar.html"]' 'Bar'
+// @has 'submodule_outer/trait.Foo.html' '//a[@href="trait.Baz.html"]' 'Baz'
 pub use ::bar_::{Foo, Baz};
diff --git a/src/test/rustdoc/intra-doc/disambiguators-removed.rs b/src/test/rustdoc/intra-doc/disambiguators-removed.rs
index 12c3cee29c3..d782c5cf5dc 100644
--- a/src/test/rustdoc/intra-doc/disambiguators-removed.rs
+++ b/src/test/rustdoc/intra-doc/disambiguators-removed.rs
@@ -2,26 +2,26 @@
 // first try backticks
 /// Trait: [`trait@Name`], fn: [`fn@Name`], [`Name`][`macro@Name`]
 // @has disambiguators_removed/struct.AtDisambiguator.html
-// @has - '//a[@href="../disambiguators_removed/trait.Name.html"][code]' "Name"
-// @has - '//a[@href="../disambiguators_removed/fn.Name.html"][code]' "Name"
-// @has - '//a[@href="../disambiguators_removed/macro.Name.html"][code]' "Name"
+// @has - '//a[@href="trait.Name.html"][code]' "Name"
+// @has - '//a[@href="fn.Name.html"][code]' "Name"
+// @has - '//a[@href="macro.Name.html"][code]' "Name"
 pub struct AtDisambiguator;
 
 /// fn: [`Name()`], macro: [`Name!`]
 // @has disambiguators_removed/struct.SymbolDisambiguator.html
-// @has - '//a[@href="../disambiguators_removed/fn.Name.html"][code]' "Name()"
-// @has - '//a[@href="../disambiguators_removed/macro.Name.html"][code]' "Name!"
+// @has - '//a[@href="fn.Name.html"][code]' "Name()"
+// @has - '//a[@href="macro.Name.html"][code]' "Name!"
 pub struct SymbolDisambiguator;
 
 // Now make sure that backticks aren't added if they weren't already there
 /// [fn@Name]
 // @has disambiguators_removed/trait.Name.html
-// @has - '//a[@href="../disambiguators_removed/fn.Name.html"]' "Name"
-// @!has - '//a[@href="../disambiguators_removed/fn.Name.html"][code]' "Name"
+// @has - '//a[@href="fn.Name.html"]' "Name"
+// @!has - '//a[@href="fn.Name.html"][code]' "Name"
 
 // FIXME: this will turn !() into ! alone
 /// [Name!()]
-// @has - '//a[@href="../disambiguators_removed/macro.Name.html"]' "Name!"
+// @has - '//a[@href="macro.Name.html"]' "Name!"
 pub trait Name {}
 
 #[allow(non_snake_case)]
@@ -29,22 +29,22 @@ pub trait Name {}
 // Try collapsed reference links
 /// [macro@Name][]
 // @has disambiguators_removed/fn.Name.html
-// @has - '//a[@href="../disambiguators_removed/macro.Name.html"]' "Name"
+// @has - '//a[@href="macro.Name.html"]' "Name"
 
 // Try links that have the same text as a generated URL
-/// Weird URL aligned [../disambiguators_removed/macro.Name.html][trait@Name]
-// @has - '//a[@href="../disambiguators_removed/trait.Name.html"]' "../disambiguators_removed/macro.Name.html"
+/// Weird URL aligned [macro.Name.html][trait@Name]
+// @has - '//a[@href="trait.Name.html"]' "macro.Name.html"
 pub fn Name() {}
 
 #[macro_export]
 // Rustdoc doesn't currently handle links that have weird interspersing of inline code blocks.
 /// [fn@Na`m`e]
 // @has disambiguators_removed/macro.Name.html
-// @has - '//a[@href="../disambiguators_removed/fn.Name.html"]' "fn@Name"
+// @has - '//a[@href="fn.Name.html"]' "fn@Name"
 
 // It also doesn't handle any case where the code block isn't the whole link text:
 /// [trait@`Name`]
-// @has - '//a[@href="../disambiguators_removed/trait.Name.html"]' "trait@Name"
+// @has - '//a[@href="trait.Name.html"]' "trait@Name"
 macro_rules! Name {
     () => ()
 }
diff --git a/src/test/rustdoc/intra-doc/enum-struct-field.rs b/src/test/rustdoc/intra-doc/enum-struct-field.rs
index 70bf343a9a5..2270a1fafa1 100644
--- a/src/test/rustdoc/intra-doc/enum-struct-field.rs
+++ b/src/test/rustdoc/intra-doc/enum-struct-field.rs
@@ -11,4 +11,4 @@ pub enum Foo {
 /// I want [Foo::X::y].
 pub fn foo() {}
 
-// @has foo/fn.foo.html '//a/@href' '../foo/enum.Foo.html#variant.X.field.y'
+// @has foo/fn.foo.html '//a/@href' 'enum.Foo.html#variant.X.field.y'
diff --git a/src/test/rustdoc/intra-doc/extern-type.rs b/src/test/rustdoc/intra-doc/extern-type.rs
index e1934698d1f..f37ae62dde1 100644
--- a/src/test/rustdoc/intra-doc/extern-type.rs
+++ b/src/test/rustdoc/intra-doc/extern-type.rs
@@ -12,6 +12,6 @@ impl ExternType {
 
 // @has 'extern_type/foreigntype.ExternType.html'
 // @has 'extern_type/fn.links_to_extern_type.html' \
-// 'href="../extern_type/foreigntype.ExternType.html#method.f"'
+// 'href="foreigntype.ExternType.html#method.f"'
 /// See also [ExternType::f]
 pub fn links_to_extern_type() {}
diff --git a/src/test/rustdoc/intra-doc/issue-82209.rs b/src/test/rustdoc/intra-doc/issue-82209.rs
index 76618cdce4c..68a5672a8d2 100644
--- a/src/test/rustdoc/intra-doc/issue-82209.rs
+++ b/src/test/rustdoc/intra-doc/issue-82209.rs
@@ -8,4 +8,4 @@ pub enum Foo {
     },
 }
 
-// @has foo/enum.Foo.html '//a/@href' '../foo/enum.Foo.html#variant.Bar.field.abc'
+// @has foo/enum.Foo.html '//a/@href' 'enum.Foo.html#variant.Bar.field.abc'
diff --git a/src/test/rustdoc/intra-doc/mod-ambiguity.rs b/src/test/rustdoc/intra-doc/mod-ambiguity.rs
index feb013b22be..24b9dc30a9e 100644
--- a/src/test/rustdoc/intra-doc/mod-ambiguity.rs
+++ b/src/test/rustdoc/intra-doc/mod-ambiguity.rs
@@ -6,11 +6,11 @@ pub fn foo() {
 }
 
 pub mod foo {}
-// @has mod_ambiguity/struct.A.html '//a/@href' '../mod_ambiguity/foo/index.html'
+// @has mod_ambiguity/struct.A.html '//a/@href' 'foo/index.html'
 /// Module is [`module@foo`]
 pub struct A;
 
 
-// @has mod_ambiguity/struct.B.html '//a/@href' '../mod_ambiguity/fn.foo.html'
+// @has mod_ambiguity/struct.B.html '//a/@href' 'fn.foo.html'
 /// Function is [`fn@foo`]
 pub struct B;
diff --git a/src/test/rustdoc/intra-doc/prim-precedence.rs b/src/test/rustdoc/intra-doc/prim-precedence.rs
index ab6e3da17f4..478b40b0b51 100644
--- a/src/test/rustdoc/intra-doc/prim-precedence.rs
+++ b/src/test/rustdoc/intra-doc/prim-precedence.rs
@@ -11,6 +11,6 @@ pub mod char {
 pub struct MyString;
 
 /// See also [crate::char] and [mod@char]
-// @has prim_precedence/struct.MyString2.html '//*[@href="../prim_precedence/char/index.html"]' 'crate::char'
-// @has - '//*[@href="../prim_precedence/char/index.html"]' 'mod@char'
+// @has prim_precedence/struct.MyString2.html '//*[@href="char/index.html"]' 'crate::char'
+// @has - '//*[@href="char/index.html"]' 'mod@char'
 pub struct MyString2;
diff --git a/src/test/rustdoc/intra-doc/private.rs b/src/test/rustdoc/intra-doc/private.rs
index 337102d6ab3..2756a7998e8 100644
--- a/src/test/rustdoc/intra-doc/private.rs
+++ b/src/test/rustdoc/intra-doc/private.rs
@@ -4,9 +4,9 @@
 // make sure to update `rustdoc-ui/intra-doc/private.rs` if you update this file
 
 /// docs [DontDocMe] [DontDocMe::f] [DontDocMe::x]
-// @has private/struct.DocMe.html '//*a[@href="../private/struct.DontDocMe.html"]' 'DontDocMe'
-// @has private/struct.DocMe.html '//*a[@href="../private/struct.DontDocMe.html#method.f"]' 'DontDocMe::f'
-// @has private/struct.DocMe.html '//*a[@href="../private/struct.DontDocMe.html#structfield.x"]' 'DontDocMe::x'
+// @has private/struct.DocMe.html '//*a[@href="struct.DontDocMe.html"]' 'DontDocMe'
+// @has private/struct.DocMe.html '//*a[@href="struct.DontDocMe.html#method.f"]' 'DontDocMe::f'
+// @has private/struct.DocMe.html '//*a[@href="struct.DontDocMe.html#structfield.x"]' 'DontDocMe::x'
 pub struct DocMe;
 struct DontDocMe {
     x: usize,
diff --git a/src/test/rustdoc/intra-doc/proc-macro.rs b/src/test/rustdoc/intra-doc/proc-macro.rs
index ab4626ccfc3..fce10a130be 100644
--- a/src/test/rustdoc/intra-doc/proc-macro.rs
+++ b/src/test/rustdoc/intra-doc/proc-macro.rs
@@ -9,17 +9,17 @@ pub use proc_macro_macro::{DeriveA, attr_a};
 use proc_macro_macro::{DeriveB, attr_b};
 
 // @has proc_macro/struct.Foo.html
-// @has - '//a/@href' '../proc_macro/derive.DeriveA.html'
-// @has - '//a/@href' '../proc_macro/attr.attr_a.html'
-// @has - '//a/@href' '../proc_macro/trait.DeriveTrait.html'
+// @has - '//a/@href' 'derive.DeriveA.html'
+// @has - '//a/@href' 'attr.attr_a.html'
+// @has - '//a/@href' 'trait.DeriveTrait.html'
 // @has - '//a/@href' '../proc_macro_macro/derive.DeriveB.html'
 // @has - '//a/@href' '../proc_macro_macro/attr.attr_b.html'
 /// Link to [DeriveA], [attr_a], [DeriveB], [attr_b], [DeriveTrait]
 pub struct Foo;
 
 // @has proc_macro/struct.Bar.html
-// @has - '//a/@href' '../proc_macro/derive.DeriveA.html'
-// @has - '//a/@href' '../proc_macro/attr.attr_a.html'
+// @has - '//a/@href' 'derive.DeriveA.html'
+// @has - '//a/@href' 'attr.attr_a.html'
 /// Link to [deriveA](derive@DeriveA) [attr](macro@attr_a)
 pub struct Bar;
 
diff --git a/src/test/rustdoc/intra-doc/pub-use.rs b/src/test/rustdoc/intra-doc/pub-use.rs
index dd52249abc6..579fa68cee8 100644
--- a/src/test/rustdoc/intra-doc/pub-use.rs
+++ b/src/test/rustdoc/intra-doc/pub-use.rs
@@ -13,7 +13,7 @@ extern crate inner;
 
 // @has outer/index.html
 // @ has - '//a[@href="https://doc.rust-lang.org/nightly/std/env/fn.var.html"]' "std::env"
-// @ has - '//a[@href="../outer/fn.f.html"]' "g"
+// @ has - '//a[@href="fn.f.html"]' "g"
 pub use f as g;
 
 // FIXME: same as above
diff --git a/src/test/rustdoc/intra-doc/raw-ident-self.rs b/src/test/rustdoc/intra-doc/raw-ident-self.rs
index d289797f146..177c3016fb1 100644
--- a/src/test/rustdoc/intra-doc/raw-ident-self.rs
+++ b/src/test/rustdoc/intra-doc/raw-ident-self.rs
@@ -5,7 +5,7 @@ pub mod r#impl {
     impl S {
         /// See [Self::b].
         // @has raw_ident_self/impl/struct.S.html
-        // @has - '//a[@href="../../raw_ident_self/impl/struct.S.html#method.b"]' 'Self::b'
+        // @has - '//a[@href="struct.S.html#method.b"]' 'Self::b'
         pub fn a() {}
 
         pub fn b() {}
diff --git a/src/test/rustdoc/intra-doc/reexport-additional-docs.rs b/src/test/rustdoc/intra-doc/reexport-additional-docs.rs
index 96f3580f305..64683bacd65 100644
--- a/src/test/rustdoc/intra-doc/reexport-additional-docs.rs
+++ b/src/test/rustdoc/intra-doc/reexport-additional-docs.rs
@@ -3,13 +3,13 @@
 #![crate_name = "foo"]
 extern crate inner;
 
-// @has foo/struct.Inner.html '//a[@href="../foo/fn.with_code.html"]' 'crate::with_code'
+// @has foo/struct.Inner.html '//a[@href="fn.with_code.html"]' 'crate::with_code'
 /// [crate::with_code]
-// @has - '//a[@href="../foo/fn.with_code.html"]' 'different text'
+// @has - '//a[@href="fn.with_code.html"]' 'different text'
 /// [different text][with_code]
-// @has - '//a[@href="../foo/fn.me_too.html"]' 'me_too'
+// @has - '//a[@href="fn.me_too.html"]' 'me_too'
 #[doc = "[me_too]"]
-// @has - '//a[@href="../foo/fn.me_three.html"]' 'reference link'
+// @has - '//a[@href="fn.me_three.html"]' 'reference link'
 /// This [reference link]
 #[doc = "has an attr in the way"]
 ///
diff --git a/src/test/rustdoc/intra-doc/self.rs b/src/test/rustdoc/intra-doc/self.rs
index b2b75127b31..0ba7df8a78a 100644
--- a/src/test/rustdoc/intra-doc/self.rs
+++ b/src/test/rustdoc/intra-doc/self.rs
@@ -1,8 +1,8 @@
 #![crate_name = "foo"]
 
 
-// @has foo/index.html '//a/@href' '../foo/struct.Foo.html#method.new'
-// @has foo/struct.Foo.html '//a/@href' '../foo/struct.Foo.html#method.new'
+// @has foo/index.html '//a/@href' 'struct.Foo.html#method.new'
+// @has foo/struct.Foo.html '//a/@href' 'struct.Foo.html#method.new'
 
 /// Use [`new`] to create a new instance.
 ///
@@ -15,8 +15,8 @@ impl Foo {
     }
 }
 
-// @has foo/index.html '//a/@href' '../foo/struct.Bar.html#method.new2'
-// @has foo/struct.Bar.html '//a/@href' '../foo/struct.Bar.html#method.new2'
+// @has foo/index.html '//a/@href' 'struct.Bar.html#method.new2'
+// @has foo/struct.Bar.html '//a/@href' 'struct.Bar.html#method.new2'
 
 /// Use [`new2`] to create a new instance.
 ///
@@ -30,7 +30,7 @@ impl Bar {
 }
 
 pub struct MyStruct {
-    // @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#structfield.struct_field'
+    // @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#structfield.struct_field'
 
     /// [`struct_field`]
     ///
@@ -39,7 +39,7 @@ pub struct MyStruct {
 }
 
 pub enum MyEnum {
-    // @has foo/enum.MyEnum.html '//a/@href' '../foo/enum.MyEnum.html#variant.EnumVariant'
+    // @has foo/enum.MyEnum.html '//a/@href' 'enum.MyEnum.html#variant.EnumVariant'
 
     /// [`EnumVariant`]
     ///
@@ -48,7 +48,7 @@ pub enum MyEnum {
 }
 
 pub union MyUnion {
-    // @has foo/union.MyUnion.html '//a/@href' '../foo/union.MyUnion.html#structfield.union_field'
+    // @has foo/union.MyUnion.html '//a/@href' 'union.MyUnion.html#structfield.union_field'
 
     /// [`union_field`]
     ///
@@ -57,21 +57,21 @@ pub union MyUnion {
 }
 
 pub trait MyTrait {
-    // @has foo/trait.MyTrait.html '//a/@href' '../foo/trait.MyTrait.html#associatedtype.AssoType'
+    // @has foo/trait.MyTrait.html '//a/@href' 'trait.MyTrait.html#associatedtype.AssoType'
 
     /// [`AssoType`]
     ///
     /// [`AssoType`]: Self::AssoType
     type AssoType;
 
-    // @has foo/trait.MyTrait.html '//a/@href' '../foo/trait.MyTrait.html#associatedconstant.ASSO_CONST'
+    // @has foo/trait.MyTrait.html '//a/@href' 'trait.MyTrait.html#associatedconstant.ASSO_CONST'
 
     /// [`ASSO_CONST`]
     ///
     /// [`ASSO_CONST`]: Self::ASSO_CONST
     const ASSO_CONST: i32 = 1;
 
-    // @has foo/trait.MyTrait.html '//a/@href' '../foo/trait.MyTrait.html#method.asso_fn'
+    // @has foo/trait.MyTrait.html '//a/@href' 'trait.MyTrait.html#method.asso_fn'
 
     /// [`asso_fn`]
     ///
@@ -80,7 +80,7 @@ pub trait MyTrait {
 }
 
 impl MyStruct {
-    // @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#method.for_impl'
+    // @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#method.for_impl'
 
     /// [`for_impl`]
     ///
@@ -91,21 +91,21 @@ impl MyStruct {
 }
 
 impl MyTrait for MyStruct {
-    // @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#associatedtype.AssoType'
+    // @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#associatedtype.AssoType'
 
     /// [`AssoType`]
     ///
     /// [`AssoType`]: Self::AssoType
     type AssoType = u32;
 
-    // @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#associatedconstant.ASSO_CONST'
+    // @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#associatedconstant.ASSO_CONST'
 
     /// [`ASSO_CONST`]
     ///
     /// [`ASSO_CONST`]: Self::ASSO_CONST
     const ASSO_CONST: i32 = 10;
 
-    // @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#method.asso_fn'
+    // @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#method.asso_fn'
 
     /// [`asso_fn`]
     ///
diff --git a/src/test/rustdoc/intra-doc/trait-impl.rs b/src/test/rustdoc/intra-doc/trait-impl.rs
index ef1987a829a..cf60dc1dbd5 100644
--- a/src/test/rustdoc/intra-doc/trait-impl.rs
+++ b/src/test/rustdoc/intra-doc/trait-impl.rs
@@ -5,21 +5,21 @@ pub struct MyStruct;
 
 impl MyTrait for MyStruct {
 
-// @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#associatedtype.AssoType'
+// @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#associatedtype.AssoType'
 
     /// [`AssoType`]
     ///
     /// [`AssoType`]: MyStruct::AssoType
     type AssoType = u32;
 
-// @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#associatedconstant.ASSO_CONST'
+// @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#associatedconstant.ASSO_CONST'
 
     /// [`ASSO_CONST`]
     ///
     /// [`ASSO_CONST`]: MyStruct::ASSO_CONST
     const ASSO_CONST: i32 = 10;
 
-// @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#method.trait_fn'
+// @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#method.trait_fn'
 
     /// [`trait_fn`]
     ///
diff --git a/src/test/rustdoc/intra-doc/trait-item.rs b/src/test/rustdoc/intra-doc/trait-item.rs
index affd2aaec2d..7602aced564 100644
--- a/src/test/rustdoc/intra-doc/trait-item.rs
+++ b/src/test/rustdoc/intra-doc/trait-item.rs
@@ -2,7 +2,7 @@
 
 /// Link to [S::assoc_fn()]
 /// Link to [Default::default()]
-// @has trait_item/struct.S.html '//*[@href="../trait_item/struct.S.html#method.assoc_fn"]' 'S::assoc_fn()'
+// @has trait_item/struct.S.html '//*[@href="struct.S.html#method.assoc_fn"]' 'S::assoc_fn()'
 // @has - '//*[@href="https://doc.rust-lang.org/nightly/core/default/trait.Default.html#tymethod.default"]' 'Default::default()'
 pub struct S;
 
diff --git a/src/test/rustdoc/intra-link-self-cache.rs b/src/test/rustdoc/intra-link-self-cache.rs
index add1530a5a6..63bf7fa5768 100644
--- a/src/test/rustdoc/intra-link-self-cache.rs
+++ b/src/test/rustdoc/intra-link-self-cache.rs
@@ -1,12 +1,12 @@
 #![crate_name = "foo"]
-// @has foo/enum.E1.html '//a/@href' '../foo/enum.E1.html#variant.A'
+// @has foo/enum.E1.html '//a/@href' 'enum.E1.html#variant.A'
 
 /// [Self::A::b]
 pub enum E1 {
     A { b: usize }
 }
 
-// @has foo/enum.E2.html '//a/@href' '../foo/enum.E2.html#variant.A'
+// @has foo/enum.E2.html '//a/@href' 'enum.E2.html#variant.A'
 
 /// [Self::A::b]
 pub enum E2 {
diff --git a/src/test/rustdoc/issue-28478.rs b/src/test/rustdoc/issue-28478.rs
index 4cc40560254..497276e6826 100644
--- a/src/test/rustdoc/issue-28478.rs
+++ b/src/test/rustdoc/issue-28478.rs
@@ -23,9 +23,9 @@ impl Foo {
 }
 
 impl Bar for Foo {
-    // @has - '//*[@href="../issue_28478/trait.Bar.html#associatedtype.Bar"]' 'Bar'
-    // @has - '//*[@href="../issue_28478/trait.Bar.html#associatedconstant.Baz"]' 'Baz'
-    // @has - '//*[@href="../issue_28478/trait.Bar.html#tymethod.bar"]' 'bar'
+    // @has - '//*[@href="trait.Bar.html#associatedtype.Bar"]' 'Bar'
+    // @has - '//*[@href="trait.Bar.html#associatedconstant.Baz"]' 'Baz'
+    // @has - '//*[@href="trait.Bar.html#tymethod.bar"]' 'bar'
     fn bar() {}
-    // @has - '//*[@href="../issue_28478/trait.Bar.html#method.baz"]' 'baz'
+    // @has - '//*[@href="trait.Bar.html#method.baz"]' 'baz'
 }
diff --git a/src/test/rustdoc/issue-55364.rs b/src/test/rustdoc/issue-55364.rs
index 4aa553f7793..f156d225bd7 100644
--- a/src/test/rustdoc/issue-55364.rs
+++ b/src/test/rustdoc/issue-55364.rs
@@ -2,19 +2,19 @@
 
 // @has issue_55364/subone/index.html
 // These foo/bar links in the module's documentation should refer inside `subone`
-// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subone/fn.foo.html"]' 'foo'
-// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subone/fn.bar.html"]' 'bar'
+// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.foo.html"]' 'foo'
+// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.bar.html"]' 'bar'
 pub mod subone {
     //! See either [foo] or [bar].
 
     // This should refer to subone's `bar`
     // @has issue_55364/subone/fn.foo.html
-    // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subone/fn.bar.html"]' 'bar'
+    // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.bar.html"]' 'bar'
     /// See [bar]
     pub fn foo() {}
     // This should refer to subone's `foo`
     // @has issue_55364/subone/fn.bar.html
-    // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subone/fn.foo.html"]' 'foo'
+    // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.foo.html"]' 'foo'
     /// See [foo]
     pub fn bar() {}
 }
@@ -23,11 +23,11 @@ pub mod subone {
 
 // @has issue_55364/subtwo/index.html
 // These foo/bar links in the module's documentation should not reference inside `subtwo`
-// @!has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subtwo/fn.foo.html"]' 'foo'
-// @!has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subtwo/fn.bar.html"]' 'bar'
+// @!has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.foo.html"]' 'foo'
+// @!has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.bar.html"]' 'bar'
 // Instead it should be referencing the top level functions
-// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/fn.foo.html"]' 'foo'
-// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/fn.bar.html"]' 'bar'
+// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../fn.foo.html"]' 'foo'
+// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../fn.bar.html"]' 'bar'
 // Though there should be such links later
 // @has - '//section[@id="main"]/table//tr[@class="module-item"]/td/a[@class="fn"][@href="fn.foo.html"]' 'foo'
 // @has - '//section[@id="main"]/table//tr[@class="module-item"]/td/a[@class="fn"][@href="fn.bar.html"]' 'bar'
@@ -37,13 +37,13 @@ pub mod subtwo {
     // Despite the module's docs referring to the top level foo/bar,
     // this should refer to subtwo's `bar`
     // @has issue_55364/subtwo/fn.foo.html
-    // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subtwo/fn.bar.html"]' 'bar'
+    // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.bar.html"]' 'bar'
     /// See [bar]
     pub fn foo() {}
     // Despite the module's docs referring to the top level foo/bar,
     // this should refer to subtwo's `foo`
     // @has issue_55364/subtwo/fn.bar.html
-    // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subtwo/fn.foo.html"]' 'foo'
+    // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.foo.html"]' 'foo'
     /// See [foo]
     pub fn bar() {}
 }
@@ -59,8 +59,8 @@ pub fn bar() {}
 
 // @has issue_55364/subthree/index.html
 // This module should also refer to the top level foo/bar
-// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/fn.foo.html"]' 'foo'
-// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/fn.bar.html"]' 'bar'
+// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../fn.foo.html"]' 'foo'
+// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../fn.bar.html"]' 'bar'
 pub mod subthree {
     //! See either [foo][super::foo] or [bar][super::bar]
 }
@@ -68,8 +68,8 @@ pub mod subthree {
 // Next we go *deeper* - In order to ensure it's not just "this or parent"
 // we test `crate::` and a `super::super::...` chain
 // @has issue_55364/subfour/subfive/subsix/subseven/subeight/index.html
-// @has - '//section[@id="main"]/table//tr[@class="module-item"]/td[@class="docblock-short"]//a[@href="../../../../../../issue_55364/subone/fn.foo.html"]' 'other foo'
-// @has - '//section[@id="main"]/table//tr[@class="module-item"]/td[@class="docblock-short"]//a[@href="../../../../../../issue_55364/subtwo/fn.bar.html"]' 'other bar'
+// @has - '//section[@id="main"]/table//tr[@class="module-item"]/td[@class="docblock-short"]//a[@href="../../../../../subone/fn.foo.html"]' 'other foo'
+// @has - '//section[@id="main"]/table//tr[@class="module-item"]/td[@class="docblock-short"]//a[@href="../../../../../subtwo/fn.bar.html"]' 'other bar'
 pub mod subfour {
     pub mod subfive {
         pub mod subsix {
diff --git a/src/test/rustdoc/issue-72340.rs b/src/test/rustdoc/issue-72340.rs
index 6ed3bfbe3e5..64044cfe947 100644
--- a/src/test/rustdoc/issue-72340.rs
+++ b/src/test/rustdoc/issue-72340.rs
@@ -10,7 +10,7 @@ impl Body {
 }
 
 impl Default for Body {
-    // @has foo/struct.Body.html '//a/@href' '../foo/struct.Body.html#method.empty'
+    // @has foo/struct.Body.html '//a/@href' 'struct.Body.html#method.empty'
 
     /// Returns [`Body::empty()`](Body::empty).
     fn default() -> Body {
diff --git a/src/test/rustdoc/link-assoc-const.rs b/src/test/rustdoc/link-assoc-const.rs
index f9eb2b722d6..75a2531a308 100644
--- a/src/test/rustdoc/link-assoc-const.rs
+++ b/src/test/rustdoc/link-assoc-const.rs
@@ -1,7 +1,7 @@
 #![crate_name = "foo"]
 
-// @has foo/index.html '//a[@href="../foo/foo/constant.FIRSTCONST.html"]' 'foo::FIRSTCONST'
-// @has foo/index.html '//a[@href="../foo/struct.Bar.html#associatedconstant.CONST"]' 'Bar::CONST'
+// @has foo/index.html '//a[@href="foo/constant.FIRSTCONST.html"]' 'foo::FIRSTCONST'
+// @has foo/index.html '//a[@href="struct.Bar.html#associatedconstant.CONST"]' 'Bar::CONST'
 
 //! We have here [`foo::FIRSTCONST`] and [`Bar::CONST`].
 
diff --git a/src/test/rustdoc/proc-macro.rs b/src/test/rustdoc/proc-macro.rs
index 82196e413e9..f6d1f2cf91b 100644
--- a/src/test/rustdoc/proc-macro.rs
+++ b/src/test/rustdoc/proc-macro.rs
@@ -61,12 +61,12 @@ pub fn some_derive(_item: TokenStream) -> TokenStream {
 // @has some_macros/foo/index.html
 mod foo {
     // @has - '//code' 'pub use some_proc_macro;'
-    // @has - '//a/@href' '../../some_macros/macro.some_proc_macro.html'
+    // @has - '//a/@href' '../macro.some_proc_macro.html'
     pub use some_proc_macro;
     // @has - '//code' 'pub use some_proc_attr;'
-    // @has - '//a/@href' '../../some_macros/attr.some_proc_attr.html'
+    // @has - '//a/@href' '../attr.some_proc_attr.html'
     pub use some_proc_attr;
     // @has - '//code' 'pub use some_derive;'
-    // @has - '//a/@href' '../../some_macros/derive.SomeDerive.html'
+    // @has - '//a/@href' '../derive.SomeDerive.html'
     pub use some_derive;
 }
diff --git a/src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs b/src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs
index 3ecf434c39e..ad190361267 100644
--- a/src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs
+++ b/src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs
@@ -8,13 +8,13 @@ pub mod internal {
     ///
     /// [name]: mod
     /// [other name]: crate::internal::mod
-    // @has 'raw_ident_eliminate_r_hashtag/internal/struct.B.html' '//*a[@href="../../raw_ident_eliminate_r_hashtag/internal/struct.mod.html"]' 'name'
-    // @has 'raw_ident_eliminate_r_hashtag/internal/struct.B.html' '//*a[@href="../../raw_ident_eliminate_r_hashtag/internal/struct.mod.html"]' 'other name'
+    // @has 'raw_ident_eliminate_r_hashtag/internal/struct.B.html' '//*a[@href="struct.mod.html"]' 'name'
+    // @has 'raw_ident_eliminate_r_hashtag/internal/struct.B.html' '//*a[@href="struct.mod.html"]' 'other name'
     pub struct B;
 }
 
 /// See [name].
 ///
 /// [name]: internal::mod
-// @has 'raw_ident_eliminate_r_hashtag/struct.A.html' '//*a[@href="../raw_ident_eliminate_r_hashtag/internal/struct.mod.html"]' 'name'
+// @has 'raw_ident_eliminate_r_hashtag/struct.A.html' '//*a[@href="internal/struct.mod.html"]' 'name'
 pub struct A;
diff --git a/src/test/rustdoc/struct-field.rs b/src/test/rustdoc/struct-field.rs
index 974b863bb16..998683bdde7 100644
--- a/src/test/rustdoc/struct-field.rs
+++ b/src/test/rustdoc/struct-field.rs
@@ -1,9 +1,9 @@
 #![crate_name = "foo"]
 
 
-// @has foo/index.html '//*[@class="docblock"]/p/a[@href="../foo/struct.Foo.html#structfield.bar"]' 'Foo::bar'
-// @has foo/index.html '//*[@class="docblock"]/p/a[@href="../foo/union.Bar.html#structfield.foo"]' 'Bar::foo'
-// @has foo/index.html '//*[@class="docblock"]/p/a[@href="../foo/enum.Uniooon.html#variant.X"]' 'Uniooon::X'
+// @has foo/index.html '//*[@class="docblock"]/p/a[@href="struct.Foo.html#structfield.bar"]' 'Foo::bar'
+// @has foo/index.html '//*[@class="docblock"]/p/a[@href="union.Bar.html#structfield.foo"]' 'Bar::foo'
+// @has foo/index.html '//*[@class="docblock"]/p/a[@href="enum.Uniooon.html#variant.X"]' 'Uniooon::X'
 
 //! Test with [Foo::bar], [Bar::foo], [Uniooon::X]
 
diff --git a/src/test/rustdoc/trait-impl-items-links-and-anchors.rs b/src/test/rustdoc/trait-impl-items-links-and-anchors.rs
index 6c09be1144a..c6a9313e821 100644
--- a/src/test/rustdoc/trait-impl-items-links-and-anchors.rs
+++ b/src/test/rustdoc/trait-impl-items-links-and-anchors.rs
@@ -40,25 +40,25 @@ impl MyTrait for Vec<u8> {
 impl MyTrait for MyStruct {
     // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="associatedtype.Assoc-3"]//a[@class="type"]/@href' #associatedtype.Assoc
     // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="associatedtype.Assoc-3"]//a[@class="anchor"]/@href' #associatedtype.Assoc-3
-    // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedtype.Assoc"]//a[@class="type"]/@href' ../trait_impl_items_links_and_anchors/trait.MyTrait.html#associatedtype.Assoc
+    // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedtype.Assoc"]//a[@class="type"]/@href' trait.MyTrait.html#associatedtype.Assoc
     // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedtype.Assoc"]//a[@class="anchor"]/@href' #associatedtype.Assoc
     type Assoc = bool;
     // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="associatedconstant.VALUE-3"]//a[@class="constant"]/@href' #associatedconstant.VALUE
     // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="associatedconstant.VALUE-3"]//a[@class="anchor"]/@href' #associatedconstant.VALUE-3
-    // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedconstant.VALUE"]//a[@class="constant"]/@href' ../trait_impl_items_links_and_anchors/trait.MyTrait.html#associatedconstant.VALUE
+    // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedconstant.VALUE"]//a[@class="constant"]/@href' trait.MyTrait.html#associatedconstant.VALUE
     // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedconstant.VALUE"]//a[@class="anchor"]/@href' #associatedconstant.VALUE
     const VALUE: u32 = 20;
     // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="method.trait_function-2"]//a[@class="fnname"]/@href' #tymethod.trait_function
     // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="method.trait_function-2"]//a[@class="anchor"]/@href' #method.trait_function-2
-    // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.trait_function"]//a[@class="fnname"]/@href' ../trait_impl_items_links_and_anchors/trait.MyTrait.html#tymethod.trait_function
+    // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.trait_function"]//a[@class="fnname"]/@href' trait.MyTrait.html#tymethod.trait_function
     // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.trait_function"]//a[@class="anchor"]/@href' #method.trait_function
     fn trait_function(&self) {}
     // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="method.defaulted_override-3"]//a[@class="fnname"]/@href' #method.defaulted_override
     // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="method.defaulted_override-3"]//a[@class="anchor"]/@href' #method.defaulted_override-3
-    // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted_override"]//a[@class="fnname"]/@href' ../trait_impl_items_links_and_anchors/trait.MyTrait.html#method.defaulted_override
+    // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted_override"]//a[@class="fnname"]/@href' trait.MyTrait.html#method.defaulted_override
     // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted_override"]//a[@class="anchor"]/@href' #method.defaulted_override
     fn defaulted_override(&self) {}
-    // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted"]//a[@class="fnname"]/@href' ../trait_impl_items_links_and_anchors/trait.MyTrait.html#method.defaulted
+    // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted"]//a[@class="fnname"]/@href' trait.MyTrait.html#method.defaulted
     // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted"]//a[@class="anchor"]/@href' #method.defaulted
 }
 
diff --git a/src/test/rustdoc/trait-self-link.rs b/src/test/rustdoc/trait-self-link.rs
index bac28b44012..e311dadff0e 100644
--- a/src/test/rustdoc/trait-self-link.rs
+++ b/src/test/rustdoc/trait-self-link.rs
@@ -1,4 +1,4 @@
-// @has trait_self_link/trait.Foo.html //a/@href ../trait_self_link/trait.Foo.html
+// @has trait_self_link/trait.Foo.html //a/@href trait.Foo.html
 pub trait Foo {}
 
 pub struct Bar;
diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js
index a551a97bda5..e583bd225a9 100644
--- a/src/tools/rustdoc-js/tester.js
+++ b/src/tools/rustdoc-js/tester.js
@@ -246,7 +246,7 @@ function lookForEntry(entry, data) {
     return null;
 }
 
-function loadMainJsAndIndex(mainJs, searchIndex, storageJs, crate) {
+function loadSearchJsAndIndex(searchJs, searchIndex, storageJs, crate) {
     if (searchIndex[searchIndex.length - 1].length === 0) {
         searchIndex.pop();
     }
@@ -270,9 +270,9 @@ function loadMainJsAndIndex(mainJs, searchIndex, storageJs, crate) {
     ALIASES = {};
     finalJS += 'window = { "currentCrate": "' + crate + '", rootPath: "../" };\n';
     finalJS += loadThings(["hasOwnProperty", "onEach"], 'function', extractFunction, storageJs);
-    finalJS += loadThings(arraysToLoad, 'array', extractArrayVariable, mainJs);
-    finalJS += loadThings(variablesToLoad, 'variable', extractVariable, mainJs);
-    finalJS += loadThings(functionsToLoad, 'function', extractFunction, mainJs);
+    finalJS += loadThings(arraysToLoad, 'array', extractArrayVariable, searchJs);
+    finalJS += loadThings(variablesToLoad, 'variable', extractVariable, searchJs);
+    finalJS += loadThings(functionsToLoad, 'function', extractFunction, searchJs);
 
     var loaded = loadContent(finalJS);
     var index = loaded.buildIndex(searchIndex.rawSearchIndex);
@@ -382,12 +382,12 @@ function runChecks(testFile, loaded, index) {
 }
 
 function load_files(doc_folder, resource_suffix, crate) {
-    var mainJs = readFile(path.join(doc_folder, "main" + resource_suffix + ".js"));
+    var searchJs = readFile(path.join(doc_folder, "search" + resource_suffix + ".js"));
     var storageJs = readFile(path.join(doc_folder, "storage" + resource_suffix + ".js"));
     var searchIndex = readFile(
         path.join(doc_folder, "search-index" + resource_suffix + ".js")).split("\n");
 
-    return loadMainJsAndIndex(mainJs, searchIndex, storageJs, crate);
+    return loadSearchJsAndIndex(searchJs, searchIndex, storageJs, crate);
 }
 
 function showHelp() {