diff --git a/compiler/rustc/src/main.rs b/compiler/rustc/src/main.rs
index 6bc5aa6382c4c..3f03d1b68695e 100644
--- a/compiler/rustc/src/main.rs
+++ b/compiler/rustc/src/main.rs
@@ -24,6 +24,20 @@ fn main() {
         static _F5: unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void = jemalloc_sys::realloc;
         #[used]
         static _F6: unsafe extern "C" fn(*mut c_void) = jemalloc_sys::free;
+
+        // On OSX, jemalloc doesn't directly override malloc/free, but instead
+        // registers itself with the allocator's zone APIs in a ctor. However,
+        // the linker doesn't seem to consider ctors as "used" when statically
+        // linking, so we need to explicitly depend on the function.
+        #[cfg(target_os = "macos")]
+        {
+            extern "C" {
+                fn _rjem_je_zone_register();
+            }
+
+            #[used]
+            static _F7: unsafe extern "C" fn() = _rjem_je_zone_register();
+        }
     }
 
     rustc_driver::set_sigpipe_handler();
diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs
index c293fbe4ef8ca..f2f7242a8fdde 100644
--- a/compiler/rustc_middle/src/mir/query.rs
+++ b/compiler/rustc_middle/src/mir/query.rs
@@ -438,18 +438,6 @@ impl<'tcx> TyCtxt<'tcx> {
         }
     }
 
-    #[inline]
-    pub fn optimized_mir_or_const_arg_mir(
-        self,
-        def: ty::WithOptConstParam<DefId>,
-    ) -> &'tcx Body<'tcx> {
-        if let Some((did, param_did)) = def.as_const_arg() {
-            self.mir_for_ctfe_of_const_arg((did, param_did))
-        } else {
-            self.optimized_mir(def.did)
-        }
-    }
-
     #[inline]
     pub fn mir_for_ctfe_opt_const_arg(self, def: ty::WithOptConstParam<DefId>) -> &'tcx Body<'tcx> {
         if let Some((did, param_did)) = def.as_const_arg() {
diff --git a/compiler/rustc_middle/src/ty/instance.rs b/compiler/rustc_middle/src/ty/instance.rs
index de012a6957419..23cedfd499eaa 100644
--- a/compiler/rustc_middle/src/ty/instance.rs
+++ b/compiler/rustc_middle/src/ty/instance.rs
@@ -499,7 +499,7 @@ impl<'tcx> Instance<'tcx> {
     }
 
     /// Returns a new `Instance` where generic parameters in `instance.substs` are replaced by
-    /// identify parameters if they are determined to be unused in `instance.def`.
+    /// identity parameters if they are determined to be unused in `instance.def`.
     pub fn polymorphize(self, tcx: TyCtxt<'tcx>) -> Self {
         debug!("polymorphize: running polymorphization analysis");
         if !tcx.sess.opts.debugging_opts.polymorphize {
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index 2e077827873d1..c27a337554e67 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -2963,7 +2963,10 @@ impl<'tcx> TyCtxt<'tcx> {
                 | DefKind::AnonConst => self.mir_for_ctfe_opt_const_arg(def),
                 // If the caller wants `mir_for_ctfe` of a function they should not be using
                 // `instance_mir`, so we'll assume const fn also wants the optimized version.
-                _ => self.optimized_mir_or_const_arg_mir(def),
+                _ => {
+                    assert_eq!(def.const_param_did, None);
+                    self.optimized_mir(def.did)
+                }
             },
             ty::InstanceDef::VtableShim(..)
             | ty::InstanceDef::ReifyShim(..)
diff --git a/compiler/rustc_mir/src/monomorphize/polymorphize.rs b/compiler/rustc_mir/src/monomorphize/polymorphize.rs
index 4ad71ab4913ba..05b0e3a7dab95 100644
--- a/compiler/rustc_mir/src/monomorphize/polymorphize.rs
+++ b/compiler/rustc_mir/src/monomorphize/polymorphize.rs
@@ -30,9 +30,8 @@ pub fn provide(providers: &mut Providers) {
 /// Determine which generic parameters are used by the function/method/closure represented by
 /// `def_id`. Returns a bitset where bits representing unused parameters are set (`is_empty`
 /// indicates all parameters are used).
+#[instrument(skip(tcx))]
 fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
-    debug!("unused_generic_params({:?})", def_id);
-
     if !tcx.sess.opts.debugging_opts.polymorphize {
         // If polymorphization disabled, then all parameters are used.
         return FiniteBitSet::new_empty();
@@ -46,7 +45,7 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
     }
 
     let generics = tcx.generics_of(def_id);
-    debug!("unused_generic_params: generics={:?}", generics);
+    debug!(?generics);
 
     // Exit early when there are no parameters to be unused.
     if generics.count() == 0 {
@@ -57,11 +56,11 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
     let context = tcx.hir().body_const_context(def_id.expect_local());
     match context {
         Some(ConstContext::ConstFn) | None if !tcx.is_mir_available(def_id) => {
-            debug!("unused_generic_params: (no mir available) def_id={:?}", def_id);
+            debug!("no mir available");
             return FiniteBitSet::new_empty();
         }
         Some(_) if !tcx.is_ctfe_mir_available(def_id) => {
-            debug!("unused_generic_params: (no ctfe mir available) def_id={:?}", def_id);
+            debug!("no ctfe mir available");
             return FiniteBitSet::new_empty();
         }
         _ => {}
@@ -72,9 +71,9 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
         generics.count().try_into().expect("more generic parameters than can fit into a `u32`");
     let mut unused_parameters = FiniteBitSet::<u32>::new_empty();
     unused_parameters.set_range(0..generics_count);
-    debug!("unused_generic_params: (start) unused_parameters={:?}", unused_parameters);
+    debug!(?unused_parameters, "(start)");
     mark_used_by_default_parameters(tcx, def_id, generics, &mut unused_parameters);
-    debug!("unused_generic_params: (after default) unused_parameters={:?}", unused_parameters);
+    debug!(?unused_parameters, "(after default)");
 
     // Visit MIR and accumululate used generic parameters.
     let body = match context {
@@ -85,10 +84,10 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
     };
     let mut vis = MarkUsedGenericParams { tcx, def_id, unused_parameters: &mut unused_parameters };
     vis.visit_body(body);
-    debug!("unused_generic_params: (after visitor) unused_parameters={:?}", unused_parameters);
+    debug!(?unused_parameters, "(after visitor)");
 
     mark_used_by_predicates(tcx, def_id, &mut unused_parameters);
-    debug!("unused_generic_params: (end) unused_parameters={:?}", unused_parameters);
+    debug!(?unused_parameters, "(end)");
 
     // Emit errors for debugging and testing if enabled.
     if !unused_parameters.is_empty() {
@@ -101,24 +100,55 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
 /// Some parameters are considered used-by-default, such as non-generic parameters and the dummy
 /// generic parameters from closures, this function marks them as used. `leaf_is_closure` should
 /// be `true` if the item that `unused_generic_params` was invoked on is a closure.
+#[instrument(skip(tcx, def_id, generics, unused_parameters))]
 fn mark_used_by_default_parameters<'tcx>(
     tcx: TyCtxt<'tcx>,
     def_id: DefId,
     generics: &'tcx ty::Generics,
     unused_parameters: &mut FiniteBitSet<u32>,
 ) {
-    if !tcx.is_trait(def_id) && (tcx.is_closure(def_id) || tcx.type_of(def_id).is_generator()) {
-        for param in &generics.params {
-            debug!("mark_used_by_default_parameters: (closure/gen) param={:?}", param);
-            unused_parameters.clear(param.index);
-        }
-    } else {
-        for param in &generics.params {
-            debug!("mark_used_by_default_parameters: (other) param={:?}", param);
-            if let ty::GenericParamDefKind::Lifetime = param.kind {
+    match tcx.def_kind(def_id) {
+        DefKind::Closure | DefKind::Generator => {
+            for param in &generics.params {
+                debug!(?param, "(closure/gen)");
                 unused_parameters.clear(param.index);
             }
         }
+        DefKind::Mod
+        | DefKind::Struct
+        | DefKind::Union
+        | DefKind::Enum
+        | DefKind::Variant
+        | DefKind::Trait
+        | DefKind::TyAlias
+        | DefKind::ForeignTy
+        | DefKind::TraitAlias
+        | DefKind::AssocTy
+        | DefKind::TyParam
+        | DefKind::Fn
+        | DefKind::Const
+        | DefKind::ConstParam
+        | DefKind::Static
+        | DefKind::Ctor(_, _)
+        | DefKind::AssocFn
+        | DefKind::AssocConst
+        | DefKind::Macro(_)
+        | DefKind::ExternCrate
+        | DefKind::Use
+        | DefKind::ForeignMod
+        | DefKind::AnonConst
+        | DefKind::OpaqueTy
+        | DefKind::Field
+        | DefKind::LifetimeParam
+        | DefKind::GlobalAsm
+        | DefKind::Impl => {
+            for param in &generics.params {
+                debug!(?param, "(other)");
+                if let ty::GenericParamDefKind::Lifetime = param.kind {
+                    unused_parameters.clear(param.index);
+                }
+            }
+        }
     }
 
     if let Some(parent) = generics.parent {
@@ -128,6 +158,7 @@ fn mark_used_by_default_parameters<'tcx>(
 
 /// Search the predicates on used generic parameters for any unused generic parameters, and mark
 /// those as used.
+#[instrument(skip(tcx, def_id))]
 fn mark_used_by_predicates<'tcx>(
     tcx: TyCtxt<'tcx>,
     def_id: DefId,
@@ -135,16 +166,12 @@ fn mark_used_by_predicates<'tcx>(
 ) {
     let def_id = tcx.closure_base_def_id(def_id);
     let predicates = tcx.explicit_predicates_of(def_id);
-    debug!("mark_used_by_predicates: predicates_of={:?}", predicates);
 
     let mut current_unused_parameters = FiniteBitSet::new_empty();
     // Run to a fixed point to support `where T: Trait<U>, U: Trait<V>`, starting with an empty
     // bit set so that this is skipped if all parameters are already used.
     while current_unused_parameters != *unused_parameters {
-        debug!(
-            "mark_used_by_predicates: current_unused_parameters={:?} = unused_parameters={:?}",
-            current_unused_parameters, unused_parameters
-        );
+        debug!(?current_unused_parameters, ?unused_parameters);
         current_unused_parameters = *unused_parameters;
 
         for (predicate, _) in predicates.predicates {
@@ -169,13 +196,13 @@ fn mark_used_by_predicates<'tcx>(
 
 /// Emit errors for the function annotated by `#[rustc_polymorphize_error]`, labelling each generic
 /// parameter which was unused.
+#[instrument(skip(tcx, generics))]
 fn emit_unused_generic_params_error<'tcx>(
     tcx: TyCtxt<'tcx>,
     def_id: DefId,
     generics: &'tcx ty::Generics,
     unused_parameters: &FiniteBitSet<u32>,
 ) {
-    debug!("emit_unused_generic_params_error: def_id={:?}", def_id);
     let base_def_id = tcx.closure_base_def_id(def_id);
     if !tcx
         .get_attrs(base_def_id)
@@ -185,7 +212,6 @@ fn emit_unused_generic_params_error<'tcx>(
         return;
     }
 
-    debug!("emit_unused_generic_params_error: unused_parameters={:?}", unused_parameters);
     let fn_span = match tcx.opt_item_name(def_id) {
         Some(ident) => ident.span,
         _ => tcx.def_span(def_id),
@@ -197,7 +223,7 @@ fn emit_unused_generic_params_error<'tcx>(
     while let Some(generics) = next_generics {
         for param in &generics.params {
             if unused_parameters.contains(param.index).unwrap_or(false) {
-                debug!("emit_unused_generic_params_error: param={:?}", param);
+                debug!(?param);
                 let def_span = tcx.def_span(param.def_id);
                 err.span_label(def_span, &format!("generic parameter `{}` is unused", param.name));
             }
@@ -219,25 +245,23 @@ struct MarkUsedGenericParams<'a, 'tcx> {
 impl<'a, 'tcx> MarkUsedGenericParams<'a, 'tcx> {
     /// Invoke `unused_generic_params` on a body contained within the current item (e.g.
     /// a closure, generator or constant).
+    #[instrument(skip(self, def_id, substs))]
     fn visit_child_body(&mut self, def_id: DefId, substs: SubstsRef<'tcx>) {
         let unused = self.tcx.unused_generic_params(def_id);
-        debug!(
-            "visit_child_body: unused_parameters={:?} unused={:?}",
-            self.unused_parameters, unused
-        );
+        debug!(?self.unused_parameters, ?unused);
         for (i, arg) in substs.iter().enumerate() {
             let i = i.try_into().unwrap();
             if !unused.contains(i).unwrap_or(false) {
                 arg.visit_with(self);
             }
         }
-        debug!("visit_child_body: unused_parameters={:?}", self.unused_parameters);
+        debug!(?self.unused_parameters);
     }
 }
 
 impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
+    #[instrument(skip(self, local))]
     fn visit_local_decl(&mut self, local: Local, local_decl: &LocalDecl<'tcx>) {
-        debug!("visit_local_decl: local_decl={:?}", local_decl);
         if local == Local::from_usize(1) {
             let def_kind = self.tcx.def_kind(self.def_id);
             if matches!(def_kind, DefKind::Closure | DefKind::Generator) {
@@ -245,7 +269,7 @@ impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
                 // happens because the first argument to the closure is a reference to itself and
                 // that will call `visit_substs`, resulting in each generic parameter captured being
                 // considered used by default.
-                debug!("visit_local_decl: skipping closure substs");
+                debug!("skipping closure substs");
                 return;
             }
         }
@@ -263,15 +287,15 @@ impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
 }
 
 impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
+    #[instrument(skip(self))]
     fn visit_const(&mut self, c: &'tcx Const<'tcx>) -> ControlFlow<Self::BreakTy> {
-        debug!("visit_const: c={:?}", c);
         if !c.has_param_types_or_consts() {
             return ControlFlow::CONTINUE;
         }
 
         match c.val {
             ty::ConstKind::Param(param) => {
-                debug!("visit_const: param={:?}", param);
+                debug!(?param);
                 self.unused_parameters.clear(param.index);
                 ControlFlow::CONTINUE
             }
@@ -296,15 +320,15 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
         }
     }
 
+    #[instrument(skip(self))]
     fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-        debug!("visit_ty: ty={:?}", ty);
         if !ty.has_param_types_or_consts() {
             return ControlFlow::CONTINUE;
         }
 
         match *ty.kind() {
             ty::Closure(def_id, substs) | ty::Generator(def_id, substs, ..) => {
-                debug!("visit_ty: def_id={:?}", def_id);
+                debug!(?def_id);
                 // Avoid cycle errors with generators.
                 if def_id == self.def_id {
                     return ControlFlow::CONTINUE;
@@ -316,7 +340,7 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
                 ControlFlow::CONTINUE
             }
             ty::Param(param) => {
-                debug!("visit_ty: param={:?}", param);
+                debug!(?param);
                 self.unused_parameters.clear(param.index);
                 ControlFlow::CONTINUE
             }
@@ -333,8 +357,8 @@ struct HasUsedGenericParams<'a> {
 impl<'a, 'tcx> TypeVisitor<'tcx> for HasUsedGenericParams<'a> {
     type BreakTy = ();
 
+    #[instrument(skip(self))]
     fn visit_const(&mut self, c: &'tcx Const<'tcx>) -> ControlFlow<Self::BreakTy> {
-        debug!("visit_const: c={:?}", c);
         if !c.has_param_types_or_consts() {
             return ControlFlow::CONTINUE;
         }
@@ -351,8 +375,8 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for HasUsedGenericParams<'a> {
         }
     }
 
+    #[instrument(skip(self))]
     fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-        debug!("visit_ty: ty={:?}", ty);
         if !ty.has_param_types_or_consts() {
             return ControlFlow::CONTINUE;
         }
diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs
index bd0296751a535..61f4c00a4ca42 100644
--- a/compiler/rustc_resolve/src/imports.rs
+++ b/compiler/rustc_resolve/src/imports.rs
@@ -156,6 +156,21 @@ impl<'a> NameResolution<'a> {
     }
 }
 
+// Reexports of the form `pub use foo as bar;` where `foo` is `extern crate foo;`
+// are permitted for backward-compatibility under a deprecation lint.
+fn pub_use_of_private_extern_crate_hack(import: &Import<'_>, binding: &NameBinding<'_>) -> bool {
+    match (&import.kind, &binding.kind) {
+        (
+            ImportKind::Single { .. },
+            NameBindingKind::Import {
+                import: Import { kind: ImportKind::ExternCrate { .. }, .. },
+                ..
+            },
+        ) => import.vis.get() == ty::Visibility::Public,
+        _ => false,
+    }
+}
+
 impl<'a> Resolver<'a> {
     crate fn resolve_ident_in_module_unadjusted(
         &mut self,
@@ -263,10 +278,7 @@ impl<'a> Resolver<'a> {
                     return Err((Determined, Weak::No));
                 }
             }
-            // `extern crate` are always usable for backwards compatibility, see issue #37020,
-            // remove this together with `PUB_USE_OF_PRIVATE_EXTERN_CRATE`.
-            let usable = this.is_accessible_from(binding.vis, parent_scope.module)
-                || binding.is_extern_crate();
+            let usable = this.is_accessible_from(binding.vis, parent_scope.module);
             if usable { Ok(binding) } else { Err((Determined, Weak::No)) }
         };
 
@@ -309,10 +321,7 @@ impl<'a> Resolver<'a> {
                             }
                         }
 
-                        if !(self.is_accessible_from(binding.vis, parent_scope.module) ||
-                       // Remove this together with `PUB_USE_OF_PRIVATE_EXTERN_CRATE`
-                       (self.last_import_segment && binding.is_extern_crate()))
-                        {
+                        if !self.is_accessible_from(binding.vis, parent_scope.module) {
                             self.privacy_errors.push(PrivacyError {
                                 ident,
                                 binding,
@@ -455,9 +464,8 @@ impl<'a> Resolver<'a> {
         binding: &'a NameBinding<'a>,
         import: &'a Import<'a>,
     ) -> &'a NameBinding<'a> {
-        let vis = if binding.vis.is_at_least(import.vis.get(), self) ||
-                     // cf. `PUB_USE_OF_PRIVATE_EXTERN_CRATE`
-                     !import.is_glob() && binding.is_extern_crate()
+        let vis = if binding.vis.is_at_least(import.vis.get(), self)
+            || pub_use_of_private_extern_crate_hack(import, binding)
         {
             import.vis.get()
         } else {
@@ -1188,7 +1196,7 @@ impl<'a, 'b> ImportResolver<'a, 'b> {
         // All namespaces must be re-exported with extra visibility for an error to occur.
         if !any_successful_reexport {
             let (ns, binding) = reexport_error.unwrap();
-            if ns == TypeNS && binding.is_extern_crate() {
+            if pub_use_of_private_extern_crate_hack(import, binding) {
                 let msg = format!(
                     "extern crate `{}` is private, and cannot be \
                                    re-exported (error E0365), consider declaring with \
diff --git a/library/alloc/src/collections/btree/map.rs b/library/alloc/src/collections/btree/map.rs
index 783f88f026b8f..622983996aa08 100644
--- a/library/alloc/src/collections/btree/map.rs
+++ b/library/alloc/src/collections/btree/map.rs
@@ -14,7 +14,7 @@ use super::node::{self, marker, ForceResult::*, Handle, NodeRef, Root};
 use super::search::SearchResult::*;
 
 mod entry;
-pub use entry::{Entry, OccupiedEntry, VacantEntry};
+pub use entry::{Entry, OccupiedEntry, OccupiedError, VacantEntry};
 use Entry::*;
 
 /// Minimum number of elements in nodes that are not a root.
@@ -836,6 +836,40 @@ impl<K, V> BTreeMap<K, V> {
         }
     }
 
+    /// Tries to insert a key-value pair into the map, and returns
+    /// a mutable reference to the value in the entry.
+    ///
+    /// If the map already had this key present, nothing is updated, and
+    /// an error containing the occupied entry and the value is returned.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// #![feature(map_try_insert)]
+    ///
+    /// use std::collections::BTreeMap;
+    ///
+    /// let mut map = BTreeMap::new();
+    /// assert_eq!(map.try_insert(37, "a").unwrap(), &"a");
+    ///
+    /// let err = map.try_insert(37, "b").unwrap_err();
+    /// assert_eq!(err.entry.key(), &37);
+    /// assert_eq!(err.entry.get(), &"a");
+    /// assert_eq!(err.value, "b");
+    /// ```
+    #[unstable(feature = "map_try_insert", issue = "82766")]
+    pub fn try_insert(&mut self, key: K, value: V) -> Result<&mut V, OccupiedError<'_, K, V>>
+    where
+        K: Ord,
+    {
+        match self.entry(key) {
+            Occupied(entry) => Err(OccupiedError { entry, value }),
+            Vacant(entry) => Ok(entry.insert(value)),
+        }
+    }
+
     /// Removes a key from the map, returning the value at the key if the key
     /// was previously in the map.
     ///
diff --git a/library/alloc/src/collections/btree/map/entry.rs b/library/alloc/src/collections/btree/map/entry.rs
index 941f82a8070a0..6b30d95977395 100644
--- a/library/alloc/src/collections/btree/map/entry.rs
+++ b/library/alloc/src/collections/btree/map/entry.rs
@@ -71,6 +71,41 @@ impl<K: Debug + Ord, V: Debug> Debug for OccupiedEntry<'_, K, V> {
     }
 }
 
+/// The error returned by [`try_insert`](BTreeMap::try_insert) when the key already exists.
+///
+/// Contains the occupied entry, and the value that was not inserted.
+#[unstable(feature = "map_try_insert", issue = "82766")]
+pub struct OccupiedError<'a, K: 'a, V: 'a> {
+    /// The entry in the map that was already occupied.
+    pub entry: OccupiedEntry<'a, K, V>,
+    /// The value which was not inserted, because the entry was already occupied.
+    pub value: V,
+}
+
+#[unstable(feature = "map_try_insert", issue = "82766")]
+impl<K: Debug + Ord, V: Debug> Debug for OccupiedError<'_, K, V> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("OccupiedError")
+            .field("key", self.entry.key())
+            .field("old_value", self.entry.get())
+            .field("new_value", &self.value)
+            .finish()
+    }
+}
+
+#[unstable(feature = "map_try_insert", issue = "82766")]
+impl<'a, K: Debug + Ord, V: Debug> fmt::Display for OccupiedError<'a, K, V> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "failed to insert {:?}, key {:?} already exists with value {:?}",
+            self.value,
+            self.entry.key(),
+            self.entry.get(),
+        )
+    }
+}
+
 impl<'a, K: Ord, V> Entry<'a, K, V> {
     /// Ensures a value is in the entry by inserting the default if empty, and returns
     /// a mutable reference to the value in the entry.
diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs
index 9a54921f07b49..3e70ba81d4997 100644
--- a/library/core/src/macros/mod.rs
+++ b/library/core/src/macros/mod.rs
@@ -110,6 +110,60 @@ macro_rules! assert_ne {
     });
 }
 
+/// Asserts that an expression matches any of the given patterns.
+///
+/// Like in a `match` expression, the pattern can be optionally followed by `if`
+/// and a guard expression that has access to names bound by the pattern.
+///
+/// On panic, this macro will print the value of the expression with its
+/// debug representation.
+///
+/// Like [`assert!`], this macro has a second form, where a custom
+/// panic message can be provided.
+///
+/// # Examples
+///
+/// ```
+/// #![feature(assert_matches)]
+///
+/// let a = 1u32.checked_add(2);
+/// let b = 1u32.checked_sub(2);
+/// assert_matches!(a, Some(_));
+/// assert_matches!(b, None);
+///
+/// let c = Ok("abc".to_string());
+/// assert_matches!(c, Ok(x) | Err(x) if x.len() < 100);
+/// ```
+#[macro_export]
+#[unstable(feature = "assert_matches", issue = "82775")]
+#[allow_internal_unstable(core_panic)]
+macro_rules! assert_matches {
+    ($left:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => ({
+        match $left {
+            $( $pattern )|+ $( if $guard )? => {}
+            ref left_val => {
+                $crate::panicking::assert_matches_failed(
+                    left_val,
+                    $crate::stringify!($($pattern)|+ $(if $guard)?),
+                    $crate::option::Option::None
+                );
+            }
+        }
+    });
+    ($left:expr, $( $pattern:pat )|+ $( if $guard: expr )?, $($arg:tt)+) => ({
+        match $left {
+            $( $pattern )|+ $( if $guard )? => {}
+            ref left_val => {
+                $crate::panicking::assert_matches_failed(
+                    left_val,
+                    $crate::stringify!($($pattern)|+ $(if $guard)?),
+                    $crate::option::Option::Some($crate::format_args!($($arg)+))
+                );
+            }
+        }
+    });
+}
+
 /// Asserts that a boolean expression is `true` at runtime.
 ///
 /// This will invoke the [`panic!`] macro if the provided expression cannot be
@@ -208,6 +262,42 @@ macro_rules! debug_assert_ne {
     ($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_ne!($($arg)*); })
 }
 
+/// Asserts that an expression matches any of the given patterns.
+///
+/// Like in a `match` expression, the pattern can be optionally followed by `if`
+/// and a guard expression that has access to names bound by the pattern.
+///
+/// On panic, this macro will print the value of the expression with its
+/// debug representation.
+///
+/// Unlike [`assert_matches!`], `debug_assert_matches!` statements are only
+/// enabled in non optimized builds by default. An optimized build will not
+/// execute `debug_assert_matches!` statements unless `-C debug-assertions` is
+/// passed to the compiler. This makes `debug_assert_matches!` useful for
+/// checks that are too expensive to be present in a release build but may be
+/// helpful during development. The result of expanding `debug_assert_matches!`
+/// is always type checked.
+///
+/// # Examples
+///
+/// ```
+/// #![feature(assert_matches)]
+///
+/// let a = 1u32.checked_add(2);
+/// let b = 1u32.checked_sub(2);
+/// debug_assert_matches!(a, Some(_));
+/// debug_assert_matches!(b, None);
+///
+/// let c = Ok("abc".to_string());
+/// debug_assert_matches!(c, Ok(x) | Err(x) if x.len() < 100);
+/// ```
+#[macro_export]
+#[unstable(feature = "assert_matches", issue = "82775")]
+#[allow_internal_unstable(assert_matches)]
+macro_rules! debug_assert_matches {
+    ($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_matches!($($arg)*); })
+}
+
 /// Returns whether the given expression matches any of the given patterns.
 ///
 /// Like in a `match` expression, the pattern can be optionally followed by `if`
diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs
index af8a6101392a4..12acf5b4329db 100644
--- a/library/core/src/panicking.rs
+++ b/library/core/src/panicking.rs
@@ -97,6 +97,7 @@ pub fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
 pub enum AssertKind {
     Eq,
     Ne,
+    Match,
 }
 
 /// Internal function for `assert_eq!` and `assert_ne!` macros
@@ -113,32 +114,54 @@ where
     T: fmt::Debug + ?Sized,
     U: fmt::Debug + ?Sized,
 {
-    #[track_caller]
-    fn inner(
-        kind: AssertKind,
-        left: &dyn fmt::Debug,
-        right: &dyn fmt::Debug,
-        args: Option<fmt::Arguments<'_>>,
-    ) -> ! {
-        let op = match kind {
-            AssertKind::Eq => "==",
-            AssertKind::Ne => "!=",
-        };
-
-        match args {
-            Some(args) => panic!(
-                r#"assertion failed: `(left {} right)`
+    assert_failed_inner(kind, &left, &right, args)
+}
+
+/// Internal function for `assert_match!`
+#[cold]
+#[track_caller]
+#[doc(hidden)]
+pub fn assert_matches_failed<T: fmt::Debug + ?Sized>(
+    left: &T,
+    right: &str,
+    args: Option<fmt::Arguments<'_>>,
+) -> ! {
+    // Use the Display implementation to display the pattern.
+    struct Pattern<'a>(&'a str);
+    impl fmt::Debug for Pattern<'_> {
+        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+            fmt::Display::fmt(self.0, f)
+        }
+    }
+    assert_failed_inner(AssertKind::Match, &left, &Pattern(right), args);
+}
+
+/// Non-generic version of the above functions, to avoid code bloat.
+#[track_caller]
+fn assert_failed_inner(
+    kind: AssertKind,
+    left: &dyn fmt::Debug,
+    right: &dyn fmt::Debug,
+    args: Option<fmt::Arguments<'_>>,
+) -> ! {
+    let op = match kind {
+        AssertKind::Eq => "==",
+        AssertKind::Ne => "!=",
+        AssertKind::Match => "matches",
+    };
+
+    match args {
+        Some(args) => panic!(
+            r#"assertion failed: `(left {} right)`
   left: `{:?}`,
  right: `{:?}: {}`"#,
-                op, left, right, args
-            ),
-            None => panic!(
-                r#"assertion failed: `(left {} right)`
+            op, left, right, args
+        ),
+        None => panic!(
+            r#"assertion failed: `(left {} right)`
   left: `{:?}`,
  right: `{:?}`"#,
-                op, left, right,
-            ),
-        }
+            op, left, right,
+        ),
     }
-    inner(kind, &left, &right, args)
 }
diff --git a/library/std/src/collections/hash/map.rs b/library/std/src/collections/hash/map.rs
index 27f7191831d41..233afa9238999 100644
--- a/library/std/src/collections/hash/map.rs
+++ b/library/std/src/collections/hash/map.rs
@@ -1,3 +1,5 @@
+// ignore-tidy-filelength
+
 #[cfg(test)]
 mod tests;
 
@@ -842,6 +844,37 @@ where
         self.base.insert(k, v)
     }
 
+    /// Tries to insert a key-value pair into the map, and returns
+    /// a mutable reference to the value in the entry.
+    ///
+    /// If the map already had this key present, nothing is updated, and
+    /// an error containing the occupied entry and the value is returned.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// #![feature(map_try_insert)]
+    ///
+    /// use std::collections::HashMap;
+    ///
+    /// let mut map = HashMap::new();
+    /// assert_eq!(map.try_insert(37, "a").unwrap(), &"a");
+    ///
+    /// let err = map.try_insert(37, "b").unwrap_err();
+    /// assert_eq!(err.entry.key(), &37);
+    /// assert_eq!(err.entry.get(), &"a");
+    /// assert_eq!(err.value, "b");
+    /// ```
+    #[unstable(feature = "map_try_insert", issue = "82766")]
+    pub fn try_insert(&mut self, key: K, value: V) -> Result<&mut V, OccupiedError<'_, K, V>> {
+        match self.entry(key) {
+            Occupied(entry) => Err(OccupiedError { entry, value }),
+            Vacant(entry) => Ok(entry.insert(value)),
+        }
+    }
+
     /// Removes a key from the map, returning the value at the key if the key
     /// was previously in the map.
     ///
@@ -1851,6 +1884,41 @@ impl<K: Debug, V> Debug for VacantEntry<'_, K, V> {
     }
 }
 
+/// The error returned by [`try_insert`](HashMap::try_insert) when the key already exists.
+///
+/// Contains the occupied entry, and the value that was not inserted.
+#[unstable(feature = "map_try_insert", issue = "82766")]
+pub struct OccupiedError<'a, K: 'a, V: 'a> {
+    /// The entry in the map that was already occupied.
+    pub entry: OccupiedEntry<'a, K, V>,
+    /// The value which was not inserted, because the entry was already occupied.
+    pub value: V,
+}
+
+#[unstable(feature = "map_try_insert", issue = "82766")]
+impl<K: Debug, V: Debug> Debug for OccupiedError<'_, K, V> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("OccupiedError")
+            .field("key", self.entry.key())
+            .field("old_value", self.entry.get())
+            .field("new_value", &self.value)
+            .finish()
+    }
+}
+
+#[unstable(feature = "map_try_insert", issue = "82766")]
+impl<'a, K: Debug, V: Debug> fmt::Display for OccupiedError<'a, K, V> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "failed to insert {:?}, key {:?} already exists with value {:?}",
+            self.value,
+            self.entry.key(),
+            self.entry.get(),
+        )
+    }
+}
+
 #[stable(feature = "rust1", since = "1.0.0")]
 impl<'a, K, V, S> IntoIterator for &'a HashMap<K, V, S> {
     type Item = (&'a K, &'a V);
diff --git a/library/std/src/error.rs b/library/std/src/error.rs
index 94338c7b04d06..80c35307d52ac 100644
--- a/library/std/src/error.rs
+++ b/library/std/src/error.rs
@@ -470,6 +470,24 @@ impl Error for char::DecodeUtf16Error {
     }
 }
 
+#[unstable(feature = "map_try_insert", issue = "82766")]
+impl<'a, K: Debug + Ord, V: Debug> Error
+    for crate::collections::btree_map::OccupiedError<'a, K, V>
+{
+    #[allow(deprecated)]
+    fn description(&self) -> &str {
+        "key already exists"
+    }
+}
+
+#[unstable(feature = "map_try_insert", issue = "82766")]
+impl<'a, K: Debug, V: Debug> Error for crate::collections::hash_map::OccupiedError<'a, K, V> {
+    #[allow(deprecated)]
+    fn description(&self) -> &str {
+        "key already exists"
+    }
+}
+
 #[stable(feature = "box_error", since = "1.8.0")]
 impl<T: Error> Error for Box<T> {
     #[allow(deprecated, deprecated_in_future)]
diff --git a/library/std/src/io/buffered/bufreader.rs b/library/std/src/io/buffered/bufreader.rs
index 987371f50ec22..839c64ee5c443 100644
--- a/library/std/src/io/buffered/bufreader.rs
+++ b/library/std/src/io/buffered/bufreader.rs
@@ -1,6 +1,8 @@
 use crate::cmp;
 use crate::fmt;
-use crate::io::{self, BufRead, Initializer, IoSliceMut, Read, Seek, SeekFrom, DEFAULT_BUF_SIZE};
+use crate::io::{
+    self, BufRead, Initializer, IoSliceMut, Read, Seek, SeekFrom, SizeHint, DEFAULT_BUF_SIZE,
+};
 
 /// The `BufReader<R>` struct adds buffering to any reader.
 ///
@@ -435,3 +437,9 @@ impl<R: Seek> Seek for BufReader<R> {
         })
     }
 }
+
+impl<T> SizeHint for BufReader<T> {
+    fn lower_bound(&self) -> usize {
+        self.buffer().len()
+    }
+}
diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs
index 2291498740510..17002e3b8602d 100644
--- a/library/std/src/io/mod.rs
+++ b/library/std/src/io/mod.rs
@@ -2238,6 +2238,19 @@ impl<T: BufRead, U: BufRead> BufRead for Chain<T, U> {
     }
 }
 
+impl<T, U> SizeHint for Chain<T, U> {
+    fn lower_bound(&self) -> usize {
+        SizeHint::lower_bound(&self.first) + SizeHint::lower_bound(&self.second)
+    }
+
+    fn upper_bound(&self) -> Option<usize> {
+        match (SizeHint::upper_bound(&self.first), SizeHint::upper_bound(&self.second)) {
+            (Some(first), Some(second)) => Some(first + second),
+            _ => None,
+        }
+    }
+}
+
 /// Reader adaptor which limits the bytes read from an underlying reader.
 ///
 /// This struct is generally created by calling [`take`] on a reader.
@@ -2464,6 +2477,30 @@ impl<R: Read> Iterator for Bytes<R> {
             };
         }
     }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        SizeHint::size_hint(&self.inner)
+    }
+}
+
+trait SizeHint {
+    fn lower_bound(&self) -> usize;
+
+    fn upper_bound(&self) -> Option<usize>;
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        (self.lower_bound(), self.upper_bound())
+    }
+}
+
+impl<T> SizeHint for T {
+    default fn lower_bound(&self) -> usize {
+        0
+    }
+
+    default fn upper_bound(&self) -> Option<usize> {
+        None
+    }
 }
 
 /// An iterator over the contents of an instance of `BufRead` split on a
diff --git a/library/std/src/io/tests.rs b/library/std/src/io/tests.rs
index f176c2f088cb3..a85dd0d982715 100644
--- a/library/std/src/io/tests.rs
+++ b/library/std/src/io/tests.rs
@@ -1,7 +1,7 @@
 use super::{repeat, Cursor, SeekFrom};
 use crate::cmp::{self, min};
 use crate::io::{self, IoSlice, IoSliceMut};
-use crate::io::{BufRead, Read, Seek, Write};
+use crate::io::{BufRead, BufReader, Read, Seek, Write};
 use crate::ops::Deref;
 
 #[test]
@@ -198,6 +198,53 @@ fn chain_bufread() {
     cmp_bufread(chain1, chain2, &testdata[..]);
 }
 
+#[test]
+fn bufreader_size_hint() {
+    let testdata = b"ABCDEFGHIJKL";
+    let mut buf_reader = BufReader::new(&testdata[..]);
+    assert_eq!(buf_reader.buffer().len(), 0);
+
+    let buffer_length = testdata.len();
+    buf_reader.fill_buf().unwrap();
+
+    // Check that size hint matches buffer contents
+    let mut buffered_bytes = buf_reader.bytes();
+    let (lower_bound, _upper_bound) = buffered_bytes.size_hint();
+    assert_eq!(lower_bound, buffer_length);
+
+    // Check that size hint matches buffer contents after advancing
+    buffered_bytes.next().unwrap().unwrap();
+    let (lower_bound, _upper_bound) = buffered_bytes.size_hint();
+    assert_eq!(lower_bound, buffer_length - 1);
+}
+
+#[test]
+fn empty_size_hint() {
+    let size_hint = io::empty().bytes().size_hint();
+    assert_eq!(size_hint, (0, Some(0)));
+}
+
+#[test]
+fn chain_empty_size_hint() {
+    let chain = io::empty().chain(io::empty());
+    let size_hint = chain.bytes().size_hint();
+    assert_eq!(size_hint, (0, Some(0)));
+}
+
+#[test]
+fn chain_size_hint() {
+    let testdata = b"ABCDEFGHIJKL";
+    let mut buf_reader_1 = BufReader::new(&testdata[..6]);
+    let mut buf_reader_2 = BufReader::new(&testdata[6..]);
+
+    buf_reader_1.fill_buf().unwrap();
+    buf_reader_2.fill_buf().unwrap();
+
+    let chain = buf_reader_1.chain(buf_reader_2);
+    let size_hint = chain.bytes().size_hint();
+    assert_eq!(size_hint, (testdata.len(), None));
+}
+
 #[test]
 fn chain_zero_length_read_is_not_eof() {
     let a = b"A";
diff --git a/library/std/src/io/util.rs b/library/std/src/io/util.rs
index e43ce4cdb4b8e..f472361f916db 100644
--- a/library/std/src/io/util.rs
+++ b/library/std/src/io/util.rs
@@ -4,7 +4,9 @@
 mod tests;
 
 use crate::fmt;
-use crate::io::{self, BufRead, Initializer, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
+use crate::io::{
+    self, BufRead, Initializer, IoSlice, IoSliceMut, Read, Seek, SeekFrom, SizeHint, Write,
+};
 
 /// A reader which is always at EOF.
 ///
@@ -80,6 +82,12 @@ impl fmt::Debug for Empty {
     }
 }
 
+impl SizeHint for Empty {
+    fn upper_bound(&self) -> Option<usize> {
+        Some(0)
+    }
+}
+
 /// A reader which yields one byte over and over and over and over and over and...
 ///
 /// This struct is generally created by calling [`repeat()`]. Please
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index ba49dee38e642..07925f255dd54 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -228,6 +228,7 @@
 #![feature(arbitrary_self_types)]
 #![feature(array_error_internals)]
 #![feature(asm)]
+#![feature(assert_matches)]
 #![feature(associated_type_bounds)]
 #![feature(atomic_mut_ptr)]
 #![feature(box_syntax)]
@@ -281,6 +282,7 @@
 #![feature(linkage)]
 #![feature(llvm_asm)]
 #![feature(log_syntax)]
+#![feature(map_try_insert)]
 #![feature(maybe_uninit_extra)]
 #![feature(maybe_uninit_ref)]
 #![feature(maybe_uninit_slice)]
@@ -550,8 +552,8 @@ pub use std_detect::detect;
 #[stable(feature = "rust1", since = "1.0.0")]
 #[allow(deprecated, deprecated_in_future)]
 pub use core::{
-    assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, matches, r#try, todo,
-    unimplemented, unreachable, write, writeln,
+    assert_eq, assert_matches, assert_ne, debug_assert, debug_assert_eq, debug_assert_matches,
+    debug_assert_ne, matches, r#try, todo, unimplemented, unreachable, write, writeln,
 };
 
 // Re-export built-in macros defined through libcore.
diff --git a/src/doc/rustdoc/src/SUMMARY.md b/src/doc/rustdoc/src/SUMMARY.md
index 943aa09f678d7..ae94527e2b4ca 100644
--- a/src/doc/rustdoc/src/SUMMARY.md
+++ b/src/doc/rustdoc/src/SUMMARY.md
@@ -8,7 +8,7 @@
 - [Documentation tests](documentation-tests.md)
 - [Linking to items by name](linking-to-items-by-name.md)
 - [Lints](lints.md)
-- [Passes](passes.md)
 - [Advanced features](advanced-features.md)
 - [Unstable features](unstable-features.md)
+- [Passes](passes.md)
 - [References](references.md)
diff --git a/src/doc/rustdoc/src/command-line-arguments.md b/src/doc/rustdoc/src/command-line-arguments.md
index 0302fbecb6ed0..2e4016e24bc3f 100644
--- a/src/doc/rustdoc/src/command-line-arguments.md
+++ b/src/doc/rustdoc/src/command-line-arguments.md
@@ -57,23 +57,6 @@ release: 1.17.0
 LLVM version: 3.9
 ```
 
-## `-r`/`--input-format`: input format
-
-This flag is currently ignored; the idea is that `rustdoc` would support various
-input formats, and you could specify them via this flag.
-
-Rustdoc only supports Rust source code and Markdown input formats. If the
-file ends in `.md` or `.markdown`, `rustdoc` treats it as a Markdown file.
-Otherwise, it assumes that the input file is Rust.
-
-
-## `-w`/`--output-format`: output format
-
-This flag is currently ignored; the idea is that `rustdoc` would support
-various output formats, and you could specify them via this flag.
-
-Rustdoc only supports HTML output, and so this flag is redundant today.
-
 ## `-o`/`--output`: output path
 
 Using this flag looks like this:
@@ -100,6 +83,25 @@ By default, `rustdoc` assumes that the name of your crate is the same name
 as the `.rs` file. `--crate-name` lets you override this assumption with
 whatever name you choose.
 
+## `--document-private-items`: Show items that are not public
+
+Using this flag looks like this:
+
+```bash
+$ rustdoc src/lib.rs --document-private-items
+```
+
+By default, `rustdoc` only documents items that are publicly reachable.
+
+```rust
+pub fn public() {} // this item is public and will documented
+mod private { // this item is private and will not be documented
+    pub fn unreachable() {} // this item is public, but unreachable, so it will not be documented
+}
+```
+
+`--document-private-items` documents all items, even if they're not public.
+
 ## `-L`/`--library-path`: where to look for dependencies
 
 Using this flag looks like this:
@@ -166,38 +168,6 @@ affect that.
 The arguments to this flag are the same as those for the `-C` flag on rustc. Run `rustc -C help` to
 get the full list.
 
-## `--passes`: add more rustdoc passes
-
-Using this flag looks like this:
-
-```bash
-$ rustdoc --passes list
-$ rustdoc src/lib.rs --passes strip-priv-imports
-```
-
-An argument of "list" will print a list of possible "rustdoc passes", and other
-arguments will be the name of which passes to run in addition to the defaults.
-
-For more details on passes, see [the chapter on them](passes.md).
-
-See also `--no-defaults`.
-
-## `--no-defaults`: don't run default passes
-
-Using this flag looks like this:
-
-```bash
-$ rustdoc src/lib.rs --no-defaults
-```
-
-By default, `rustdoc` will run several passes over your code. This
-removes those defaults, allowing you to use `--passes` to specify
-exactly which passes you want.
-
-For more details on passes, see [the chapter on them](passes.md).
-
-See also `--passes`.
-
 ## `--test`: run code examples as tests
 
 Using this flag looks like this:
@@ -429,3 +399,21 @@ If you specify `@path` on the command-line, then it will open `path` and read
 command line options from it. These options are one per line; a blank line indicates
 an empty option. The file can use Unix or Windows style line endings, and must be
 encoded as UTF-8.
+
+## `--passes`: add more rustdoc passes
+
+This flag is **deprecated**.
+For more details on passes, see [the chapter on them](passes.md).
+
+## `--no-defaults`: don't run default passes
+
+This flag is **deprecated**.
+For more details on passes, see [the chapter on them](passes.md).
+
+## `-r`/`--input-format`: input format
+
+This flag is **deprecated** and **has no effect**.
+
+Rustdoc only supports Rust source code and Markdown input formats. If the
+file ends in `.md` or `.markdown`, `rustdoc` treats it as a Markdown file.
+Otherwise, it assumes that the input file is Rust.
diff --git a/src/doc/rustdoc/src/passes.md b/src/doc/rustdoc/src/passes.md
index 140b832f19a54..c3c3fd3068ec4 100644
--- a/src/doc/rustdoc/src/passes.md
+++ b/src/doc/rustdoc/src/passes.md
@@ -3,86 +3,9 @@
 Rustdoc has a concept called "passes". These are transformations that
 `rustdoc` runs on your documentation before producing its final output.
 
-In addition to the passes below, check out the docs for these flags:
+Customizing passes is **deprecated**. The available passes are not considered stable and may
+change in any release.
 
-* [`--passes`](command-line-arguments.md#--passes-add-more-rustdoc-passes)
-* [`--no-defaults`](command-line-arguments.md#--no-defaults-dont-run-default-passes)
-
-## Default passes
-
-By default, rustdoc will run some passes, namely:
-
-* `strip-hidden`
-* `strip-private`
-* `collapse-docs`
-* `unindent-comments`
-
-However, `strip-private` implies `strip-priv-imports`, and so effectively,
-all passes are run by default.
-
-## `strip-hidden`
-
-This pass implements the `#[doc(hidden)]` attribute. When this pass runs, it
-checks each item, and if it is annotated with this attribute, it removes it
-from `rustdoc`'s output.
-
-Without this pass, these items will remain in the output.
-
-## `unindent-comments`
-
-When you write a doc comment like this:
-
-```rust,no_run
-/// This is a documentation comment.
-# fn f() {}
-```
-
-There's a space between the `///` and that `T`. That spacing isn't intended
-to be a part of the output; it's there for humans, to help separate the doc
-comment syntax from the text of the comment. This pass is what removes that
-space.
-
-The exact rules are left under-specified so that we can fix issues that we find.
-
-Without this pass, the exact number of spaces is preserved.
-
-## `collapse-docs`
-
-With this pass, multiple `#[doc]` attributes are converted into one single
-documentation string.
-
-For example:
-
-```rust,no_run
-#[doc = "This is the first line."]
-#[doc = "This is the second line."]
-# fn f() {}
-```
-
-Gets collapsed into a single doc string of
-
-```text
-This is the first line.
-This is the second line.
-```
-
-## `strip-private`
-
-This removes documentation for any non-public items, so for example:
-
-```rust,no_run
-/// These are private docs.
-struct Private;
-
-/// These are public docs.
-pub struct Public;
-```
-
-This pass removes the docs for `Private`, since they're not public.
-
-This pass implies `strip-priv-imports`.
-
-## `strip-priv-imports`
-
-This is the same as `strip-private`, but for `extern crate` and `use`
-statements instead of items.
+In the past the most common use case for customizing passes was to omit the `strip-private` pass.
+You can do this more easily, and without risk of the pass being changed, by passing
+[`--document-private-items`](./unstable-features.md#--document-private-items).
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index b43070510413a..7d1845dc9578e 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -340,6 +340,30 @@ Some methodology notes about what rustdoc counts in this metric:
 Public items that are not documented can be seen with the built-in `missing_docs` lint. Private
 items that are not documented can be seen with Clippy's `missing_docs_in_private_items` lint.
 
+## `-w`/`--output-format`: output format
+
+When using
+[`--show-coverage`](https://doc.rust-lang.org/nightly/rustdoc/unstable-features.html#--show-coverage-get-statistics-about-code-documentation-coverage),
+passing `--output-format json` will display the coverage information in JSON format. For example,
+here is the JSON for a file with one documented item and one undocumented item:
+
+```rust
+/// This item has documentation
+pub fn foo() {}
+
+pub fn no_documentation() {}
+```
+
+```json
+{"no_std.rs":{"total":3,"with_docs":1,"total_examples":3,"with_examples":0}}
+```
+
+Note that the third item is the crate root, which in this case is undocumented.
+
+When not using `--show-coverage`, `--output-format json` emits documentation in the experimental
+[JSON format](https://github.com/rust-lang/rfcs/pull/2963). `--output-format html` has no effect,
+and is also accepted on stable toolchains.
+
 ### `--enable-per-target-ignores`: allow `ignore-foo` style filters for doctests
 
 Using this flag looks like this:
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
new file mode 100644
index 0000000000000..976168a9ac407
--- /dev/null
+++ b/src/librustdoc/html/render/context.rs
@@ -0,0 +1,617 @@
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+use std::io;
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::sync::mpsc::{channel, Receiver};
+use std::sync::Arc;
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def_id::{DefId, LOCAL_CRATE};
+use rustc_middle::ty::TyCtxt;
+use rustc_session::Session;
+use rustc_span::edition::Edition;
+use rustc_span::source_map::FileName;
+use rustc_span::symbol::sym;
+
+use super::cache::{build_index, ExternalLocation};
+use super::print_item::{full_path, item_path, print_item};
+use super::write_shared::write_shared;
+use super::{
+    print_sidebar, settings, AllTypes, NameDoc, SharedContext, StylePath, BASIC_KEYWORDS,
+    CURRENT_DEPTH, INITIAL_IDS,
+};
+
+use crate::clean::{self, AttributesExt};
+use crate::config::RenderOptions;
+use crate::docfs::{DocFS, PathError};
+use crate::error::Error;
+use crate::formats::cache::Cache;
+use crate::formats::item_type::ItemType;
+use crate::formats::FormatRenderer;
+use crate::html::escape::Escape;
+use crate::html::format::Buffer;
+use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
+use crate::html::{layout, sources};
+
+/// Major driving force in all rustdoc rendering. This contains information
+/// about where in the tree-like hierarchy rendering is occurring and controls
+/// how the current page is being rendered.
+///
+/// It is intended that this context is a lightweight object which can be fairly
+/// easily cloned because it is cloned per work-job (about once per item in the
+/// rustdoc tree).
+#[derive(Clone)]
+crate struct Context<'tcx> {
+    /// Current hierarchy of components leading down to what's currently being
+    /// rendered
+    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.
+    crate dst: PathBuf,
+    /// A flag, which when `true`, will render pages which redirect to the
+    /// real location of an item. This is used to allow external links to
+    /// publicly reused items to redirect to the right location.
+    crate render_redirect_pages: bool,
+    /// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set
+    /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of
+    /// the crate.
+    crate redirections: Option<Rc<RefCell<FxHashMap<String, String>>>>,
+    /// The map used to ensure all generated 'id=' attributes are unique.
+    pub(super) id_map: Rc<RefCell<IdMap>>,
+    /// Tracks section IDs for `Deref` targets so they match in both the main
+    /// body and the sidebar.
+    pub(super) deref_id_map: Rc<RefCell<FxHashMap<DefId, String>>>,
+    crate shared: Arc<SharedContext<'tcx>>,
+    all: Rc<RefCell<AllTypes>>,
+    /// Storage for the errors produced while generating documentation so they
+    /// can be printed together at the end.
+    crate errors: Rc<Receiver<String>>,
+    crate cache: Rc<Cache>,
+}
+
+impl<'tcx> Context<'tcx> {
+    pub(super) fn path(&self, filename: &str) -> PathBuf {
+        // We use splitn vs Path::extension here because we might get a filename
+        // like `style.min.css` and we want to process that into
+        // `style-suffix.min.css`.  Path::extension would just return `css`
+        // which would result in `style.min-suffix.css` which isn't what we
+        // want.
+        let (base, ext) = filename.split_once('.').unwrap();
+        let filename = format!("{}{}.{}", base, self.shared.resource_suffix, ext);
+        self.dst.join(&filename)
+    }
+
+    pub(super) fn tcx(&self) -> TyCtxt<'tcx> {
+        self.shared.tcx
+    }
+
+    fn sess(&self) -> &'tcx Session {
+        &self.shared.tcx.sess
+    }
+
+    pub(super) fn derive_id(&self, id: String) -> String {
+        let mut map = self.id_map.borrow_mut();
+        map.derive(id)
+    }
+
+    /// String representation of how to get back to the root path of the 'doc/'
+    /// folder in terms of a relative URL.
+    pub(super) fn root_path(&self) -> String {
+        "../".repeat(self.current.len())
+    }
+
+    fn render_item(&self, it: &clean::Item, pushname: bool) -> String {
+        // A little unfortunate that this is done like this, but it sure
+        // does make formatting *a lot* nicer.
+        CURRENT_DEPTH.with(|slot| {
+            slot.set(self.current.len());
+        });
+
+        let mut title = if it.is_primitive() || it.is_keyword() {
+            // No need to include the namespace for primitive types and keywords
+            String::new()
+        } else {
+            self.current.join("::")
+        };
+        if pushname {
+            if !title.is_empty() {
+                title.push_str("::");
+            }
+            title.push_str(&it.name.unwrap().as_str());
+        }
+        title.push_str(" - Rust");
+        let tyname = it.type_();
+        let desc = it.doc_value().as_ref().map(|doc| plain_text_summary(&doc));
+        let desc = if let Some(desc) = desc {
+            desc
+        } else if it.is_crate() {
+            format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate)
+        } else {
+            format!(
+                "API documentation for the Rust `{}` {} in crate `{}`.",
+                it.name.as_ref().unwrap(),
+                tyname,
+                self.shared.layout.krate
+            )
+        };
+        let keywords = make_item_keywords(it);
+        let page = layout::Page {
+            css_class: tyname.as_str(),
+            root_path: &self.root_path(),
+            static_root_path: self.shared.static_root_path.as_deref(),
+            title: &title,
+            description: &desc,
+            keywords: &keywords,
+            resource_suffix: &self.shared.resource_suffix,
+            extra_scripts: &[],
+            static_extra_scripts: &[],
+        };
+
+        {
+            self.id_map.borrow_mut().reset();
+            self.id_map.borrow_mut().populate(&INITIAL_IDS);
+        }
+
+        if !self.render_redirect_pages {
+            layout::render(
+                &self.shared.layout,
+                &page,
+                |buf: &mut _| print_sidebar(self, it, buf),
+                |buf: &mut _| print_item(self, it, buf),
+                &self.shared.style_files,
+            )
+        } else {
+            if let Some(&(ref names, ty)) = self.cache.paths.get(&it.def_id) {
+                let mut path = String::new();
+                for name in &names[..names.len() - 1] {
+                    path.push_str(name);
+                    path.push('/');
+                }
+                path.push_str(&item_path(ty, names.last().unwrap()));
+                match self.redirections {
+                    Some(ref redirections) => {
+                        let mut current_path = String::new();
+                        for name in &self.current {
+                            current_path.push_str(name);
+                            current_path.push('/');
+                        }
+                        current_path.push_str(&item_path(ty, names.last().unwrap()));
+                        redirections.borrow_mut().insert(current_path, path);
+                    }
+                    None => return layout::redirect(&format!("{}{}", self.root_path(), path)),
+                }
+            }
+            String::new()
+        }
+    }
+
+    /// Construct a map of items shown in the sidebar to a plain-text summary of their docs.
+    fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<NameDoc>> {
+        // BTreeMap instead of HashMap to get a sorted output
+        let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new();
+        for item in &m.items {
+            if item.is_stripped() {
+                continue;
+            }
+
+            let short = item.type_();
+            let myname = match item.name {
+                None => continue,
+                Some(ref s) => s.to_string(),
+            };
+            let short = short.to_string();
+            map.entry(short).or_default().push((
+                myname,
+                Some(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))),
+            ));
+        }
+
+        if self.shared.sort_modules_alphabetically {
+            for items in map.values_mut() {
+                items.sort();
+            }
+        }
+        map
+    }
+
+    /// Generates a url appropriate for an `href` attribute back to the source of
+    /// this item.
+    ///
+    /// The url generated, when clicked, will redirect the browser back to the
+    /// original source code.
+    ///
+    /// If `None` is returned, then a source link couldn't be generated. This
+    /// may happen, for example, with externally inlined items where the source
+    /// of their crate documentation isn't known.
+    pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> {
+        if item.source.is_dummy() {
+            return None;
+        }
+        let mut root = self.root_path();
+        let mut path = String::new();
+        let cnum = item.source.cnum(self.sess());
+
+        // We can safely ignore synthetic `SourceFile`s.
+        let file = match item.source.filename(self.sess()) {
+            FileName::Real(ref path) => path.local_path().to_path_buf(),
+            _ => return None,
+        };
+        let file = &file;
+
+        let symbol;
+        let (krate, path) = if cnum == LOCAL_CRATE {
+            if let Some(path) = self.shared.local_sources.get(file) {
+                (self.shared.layout.krate.as_str(), path)
+            } else {
+                return None;
+            }
+        } else {
+            let (krate, src_root) = match *self.cache.extern_locations.get(&cnum)? {
+                (name, ref src, ExternalLocation::Local) => (name, src),
+                (name, ref src, ExternalLocation::Remote(ref s)) => {
+                    root = s.to_string();
+                    (name, src)
+                }
+                (_, _, ExternalLocation::Unknown) => return None,
+            };
+
+            sources::clean_path(&src_root, file, false, |component| {
+                path.push_str(&component.to_string_lossy());
+                path.push('/');
+            });
+            let mut fname = file.file_name().expect("source has no filename").to_os_string();
+            fname.push(".html");
+            path.push_str(&fname.to_string_lossy());
+            symbol = krate.as_str();
+            (&*symbol, &path)
+        };
+
+        let loline = item.source.lo(self.sess()).line;
+        let hiline = item.source.hi(self.sess()).line;
+        let lines =
+            if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) };
+        Some(format!(
+            "{root}src/{krate}/{path}#{lines}",
+            root = Escape(&root),
+            krate = krate,
+            path = path,
+            lines = lines
+        ))
+    }
+}
+
+/// Generates the documentation for `crate` into the directory `dst`
+impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
+    fn descr() -> &'static str {
+        "html"
+    }
+
+    fn init(
+        mut krate: clean::Crate,
+        options: RenderOptions,
+        edition: Edition,
+        mut cache: Cache,
+        tcx: TyCtxt<'tcx>,
+    ) -> Result<(Self, clean::Crate), Error> {
+        // need to save a copy of the options for rendering the index page
+        let md_opts = options.clone();
+        let RenderOptions {
+            output,
+            external_html,
+            id_map,
+            playground_url,
+            sort_modules_alphabetically,
+            themes: style_files,
+            default_settings,
+            extension_css,
+            resource_suffix,
+            static_root_path,
+            generate_search_filter,
+            unstable_features,
+            generate_redirect_map,
+            ..
+        } = options;
+
+        let src_root = match krate.src {
+            FileName::Real(ref p) => match p.local_path().parent() {
+                Some(p) => p.to_path_buf(),
+                None => PathBuf::new(),
+            },
+            _ => PathBuf::new(),
+        };
+        // If user passed in `--playground-url` arg, we fill in crate name here
+        let mut playground = None;
+        if let Some(url) = playground_url {
+            playground =
+                Some(markdown::Playground { crate_name: Some(krate.name.to_string()), url });
+        }
+        let mut layout = layout::Layout {
+            logo: String::new(),
+            favicon: String::new(),
+            external_html,
+            default_settings,
+            krate: krate.name.to_string(),
+            css_file_extension: extension_css,
+            generate_search_filter,
+        };
+        let mut issue_tracker_base_url = None;
+        let mut include_sources = true;
+
+        // Crawl the crate attributes looking for attributes which control how we're
+        // going to emit HTML
+        if let Some(attrs) = krate.module.as_ref().map(|m| &m.attrs) {
+            for attr in attrs.lists(sym::doc) {
+                match (attr.name_or_empty(), attr.value_str()) {
+                    (sym::html_favicon_url, Some(s)) => {
+                        layout.favicon = s.to_string();
+                    }
+                    (sym::html_logo_url, Some(s)) => {
+                        layout.logo = s.to_string();
+                    }
+                    (sym::html_playground_url, Some(s)) => {
+                        playground = Some(markdown::Playground {
+                            crate_name: Some(krate.name.to_string()),
+                            url: s.to_string(),
+                        });
+                    }
+                    (sym::issue_tracker_base_url, Some(s)) => {
+                        issue_tracker_base_url = Some(s.to_string());
+                    }
+                    (sym::html_no_source, None) if attr.is_word() => {
+                        include_sources = false;
+                    }
+                    _ => {}
+                }
+            }
+        }
+        let (sender, receiver) = channel();
+        let mut scx = SharedContext {
+            tcx,
+            collapsed: krate.collapsed,
+            src_root,
+            include_sources,
+            local_sources: Default::default(),
+            issue_tracker_base_url,
+            layout,
+            created_dirs: Default::default(),
+            sort_modules_alphabetically,
+            style_files,
+            resource_suffix,
+            static_root_path,
+            fs: DocFS::new(sender),
+            edition,
+            codes: ErrorCodes::from(unstable_features.is_nightly_build()),
+            playground,
+        };
+
+        // Add the default themes to the `Vec` of stylepaths
+        //
+        // Note that these must be added before `sources::render` is called
+        // so that the resulting source pages are styled
+        //
+        // `light.css` is not disabled because it is the stylesheet that stays loaded
+        // by the browser as the theme stylesheet. The theme system (hackily) works by
+        // changing the href to this stylesheet. All other themes are disabled to
+        // prevent rule conflicts
+        scx.style_files.push(StylePath { path: PathBuf::from("light.css"), disabled: false });
+        scx.style_files.push(StylePath { path: PathBuf::from("dark.css"), disabled: true });
+        scx.style_files.push(StylePath { path: PathBuf::from("ayu.css"), disabled: true });
+
+        let dst = output;
+        scx.ensure_dir(&dst)?;
+        krate = sources::render(&dst, &mut scx, krate)?;
+
+        // Build our search index
+        let index = build_index(&krate, &mut cache, tcx);
+
+        let mut cx = Context {
+            current: Vec::new(),
+            dst,
+            render_redirect_pages: false,
+            id_map: Rc::new(RefCell::new(id_map)),
+            deref_id_map: Rc::new(RefCell::new(FxHashMap::default())),
+            shared: Arc::new(scx),
+            all: Rc::new(RefCell::new(AllTypes::new())),
+            errors: Rc::new(receiver),
+            cache: Rc::new(cache),
+            redirections: if generate_redirect_map { Some(Default::default()) } else { None },
+        };
+
+        CURRENT_DEPTH.with(|s| s.set(0));
+
+        // Write shared runs within a flock; disable thread dispatching of IO temporarily.
+        Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
+        write_shared(&cx, &krate, index, &md_opts)?;
+        Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
+        Ok((cx, krate))
+    }
+
+    fn after_krate(
+        &mut self,
+        krate: &clean::Crate,
+        diag: &rustc_errors::Handler,
+    ) -> Result<(), Error> {
+        let final_file = self.dst.join(&*krate.name.as_str()).join("all.html");
+        let settings_file = self.dst.join("settings.html");
+        let crate_name = krate.name;
+
+        let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
+        if !root_path.ends_with('/') {
+            root_path.push('/');
+        }
+        let mut page = layout::Page {
+            title: "List of all items in this crate",
+            css_class: "mod",
+            root_path: "../",
+            static_root_path: self.shared.static_root_path.as_deref(),
+            description: "List of all items in this crate",
+            keywords: BASIC_KEYWORDS,
+            resource_suffix: &self.shared.resource_suffix,
+            extra_scripts: &[],
+            static_extra_scripts: &[],
+        };
+        let sidebar = if let Some(ref version) = self.cache.crate_version {
+            format!(
+                "<p class=\"location\">Crate {}</p>\
+                     <div class=\"block version\">\
+                         <p>Version {}</p>\
+                     </div>\
+                     <a id=\"all-types\" href=\"index.html\"><p>Back to index</p></a>",
+                crate_name,
+                Escape(version),
+            )
+        } else {
+            String::new()
+        };
+        let all = self.all.replace(AllTypes::new());
+        let v = layout::render(
+            &self.shared.layout,
+            &page,
+            sidebar,
+            |buf: &mut Buffer| all.print(buf),
+            &self.shared.style_files,
+        );
+        self.shared.fs.write(&final_file, v.as_bytes())?;
+
+        // Generating settings page.
+        page.title = "Rustdoc settings";
+        page.description = "Settings of Rustdoc";
+        page.root_path = "./";
+
+        let mut style_files = self.shared.style_files.clone();
+        let sidebar = "<p class=\"location\">Settings</p><div class=\"sidebar-elems\"></div>";
+        style_files.push(StylePath { path: PathBuf::from("settings.css"), disabled: false });
+        let v = layout::render(
+            &self.shared.layout,
+            &page,
+            sidebar,
+            settings(
+                self.shared.static_root_path.as_deref().unwrap_or("./"),
+                &self.shared.resource_suffix,
+                &self.shared.style_files,
+            )?,
+            &style_files,
+        );
+        self.shared.fs.write(&settings_file, v.as_bytes())?;
+        if let Some(redirections) = self.redirections.take() {
+            if !redirections.borrow().is_empty() {
+                let redirect_map_path =
+                    self.dst.join(&*krate.name.as_str()).join("redirect-map.json");
+                let paths = serde_json::to_string(&*redirections.borrow()).unwrap();
+                self.shared.ensure_dir(&self.dst.join(&*krate.name.as_str()))?;
+                self.shared.fs.write(&redirect_map_path, paths.as_bytes())?;
+            }
+        }
+
+        // Flush pending errors.
+        Arc::get_mut(&mut self.shared).unwrap().fs.close();
+        let nb_errors = self.errors.iter().map(|err| diag.struct_err(&err).emit()).count();
+        if nb_errors > 0 {
+            Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), ""))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn mod_item_in(&mut self, item: &clean::Item, item_name: &str) -> Result<(), Error> {
+        // Stripped modules survive the rustdoc passes (i.e., `strip-private`)
+        // if they contain impls for public types. These modules can also
+        // contain items such as publicly re-exported structures.
+        //
+        // External crates will provide links to these structures, so
+        // these modules are recursed into, but not rendered normally
+        // (a flag on the context).
+        if !self.render_redirect_pages {
+            self.render_redirect_pages = item.is_stripped();
+        }
+        let scx = &self.shared;
+        self.dst.push(item_name);
+        self.current.push(item_name.to_owned());
+
+        info!("Recursing into {}", self.dst.display());
+
+        let buf = self.render_item(item, false);
+        // buf will be empty if the module is stripped and there is no redirect for it
+        if !buf.is_empty() {
+            self.shared.ensure_dir(&self.dst)?;
+            let joint_dst = self.dst.join("index.html");
+            scx.fs.write(&joint_dst, buf.as_bytes())?;
+        }
+
+        // Render sidebar-items.js used throughout this module.
+        if !self.render_redirect_pages {
+            let module = match *item.kind {
+                clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m,
+                _ => unreachable!(),
+            };
+            let items = self.build_sidebar_items(module);
+            let js_dst = self.dst.join("sidebar-items.js");
+            let v = format!("initSidebarItems({});", serde_json::to_string(&items).unwrap());
+            scx.fs.write(&js_dst, &v)?;
+        }
+        Ok(())
+    }
+
+    fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> {
+        info!("Recursed; leaving {}", self.dst.display());
+
+        // Go back to where we were at
+        self.dst.pop();
+        self.current.pop();
+        Ok(())
+    }
+
+    fn item(&mut self, item: clean::Item) -> Result<(), Error> {
+        // Stripped modules survive the rustdoc passes (i.e., `strip-private`)
+        // if they contain impls for public types. These modules can also
+        // contain items such as publicly re-exported structures.
+        //
+        // External crates will provide links to these structures, so
+        // these modules are recursed into, but not rendered normally
+        // (a flag on the context).
+        if !self.render_redirect_pages {
+            self.render_redirect_pages = item.is_stripped();
+        }
+
+        let buf = self.render_item(&item, true);
+        // buf will be empty if the item is stripped and there is no redirect for it
+        if !buf.is_empty() {
+            let name = item.name.as_ref().unwrap();
+            let item_type = item.type_();
+            let file_name = &item_path(item_type, &name.as_str());
+            self.shared.ensure_dir(&self.dst)?;
+            let joint_dst = self.dst.join(file_name);
+            self.shared.fs.write(&joint_dst, buf.as_bytes())?;
+
+            if !self.render_redirect_pages {
+                self.all.borrow_mut().append(full_path(self, &item), &item_type);
+            }
+            // If the item is a macro, redirect from the old macro URL (with !)
+            // to the new one (without).
+            if item_type == ItemType::Macro {
+                let redir_name = format!("{}.{}!.html", item_type, name);
+                if let Some(ref redirections) = self.redirections {
+                    let crate_name = &self.shared.layout.krate;
+                    redirections.borrow_mut().insert(
+                        format!("{}/{}", crate_name, redir_name),
+                        format!("{}/{}", crate_name, file_name),
+                    );
+                } else {
+                    let v = layout::redirect(file_name);
+                    let redir_dst = self.dst.join(redir_name);
+                    self.shared.fs.write(&redir_dst, v.as_bytes())?;
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn cache(&self) -> &Cache {
+        &self.cache
+    }
+}
+
+fn make_item_keywords(it: &clean::Item) -> String {
+    format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap())
+}
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index fbeeb2a6fa2f2..50cae50c2c3eb 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -1,5 +1,3 @@
-// ignore-tidy-filelength
-
 //! Rustdoc's HTML rendering module.
 //!
 //! This modules contains the bulk of the logic necessary for rendering a
@@ -30,58 +28,48 @@ crate mod cache;
 #[cfg(test)]
 mod tests;
 
+mod context;
+mod print_item;
+mod write_shared;
+
+crate use context::*;
+
 use std::cell::{Cell, RefCell};
-use std::cmp::Ordering;
-use std::collections::{BTreeMap, VecDeque};
+use std::collections::VecDeque;
 use std::default::Default;
-use std::ffi::OsStr;
-use std::fmt::{self, Write};
-use std::fs::{self, File};
-use std::io::prelude::*;
-use std::io::{self, BufReader};
-use std::path::{Component, Path, PathBuf};
-use std::rc::Rc;
+use std::fmt;
+use std::path::{Path, PathBuf};
 use std::str;
 use std::string::ToString;
-use std::sync::mpsc::{channel, Receiver};
-use std::sync::Arc;
 
 use itertools::Itertools;
 use rustc_ast_pretty::pprust;
 use rustc_attr::{Deprecation, StabilityLevel};
-use rustc_data_structures::flock;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_hir as hir;
 use rustc_hir::def::CtorKind;
-use rustc_hir::def_id::{DefId, LOCAL_CRATE};
+use rustc_hir::def_id::DefId;
 use rustc_hir::Mutability;
 use rustc_middle::middle::stability;
 use rustc_middle::ty::TyCtxt;
-use rustc_session::Session;
 use rustc_span::edition::Edition;
-use rustc_span::hygiene::MacroKind;
-use rustc_span::source_map::FileName;
 use rustc_span::symbol::{kw, sym, Symbol};
 use serde::ser::SerializeSeq;
 use serde::{Serialize, Serializer};
 
-use crate::clean::{self, AttributesExt, GetDefId, RenderedLink, SelfTy, TypeKind};
-use crate::config::RenderOptions;
+use crate::clean::{self, GetDefId, RenderedLink, SelfTy, TypeKind};
 use crate::docfs::{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::html::escape::Escape;
-use crate::html::format::Function;
-use crate::html::format::{href, print_default_space, print_generic_bounds, WhereClause};
-use crate::html::format::{print_abi_with_space, Buffer, PrintWithSpace};
-use crate::html::markdown::{
-    self, plain_text_summary, ErrorCodes, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine,
+use crate::html::format::{
+    href, print_abi_with_space, print_default_space, print_generic_bounds, Buffer, Function,
+    PrintWithSpace, WhereClause,
 };
-use crate::html::sources;
-use crate::html::{highlight, layout, static_files};
-use cache::{build_index, ExternalLocation};
+use crate::html::layout;
+use crate::html::markdown::{self, ErrorCodes, Markdown, MarkdownHtml, MarkdownSummaryLine};
 
 /// A pair of name and its optional document.
 crate type NameDoc = (String, Option<String>);
@@ -92,42 +80,6 @@ crate fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ {
     })
 }
 
-/// Major driving force in all rustdoc rendering. This contains information
-/// about where in the tree-like hierarchy rendering is occurring and controls
-/// how the current page is being rendered.
-///
-/// It is intended that this context is a lightweight object which can be fairly
-/// easily cloned because it is cloned per work-job (about once per item in the
-/// rustdoc tree).
-#[derive(Clone)]
-crate struct Context<'tcx> {
-    /// Current hierarchy of components leading down to what's currently being
-    /// rendered
-    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.
-    crate dst: PathBuf,
-    /// A flag, which when `true`, will render pages which redirect to the
-    /// real location of an item. This is used to allow external links to
-    /// publicly reused items to redirect to the right location.
-    crate render_redirect_pages: bool,
-    /// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set
-    /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of
-    /// the crate.
-    crate redirections: Option<Rc<RefCell<FxHashMap<String, String>>>>,
-    /// The map used to ensure all generated 'id=' attributes are unique.
-    id_map: Rc<RefCell<IdMap>>,
-    /// Tracks section IDs for `Deref` targets so they match in both the main
-    /// body and the sidebar.
-    deref_id_map: Rc<RefCell<FxHashMap<DefId, String>>>,
-    crate shared: Arc<SharedContext<'tcx>>,
-    all: Rc<RefCell<AllTypes>>,
-    /// Storage for the errors produced while generating documentation so they
-    /// can be printed together at the end.
-    crate errors: Rc<Receiver<String>>,
-    crate cache: Rc<Cache>,
-}
-
 crate struct SharedContext<'tcx> {
     crate tcx: TyCtxt<'tcx>,
     /// The path to the crate root source minus the file name.
@@ -169,27 +121,6 @@ crate struct SharedContext<'tcx> {
     playground: Option<markdown::Playground>,
 }
 
-impl<'tcx> Context<'tcx> {
-    fn path(&self, filename: &str) -> PathBuf {
-        // We use splitn vs Path::extension here because we might get a filename
-        // like `style.min.css` and we want to process that into
-        // `style-suffix.min.css`.  Path::extension would just return `css`
-        // which would result in `style.min-suffix.css` which isn't what we
-        // want.
-        let (base, ext) = filename.split_once('.').unwrap();
-        let filename = format!("{}{}.{}", base, self.shared.resource_suffix, ext);
-        self.dst.join(&filename)
-    }
-
-    fn tcx(&self) -> TyCtxt<'tcx> {
-        self.shared.tcx
-    }
-
-    fn sess(&self) -> &'tcx Session {
-        &self.shared.tcx.sess
-    }
-}
-
 impl SharedContext<'_> {
     crate fn ensure_dir(&self, dst: &Path) -> Result<(), Error> {
         let mut dirs = self.created_dirs.borrow_mut();
@@ -380,860 +311,6 @@ crate const INITIAL_IDS: [&'static str; 15] = [
     "implementations",
 ];
 
-/// Generates the documentation for `crate` into the directory `dst`
-impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
-    fn descr() -> &'static str {
-        "html"
-    }
-
-    fn init(
-        mut krate: clean::Crate,
-        options: RenderOptions,
-        edition: Edition,
-        mut cache: Cache,
-        tcx: TyCtxt<'tcx>,
-    ) -> Result<(Self, clean::Crate), Error> {
-        // need to save a copy of the options for rendering the index page
-        let md_opts = options.clone();
-        let RenderOptions {
-            output,
-            external_html,
-            id_map,
-            playground_url,
-            sort_modules_alphabetically,
-            themes: style_files,
-            default_settings,
-            extension_css,
-            resource_suffix,
-            static_root_path,
-            generate_search_filter,
-            unstable_features,
-            generate_redirect_map,
-            ..
-        } = options;
-
-        let src_root = match krate.src {
-            FileName::Real(ref p) => match p.local_path().parent() {
-                Some(p) => p.to_path_buf(),
-                None => PathBuf::new(),
-            },
-            _ => PathBuf::new(),
-        };
-        // If user passed in `--playground-url` arg, we fill in crate name here
-        let mut playground = None;
-        if let Some(url) = playground_url {
-            playground =
-                Some(markdown::Playground { crate_name: Some(krate.name.to_string()), url });
-        }
-        let mut layout = layout::Layout {
-            logo: String::new(),
-            favicon: String::new(),
-            external_html,
-            default_settings,
-            krate: krate.name.to_string(),
-            css_file_extension: extension_css,
-            generate_search_filter,
-        };
-        let mut issue_tracker_base_url = None;
-        let mut include_sources = true;
-
-        // Crawl the crate attributes looking for attributes which control how we're
-        // going to emit HTML
-        if let Some(attrs) = krate.module.as_ref().map(|m| &m.attrs) {
-            for attr in attrs.lists(sym::doc) {
-                match (attr.name_or_empty(), attr.value_str()) {
-                    (sym::html_favicon_url, Some(s)) => {
-                        layout.favicon = s.to_string();
-                    }
-                    (sym::html_logo_url, Some(s)) => {
-                        layout.logo = s.to_string();
-                    }
-                    (sym::html_playground_url, Some(s)) => {
-                        playground = Some(markdown::Playground {
-                            crate_name: Some(krate.name.to_string()),
-                            url: s.to_string(),
-                        });
-                    }
-                    (sym::issue_tracker_base_url, Some(s)) => {
-                        issue_tracker_base_url = Some(s.to_string());
-                    }
-                    (sym::html_no_source, None) if attr.is_word() => {
-                        include_sources = false;
-                    }
-                    _ => {}
-                }
-            }
-        }
-        let (sender, receiver) = channel();
-        let mut scx = SharedContext {
-            tcx,
-            collapsed: krate.collapsed,
-            src_root,
-            include_sources,
-            local_sources: Default::default(),
-            issue_tracker_base_url,
-            layout,
-            created_dirs: Default::default(),
-            sort_modules_alphabetically,
-            style_files,
-            resource_suffix,
-            static_root_path,
-            fs: DocFS::new(sender),
-            edition,
-            codes: ErrorCodes::from(unstable_features.is_nightly_build()),
-            playground,
-        };
-
-        // Add the default themes to the `Vec` of stylepaths
-        //
-        // Note that these must be added before `sources::render` is called
-        // so that the resulting source pages are styled
-        //
-        // `light.css` is not disabled because it is the stylesheet that stays loaded
-        // by the browser as the theme stylesheet. The theme system (hackily) works by
-        // changing the href to this stylesheet. All other themes are disabled to
-        // prevent rule conflicts
-        scx.style_files.push(StylePath { path: PathBuf::from("light.css"), disabled: false });
-        scx.style_files.push(StylePath { path: PathBuf::from("dark.css"), disabled: true });
-        scx.style_files.push(StylePath { path: PathBuf::from("ayu.css"), disabled: true });
-
-        let dst = output;
-        scx.ensure_dir(&dst)?;
-        krate = sources::render(&dst, &mut scx, krate)?;
-
-        // Build our search index
-        let index = build_index(&krate, &mut cache, tcx);
-
-        let mut cx = Context {
-            current: Vec::new(),
-            dst,
-            render_redirect_pages: false,
-            id_map: Rc::new(RefCell::new(id_map)),
-            deref_id_map: Rc::new(RefCell::new(FxHashMap::default())),
-            shared: Arc::new(scx),
-            all: Rc::new(RefCell::new(AllTypes::new())),
-            errors: Rc::new(receiver),
-            cache: Rc::new(cache),
-            redirections: if generate_redirect_map { Some(Default::default()) } else { None },
-        };
-
-        CURRENT_DEPTH.with(|s| s.set(0));
-
-        // Write shared runs within a flock; disable thread dispatching of IO temporarily.
-        Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
-        write_shared(&cx, &krate, index, &md_opts)?;
-        Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
-        Ok((cx, krate))
-    }
-
-    fn after_krate(
-        &mut self,
-        krate: &clean::Crate,
-        diag: &rustc_errors::Handler,
-    ) -> Result<(), Error> {
-        let final_file = self.dst.join(&*krate.name.as_str()).join("all.html");
-        let settings_file = self.dst.join("settings.html");
-        let crate_name = krate.name;
-
-        let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
-        if !root_path.ends_with('/') {
-            root_path.push('/');
-        }
-        let mut page = layout::Page {
-            title: "List of all items in this crate",
-            css_class: "mod",
-            root_path: "../",
-            static_root_path: self.shared.static_root_path.as_deref(),
-            description: "List of all items in this crate",
-            keywords: BASIC_KEYWORDS,
-            resource_suffix: &self.shared.resource_suffix,
-            extra_scripts: &[],
-            static_extra_scripts: &[],
-        };
-        let sidebar = if let Some(ref version) = self.cache.crate_version {
-            format!(
-                "<p class=\"location\">Crate {}</p>\
-                     <div class=\"block version\">\
-                         <p>Version {}</p>\
-                     </div>\
-                     <a id=\"all-types\" href=\"index.html\"><p>Back to index</p></a>",
-                crate_name,
-                Escape(version),
-            )
-        } else {
-            String::new()
-        };
-        let all = self.all.replace(AllTypes::new());
-        let v = layout::render(
-            &self.shared.layout,
-            &page,
-            sidebar,
-            |buf: &mut Buffer| all.print(buf),
-            &self.shared.style_files,
-        );
-        self.shared.fs.write(&final_file, v.as_bytes())?;
-
-        // Generating settings page.
-        page.title = "Rustdoc settings";
-        page.description = "Settings of Rustdoc";
-        page.root_path = "./";
-
-        let mut style_files = self.shared.style_files.clone();
-        let sidebar = "<p class=\"location\">Settings</p><div class=\"sidebar-elems\"></div>";
-        style_files.push(StylePath { path: PathBuf::from("settings.css"), disabled: false });
-        let v = layout::render(
-            &self.shared.layout,
-            &page,
-            sidebar,
-            settings(
-                self.shared.static_root_path.as_deref().unwrap_or("./"),
-                &self.shared.resource_suffix,
-                &self.shared.style_files,
-            )?,
-            &style_files,
-        );
-        self.shared.fs.write(&settings_file, v.as_bytes())?;
-        if let Some(redirections) = self.redirections.take() {
-            if !redirections.borrow().is_empty() {
-                let redirect_map_path =
-                    self.dst.join(&*krate.name.as_str()).join("redirect-map.json");
-                let paths = serde_json::to_string(&*redirections.borrow()).unwrap();
-                self.shared.ensure_dir(&self.dst.join(&*krate.name.as_str()))?;
-                self.shared.fs.write(&redirect_map_path, paths.as_bytes())?;
-            }
-        }
-
-        // Flush pending errors.
-        Arc::get_mut(&mut self.shared).unwrap().fs.close();
-        let nb_errors = self.errors.iter().map(|err| diag.struct_err(&err).emit()).count();
-        if nb_errors > 0 {
-            Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), ""))
-        } else {
-            Ok(())
-        }
-    }
-
-    fn mod_item_in(&mut self, item: &clean::Item, item_name: &str) -> Result<(), Error> {
-        // Stripped modules survive the rustdoc passes (i.e., `strip-private`)
-        // if they contain impls for public types. These modules can also
-        // contain items such as publicly re-exported structures.
-        //
-        // External crates will provide links to these structures, so
-        // these modules are recursed into, but not rendered normally
-        // (a flag on the context).
-        if !self.render_redirect_pages {
-            self.render_redirect_pages = item.is_stripped();
-        }
-        let scx = &self.shared;
-        self.dst.push(item_name);
-        self.current.push(item_name.to_owned());
-
-        info!("Recursing into {}", self.dst.display());
-
-        let buf = self.render_item(item, false);
-        // buf will be empty if the module is stripped and there is no redirect for it
-        if !buf.is_empty() {
-            self.shared.ensure_dir(&self.dst)?;
-            let joint_dst = self.dst.join("index.html");
-            scx.fs.write(&joint_dst, buf.as_bytes())?;
-        }
-
-        // Render sidebar-items.js used throughout this module.
-        if !self.render_redirect_pages {
-            let module = match *item.kind {
-                clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m,
-                _ => unreachable!(),
-            };
-            let items = self.build_sidebar_items(module);
-            let js_dst = self.dst.join("sidebar-items.js");
-            let v = format!("initSidebarItems({});", serde_json::to_string(&items).unwrap());
-            scx.fs.write(&js_dst, &v)?;
-        }
-        Ok(())
-    }
-
-    fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> {
-        info!("Recursed; leaving {}", self.dst.display());
-
-        // Go back to where we were at
-        self.dst.pop();
-        self.current.pop();
-        Ok(())
-    }
-
-    fn item(&mut self, item: clean::Item) -> Result<(), Error> {
-        // Stripped modules survive the rustdoc passes (i.e., `strip-private`)
-        // if they contain impls for public types. These modules can also
-        // contain items such as publicly re-exported structures.
-        //
-        // External crates will provide links to these structures, so
-        // these modules are recursed into, but not rendered normally
-        // (a flag on the context).
-        if !self.render_redirect_pages {
-            self.render_redirect_pages = item.is_stripped();
-        }
-
-        let buf = self.render_item(&item, true);
-        // buf will be empty if the item is stripped and there is no redirect for it
-        if !buf.is_empty() {
-            let name = item.name.as_ref().unwrap();
-            let item_type = item.type_();
-            let file_name = &item_path(item_type, &name.as_str());
-            self.shared.ensure_dir(&self.dst)?;
-            let joint_dst = self.dst.join(file_name);
-            self.shared.fs.write(&joint_dst, buf.as_bytes())?;
-
-            if !self.render_redirect_pages {
-                self.all.borrow_mut().append(full_path(self, &item), &item_type);
-            }
-            // If the item is a macro, redirect from the old macro URL (with !)
-            // to the new one (without).
-            if item_type == ItemType::Macro {
-                let redir_name = format!("{}.{}!.html", item_type, name);
-                if let Some(ref redirections) = self.redirections {
-                    let crate_name = &self.shared.layout.krate;
-                    redirections.borrow_mut().insert(
-                        format!("{}/{}", crate_name, redir_name),
-                        format!("{}/{}", crate_name, file_name),
-                    );
-                } else {
-                    let v = layout::redirect(file_name);
-                    let redir_dst = self.dst.join(redir_name);
-                    self.shared.fs.write(&redir_dst, v.as_bytes())?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn cache(&self) -> &Cache {
-        &self.cache
-    }
-}
-
-fn write_shared(
-    cx: &Context<'_>,
-    krate: &clean::Crate,
-    search_index: String,
-    options: &RenderOptions,
-) -> Result<(), Error> {
-    // Write out the shared files. Note that these are shared among all rustdoc
-    // docs placed in the output directory, so this needs to be a synchronized
-    // operation with respect to all other rustdocs running around.
-    let lock_file = cx.dst.join(".lock");
-    let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
-
-    // Add all the static files. These may already exist, but we just
-    // overwrite them anyway to make sure that they're fresh and up-to-date.
-
-    write_minify(
-        &cx.shared.fs,
-        cx.path("rustdoc.css"),
-        static_files::RUSTDOC_CSS,
-        options.enable_minification,
-    )?;
-    write_minify(
-        &cx.shared.fs,
-        cx.path("settings.css"),
-        static_files::SETTINGS_CSS,
-        options.enable_minification,
-    )?;
-    write_minify(
-        &cx.shared.fs,
-        cx.path("noscript.css"),
-        static_files::NOSCRIPT_CSS,
-        options.enable_minification,
-    )?;
-
-    // To avoid "light.css" to be overwritten, we'll first run over the received themes and only
-    // then we'll run over the "official" styles.
-    let mut themes: FxHashSet<String> = FxHashSet::default();
-
-    for entry in &cx.shared.style_files {
-        let theme = try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path);
-        let extension =
-            try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
-
-        // Handle the official themes
-        match theme {
-            "light" => write_minify(
-                &cx.shared.fs,
-                cx.path("light.css"),
-                static_files::themes::LIGHT,
-                options.enable_minification,
-            )?,
-            "dark" => write_minify(
-                &cx.shared.fs,
-                cx.path("dark.css"),
-                static_files::themes::DARK,
-                options.enable_minification,
-            )?,
-            "ayu" => write_minify(
-                &cx.shared.fs,
-                cx.path("ayu.css"),
-                static_files::themes::AYU,
-                options.enable_minification,
-            )?,
-            _ => {
-                // Handle added third-party themes
-                let content = try_err!(fs::read(&entry.path), &entry.path);
-                cx.shared
-                    .fs
-                    .write(cx.path(&format!("{}.{}", theme, extension)), content.as_slice())?;
-            }
-        };
-
-        themes.insert(theme.to_owned());
-    }
-
-    let write = |p, c| cx.shared.fs.write(p, c);
-    if (*cx.shared).layout.logo.is_empty() {
-        write(cx.path("rust-logo.png"), static_files::RUST_LOGO)?;
-    }
-    if (*cx.shared).layout.favicon.is_empty() {
-        write(cx.path("favicon.svg"), static_files::RUST_FAVICON_SVG)?;
-        write(cx.path("favicon-16x16.png"), static_files::RUST_FAVICON_PNG_16)?;
-        write(cx.path("favicon-32x32.png"), static_files::RUST_FAVICON_PNG_32)?;
-    }
-    write(cx.path("brush.svg"), static_files::BRUSH_SVG)?;
-    write(cx.path("wheel.svg"), static_files::WHEEL_SVG)?;
-    write(cx.path("down-arrow.svg"), static_files::DOWN_ARROW_SVG)?;
-
-    let mut themes: Vec<&String> = themes.iter().collect();
-    themes.sort();
-    // To avoid theme switch latencies as much as possible, we put everything theme related
-    // at the beginning of the html files into another js file.
-    let theme_js = format!(
-        r#"var themes = document.getElementById("theme-choices");
-var themePicker = document.getElementById("theme-picker");
-
-function showThemeButtonState() {{
-    themes.style.display = "block";
-    themePicker.style.borderBottomRightRadius = "0";
-    themePicker.style.borderBottomLeftRadius = "0";
-}}
-
-function hideThemeButtonState() {{
-    themes.style.display = "none";
-    themePicker.style.borderBottomRightRadius = "3px";
-    themePicker.style.borderBottomLeftRadius = "3px";
-}}
-
-function switchThemeButtonState() {{
-    if (themes.style.display === "block") {{
-        hideThemeButtonState();
-    }} else {{
-        showThemeButtonState();
-    }}
-}};
-
-function handleThemeButtonsBlur(e) {{
-    var active = document.activeElement;
-    var related = e.relatedTarget;
-
-    if (active.id !== "theme-picker" &&
-        (!active.parentNode || active.parentNode.id !== "theme-choices") &&
-        (!related ||
-         (related.id !== "theme-picker" &&
-          (!related.parentNode || related.parentNode.id !== "theme-choices")))) {{
-        hideThemeButtonState();
-    }}
-}}
-
-themePicker.onclick = switchThemeButtonState;
-themePicker.onblur = handleThemeButtonsBlur;
-{}.forEach(function(item) {{
-    var but = document.createElement("button");
-    but.textContent = item;
-    but.onclick = function(el) {{
-        switchTheme(currentTheme, mainTheme, item, true);
-        useSystemTheme(false);
-    }};
-    but.onblur = handleThemeButtonsBlur;
-    themes.appendChild(but);
-}});"#,
-        serde_json::to_string(&themes).unwrap()
-    );
-
-    write_minify(&cx.shared.fs, cx.path("theme.js"), &theme_js, options.enable_minification)?;
-    write_minify(
-        &cx.shared.fs,
-        cx.path("main.js"),
-        static_files::MAIN_JS,
-        options.enable_minification,
-    )?;
-    write_minify(
-        &cx.shared.fs,
-        cx.path("settings.js"),
-        static_files::SETTINGS_JS,
-        options.enable_minification,
-    )?;
-    if cx.shared.include_sources {
-        write_minify(
-            &cx.shared.fs,
-            cx.path("source-script.js"),
-            static_files::sidebar::SOURCE_SCRIPT,
-            options.enable_minification,
-        )?;
-    }
-
-    {
-        write_minify(
-            &cx.shared.fs,
-            cx.path("storage.js"),
-            &format!(
-                "var resourcesSuffix = \"{}\";{}",
-                cx.shared.resource_suffix,
-                static_files::STORAGE_JS
-            ),
-            options.enable_minification,
-        )?;
-    }
-
-    if let Some(ref css) = cx.shared.layout.css_file_extension {
-        let out = cx.path("theme.css");
-        let buffer = try_err!(fs::read_to_string(css), css);
-        if !options.enable_minification {
-            cx.shared.fs.write(&out, &buffer)?;
-        } else {
-            write_minify(&cx.shared.fs, out, &buffer, options.enable_minification)?;
-        }
-    }
-    write_minify(
-        &cx.shared.fs,
-        cx.path("normalize.css"),
-        static_files::NORMALIZE_CSS,
-        options.enable_minification,
-    )?;
-    write(cx.dst.join("FiraSans-Regular.woff2"), static_files::fira_sans::REGULAR2)?;
-    write(cx.dst.join("FiraSans-Medium.woff2"), static_files::fira_sans::MEDIUM2)?;
-    write(cx.dst.join("FiraSans-Regular.woff"), static_files::fira_sans::REGULAR)?;
-    write(cx.dst.join("FiraSans-Medium.woff"), static_files::fira_sans::MEDIUM)?;
-    write(cx.dst.join("FiraSans-LICENSE.txt"), static_files::fira_sans::LICENSE)?;
-    write(cx.dst.join("SourceSerifPro-Regular.ttf.woff"), static_files::source_serif_pro::REGULAR)?;
-    write(cx.dst.join("SourceSerifPro-Bold.ttf.woff"), static_files::source_serif_pro::BOLD)?;
-    write(cx.dst.join("SourceSerifPro-It.ttf.woff"), static_files::source_serif_pro::ITALIC)?;
-    write(cx.dst.join("SourceSerifPro-LICENSE.md"), static_files::source_serif_pro::LICENSE)?;
-    write(cx.dst.join("SourceCodePro-Regular.woff"), static_files::source_code_pro::REGULAR)?;
-    write(cx.dst.join("SourceCodePro-Semibold.woff"), static_files::source_code_pro::SEMIBOLD)?;
-    write(cx.dst.join("SourceCodePro-LICENSE.txt"), static_files::source_code_pro::LICENSE)?;
-    write(cx.dst.join("LICENSE-MIT.txt"), static_files::LICENSE_MIT)?;
-    write(cx.dst.join("LICENSE-APACHE.txt"), static_files::LICENSE_APACHE)?;
-    write(cx.dst.join("COPYRIGHT.txt"), static_files::COPYRIGHT)?;
-
-    fn collect(path: &Path, krate: &str, key: &str) -> io::Result<(Vec<String>, Vec<String>)> {
-        let mut ret = Vec::new();
-        let mut krates = Vec::new();
-
-        if path.exists() {
-            let prefix = format!(r#"{}["{}"]"#, key, krate);
-            for line in BufReader::new(File::open(path)?).lines() {
-                let line = line?;
-                if !line.starts_with(key) {
-                    continue;
-                }
-                if line.starts_with(&prefix) {
-                    continue;
-                }
-                ret.push(line.to_string());
-                krates.push(
-                    line[key.len() + 2..]
-                        .split('"')
-                        .next()
-                        .map(|s| s.to_owned())
-                        .unwrap_or_else(String::new),
-                );
-            }
-        }
-        Ok((ret, krates))
-    }
-
-    fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec<String>, Vec<String>)> {
-        let mut ret = Vec::new();
-        let mut krates = Vec::new();
-
-        if path.exists() {
-            let prefix = format!("\"{}\"", krate);
-            for line in BufReader::new(File::open(path)?).lines() {
-                let line = line?;
-                if !line.starts_with('"') {
-                    continue;
-                }
-                if line.starts_with(&prefix) {
-                    continue;
-                }
-                if line.ends_with(",\\") {
-                    ret.push(line[..line.len() - 2].to_string());
-                } else {
-                    // Ends with "\\" (it's the case for the last added crate line)
-                    ret.push(line[..line.len() - 1].to_string());
-                }
-                krates.push(
-                    line.split('"')
-                        .find(|s| !s.is_empty())
-                        .map(|s| s.to_owned())
-                        .unwrap_or_else(String::new),
-                );
-            }
-        }
-        Ok((ret, krates))
-    }
-
-    use std::ffi::OsString;
-
-    #[derive(Debug)]
-    struct Hierarchy {
-        elem: OsString,
-        children: FxHashMap<OsString, Hierarchy>,
-        elems: FxHashSet<OsString>,
-    }
-
-    impl Hierarchy {
-        fn new(elem: OsString) -> Hierarchy {
-            Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() }
-        }
-
-        fn to_json_string(&self) -> String {
-            let mut subs: Vec<&Hierarchy> = self.children.values().collect();
-            subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
-            let mut files = self
-                .elems
-                .iter()
-                .map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion")))
-                .collect::<Vec<_>>();
-            files.sort_unstable();
-            let subs = subs.iter().map(|s| s.to_json_string()).collect::<Vec<_>>().join(",");
-            let dirs =
-                if subs.is_empty() { String::new() } else { format!(",\"dirs\":[{}]", subs) };
-            let files = files.join(",");
-            let files =
-                if files.is_empty() { String::new() } else { format!(",\"files\":[{}]", files) };
-            format!(
-                "{{\"name\":\"{name}\"{dirs}{files}}}",
-                name = self.elem.to_str().expect("invalid osstring conversion"),
-                dirs = dirs,
-                files = files
-            )
-        }
-    }
-
-    if cx.shared.include_sources {
-        let mut hierarchy = Hierarchy::new(OsString::new());
-        for source in cx
-            .shared
-            .local_sources
-            .iter()
-            .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
-        {
-            let mut h = &mut hierarchy;
-            let mut elems = source
-                .components()
-                .filter_map(|s| match s {
-                    Component::Normal(s) => Some(s.to_owned()),
-                    _ => None,
-                })
-                .peekable();
-            loop {
-                let cur_elem = elems.next().expect("empty file path");
-                if elems.peek().is_none() {
-                    h.elems.insert(cur_elem);
-                    break;
-                } else {
-                    let e = cur_elem.clone();
-                    h = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e));
-                }
-            }
-        }
-
-        let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix));
-        let (mut all_sources, _krates) =
-            try_err!(collect(&dst, &krate.name.as_str(), "sourcesIndex"), &dst);
-        all_sources.push(format!(
-            "sourcesIndex[\"{}\"] = {};",
-            &krate.name,
-            hierarchy.to_json_string()
-        ));
-        all_sources.sort();
-        let v = format!(
-            "var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();\n",
-            all_sources.join("\n")
-        );
-        cx.shared.fs.write(&dst, v.as_bytes())?;
-    }
-
-    // Update the search index and crate list.
-    let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix));
-    let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst);
-    all_indexes.push(search_index);
-    krates.push(krate.name.to_string());
-    krates.sort();
-
-    // Sort the indexes by crate so the file will be generated identically even
-    // with rustdoc running in parallel.
-    all_indexes.sort();
-    {
-        let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
-        v.push_str(&all_indexes.join(",\\\n"));
-        v.push_str("\\\n}');\ninitSearch(searchIndex);");
-        cx.shared.fs.write(&dst, &v)?;
-    }
-
-    let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix));
-    let crate_list =
-        format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(","));
-    cx.shared.fs.write(&crate_list_dst, &crate_list)?;
-
-    if options.enable_index_page {
-        if let Some(index_page) = options.index_page.clone() {
-            let mut md_opts = options.clone();
-            md_opts.output = cx.dst.clone();
-            md_opts.external_html = (*cx.shared).layout.external_html.clone();
-
-            crate::markdown::render(&index_page, md_opts, cx.shared.edition)
-                .map_err(|e| Error::new(e, &index_page))?;
-        } else {
-            let dst = cx.dst.join("index.html");
-            let page = layout::Page {
-                title: "Index of crates",
-                css_class: "mod",
-                root_path: "./",
-                static_root_path: cx.shared.static_root_path.as_deref(),
-                description: "List of crates",
-                keywords: BASIC_KEYWORDS,
-                resource_suffix: &cx.shared.resource_suffix,
-                extra_scripts: &[],
-                static_extra_scripts: &[],
-            };
-
-            let content = format!(
-                "<h1 class=\"fqn\">\
-                     <span class=\"in-band\">List of all crates</span>\
-                </h1><ul class=\"crate mod\">{}</ul>",
-                krates
-                    .iter()
-                    .map(|s| {
-                        format!(
-                            "<li><a class=\"crate mod\" href=\"{}index.html\">{}</a></li>",
-                            ensure_trailing_slash(s),
-                            s
-                        )
-                    })
-                    .collect::<String>()
-            );
-            let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files);
-            cx.shared.fs.write(&dst, v.as_bytes())?;
-        }
-    }
-
-    // Update the list of all implementors for traits
-    let dst = cx.dst.join("implementors");
-    for (&did, imps) in &cx.cache.implementors {
-        // Private modules can leak through to this phase of rustdoc, which
-        // could contain implementations for otherwise private types. In some
-        // rare cases we could find an implementation for an item which wasn't
-        // indexed, so we just skip this step in that case.
-        //
-        // FIXME: this is a vague explanation for why this can't be a `get`, in
-        //        theory it should be...
-        let &(ref remote_path, remote_item_type) = match cx.cache.paths.get(&did) {
-            Some(p) => p,
-            None => match cx.cache.external_paths.get(&did) {
-                Some(p) => p,
-                None => continue,
-            },
-        };
-
-        #[derive(Serialize)]
-        struct Implementor {
-            text: String,
-            synthetic: bool,
-            types: Vec<String>,
-        }
-
-        let implementors = imps
-            .iter()
-            .filter_map(|imp| {
-                // If the trait and implementation are in the same crate, then
-                // there's no need to emit information about it (there's inlining
-                // going on). If they're in different crates then the crate defining
-                // the trait will be interested in our implementation.
-                //
-                // If the implementation is from another crate then that crate
-                // should add it.
-                if imp.impl_item.def_id.krate == did.krate || !imp.impl_item.def_id.is_local() {
-                    None
-                } else {
-                    Some(Implementor {
-                        text: imp.inner_impl().print(cx.cache(), false).to_string(),
-                        synthetic: imp.inner_impl().synthetic,
-                        types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()),
-                    })
-                }
-            })
-            .collect::<Vec<_>>();
-
-        // Only create a js file if we have impls to add to it. If the trait is
-        // documented locally though we always create the file to avoid dead
-        // links.
-        if implementors.is_empty() && !cx.cache.paths.contains_key(&did) {
-            continue;
-        }
-
-        let implementors = format!(
-            r#"implementors["{}"] = {};"#,
-            krate.name,
-            serde_json::to_string(&implementors).unwrap()
-        );
-
-        let mut mydst = dst.clone();
-        for part in &remote_path[..remote_path.len() - 1] {
-            mydst.push(part);
-        }
-        cx.shared.ensure_dir(&mydst)?;
-        mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1]));
-
-        let (mut all_implementors, _) =
-            try_err!(collect(&mydst, &krate.name.as_str(), "implementors"), &mydst);
-        all_implementors.push(implementors);
-        // Sort the implementors by crate so the file will be generated
-        // identically even with rustdoc running in parallel.
-        all_implementors.sort();
-
-        let mut v = String::from("(function() {var implementors = {};\n");
-        for implementor in &all_implementors {
-            writeln!(v, "{}", *implementor).unwrap();
-        }
-        v.push_str(
-            "if (window.register_implementors) {\
-                 window.register_implementors(implementors);\
-             } else {\
-                 window.pending_implementors = implementors;\
-             }",
-        );
-        v.push_str("})()");
-        cx.shared.fs.write(&mydst, &v)?;
-    }
-    Ok(())
-}
-
-fn write_minify(
-    fs: &DocFS,
-    dst: PathBuf,
-    contents: &str,
-    enable_minification: bool,
-) -> Result<(), Error> {
-    if enable_minification {
-        if dst.extension() == Some(&OsStr::new("css")) {
-            let res = try_none!(minifier::css::minify(contents).ok(), &dst);
-            fs.write(dst, res.as_bytes())
-        } else {
-            fs.write(dst, minifier::js::minify(contents).as_bytes())
-        }
-    } else {
-        fs.write(dst, contents.as_bytes())
-    }
-}
-
 fn write_srclink(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) {
     if let Some(l) = cx.src_href(item) {
         write!(buf, "<a class=\"srclink\" href=\"{}\" title=\"goto source code\">[src]</a>", l)
@@ -1541,390 +618,68 @@ fn settings(root_path: &str, suffix: &str, themes: &[StylePath]) -> Result<Strin
     ))
 }
 
-impl Context<'_> {
-    fn derive_id(&self, id: String) -> String {
-        let mut map = self.id_map.borrow_mut();
-        map.derive(id)
-    }
-
-    /// String representation of how to get back to the root path of the 'doc/'
-    /// folder in terms of a relative URL.
-    fn root_path(&self) -> String {
-        "../".repeat(self.current.len())
+fn document(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, parent: Option<&clean::Item>) {
+    if let Some(ref name) = item.name {
+        info!("Documenting {}", name);
     }
+    document_item_info(w, cx, item, false, parent);
+    document_full(w, item, cx, "", false);
+}
 
-    fn render_item(&self, it: &clean::Item, pushname: bool) -> String {
-        // A little unfortunate that this is done like this, but it sure
-        // does make formatting *a lot* nicer.
-        CURRENT_DEPTH.with(|slot| {
-            slot.set(self.current.len());
-        });
+/// Render md_text as markdown.
+fn render_markdown(
+    w: &mut Buffer,
+    cx: &Context<'_>,
+    md_text: &str,
+    links: Vec<RenderedLink>,
+    prefix: &str,
+    is_hidden: bool,
+) {
+    let mut ids = cx.id_map.borrow_mut();
+    write!(
+        w,
+        "<div class=\"docblock{}\">{}{}</div>",
+        if is_hidden { " hidden" } else { "" },
+        prefix,
+        Markdown(
+            md_text,
+            &links,
+            &mut ids,
+            cx.shared.codes,
+            cx.shared.edition,
+            &cx.shared.playground
+        )
+        .into_string()
+    )
+}
 
-        let mut title = if it.is_primitive() || it.is_keyword() {
-            // No need to include the namespace for primitive types and keywords
-            String::new()
-        } else {
-            self.current.join("::")
-        };
-        if pushname {
-            if !title.is_empty() {
-                title.push_str("::");
-            }
-            title.push_str(&it.name.unwrap().as_str());
-        }
-        title.push_str(" - Rust");
-        let tyname = it.type_();
-        let desc = it.doc_value().as_ref().map(|doc| plain_text_summary(&doc));
-        let desc = if let Some(desc) = desc {
-            desc
-        } else if it.is_crate() {
-            format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate)
-        } else {
-            format!(
-                "API documentation for the Rust `{}` {} in crate `{}`.",
-                it.name.as_ref().unwrap(),
-                tyname,
-                self.shared.layout.krate
-            )
-        };
-        let keywords = make_item_keywords(it);
-        let page = layout::Page {
-            css_class: tyname.as_str(),
-            root_path: &self.root_path(),
-            static_root_path: self.shared.static_root_path.as_deref(),
-            title: &title,
-            description: &desc,
-            keywords: &keywords,
-            resource_suffix: &self.shared.resource_suffix,
-            extra_scripts: &[],
-            static_extra_scripts: &[],
-        };
+/// Writes a documentation block containing only the first paragraph of the documentation. If the
+/// docs are longer, a "Read more" link is appended to the end.
+fn document_short(
+    w: &mut Buffer,
+    item: &clean::Item,
+    cx: &Context<'_>,
+    link: AssocItemLink<'_>,
+    prefix: &str,
+    is_hidden: bool,
+    parent: Option<&clean::Item>,
+    show_def_docs: bool,
+) {
+    document_item_info(w, cx, item, is_hidden, parent);
+    if !show_def_docs {
+        return;
+    }
+    if let Some(s) = item.doc_value() {
+        let mut summary_html = MarkdownSummaryLine(&s, &item.links(&cx.cache)).into_string();
 
-        {
-            self.id_map.borrow_mut().reset();
-            self.id_map.borrow_mut().populate(&INITIAL_IDS);
-        }
+        if s.contains('\n') {
+            let link =
+                format!(r#" <a href="{}">Read more</a>"#, naive_assoc_href(item, link, cx.cache()));
 
-        if !self.render_redirect_pages {
-            layout::render(
-                &self.shared.layout,
-                &page,
-                |buf: &mut _| print_sidebar(self, it, buf),
-                |buf: &mut _| print_item(self, it, buf),
-                &self.shared.style_files,
-            )
-        } else {
-            if let Some(&(ref names, ty)) = self.cache.paths.get(&it.def_id) {
-                let mut path = String::new();
-                for name in &names[..names.len() - 1] {
-                    path.push_str(name);
-                    path.push('/');
-                }
-                path.push_str(&item_path(ty, names.last().unwrap()));
-                match self.redirections {
-                    Some(ref redirections) => {
-                        let mut current_path = String::new();
-                        for name in &self.current {
-                            current_path.push_str(name);
-                            current_path.push('/');
-                        }
-                        current_path.push_str(&item_path(ty, names.last().unwrap()));
-                        redirections.borrow_mut().insert(current_path, path);
-                    }
-                    None => return layout::redirect(&format!("{}{}", self.root_path(), path)),
-                }
-            }
-            String::new()
-        }
-    }
-
-    /// Construct a map of items shown in the sidebar to a plain-text summary of their docs.
-    fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<NameDoc>> {
-        // BTreeMap instead of HashMap to get a sorted output
-        let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new();
-        for item in &m.items {
-            if item.is_stripped() {
-                continue;
-            }
-
-            let short = item.type_();
-            let myname = match item.name {
-                None => continue,
-                Some(ref s) => s.to_string(),
-            };
-            let short = short.to_string();
-            map.entry(short).or_default().push((
-                myname,
-                Some(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))),
-            ));
-        }
-
-        if self.shared.sort_modules_alphabetically {
-            for items in map.values_mut() {
-                items.sort();
-            }
-        }
-        map
-    }
-
-    /// Generates a url appropriate for an `href` attribute back to the source of
-    /// this item.
-    ///
-    /// The url generated, when clicked, will redirect the browser back to the
-    /// original source code.
-    ///
-    /// If `None` is returned, then a source link couldn't be generated. This
-    /// may happen, for example, with externally inlined items where the source
-    /// of their crate documentation isn't known.
-    fn src_href(&self, item: &clean::Item) -> Option<String> {
-        if item.source.is_dummy() {
-            return None;
-        }
-        let mut root = self.root_path();
-        let mut path = String::new();
-        let cnum = item.source.cnum(self.sess());
-
-        // We can safely ignore synthetic `SourceFile`s.
-        let file = match item.source.filename(self.sess()) {
-            FileName::Real(ref path) => path.local_path().to_path_buf(),
-            _ => return None,
-        };
-        let file = &file;
-
-        let symbol;
-        let (krate, path) = if cnum == LOCAL_CRATE {
-            if let Some(path) = self.shared.local_sources.get(file) {
-                (self.shared.layout.krate.as_str(), path)
-            } else {
-                return None;
-            }
-        } else {
-            let (krate, src_root) = match *self.cache.extern_locations.get(&cnum)? {
-                (name, ref src, ExternalLocation::Local) => (name, src),
-                (name, ref src, ExternalLocation::Remote(ref s)) => {
-                    root = s.to_string();
-                    (name, src)
-                }
-                (_, _, ExternalLocation::Unknown) => return None,
-            };
-
-            sources::clean_path(&src_root, file, false, |component| {
-                path.push_str(&component.to_string_lossy());
-                path.push('/');
-            });
-            let mut fname = file.file_name().expect("source has no filename").to_os_string();
-            fname.push(".html");
-            path.push_str(&fname.to_string_lossy());
-            symbol = krate.as_str();
-            (&*symbol, &path)
-        };
-
-        let loline = item.source.lo(self.sess()).line;
-        let hiline = item.source.hi(self.sess()).line;
-        let lines =
-            if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) };
-        Some(format!(
-            "{root}src/{krate}/{path}#{lines}",
-            root = Escape(&root),
-            krate = krate,
-            path = path,
-            lines = lines
-        ))
-    }
-}
-
-fn wrap_into_docblock<F>(w: &mut Buffer, f: F)
-where
-    F: FnOnce(&mut Buffer),
-{
-    w.write_str("<div class=\"docblock type-decl hidden-by-usual-hider\">");
-    f(w);
-    w.write_str("</div>")
-}
-
-fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) {
-    debug_assert!(!item.is_stripped());
-    // Write the breadcrumb trail header for the top
-    buf.write_str("<h1 class=\"fqn\"><span class=\"in-band\">");
-    let name = match *item.kind {
-        clean::ModuleItem(ref m) => {
-            if m.is_crate {
-                "Crate "
-            } else {
-                "Module "
-            }
-        }
-        clean::FunctionItem(..) | clean::ForeignFunctionItem(..) => "Function ",
-        clean::TraitItem(..) => "Trait ",
-        clean::StructItem(..) => "Struct ",
-        clean::UnionItem(..) => "Union ",
-        clean::EnumItem(..) => "Enum ",
-        clean::TypedefItem(..) => "Type Definition ",
-        clean::MacroItem(..) => "Macro ",
-        clean::ProcMacroItem(ref mac) => match mac.kind {
-            MacroKind::Bang => "Macro ",
-            MacroKind::Attr => "Attribute Macro ",
-            MacroKind::Derive => "Derive Macro ",
-        },
-        clean::PrimitiveItem(..) => "Primitive Type ",
-        clean::StaticItem(..) | clean::ForeignStaticItem(..) => "Static ",
-        clean::ConstantItem(..) => "Constant ",
-        clean::ForeignTypeItem => "Foreign Type ",
-        clean::KeywordItem(..) => "Keyword ",
-        clean::OpaqueTyItem(..) => "Opaque Type ",
-        clean::TraitAliasItem(..) => "Trait Alias ",
-        _ => {
-            // We don't generate pages for any other type.
-            unreachable!();
-        }
-    };
-    buf.write_str(name);
-    if !item.is_primitive() && !item.is_keyword() {
-        let cur = &cx.current;
-        let amt = if item.is_mod() { cur.len() - 1 } else { cur.len() };
-        for (i, component) in cur.iter().enumerate().take(amt) {
-            write!(
-                buf,
-                "<a href=\"{}index.html\">{}</a>::<wbr>",
-                "../".repeat(cur.len() - i - 1),
-                component
-            );
-        }
-    }
-    write!(buf, "<a class=\"{}\" href=\"\">{}</a>", item.type_(), item.name.as_ref().unwrap());
-
-    buf.write_str("</span>"); // in-band
-    buf.write_str("<span class=\"out-of-band\">");
-    render_stability_since_raw(
-        buf,
-        item.stable_since(cx.tcx()).as_deref(),
-        item.const_stable_since(cx.tcx()).as_deref(),
-        None,
-        None,
-    );
-    buf.write_str(
-        "<span id=\"render-detail\">\
-                <a id=\"toggle-all-docs\" href=\"javascript:void(0)\" \
-                    title=\"collapse all docs\">\
-                    [<span class=\"inner\">&#x2212;</span>]\
-                </a>\
-            </span>",
-    );
-
-    // Write `src` tag
-    //
-    // When this item is part of a `crate use` in a downstream crate, the
-    // [src] link in the downstream documentation will actually come back to
-    // this page, and this link will be auto-clicked. The `id` attribute is
-    // used to find the link to auto-click.
-    if cx.shared.include_sources && !item.is_primitive() {
-        write_srclink(cx, item, buf);
-    }
-
-    buf.write_str("</span></h1>"); // out-of-band
-
-    match *item.kind {
-        clean::ModuleItem(ref m) => item_module(buf, cx, item, &m.items),
-        clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f) => {
-            item_function(buf, cx, item, f)
-        }
-        clean::TraitItem(ref t) => item_trait(buf, cx, item, t),
-        clean::StructItem(ref s) => item_struct(buf, cx, item, s),
-        clean::UnionItem(ref s) => item_union(buf, cx, item, s),
-        clean::EnumItem(ref e) => item_enum(buf, cx, item, e),
-        clean::TypedefItem(ref t, _) => item_typedef(buf, cx, item, t),
-        clean::MacroItem(ref m) => item_macro(buf, cx, item, m),
-        clean::ProcMacroItem(ref m) => item_proc_macro(buf, cx, item, m),
-        clean::PrimitiveItem(_) => item_primitive(buf, cx, item),
-        clean::StaticItem(ref i) | clean::ForeignStaticItem(ref i) => item_static(buf, cx, item, i),
-        clean::ConstantItem(ref c) => item_constant(buf, cx, item, c),
-        clean::ForeignTypeItem => item_foreign_type(buf, cx, item),
-        clean::KeywordItem(_) => item_keyword(buf, cx, item),
-        clean::OpaqueTyItem(ref e) => item_opaque_ty(buf, cx, item, e),
-        clean::TraitAliasItem(ref ta) => item_trait_alias(buf, cx, item, ta),
-        _ => {
-            // We don't generate pages for any other type.
-            unreachable!();
-        }
-    }
-}
-
-fn item_path(ty: ItemType, name: &str) -> String {
-    match ty {
-        ItemType::Module => format!("{}index.html", ensure_trailing_slash(name)),
-        _ => format!("{}.{}.html", ty, name),
-    }
-}
-
-fn full_path(cx: &Context<'_>, item: &clean::Item) -> String {
-    let mut s = cx.current.join("::");
-    s.push_str("::");
-    s.push_str(&item.name.unwrap().as_str());
-    s
-}
-
-fn document(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, parent: Option<&clean::Item>) {
-    if let Some(ref name) = item.name {
-        info!("Documenting {}", name);
-    }
-    document_item_info(w, cx, item, false, parent);
-    document_full(w, item, cx, "", false);
-}
-
-/// Render md_text as markdown.
-fn render_markdown(
-    w: &mut Buffer,
-    cx: &Context<'_>,
-    md_text: &str,
-    links: Vec<RenderedLink>,
-    prefix: &str,
-    is_hidden: bool,
-) {
-    let mut ids = cx.id_map.borrow_mut();
-    write!(
-        w,
-        "<div class=\"docblock{}\">{}{}</div>",
-        if is_hidden { " hidden" } else { "" },
-        prefix,
-        Markdown(
-            md_text,
-            &links,
-            &mut ids,
-            cx.shared.codes,
-            cx.shared.edition,
-            &cx.shared.playground
-        )
-        .into_string()
-    )
-}
-
-/// Writes a documentation block containing only the first paragraph of the documentation. If the
-/// docs are longer, a "Read more" link is appended to the end.
-fn document_short(
-    w: &mut Buffer,
-    item: &clean::Item,
-    cx: &Context<'_>,
-    link: AssocItemLink<'_>,
-    prefix: &str,
-    is_hidden: bool,
-    parent: Option<&clean::Item>,
-    show_def_docs: bool,
-) {
-    document_item_info(w, cx, item, is_hidden, parent);
-    if !show_def_docs {
-        return;
-    }
-    if let Some(s) = item.doc_value() {
-        let mut summary_html = MarkdownSummaryLine(&s, &item.links(&cx.cache)).into_string();
-
-        if s.contains('\n') {
-            let link =
-                format!(r#" <a href="{}">Read more</a>"#, naive_assoc_href(item, link, cx.cache()));
-
-            if let Some(idx) = summary_html.rfind("</p>") {
-                summary_html.insert_str(idx, &link);
-            } else {
-                summary_html.push_str(&link);
+            if let Some(idx) = summary_html.rfind("</p>") {
+                summary_html.insert_str(idx, &link);
+            } else {
+                summary_html.push_str(&link);
             }
         }
 
@@ -1992,324 +747,6 @@ fn document_item_info(
     }
 }
 
-fn document_non_exhaustive_header(item: &clean::Item) -> &str {
-    if item.is_non_exhaustive() { " (Non-exhaustive)" } else { "" }
-}
-
-fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) {
-    if item.is_non_exhaustive() {
-        write!(w, "<div class=\"docblock non-exhaustive non-exhaustive-{}\">", {
-            if item.is_struct() {
-                "struct"
-            } else if item.is_enum() {
-                "enum"
-            } else if item.is_variant() {
-                "variant"
-            } else {
-                "type"
-            }
-        });
-
-        if item.is_struct() {
-            w.write_str(
-                "Non-exhaustive structs could have additional fields added in future. \
-                 Therefore, non-exhaustive structs cannot be constructed in external crates \
-                 using the traditional <code>Struct {{ .. }}</code> syntax; cannot be \
-                 matched against without a wildcard <code>..</code>; and \
-                 struct update syntax will not work.",
-            );
-        } else if item.is_enum() {
-            w.write_str(
-                "Non-exhaustive enums could have additional variants added in future. \
-                 Therefore, when matching against variants of non-exhaustive enums, an \
-                 extra wildcard arm must be added to account for any future variants.",
-            );
-        } else if item.is_variant() {
-            w.write_str(
-                "Non-exhaustive enum variants could have additional fields added in future. \
-                 Therefore, non-exhaustive enum variants cannot be constructed in external \
-                 crates and cannot be matched against.",
-            );
-        } else {
-            w.write_str(
-                "This type will require a wildcard arm in any match statements or constructors.",
-            );
-        }
-
-        w.write_str("</div>");
-    }
-}
-
-/// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order).
-crate fn compare_names(mut lhs: &str, mut rhs: &str) -> Ordering {
-    /// Takes a non-numeric and a numeric part from the given &str.
-    fn take_parts<'a>(s: &mut &'a str) -> (&'a str, &'a str) {
-        let i = s.find(|c: char| c.is_ascii_digit());
-        let (a, b) = s.split_at(i.unwrap_or(s.len()));
-        let i = b.find(|c: char| !c.is_ascii_digit());
-        let (b, c) = b.split_at(i.unwrap_or(b.len()));
-        *s = c;
-        (a, b)
-    }
-
-    while !lhs.is_empty() || !rhs.is_empty() {
-        let (la, lb) = take_parts(&mut lhs);
-        let (ra, rb) = take_parts(&mut rhs);
-        // First process the non-numeric part.
-        match la.cmp(ra) {
-            Ordering::Equal => (),
-            x => return x,
-        }
-        // Then process the numeric part, if both sides have one (and they fit in a u64).
-        if let (Ok(ln), Ok(rn)) = (lb.parse::<u64>(), rb.parse::<u64>()) {
-            match ln.cmp(&rn) {
-                Ordering::Equal => (),
-                x => return x,
-            }
-        }
-        // Then process the numeric part again, but this time as strings.
-        match lb.cmp(rb) {
-            Ordering::Equal => (),
-            x => return x,
-        }
-    }
-
-    Ordering::Equal
-}
-
-fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) {
-    document(w, cx, item, None);
-
-    let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::<Vec<usize>>();
-
-    // the order of item types in the listing
-    fn reorder(ty: ItemType) -> u8 {
-        match ty {
-            ItemType::ExternCrate => 0,
-            ItemType::Import => 1,
-            ItemType::Primitive => 2,
-            ItemType::Module => 3,
-            ItemType::Macro => 4,
-            ItemType::Struct => 5,
-            ItemType::Enum => 6,
-            ItemType::Constant => 7,
-            ItemType::Static => 8,
-            ItemType::Trait => 9,
-            ItemType::Function => 10,
-            ItemType::Typedef => 12,
-            ItemType::Union => 13,
-            _ => 14 + ty as u8,
-        }
-    }
-
-    fn cmp(
-        i1: &clean::Item,
-        i2: &clean::Item,
-        idx1: usize,
-        idx2: usize,
-        tcx: TyCtxt<'_>,
-    ) -> Ordering {
-        let ty1 = i1.type_();
-        let ty2 = i2.type_();
-        if ty1 != ty2 {
-            return (reorder(ty1), idx1).cmp(&(reorder(ty2), idx2));
-        }
-        let s1 = i1.stability(tcx).as_ref().map(|s| s.level);
-        let s2 = i2.stability(tcx).as_ref().map(|s| s.level);
-        if let (Some(a), Some(b)) = (s1, s2) {
-            match (a.is_stable(), b.is_stable()) {
-                (true, true) | (false, false) => {}
-                (false, true) => return Ordering::Less,
-                (true, false) => return Ordering::Greater,
-            }
-        }
-        let lhs = i1.name.unwrap_or(kw::Empty).as_str();
-        let rhs = i2.name.unwrap_or(kw::Empty).as_str();
-        compare_names(&lhs, &rhs)
-    }
-
-    if cx.shared.sort_modules_alphabetically {
-        indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2, cx.tcx()));
-    }
-    // This call is to remove re-export duplicates in cases such as:
-    //
-    // ```
-    // crate mod foo {
-    //     crate mod bar {
-    //         crate trait Double { fn foo(); }
-    //     }
-    // }
-    //
-    // crate use foo::bar::*;
-    // crate use foo::*;
-    // ```
-    //
-    // `Double` will appear twice in the generated docs.
-    //
-    // FIXME: This code is quite ugly and could be improved. Small issue: DefId
-    // can be identical even if the elements are different (mostly in imports).
-    // So in case this is an import, we keep everything by adding a "unique id"
-    // (which is the position in the vector).
-    indices.dedup_by_key(|i| {
-        (
-            items[*i].def_id,
-            if items[*i].name.as_ref().is_some() { Some(full_path(cx, &items[*i])) } else { None },
-            items[*i].type_(),
-            if items[*i].is_import() { *i } else { 0 },
-        )
-    });
-
-    debug!("{:?}", indices);
-    let mut curty = None;
-    for &idx in &indices {
-        let myitem = &items[idx];
-        if myitem.is_stripped() {
-            continue;
-        }
-
-        let myty = Some(myitem.type_());
-        if curty == Some(ItemType::ExternCrate) && myty == Some(ItemType::Import) {
-            // Put `extern crate` and `use` re-exports in the same section.
-            curty = myty;
-        } else if myty != curty {
-            if curty.is_some() {
-                w.write_str("</table>");
-            }
-            curty = myty;
-            let (short, name) = item_ty_to_strs(&myty.unwrap());
-            write!(
-                w,
-                "<h2 id=\"{id}\" class=\"section-header\">\
-                       <a href=\"#{id}\">{name}</a></h2>\n<table>",
-                id = cx.derive_id(short.to_owned()),
-                name = name
-            );
-        }
-
-        match *myitem.kind {
-            clean::ExternCrateItem(ref name, ref src) => {
-                use crate::html::format::anchor;
-
-                match *src {
-                    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()),
-                        name
-                    ),
-                    None => write!(
-                        w,
-                        "<tr><td><code>{}extern crate {};",
-                        myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()),
-                        anchor(myitem.def_id, &*name.as_str(), cx.cache())
-                    ),
-                }
-                w.write_str("</code></td></tr>");
-            }
-
-            clean::ImportItem(ref import) => {
-                write!(
-                    w,
-                    "<tr><td><code>{}{}</code></td></tr>",
-                    myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()),
-                    import.print(cx.cache())
-                );
-            }
-
-            _ => {
-                if myitem.name.is_none() {
-                    continue;
-                }
-
-                let unsafety_flag = match *myitem.kind {
-                    clean::FunctionItem(ref func) | clean::ForeignFunctionItem(ref func)
-                        if func.header.unsafety == hir::Unsafety::Unsafe =>
-                    {
-                        "<a title=\"unsafe function\" href=\"#\"><sup>âš </sup></a>"
-                    }
-                    _ => "",
-                };
-
-                let stab = myitem.stability_class(cx.tcx());
-                let add = if stab.is_some() { " " } else { "" };
-
-                let doc_value = myitem.doc_value().unwrap_or_default();
-                write!(
-                    w,
-                    "<tr class=\"{stab}{add}module-item\">\
-                         <td><a class=\"{class}\" href=\"{href}\" \
-                             title=\"{title}\">{name}</a>{unsafety_flag}</td>\
-                         <td class=\"docblock-short\">{stab_tags}{docs}</td>\
-                     </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(),
-                    class = myitem.type_(),
-                    add = add,
-                    stab = stab.unwrap_or_else(String::new),
-                    unsafety_flag = unsafety_flag,
-                    href = item_path(myitem.type_(), &myitem.name.unwrap().as_str()),
-                    title = [full_path(cx, myitem), myitem.type_().to_string()]
-                        .iter()
-                        .filter_map(|s| if !s.is_empty() { Some(s.as_str()) } else { None })
-                        .collect::<Vec<_>>()
-                        .join(" "),
-                );
-            }
-        }
-    }
-
-    if curty.is_some() {
-        w.write_str("</table>");
-    }
-}
-
-/// Render the stability, deprecation and portability tags that are displayed in the item's summary
-/// at the module level.
-fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) -> String {
-    let mut tags = String::new();
-
-    fn tag_html(class: &str, title: &str, contents: &str) -> String {
-        format!(r#"<span class="stab {}" title="{}">{}</span>"#, class, Escape(title), contents)
-    }
-
-    // The trailing space after each tag is to space it properly against the rest of the docs.
-    if let Some(depr) = &item.deprecation(tcx) {
-        let mut message = "Deprecated";
-        if !stability::deprecation_in_effect(
-            depr.is_since_rustc_version,
-            depr.since.map(|s| s.as_str()).as_deref(),
-        ) {
-            message = "Deprecation planned";
-        }
-        tags += &tag_html("deprecated", "", message);
-    }
-
-    // The "rustc_private" crates are permanently unstable so it makes no sense
-    // to render "unstable" everywhere.
-    if item
-        .stability(tcx)
-        .as_ref()
-        .map(|s| s.level.is_unstable() && s.feature != sym::rustc_private)
-        == Some(true)
-    {
-        tags += &tag_html("unstable", "", "Experimental");
-    }
-
-    let cfg = match (&item.attrs.cfg, parent.attrs.cfg.as_ref()) {
-        (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg),
-        (cfg, _) => cfg.as_deref().cloned(),
-    };
-
-    debug!("Portability {:?} - {:?} = {:?}", item.attrs.cfg, parent.attrs.cfg, cfg);
-    if let Some(ref cfg) = cfg {
-        tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html());
-    }
-
-    tags
-}
-
 fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<String> {
     let cfg = match (&item.attrs.cfg, parent.and_then(|p| p.attrs.cfg.as_ref())) {
         (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg),
@@ -2394,504 +831,66 @@ fn short_item_info(
             ));
         }
 
-        message.push_str(&format!(" ({})", feature));
-
-        if let Some(unstable_reason) = reason {
-            let mut ids = cx.id_map.borrow_mut();
-            message = format!(
-                "<details><summary>{}</summary>{}</details>",
-                message,
-                MarkdownHtml(
-                    &unstable_reason.as_str(),
-                    &mut ids,
-                    error_codes,
-                    cx.shared.edition,
-                    &cx.shared.playground,
-                )
-                .into_string()
-            );
-        }
-
-        extra_info.push(format!("<div class=\"stab unstable\">{}</div>", message));
-    }
-
-    if let Some(portability) = portability(item, parent) {
-        extra_info.push(portability);
-    }
-
-    extra_info
-}
-
-fn item_constant(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, c: &clean::Constant) {
-    w.write_str("<pre class=\"rust const\">");
-    render_attributes(w, it, false);
-
-    write!(
-        w,
-        "{vis}const {name}: {typ}",
-        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
-        name = it.name.as_ref().unwrap(),
-        typ = c.type_.print(cx.cache()),
-    );
-
-    if c.value.is_some() || c.is_literal {
-        write!(w, " = {expr};", expr = Escape(&c.expr));
-    } else {
-        w.write_str(";");
-    }
-
-    if let Some(value) = &c.value {
-        if !c.is_literal {
-            let value_lowercase = value.to_lowercase();
-            let expr_lowercase = c.expr.to_lowercase();
-
-            if value_lowercase != expr_lowercase
-                && value_lowercase.trim_end_matches("i32") != expr_lowercase
-            {
-                write!(w, " // {value}", value = Escape(value));
-            }
-        }
-    }
-
-    w.write_str("</pre>");
-    document(w, cx, it, None)
-}
-
-fn item_static(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Static) {
-    w.write_str("<pre class=\"rust static\">");
-    render_attributes(w, it, false);
-    write!(
-        w,
-        "{vis}static {mutability}{name}: {typ}</pre>",
-        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
-        mutability = s.mutability.print_with_space(),
-        name = it.name.as_ref().unwrap(),
-        typ = s.type_.print(cx.cache())
-    );
-    document(w, cx, it, None)
-}
-
-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()),
-        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())
-    )
-    .len();
-    w.write_str("<pre class=\"rust fn\">");
-    render_attributes(w, it, false);
-    write!(
-        w,
-        "{vis}{constness}{asyncness}{unsafety}{abi}fn \
-         {name}{generics}{decl}{spotlight}{where_clause}</pre>",
-        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
-        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()),
-        where_clause =
-            WhereClause { gens: &f.generics, indent: 0, end_newline: true }.print(cx.cache()),
-        decl = Function { decl: &f.decl, header_len, indent: 0, asyncness: f.header.asyncness }
-            .print(cx.cache()),
-        spotlight = spotlight_decl(&f.decl, cx.cache()),
-    );
-    document(w, cx, it, None)
-}
-
-fn render_implementor(
-    cx: &Context<'_>,
-    implementor: &Impl,
-    trait_: &clean::Item,
-    w: &mut Buffer,
-    implementor_dups: &FxHashMap<Symbol, (DefId, bool)>,
-    aliases: &[String],
-) {
-    // If there's already another implementor that has the same abbridged name, use the
-    // full path, for example in `std::iter::ExactSizeIterator`
-    let use_absolute = match implementor.inner_impl().for_ {
-        clean::ResolvedPath { ref path, is_generic: false, .. }
-        | clean::BorrowedRef {
-            type_: box clean::ResolvedPath { ref path, is_generic: false, .. },
-            ..
-        } => implementor_dups[&path.last()].1,
-        _ => false,
-    };
-    render_impl(
-        w,
-        cx,
-        implementor,
-        trait_,
-        AssocItemLink::Anchor(None),
-        RenderMode::Normal,
-        trait_.stable_since(cx.tcx()).as_deref(),
-        trait_.const_stable_since(cx.tcx()).as_deref(),
-        false,
-        Some(use_absolute),
-        false,
-        false,
-        aliases,
-    );
-}
-
-fn render_impls(
-    cx: &Context<'_>,
-    w: &mut Buffer,
-    traits: &[&&Impl],
-    containing_item: &clean::Item,
-) {
-    let mut impls = traits
-        .iter()
-        .map(|i| {
-            let did = i.trait_did_full(cx.cache()).unwrap();
-            let assoc_link = AssocItemLink::GotoSource(did, &i.inner_impl().provided_trait_methods);
-            let mut buffer = if w.is_for_html() { Buffer::html() } else { Buffer::new() };
-            render_impl(
-                &mut buffer,
-                cx,
-                i,
-                containing_item,
-                assoc_link,
-                RenderMode::Normal,
-                containing_item.stable_since(cx.tcx()).as_deref(),
-                containing_item.const_stable_since(cx.tcx()).as_deref(),
-                true,
-                None,
-                false,
-                true,
-                &[],
-            );
-            buffer.into_inner()
-        })
-        .collect::<Vec<_>>();
-    impls.sort();
-    w.write_str(&impls.join(""));
-}
-
-fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cache: &Cache) -> String {
-    let mut bounds = String::new();
-    if !t_bounds.is_empty() {
-        if !trait_alias {
-            bounds.push_str(": ");
-        }
-        for (i, p) in t_bounds.iter().enumerate() {
-            if i > 0 {
-                bounds.push_str(" + ");
-            }
-            bounds.push_str(&p.print(cache).to_string());
-        }
-    }
-    bounds
-}
-
-fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl, cache: &Cache) -> Ordering {
-    let lhs = format!("{}", lhs.inner_impl().print(cache, false));
-    let rhs = format!("{}", rhs.inner_impl().print(cache, false));
-
-    // lhs and rhs are formatted as HTML, which may be unnecessary
-    compare_names(&lhs, &rhs)
-}
-
-fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) {
-    let bounds = bounds(&t.bounds, false, cx.cache());
-    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<_>>();
-    let provided = t.items.iter().filter(|m| m.is_method()).collect::<Vec<_>>();
-
-    // Output the trait definition
-    wrap_into_docblock(w, |w| {
-        w.write_str("<pre class=\"rust trait\">");
-        render_attributes(w, it, true);
-        write!(
-            w,
-            "{}{}{}trait {}{}{}",
-            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
-            t.unsafety.print_with_space(),
-            if t.is_auto { "auto " } else { "" },
-            it.name.as_ref().unwrap(),
-            t.generics.print(cx.cache()),
-            bounds
-        );
-
-        if !t.generics.where_predicates.is_empty() {
-            let where_ = WhereClause { gens: &t.generics, indent: 0, end_newline: true };
-            write!(w, "{}", where_.print(cx.cache()));
-        } else {
-            w.write_str(" ");
-        }
-
-        if t.items.is_empty() {
-            w.write_str("{ }");
-        } else {
-            // FIXME: we should be using a derived_id for the Anchors here
-            w.write_str("{\n");
-            for t in &types {
-                render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
-                w.write_str(";\n");
-            }
-            if !types.is_empty() && !consts.is_empty() {
-                w.write_str("\n");
-            }
-            for t in &consts {
-                render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
-                w.write_str(";\n");
-            }
-            if !consts.is_empty() && !required.is_empty() {
-                w.write_str("\n");
-            }
-            for (pos, m) in required.iter().enumerate() {
-                render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait, cx);
-                w.write_str(";\n");
-
-                if pos < required.len() - 1 {
-                    w.write_str("<div class=\"item-spacer\"></div>");
-                }
-            }
-            if !required.is_empty() && !provided.is_empty() {
-                w.write_str("\n");
-            }
-            for (pos, m) in provided.iter().enumerate() {
-                render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait, cx);
-                match *m.kind {
-                    clean::MethodItem(ref inner, _)
-                        if !inner.generics.where_predicates.is_empty() =>
-                    {
-                        w.write_str(",\n    { ... }\n");
-                    }
-                    _ => {
-                        w.write_str(" { ... }\n");
-                    }
-                }
-                if pos < provided.len() - 1 {
-                    w.write_str("<div class=\"item-spacer\"></div>");
-                }
-            }
-            w.write_str("}");
-        }
-        w.write_str("</pre>")
-    });
-
-    // Trait documentation
-    document(w, cx, it, None);
-
-    fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) {
-        write!(
-            w,
-            "<h2 id=\"{0}\" class=\"small-section-header\">\
-                {1}<a href=\"#{0}\" class=\"anchor\"></a>\
-             </h2>{2}",
-            id, title, extra_content
-        )
-    }
-
-    fn write_loading_content(w: &mut Buffer, extra_content: &str) {
-        write!(w, "{}<span class=\"loading-content\">Loading content...</span>", extra_content)
-    }
-
-    fn trait_item(w: &mut Buffer, cx: &Context<'_>, m: &clean::Item, t: &clean::Item) {
-        let name = m.name.as_ref().unwrap();
-        info!("Documenting {} on {:?}", name, t.name);
-        let item_type = m.type_();
-        let id = cx.derive_id(format!("{}.{}", item_type, name));
-        write!(w, "<h3 id=\"{id}\" class=\"method\"><code>", id = id,);
-        render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl, cx);
-        w.write_str("</code>");
-        render_stability_since(w, m, t, cx.tcx());
-        write_srclink(cx, m, w);
-        w.write_str("</h3>");
-        document(w, cx, m, Some(t));
-    }
-
-    if !types.is_empty() {
-        write_small_section_header(
-            w,
-            "associated-types",
-            "Associated Types",
-            "<div class=\"methods\">",
-        );
-        for t in types {
-            trait_item(w, cx, t, it);
-        }
-        write_loading_content(w, "</div>");
-    }
-
-    if !consts.is_empty() {
-        write_small_section_header(
-            w,
-            "associated-const",
-            "Associated Constants",
-            "<div class=\"methods\">",
-        );
-        for t in consts {
-            trait_item(w, cx, t, it);
-        }
-        write_loading_content(w, "</div>");
-    }
-
-    // Output the documentation for each function individually
-    if !required.is_empty() {
-        write_small_section_header(
-            w,
-            "required-methods",
-            "Required methods",
-            "<div class=\"methods\">",
-        );
-        for m in required {
-            trait_item(w, cx, m, it);
-        }
-        write_loading_content(w, "</div>");
-    }
-    if !provided.is_empty() {
-        write_small_section_header(
-            w,
-            "provided-methods",
-            "Provided methods",
-            "<div class=\"methods\">",
-        );
-        for m in provided {
-            trait_item(w, cx, m, it);
-        }
-        write_loading_content(w, "</div>");
-    }
-
-    // If there are methods directly on this trait object, render them here.
-    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All);
-
-    if let Some(implementors) = cx.cache.implementors.get(&it.def_id) {
-        // The DefId is for the first Type found with that name. The bool is
-        // if any Types with the same name but different DefId have been found.
-        let mut implementor_dups: FxHashMap<Symbol, (DefId, bool)> = FxHashMap::default();
-        for implementor in implementors {
-            match implementor.inner_impl().for_ {
-                clean::ResolvedPath { ref path, did, is_generic: false, .. }
-                | clean::BorrowedRef {
-                    type_: box clean::ResolvedPath { ref path, did, is_generic: false, .. },
-                    ..
-                } => {
-                    let &mut (prev_did, ref mut has_duplicates) =
-                        implementor_dups.entry(path.last()).or_insert((did, false));
-                    if prev_did != did {
-                        *has_duplicates = true;
-                    }
-                }
-                _ => {}
-            }
-        }
-
-        let (local, foreign) = implementors.iter().partition::<Vec<_>, _>(|i| {
-            i.inner_impl()
-                .for_
-                .def_id_full(cx.cache())
-                .map_or(true, |d| cx.cache.paths.contains_key(&d))
-        });
-
-        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()));
-        concrete.sort_by(|a, b| compare_impl(a, b, cx.cache()));
-
-        if !foreign.is_empty() {
-            write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", "");
-
-            for implementor in foreign {
-                let assoc_link = AssocItemLink::GotoSource(
-                    implementor.impl_item.def_id,
-                    &implementor.inner_impl().provided_trait_methods,
-                );
-                render_impl(
-                    w,
-                    cx,
-                    &implementor,
-                    it,
-                    assoc_link,
-                    RenderMode::Normal,
-                    implementor.impl_item.stable_since(cx.tcx()).as_deref(),
-                    implementor.impl_item.const_stable_since(cx.tcx()).as_deref(),
-                    false,
-                    None,
-                    true,
-                    false,
-                    &[],
-                );
-            }
-            write_loading_content(w, "");
-        }
-
-        write_small_section_header(
-            w,
-            "implementors",
-            "Implementors",
-            "<div class=\"item-list\" id=\"implementors-list\">",
-        );
-        for implementor in concrete {
-            render_implementor(cx, implementor, it, w, &implementor_dups, &[]);
-        }
-        write_loading_content(w, "</div>");
-
-        if t.is_auto {
-            write_small_section_header(
-                w,
-                "synthetic-implementors",
-                "Auto implementors",
-                "<div class=\"item-list\" id=\"synthetic-implementors-list\">",
-            );
-            for implementor in synthetic {
-                render_implementor(
-                    cx,
-                    implementor,
-                    it,
-                    w,
-                    &implementor_dups,
-                    &collect_paths_for_type(implementor.inner_impl().for_.clone(), &cx.cache),
-                );
-            }
-            write_loading_content(w, "</div>");
-        }
-    } else {
-        // even without any implementations to write in, we still want the heading and list, so the
-        // implementors javascript file pulled in below has somewhere to write the impls into
-        write_small_section_header(
-            w,
-            "implementors",
-            "Implementors",
-            "<div class=\"item-list\" id=\"implementors-list\">",
-        );
-        write_loading_content(w, "</div>");
+        message.push_str(&format!(" ({})", feature));
 
-        if t.is_auto {
-            write_small_section_header(
-                w,
-                "synthetic-implementors",
-                "Auto implementors",
-                "<div class=\"item-list\" id=\"synthetic-implementors-list\">",
+        if let Some(unstable_reason) = reason {
+            let mut ids = cx.id_map.borrow_mut();
+            message = format!(
+                "<details><summary>{}</summary>{}</details>",
+                message,
+                MarkdownHtml(
+                    &unstable_reason.as_str(),
+                    &mut ids,
+                    error_codes,
+                    cx.shared.edition,
+                    &cx.shared.playground,
+                )
+                .into_string()
             );
-            write_loading_content(w, "</div>");
         }
+
+        extra_info.push(format!("<div class=\"stab unstable\">{}</div>", message));
     }
 
-    write!(
-        w,
-        "<script type=\"text/javascript\" \
-                 src=\"{root_path}/implementors/{path}/{ty}.{name}.js\" async>\
-         </script>",
-        root_path = vec![".."; cx.current.len()].join("/"),
-        path = if it.def_id.is_local() {
-            cx.current.join("/")
-        } else {
-            let (ref path, _) = cx.cache.external_paths[&it.def_id];
-            path[..path.len() - 1].join("/")
-        },
-        ty = it.type_(),
-        name = *it.name.as_ref().unwrap()
-    );
+    if let Some(portability) = portability(item, parent) {
+        extra_info.push(portability);
+    }
+
+    extra_info
+}
+
+fn render_impls(
+    cx: &Context<'_>,
+    w: &mut Buffer,
+    traits: &[&&Impl],
+    containing_item: &clean::Item,
+) {
+    let mut impls = traits
+        .iter()
+        .map(|i| {
+            let did = i.trait_did_full(cx.cache()).unwrap();
+            let assoc_link = AssocItemLink::GotoSource(did, &i.inner_impl().provided_trait_methods);
+            let mut buffer = if w.is_for_html() { Buffer::html() } else { Buffer::new() };
+            render_impl(
+                &mut buffer,
+                cx,
+                i,
+                containing_item,
+                assoc_link,
+                RenderMode::Normal,
+                containing_item.stable_since(cx.tcx()).as_deref(),
+                containing_item.const_stable_since(cx.tcx()).as_deref(),
+                true,
+                None,
+                false,
+                true,
+                &[],
+            );
+            buffer.into_inner()
+        })
+        .collect::<Vec<_>>();
+    impls.sort();
+    w.write_str(&impls.join(""));
 }
 
 fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>, cache: &Cache) -> String {
@@ -2986,21 +985,6 @@ fn render_stability_since_raw(
     }
 }
 
-fn render_stability_since(
-    w: &mut Buffer,
-    item: &clean::Item,
-    containing_item: &clean::Item,
-    tcx: TyCtxt<'_>,
-) {
-    render_stability_since_raw(
-        w,
-        item.stable_since(tcx).as_deref(),
-        item.const_stable_since(tcx).as_deref(),
-        containing_item.stable_since(tcx).as_deref(),
-        containing_item.const_stable_since(tcx).as_deref(),
-    )
-}
-
 fn render_assoc_item(
     w: &mut Buffer,
     item: &clean::Item,
@@ -3104,227 +1088,6 @@ fn render_assoc_item(
     }
 }
 
-fn item_struct(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) {
-    wrap_into_docblock(w, |w| {
-        w.write_str("<pre class=\"rust struct\">");
-        render_attributes(w, it, true);
-        render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true, cx);
-        w.write_str("</pre>")
-    });
-
-    document(w, cx, it, None);
-    let mut fields = s
-        .fields
-        .iter()
-        .filter_map(|f| match *f.kind {
-            clean::StructFieldItem(ref ty) => Some((f, ty)),
-            _ => None,
-        })
-        .peekable();
-    if let CtorKind::Fictive = s.struct_type {
-        if fields.peek().is_some() {
-            write!(
-                w,
-                "<h2 id=\"fields\" class=\"fields small-section-header\">
-                       Fields{}<a href=\"#fields\" class=\"anchor\"></a></h2>",
-                document_non_exhaustive_header(it)
-            );
-            document_non_exhaustive(w, it);
-            for (field, ty) in fields {
-                let id = cx.derive_id(format!(
-                    "{}.{}",
-                    ItemType::StructField,
-                    field.name.as_ref().unwrap()
-                ));
-                write!(
-                    w,
-                    "<span id=\"{id}\" class=\"{item_type} small-section-header\">\
-                         <a href=\"#{id}\" class=\"anchor field\"></a>\
-                         <code>{name}: {ty}</code>\
-                     </span>",
-                    item_type = ItemType::StructField,
-                    id = id,
-                    name = field.name.as_ref().unwrap(),
-                    ty = ty.print(cx.cache())
-                );
-                document(w, cx, field, Some(it));
-            }
-        }
-    }
-    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
-}
-
-fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Union) {
-    wrap_into_docblock(w, |w| {
-        w.write_str("<pre class=\"rust union\">");
-        render_attributes(w, it, true);
-        render_union(w, it, Some(&s.generics), &s.fields, "", true, cx);
-        w.write_str("</pre>")
-    });
-
-    document(w, cx, it, None);
-    let mut fields = s
-        .fields
-        .iter()
-        .filter_map(|f| match *f.kind {
-            clean::StructFieldItem(ref ty) => Some((f, ty)),
-            _ => None,
-        })
-        .peekable();
-    if fields.peek().is_some() {
-        write!(
-            w,
-            "<h2 id=\"fields\" class=\"fields small-section-header\">
-                   Fields<a href=\"#fields\" class=\"anchor\"></a></h2>"
-        );
-        for (field, ty) in fields {
-            let name = field.name.as_ref().expect("union field name");
-            let id = format!("{}.{}", ItemType::StructField, name);
-            write!(
-                w,
-                "<span id=\"{id}\" class=\"{shortty} small-section-header\">\
-                     <a href=\"#{id}\" class=\"anchor field\"></a>\
-                     <code>{name}: {ty}</code>\
-                 </span>",
-                id = id,
-                name = name,
-                shortty = ItemType::StructField,
-                ty = ty.print(cx.cache())
-            );
-            if let Some(stability_class) = field.stability_class(cx.tcx()) {
-                write!(w, "<span class=\"stab {stab}\"></span>", stab = stability_class);
-            }
-            document(w, cx, field, Some(it));
-        }
-    }
-    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
-}
-
-fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) {
-    wrap_into_docblock(w, |w| {
-        w.write_str("<pre class=\"rust enum\">");
-        render_attributes(w, it, true);
-        write!(
-            w,
-            "{}enum {}{}{}",
-            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
-            it.name.as_ref().unwrap(),
-            e.generics.print(cx.cache()),
-            WhereClause { gens: &e.generics, indent: 0, end_newline: true }.print(cx.cache())
-        );
-        if e.variants.is_empty() && !e.variants_stripped {
-            w.write_str(" {}");
-        } else {
-            w.write_str(" {\n");
-            for v in &e.variants {
-                w.write_str("    ");
-                let name = v.name.as_ref().unwrap();
-                match *v.kind {
-                    clean::VariantItem(ref var) => match var {
-                        clean::Variant::CLike => write!(w, "{}", name),
-                        clean::Variant::Tuple(ref tys) => {
-                            write!(w, "{}(", name);
-                            for (i, ty) in tys.iter().enumerate() {
-                                if i > 0 {
-                                    w.write_str(",&nbsp;")
-                                }
-                                write!(w, "{}", ty.print(cx.cache()));
-                            }
-                            w.write_str(")");
-                        }
-                        clean::Variant::Struct(ref s) => {
-                            render_struct(w, v, None, s.struct_type, &s.fields, "    ", false, cx);
-                        }
-                    },
-                    _ => unreachable!(),
-                }
-                w.write_str(",\n");
-            }
-
-            if e.variants_stripped {
-                w.write_str("    // some variants omitted\n");
-            }
-            w.write_str("}");
-        }
-        w.write_str("</pre>")
-    });
-
-    document(w, cx, it, None);
-    if !e.variants.is_empty() {
-        write!(
-            w,
-            "<h2 id=\"variants\" class=\"variants small-section-header\">
-                   Variants{}<a href=\"#variants\" class=\"anchor\"></a></h2>\n",
-            document_non_exhaustive_header(it)
-        );
-        document_non_exhaustive(w, it);
-        for variant in &e.variants {
-            let id =
-                cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.as_ref().unwrap()));
-            write!(
-                w,
-                "<div id=\"{id}\" class=\"variant small-section-header\">\
-                    <a href=\"#{id}\" class=\"anchor field\"></a>\
-                    <code>{name}",
-                id = id,
-                name = variant.name.as_ref().unwrap()
-            );
-            if let clean::VariantItem(clean::Variant::Tuple(ref tys)) = *variant.kind {
-                w.write_str("(");
-                for (i, ty) in tys.iter().enumerate() {
-                    if i > 0 {
-                        w.write_str(",&nbsp;");
-                    }
-                    write!(w, "{}", ty.print(cx.cache()));
-                }
-                w.write_str(")");
-            }
-            w.write_str("</code></div>");
-            document(w, cx, variant, Some(it));
-            document_non_exhaustive(w, variant);
-
-            use crate::clean::Variant;
-            if let clean::VariantItem(Variant::Struct(ref s)) = *variant.kind {
-                let variant_id = cx.derive_id(format!(
-                    "{}.{}.fields",
-                    ItemType::Variant,
-                    variant.name.as_ref().unwrap()
-                ));
-                write!(w, "<div class=\"autohide sub-variant\" id=\"{id}\">", id = variant_id);
-                write!(
-                    w,
-                    "<h3>Fields of <b>{name}</b></h3><div>",
-                    name = variant.name.as_ref().unwrap()
-                );
-                for field in &s.fields {
-                    use crate::clean::StructFieldItem;
-                    if let StructFieldItem(ref ty) = *field.kind {
-                        let id = cx.derive_id(format!(
-                            "variant.{}.field.{}",
-                            variant.name.as_ref().unwrap(),
-                            field.name.as_ref().unwrap()
-                        ));
-                        write!(
-                            w,
-                            "<span id=\"{id}\" class=\"variant small-section-header\">\
-                                 <a href=\"#{id}\" class=\"anchor field\"></a>\
-                                 <code>{f}:&nbsp;{t}</code>\
-                             </span>",
-                            id = id,
-                            f = field.name.as_ref().unwrap(),
-                            t = ty.print(cx.cache())
-                        );
-                        document(w, cx, field, Some(variant));
-                    }
-                }
-                w.write_str("</div></div>");
-            }
-            render_stability_since(w, variant, it, cx.tcx());
-        }
-    }
-    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
-}
-
 const ALLOWED_ATTRIBUTES: &[Symbol] = &[
     sym::export_name,
     sym::lang,
@@ -3367,147 +1130,6 @@ fn render_attributes(w: &mut Buffer, it: &clean::Item, top: bool) {
     }
 }
 
-fn render_struct(
-    w: &mut Buffer,
-    it: &clean::Item,
-    g: Option<&clean::Generics>,
-    ty: CtorKind,
-    fields: &[clean::Item],
-    tab: &str,
-    structhead: bool,
-    cx: &Context<'_>,
-) {
-    write!(
-        w,
-        "{}{}{}",
-        it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
-        if structhead { "struct " } else { "" },
-        it.name.as_ref().unwrap()
-    );
-    if let Some(g) = g {
-        write!(w, "{}", g.print(cx.cache()))
-    }
-    match ty {
-        CtorKind::Fictive => {
-            if let Some(g) = g {
-                write!(
-                    w,
-                    "{}",
-                    WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache())
-                )
-            }
-            let mut has_visible_fields = false;
-            w.write_str(" {");
-            for field in fields {
-                if let clean::StructFieldItem(ref ty) = *field.kind {
-                    write!(
-                        w,
-                        "\n{}    {}{}: {},",
-                        tab,
-                        field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()),
-                        field.name.as_ref().unwrap(),
-                        ty.print(cx.cache())
-                    );
-                    has_visible_fields = true;
-                }
-            }
-
-            if has_visible_fields {
-                if it.has_stripped_fields().unwrap() {
-                    write!(w, "\n{}    // some fields omitted", tab);
-                }
-                write!(w, "\n{}", tab);
-            } else if it.has_stripped_fields().unwrap() {
-                // If there are no visible fields we can just display
-                // `{ /* fields omitted */ }` to save space.
-                write!(w, " /* fields omitted */ ");
-            }
-            w.write_str("}");
-        }
-        CtorKind::Fn => {
-            w.write_str("(");
-            for (i, field) in fields.iter().enumerate() {
-                if i > 0 {
-                    w.write_str(", ");
-                }
-                match *field.kind {
-                    clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"),
-                    clean::StructFieldItem(ref ty) => {
-                        write!(
-                            w,
-                            "{}{}",
-                            field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()),
-                            ty.print(cx.cache())
-                        )
-                    }
-                    _ => unreachable!(),
-                }
-            }
-            w.write_str(")");
-            if let Some(g) = g {
-                write!(
-                    w,
-                    "{}",
-                    WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache())
-                )
-            }
-            w.write_str(";");
-        }
-        CtorKind::Const => {
-            // Needed for PhantomData.
-            if let Some(g) = g {
-                write!(
-                    w,
-                    "{}",
-                    WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache())
-                )
-            }
-            w.write_str(";");
-        }
-    }
-}
-
-fn render_union(
-    w: &mut Buffer,
-    it: &clean::Item,
-    g: Option<&clean::Generics>,
-    fields: &[clean::Item],
-    tab: &str,
-    structhead: bool,
-    cx: &Context<'_>,
-) {
-    write!(
-        w,
-        "{}{}{}",
-        it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
-        if structhead { "union " } else { "" },
-        it.name.as_ref().unwrap()
-    );
-    if let Some(g) = g {
-        write!(w, "{}", g.print(cx.cache()));
-        write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache()));
-    }
-
-    write!(w, " {{\n{}", tab);
-    for field in fields {
-        if let clean::StructFieldItem(ref ty) = *field.kind {
-            write!(
-                w,
-                "    {}{}: {},\n{}",
-                field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()),
-                field.name.as_ref().unwrap(),
-                ty.print(cx.cache()),
-                tab
-            );
-        }
-    }
-
-    if it.has_stripped_fields().unwrap() {
-        write!(w, "    // some fields omitted\n{}", tab);
-    }
-    w.write_str("}");
-}
-
 #[derive(Copy, Clone)]
 enum AssocItemLink<'a> {
     Anchor(Option<&'a str>),
@@ -4078,86 +1700,6 @@ fn render_impl(
     w.write_str("</div>");
 }
 
-fn item_opaque_ty(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) {
-    w.write_str("<pre class=\"rust opaque\">");
-    render_attributes(w, it, false);
-    write!(
-        w,
-        "type {}{}{where_clause} = impl {bounds};</pre>",
-        it.name.as_ref().unwrap(),
-        t.generics.print(cx.cache()),
-        where_clause =
-            WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()),
-        bounds = bounds(&t.bounds, false, cx.cache())
-    );
-
-    document(w, cx, it, None);
-
-    // Render any items associated directly to this alias, as otherwise they
-    // won't be visible anywhere in the docs. It would be nice to also show
-    // associated items from the aliased type (see discussion in #32077), but
-    // we need #14072 to make sense of the generics.
-    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
-}
-
-fn item_trait_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::TraitAlias) {
-    w.write_str("<pre class=\"rust trait-alias\">");
-    render_attributes(w, it, false);
-    write!(
-        w,
-        "trait {}{}{} = {};</pre>",
-        it.name.as_ref().unwrap(),
-        t.generics.print(cx.cache()),
-        WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()),
-        bounds(&t.bounds, true, cx.cache())
-    );
-
-    document(w, cx, it, None);
-
-    // Render any items associated directly to this alias, as otherwise they
-    // won't be visible anywhere in the docs. It would be nice to also show
-    // associated items from the aliased type (see discussion in #32077), but
-    // we need #14072 to make sense of the generics.
-    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
-}
-
-fn item_typedef(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Typedef) {
-    w.write_str("<pre class=\"rust typedef\">");
-    render_attributes(w, it, false);
-    write!(
-        w,
-        "type {}{}{where_clause} = {type_};</pre>",
-        it.name.as_ref().unwrap(),
-        t.generics.print(cx.cache()),
-        where_clause =
-            WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()),
-        type_ = t.type_.print(cx.cache())
-    );
-
-    document(w, cx, it, None);
-
-    // Render any items associated directly to this alias, as otherwise they
-    // won't be visible anywhere in the docs. It would be nice to also show
-    // associated items from the aliased type (see discussion in #32077), but
-    // we need #14072 to make sense of the generics.
-    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
-}
-
-fn item_foreign_type(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) {
-    w.write_str("<pre class=\"rust foreigntype\">extern {\n");
-    render_attributes(w, it, false);
-    write!(
-        w,
-        "    {}type {};\n}}</pre>",
-        it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
-        it.name.as_ref().unwrap(),
-    );
-
-    document(w, cx, it, None);
-
-    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
-}
-
 fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) {
     let parentlen = cx.current.len() - if it.is_mod() { 1 } else { 0 };
 
@@ -4850,65 +2392,8 @@ fn sidebar_foreign_type(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) {
     }
 }
 
-fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) {
-    wrap_into_docblock(w, |w| {
-        highlight::render_with_highlighting(
-            &t.source,
-            w,
-            Some("macro"),
-            None,
-            None,
-            it.source.span().edition(),
-        );
-    });
-    document(w, cx, it, None)
-}
-
-fn item_proc_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, m: &clean::ProcMacro) {
-    let name = it.name.as_ref().expect("proc-macros always have names");
-    match m.kind {
-        MacroKind::Bang => {
-            w.push_str("<pre class=\"rust macro\">");
-            write!(w, "{}!() {{ /* proc-macro */ }}", name);
-            w.push_str("</pre>");
-        }
-        MacroKind::Attr => {
-            w.push_str("<pre class=\"rust attr\">");
-            write!(w, "#[{}]", name);
-            w.push_str("</pre>");
-        }
-        MacroKind::Derive => {
-            w.push_str("<pre class=\"rust derive\">");
-            write!(w, "#[derive({})]", name);
-            if !m.helpers.is_empty() {
-                w.push_str("\n{\n");
-                w.push_str("    // Attributes available to this derive:\n");
-                for attr in &m.helpers {
-                    writeln!(w, "    #[{}]", attr);
-                }
-                w.push_str("}\n");
-            }
-            w.push_str("</pre>");
-        }
-    }
-    document(w, cx, it, None)
-}
-
-fn item_primitive(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) {
-    document(w, cx, it, None);
-    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
-}
-
-fn item_keyword(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) {
-    document(w, cx, it, None)
-}
-
 crate const BASIC_KEYWORDS: &str = "rust, rustlang, rust-lang";
 
-fn make_item_keywords(it: &clean::Item) -> String {
-    format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap())
-}
-
 /// Returns a list of all paths used in the type.
 /// This is used to help deduplicate imported impls
 /// for reexported types. If any of the contained
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
new file mode 100644
index 0000000000000..6d61248f28ef0
--- /dev/null
+++ b/src/librustdoc/html/render/print_item.rs
@@ -0,0 +1,1420 @@
+use std::cmp::Ordering;
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir as hir;
+use rustc_hir::def::CtorKind;
+use rustc_hir::def_id::DefId;
+use rustc_middle::middle::stability;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::hygiene::MacroKind;
+use rustc_span::symbol::{kw, sym, Symbol};
+
+use super::{
+    collect_paths_for_type, document, ensure_trailing_slash, item_ty_to_strs, render_assoc_item,
+    render_assoc_items, render_attributes, render_impl, render_stability_since_raw, spotlight_decl,
+    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::html::escape::Escape;
+use crate::html::format::{print_abi_with_space, Buffer, Function, PrintWithSpace, WhereClause};
+use crate::html::highlight;
+use crate::html::markdown::MarkdownSummaryLine;
+
+pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) {
+    debug_assert!(!item.is_stripped());
+    // Write the breadcrumb trail header for the top
+    buf.write_str("<h1 class=\"fqn\"><span class=\"in-band\">");
+    let name = match *item.kind {
+        clean::ModuleItem(ref m) => {
+            if m.is_crate {
+                "Crate "
+            } else {
+                "Module "
+            }
+        }
+        clean::FunctionItem(..) | clean::ForeignFunctionItem(..) => "Function ",
+        clean::TraitItem(..) => "Trait ",
+        clean::StructItem(..) => "Struct ",
+        clean::UnionItem(..) => "Union ",
+        clean::EnumItem(..) => "Enum ",
+        clean::TypedefItem(..) => "Type Definition ",
+        clean::MacroItem(..) => "Macro ",
+        clean::ProcMacroItem(ref mac) => match mac.kind {
+            MacroKind::Bang => "Macro ",
+            MacroKind::Attr => "Attribute Macro ",
+            MacroKind::Derive => "Derive Macro ",
+        },
+        clean::PrimitiveItem(..) => "Primitive Type ",
+        clean::StaticItem(..) | clean::ForeignStaticItem(..) => "Static ",
+        clean::ConstantItem(..) => "Constant ",
+        clean::ForeignTypeItem => "Foreign Type ",
+        clean::KeywordItem(..) => "Keyword ",
+        clean::OpaqueTyItem(..) => "Opaque Type ",
+        clean::TraitAliasItem(..) => "Trait Alias ",
+        _ => {
+            // We don't generate pages for any other type.
+            unreachable!();
+        }
+    };
+    buf.write_str(name);
+    if !item.is_primitive() && !item.is_keyword() {
+        let cur = &cx.current;
+        let amt = if item.is_mod() { cur.len() - 1 } else { cur.len() };
+        for (i, component) in cur.iter().enumerate().take(amt) {
+            write!(
+                buf,
+                "<a href=\"{}index.html\">{}</a>::<wbr>",
+                "../".repeat(cur.len() - i - 1),
+                component
+            );
+        }
+    }
+    write!(buf, "<a class=\"{}\" href=\"\">{}</a>", item.type_(), item.name.as_ref().unwrap());
+
+    buf.write_str("</span>"); // in-band
+    buf.write_str("<span class=\"out-of-band\">");
+    render_stability_since_raw(
+        buf,
+        item.stable_since(cx.tcx()).as_deref(),
+        item.const_stable_since(cx.tcx()).as_deref(),
+        None,
+        None,
+    );
+    buf.write_str(
+        "<span id=\"render-detail\">\
+                <a id=\"toggle-all-docs\" href=\"javascript:void(0)\" \
+                    title=\"collapse all docs\">\
+                    [<span class=\"inner\">&#x2212;</span>]\
+                </a>\
+            </span>",
+    );
+
+    // Write `src` tag
+    //
+    // When this item is part of a `crate use` in a downstream crate, the
+    // [src] link in the downstream documentation will actually come back to
+    // this page, and this link will be auto-clicked. The `id` attribute is
+    // used to find the link to auto-click.
+    if cx.shared.include_sources && !item.is_primitive() {
+        write_srclink(cx, item, buf);
+    }
+
+    buf.write_str("</span></h1>"); // out-of-band
+
+    match *item.kind {
+        clean::ModuleItem(ref m) => item_module(buf, cx, item, &m.items),
+        clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f) => {
+            item_function(buf, cx, item, f)
+        }
+        clean::TraitItem(ref t) => item_trait(buf, cx, item, t),
+        clean::StructItem(ref s) => item_struct(buf, cx, item, s),
+        clean::UnionItem(ref s) => item_union(buf, cx, item, s),
+        clean::EnumItem(ref e) => item_enum(buf, cx, item, e),
+        clean::TypedefItem(ref t, _) => item_typedef(buf, cx, item, t),
+        clean::MacroItem(ref m) => item_macro(buf, cx, item, m),
+        clean::ProcMacroItem(ref m) => item_proc_macro(buf, cx, item, m),
+        clean::PrimitiveItem(_) => item_primitive(buf, cx, item),
+        clean::StaticItem(ref i) | clean::ForeignStaticItem(ref i) => item_static(buf, cx, item, i),
+        clean::ConstantItem(ref c) => item_constant(buf, cx, item, c),
+        clean::ForeignTypeItem => item_foreign_type(buf, cx, item),
+        clean::KeywordItem(_) => item_keyword(buf, cx, item),
+        clean::OpaqueTyItem(ref e) => item_opaque_ty(buf, cx, item, e),
+        clean::TraitAliasItem(ref ta) => item_trait_alias(buf, cx, item, ta),
+        _ => {
+            // We don't generate pages for any other type.
+            unreachable!();
+        }
+    }
+}
+
+fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) {
+    document(w, cx, item, None);
+
+    let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::<Vec<usize>>();
+
+    // the order of item types in the listing
+    fn reorder(ty: ItemType) -> u8 {
+        match ty {
+            ItemType::ExternCrate => 0,
+            ItemType::Import => 1,
+            ItemType::Primitive => 2,
+            ItemType::Module => 3,
+            ItemType::Macro => 4,
+            ItemType::Struct => 5,
+            ItemType::Enum => 6,
+            ItemType::Constant => 7,
+            ItemType::Static => 8,
+            ItemType::Trait => 9,
+            ItemType::Function => 10,
+            ItemType::Typedef => 12,
+            ItemType::Union => 13,
+            _ => 14 + ty as u8,
+        }
+    }
+
+    fn cmp(
+        i1: &clean::Item,
+        i2: &clean::Item,
+        idx1: usize,
+        idx2: usize,
+        tcx: TyCtxt<'_>,
+    ) -> Ordering {
+        let ty1 = i1.type_();
+        let ty2 = i2.type_();
+        if ty1 != ty2 {
+            return (reorder(ty1), idx1).cmp(&(reorder(ty2), idx2));
+        }
+        let s1 = i1.stability(tcx).as_ref().map(|s| s.level);
+        let s2 = i2.stability(tcx).as_ref().map(|s| s.level);
+        if let (Some(a), Some(b)) = (s1, s2) {
+            match (a.is_stable(), b.is_stable()) {
+                (true, true) | (false, false) => {}
+                (false, true) => return Ordering::Less,
+                (true, false) => return Ordering::Greater,
+            }
+        }
+        let lhs = i1.name.unwrap_or(kw::Empty).as_str();
+        let rhs = i2.name.unwrap_or(kw::Empty).as_str();
+        compare_names(&lhs, &rhs)
+    }
+
+    if cx.shared.sort_modules_alphabetically {
+        indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2, cx.tcx()));
+    }
+    // This call is to remove re-export duplicates in cases such as:
+    //
+    // ```
+    // crate mod foo {
+    //     crate mod bar {
+    //         crate trait Double { fn foo(); }
+    //     }
+    // }
+    //
+    // crate use foo::bar::*;
+    // crate use foo::*;
+    // ```
+    //
+    // `Double` will appear twice in the generated docs.
+    //
+    // FIXME: This code is quite ugly and could be improved. Small issue: DefId
+    // can be identical even if the elements are different (mostly in imports).
+    // So in case this is an import, we keep everything by adding a "unique id"
+    // (which is the position in the vector).
+    indices.dedup_by_key(|i| {
+        (
+            items[*i].def_id,
+            if items[*i].name.as_ref().is_some() { Some(full_path(cx, &items[*i])) } else { None },
+            items[*i].type_(),
+            if items[*i].is_import() { *i } else { 0 },
+        )
+    });
+
+    debug!("{:?}", indices);
+    let mut curty = None;
+    for &idx in &indices {
+        let myitem = &items[idx];
+        if myitem.is_stripped() {
+            continue;
+        }
+
+        let myty = Some(myitem.type_());
+        if curty == Some(ItemType::ExternCrate) && myty == Some(ItemType::Import) {
+            // Put `extern crate` and `use` re-exports in the same section.
+            curty = myty;
+        } else if myty != curty {
+            if curty.is_some() {
+                w.write_str("</table>");
+            }
+            curty = myty;
+            let (short, name) = item_ty_to_strs(&myty.unwrap());
+            write!(
+                w,
+                "<h2 id=\"{id}\" class=\"section-header\">\
+                       <a href=\"#{id}\">{name}</a></h2>\n<table>",
+                id = cx.derive_id(short.to_owned()),
+                name = name
+            );
+        }
+
+        match *myitem.kind {
+            clean::ExternCrateItem(ref name, ref src) => {
+                use crate::html::format::anchor;
+
+                match *src {
+                    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()),
+                        name
+                    ),
+                    None => write!(
+                        w,
+                        "<tr><td><code>{}extern crate {};",
+                        myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()),
+                        anchor(myitem.def_id, &*name.as_str(), cx.cache())
+                    ),
+                }
+                w.write_str("</code></td></tr>");
+            }
+
+            clean::ImportItem(ref import) => {
+                write!(
+                    w,
+                    "<tr><td><code>{}{}</code></td></tr>",
+                    myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()),
+                    import.print(cx.cache())
+                );
+            }
+
+            _ => {
+                if myitem.name.is_none() {
+                    continue;
+                }
+
+                let unsafety_flag = match *myitem.kind {
+                    clean::FunctionItem(ref func) | clean::ForeignFunctionItem(ref func)
+                        if func.header.unsafety == hir::Unsafety::Unsafe =>
+                    {
+                        "<a title=\"unsafe function\" href=\"#\"><sup>âš </sup></a>"
+                    }
+                    _ => "",
+                };
+
+                let stab = myitem.stability_class(cx.tcx());
+                let add = if stab.is_some() { " " } else { "" };
+
+                let doc_value = myitem.doc_value().unwrap_or_default();
+                write!(
+                    w,
+                    "<tr class=\"{stab}{add}module-item\">\
+                         <td><a class=\"{class}\" href=\"{href}\" \
+                             title=\"{title}\">{name}</a>{unsafety_flag}</td>\
+                         <td class=\"docblock-short\">{stab_tags}{docs}</td>\
+                     </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(),
+                    class = myitem.type_(),
+                    add = add,
+                    stab = stab.unwrap_or_else(String::new),
+                    unsafety_flag = unsafety_flag,
+                    href = item_path(myitem.type_(), &myitem.name.unwrap().as_str()),
+                    title = [full_path(cx, myitem), myitem.type_().to_string()]
+                        .iter()
+                        .filter_map(|s| if !s.is_empty() { Some(s.as_str()) } else { None })
+                        .collect::<Vec<_>>()
+                        .join(" "),
+                );
+            }
+        }
+    }
+
+    if curty.is_some() {
+        w.write_str("</table>");
+    }
+}
+
+/// Render the stability, deprecation and portability tags that are displayed in the item's summary
+/// at the module level.
+fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) -> String {
+    let mut tags = String::new();
+
+    fn tag_html(class: &str, title: &str, contents: &str) -> String {
+        format!(r#"<span class="stab {}" title="{}">{}</span>"#, class, Escape(title), contents)
+    }
+
+    // The trailing space after each tag is to space it properly against the rest of the docs.
+    if let Some(depr) = &item.deprecation(tcx) {
+        let mut message = "Deprecated";
+        if !stability::deprecation_in_effect(
+            depr.is_since_rustc_version,
+            depr.since.map(|s| s.as_str()).as_deref(),
+        ) {
+            message = "Deprecation planned";
+        }
+        tags += &tag_html("deprecated", "", message);
+    }
+
+    // The "rustc_private" crates are permanently unstable so it makes no sense
+    // to render "unstable" everywhere.
+    if item
+        .stability(tcx)
+        .as_ref()
+        .map(|s| s.level.is_unstable() && s.feature != sym::rustc_private)
+        == Some(true)
+    {
+        tags += &tag_html("unstable", "", "Experimental");
+    }
+
+    let cfg = match (&item.attrs.cfg, parent.attrs.cfg.as_ref()) {
+        (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg),
+        (cfg, _) => cfg.as_deref().cloned(),
+    };
+
+    debug!("Portability {:?} - {:?} = {:?}", item.attrs.cfg, parent.attrs.cfg, cfg);
+    if let Some(ref cfg) = cfg {
+        tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html());
+    }
+
+    tags
+}
+
+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()),
+        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())
+    )
+    .len();
+    w.write_str("<pre class=\"rust fn\">");
+    render_attributes(w, it, false);
+    write!(
+        w,
+        "{vis}{constness}{asyncness}{unsafety}{abi}fn \
+         {name}{generics}{decl}{spotlight}{where_clause}</pre>",
+        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        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()),
+        where_clause =
+            WhereClause { gens: &f.generics, indent: 0, end_newline: true }.print(cx.cache()),
+        decl = Function { decl: &f.decl, header_len, indent: 0, asyncness: f.header.asyncness }
+            .print(cx.cache()),
+        spotlight = spotlight_decl(&f.decl, cx.cache()),
+    );
+    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());
+    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<_>>();
+    let provided = t.items.iter().filter(|m| m.is_method()).collect::<Vec<_>>();
+
+    // Output the trait definition
+    wrap_into_docblock(w, |w| {
+        w.write_str("<pre class=\"rust trait\">");
+        render_attributes(w, it, true);
+        write!(
+            w,
+            "{}{}{}trait {}{}{}",
+            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+            t.unsafety.print_with_space(),
+            if t.is_auto { "auto " } else { "" },
+            it.name.as_ref().unwrap(),
+            t.generics.print(cx.cache()),
+            bounds
+        );
+
+        if !t.generics.where_predicates.is_empty() {
+            let where_ = WhereClause { gens: &t.generics, indent: 0, end_newline: true };
+            write!(w, "{}", where_.print(cx.cache()));
+        } else {
+            w.write_str(" ");
+        }
+
+        if t.items.is_empty() {
+            w.write_str("{ }");
+        } else {
+            // FIXME: we should be using a derived_id for the Anchors here
+            w.write_str("{\n");
+            for t in &types {
+                render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
+                w.write_str(";\n");
+            }
+            if !types.is_empty() && !consts.is_empty() {
+                w.write_str("\n");
+            }
+            for t in &consts {
+                render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
+                w.write_str(";\n");
+            }
+            if !consts.is_empty() && !required.is_empty() {
+                w.write_str("\n");
+            }
+            for (pos, m) in required.iter().enumerate() {
+                render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait, cx);
+                w.write_str(";\n");
+
+                if pos < required.len() - 1 {
+                    w.write_str("<div class=\"item-spacer\"></div>");
+                }
+            }
+            if !required.is_empty() && !provided.is_empty() {
+                w.write_str("\n");
+            }
+            for (pos, m) in provided.iter().enumerate() {
+                render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait, cx);
+                match *m.kind {
+                    clean::MethodItem(ref inner, _)
+                        if !inner.generics.where_predicates.is_empty() =>
+                    {
+                        w.write_str(",\n    { ... }\n");
+                    }
+                    _ => {
+                        w.write_str(" { ... }\n");
+                    }
+                }
+                if pos < provided.len() - 1 {
+                    w.write_str("<div class=\"item-spacer\"></div>");
+                }
+            }
+            w.write_str("}");
+        }
+        w.write_str("</pre>")
+    });
+
+    // Trait documentation
+    document(w, cx, it, None);
+
+    fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) {
+        write!(
+            w,
+            "<h2 id=\"{0}\" class=\"small-section-header\">\
+                {1}<a href=\"#{0}\" class=\"anchor\"></a>\
+             </h2>{2}",
+            id, title, extra_content
+        )
+    }
+
+    fn write_loading_content(w: &mut Buffer, extra_content: &str) {
+        write!(w, "{}<span class=\"loading-content\">Loading content...</span>", extra_content)
+    }
+
+    fn trait_item(w: &mut Buffer, cx: &Context<'_>, m: &clean::Item, t: &clean::Item) {
+        let name = m.name.as_ref().unwrap();
+        info!("Documenting {} on {:?}", name, t.name);
+        let item_type = m.type_();
+        let id = cx.derive_id(format!("{}.{}", item_type, name));
+        write!(w, "<h3 id=\"{id}\" class=\"method\"><code>", id = id,);
+        render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl, cx);
+        w.write_str("</code>");
+        render_stability_since(w, m, t, cx.tcx());
+        write_srclink(cx, m, w);
+        w.write_str("</h3>");
+        document(w, cx, m, Some(t));
+    }
+
+    if !types.is_empty() {
+        write_small_section_header(
+            w,
+            "associated-types",
+            "Associated Types",
+            "<div class=\"methods\">",
+        );
+        for t in types {
+            trait_item(w, cx, t, it);
+        }
+        write_loading_content(w, "</div>");
+    }
+
+    if !consts.is_empty() {
+        write_small_section_header(
+            w,
+            "associated-const",
+            "Associated Constants",
+            "<div class=\"methods\">",
+        );
+        for t in consts {
+            trait_item(w, cx, t, it);
+        }
+        write_loading_content(w, "</div>");
+    }
+
+    // Output the documentation for each function individually
+    if !required.is_empty() {
+        write_small_section_header(
+            w,
+            "required-methods",
+            "Required methods",
+            "<div class=\"methods\">",
+        );
+        for m in required {
+            trait_item(w, cx, m, it);
+        }
+        write_loading_content(w, "</div>");
+    }
+    if !provided.is_empty() {
+        write_small_section_header(
+            w,
+            "provided-methods",
+            "Provided methods",
+            "<div class=\"methods\">",
+        );
+        for m in provided {
+            trait_item(w, cx, m, it);
+        }
+        write_loading_content(w, "</div>");
+    }
+
+    // If there are methods directly on this trait object, render them here.
+    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All);
+
+    if let Some(implementors) = cx.cache.implementors.get(&it.def_id) {
+        // The DefId is for the first Type found with that name. The bool is
+        // if any Types with the same name but different DefId have been found.
+        let mut implementor_dups: FxHashMap<Symbol, (DefId, bool)> = FxHashMap::default();
+        for implementor in implementors {
+            match implementor.inner_impl().for_ {
+                clean::ResolvedPath { ref path, did, is_generic: false, .. }
+                | clean::BorrowedRef {
+                    type_: box clean::ResolvedPath { ref path, did, is_generic: false, .. },
+                    ..
+                } => {
+                    let &mut (prev_did, ref mut has_duplicates) =
+                        implementor_dups.entry(path.last()).or_insert((did, false));
+                    if prev_did != did {
+                        *has_duplicates = true;
+                    }
+                }
+                _ => {}
+            }
+        }
+
+        let (local, foreign) = implementors.iter().partition::<Vec<_>, _>(|i| {
+            i.inner_impl()
+                .for_
+                .def_id_full(cx.cache())
+                .map_or(true, |d| cx.cache.paths.contains_key(&d))
+        });
+
+        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()));
+        concrete.sort_by(|a, b| compare_impl(a, b, cx.cache()));
+
+        if !foreign.is_empty() {
+            write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", "");
+
+            for implementor in foreign {
+                let assoc_link = AssocItemLink::GotoSource(
+                    implementor.impl_item.def_id,
+                    &implementor.inner_impl().provided_trait_methods,
+                );
+                render_impl(
+                    w,
+                    cx,
+                    &implementor,
+                    it,
+                    assoc_link,
+                    RenderMode::Normal,
+                    implementor.impl_item.stable_since(cx.tcx()).as_deref(),
+                    implementor.impl_item.const_stable_since(cx.tcx()).as_deref(),
+                    false,
+                    None,
+                    true,
+                    false,
+                    &[],
+                );
+            }
+            write_loading_content(w, "");
+        }
+
+        write_small_section_header(
+            w,
+            "implementors",
+            "Implementors",
+            "<div class=\"item-list\" id=\"implementors-list\">",
+        );
+        for implementor in concrete {
+            render_implementor(cx, implementor, it, w, &implementor_dups, &[]);
+        }
+        write_loading_content(w, "</div>");
+
+        if t.is_auto {
+            write_small_section_header(
+                w,
+                "synthetic-implementors",
+                "Auto implementors",
+                "<div class=\"item-list\" id=\"synthetic-implementors-list\">",
+            );
+            for implementor in synthetic {
+                render_implementor(
+                    cx,
+                    implementor,
+                    it,
+                    w,
+                    &implementor_dups,
+                    &collect_paths_for_type(implementor.inner_impl().for_.clone(), &cx.cache),
+                );
+            }
+            write_loading_content(w, "</div>");
+        }
+    } else {
+        // even without any implementations to write in, we still want the heading and list, so the
+        // implementors javascript file pulled in below has somewhere to write the impls into
+        write_small_section_header(
+            w,
+            "implementors",
+            "Implementors",
+            "<div class=\"item-list\" id=\"implementors-list\">",
+        );
+        write_loading_content(w, "</div>");
+
+        if t.is_auto {
+            write_small_section_header(
+                w,
+                "synthetic-implementors",
+                "Auto implementors",
+                "<div class=\"item-list\" id=\"synthetic-implementors-list\">",
+            );
+            write_loading_content(w, "</div>");
+        }
+    }
+
+    write!(
+        w,
+        "<script type=\"text/javascript\" \
+                 src=\"{root_path}/implementors/{path}/{ty}.{name}.js\" async>\
+         </script>",
+        root_path = vec![".."; cx.current.len()].join("/"),
+        path = if it.def_id.is_local() {
+            cx.current.join("/")
+        } else {
+            let (ref path, _) = cx.cache.external_paths[&it.def_id];
+            path[..path.len() - 1].join("/")
+        },
+        ty = it.type_(),
+        name = *it.name.as_ref().unwrap()
+    );
+}
+
+fn item_trait_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::TraitAlias) {
+    w.write_str("<pre class=\"rust trait-alias\">");
+    render_attributes(w, it, false);
+    write!(
+        w,
+        "trait {}{}{} = {};</pre>",
+        it.name.as_ref().unwrap(),
+        t.generics.print(cx.cache()),
+        WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()),
+        bounds(&t.bounds, true, cx.cache())
+    );
+
+    document(w, cx, it, None);
+
+    // Render any items associated directly to this alias, as otherwise they
+    // won't be visible anywhere in the docs. It would be nice to also show
+    // associated items from the aliased type (see discussion in #32077), but
+    // we need #14072 to make sense of the generics.
+    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
+}
+
+fn item_opaque_ty(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) {
+    w.write_str("<pre class=\"rust opaque\">");
+    render_attributes(w, it, false);
+    write!(
+        w,
+        "type {}{}{where_clause} = impl {bounds};</pre>",
+        it.name.as_ref().unwrap(),
+        t.generics.print(cx.cache()),
+        where_clause =
+            WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()),
+        bounds = bounds(&t.bounds, false, cx.cache())
+    );
+
+    document(w, cx, it, None);
+
+    // Render any items associated directly to this alias, as otherwise they
+    // won't be visible anywhere in the docs. It would be nice to also show
+    // associated items from the aliased type (see discussion in #32077), but
+    // we need #14072 to make sense of the generics.
+    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
+}
+
+fn item_typedef(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Typedef) {
+    w.write_str("<pre class=\"rust typedef\">");
+    render_attributes(w, it, false);
+    write!(
+        w,
+        "type {}{}{where_clause} = {type_};</pre>",
+        it.name.as_ref().unwrap(),
+        t.generics.print(cx.cache()),
+        where_clause =
+            WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()),
+        type_ = t.type_.print(cx.cache())
+    );
+
+    document(w, cx, it, None);
+
+    // Render any items associated directly to this alias, as otherwise they
+    // won't be visible anywhere in the docs. It would be nice to also show
+    // associated items from the aliased type (see discussion in #32077), but
+    // we need #14072 to make sense of the generics.
+    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
+}
+
+fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Union) {
+    wrap_into_docblock(w, |w| {
+        w.write_str("<pre class=\"rust union\">");
+        render_attributes(w, it, true);
+        render_union(w, it, Some(&s.generics), &s.fields, "", true, cx);
+        w.write_str("</pre>")
+    });
+
+    document(w, cx, it, None);
+    let mut fields = s
+        .fields
+        .iter()
+        .filter_map(|f| match *f.kind {
+            clean::StructFieldItem(ref ty) => Some((f, ty)),
+            _ => None,
+        })
+        .peekable();
+    if fields.peek().is_some() {
+        write!(
+            w,
+            "<h2 id=\"fields\" class=\"fields small-section-header\">
+                   Fields<a href=\"#fields\" class=\"anchor\"></a></h2>"
+        );
+        for (field, ty) in fields {
+            let name = field.name.as_ref().expect("union field name");
+            let id = format!("{}.{}", ItemType::StructField, name);
+            write!(
+                w,
+                "<span id=\"{id}\" class=\"{shortty} small-section-header\">\
+                     <a href=\"#{id}\" class=\"anchor field\"></a>\
+                     <code>{name}: {ty}</code>\
+                 </span>",
+                id = id,
+                name = name,
+                shortty = ItemType::StructField,
+                ty = ty.print(cx.cache())
+            );
+            if let Some(stability_class) = field.stability_class(cx.tcx()) {
+                write!(w, "<span class=\"stab {stab}\"></span>", stab = stability_class);
+            }
+            document(w, cx, field, Some(it));
+        }
+    }
+    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
+}
+
+fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) {
+    wrap_into_docblock(w, |w| {
+        w.write_str("<pre class=\"rust enum\">");
+        render_attributes(w, it, true);
+        write!(
+            w,
+            "{}enum {}{}{}",
+            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+            it.name.as_ref().unwrap(),
+            e.generics.print(cx.cache()),
+            WhereClause { gens: &e.generics, indent: 0, end_newline: true }.print(cx.cache())
+        );
+        if e.variants.is_empty() && !e.variants_stripped {
+            w.write_str(" {}");
+        } else {
+            w.write_str(" {\n");
+            for v in &e.variants {
+                w.write_str("    ");
+                let name = v.name.as_ref().unwrap();
+                match *v.kind {
+                    clean::VariantItem(ref var) => match var {
+                        clean::Variant::CLike => write!(w, "{}", name),
+                        clean::Variant::Tuple(ref tys) => {
+                            write!(w, "{}(", name);
+                            for (i, ty) in tys.iter().enumerate() {
+                                if i > 0 {
+                                    w.write_str(",&nbsp;")
+                                }
+                                write!(w, "{}", ty.print(cx.cache()));
+                            }
+                            w.write_str(")");
+                        }
+                        clean::Variant::Struct(ref s) => {
+                            render_struct(w, v, None, s.struct_type, &s.fields, "    ", false, cx);
+                        }
+                    },
+                    _ => unreachable!(),
+                }
+                w.write_str(",\n");
+            }
+
+            if e.variants_stripped {
+                w.write_str("    // some variants omitted\n");
+            }
+            w.write_str("}");
+        }
+        w.write_str("</pre>")
+    });
+
+    document(w, cx, it, None);
+    if !e.variants.is_empty() {
+        write!(
+            w,
+            "<h2 id=\"variants\" class=\"variants small-section-header\">
+                   Variants{}<a href=\"#variants\" class=\"anchor\"></a></h2>\n",
+            document_non_exhaustive_header(it)
+        );
+        document_non_exhaustive(w, it);
+        for variant in &e.variants {
+            let id =
+                cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.as_ref().unwrap()));
+            write!(
+                w,
+                "<div id=\"{id}\" class=\"variant small-section-header\">\
+                    <a href=\"#{id}\" class=\"anchor field\"></a>\
+                    <code>{name}",
+                id = id,
+                name = variant.name.as_ref().unwrap()
+            );
+            if let clean::VariantItem(clean::Variant::Tuple(ref tys)) = *variant.kind {
+                w.write_str("(");
+                for (i, ty) in tys.iter().enumerate() {
+                    if i > 0 {
+                        w.write_str(",&nbsp;");
+                    }
+                    write!(w, "{}", ty.print(cx.cache()));
+                }
+                w.write_str(")");
+            }
+            w.write_str("</code></div>");
+            document(w, cx, variant, Some(it));
+            document_non_exhaustive(w, variant);
+
+            use crate::clean::Variant;
+            if let clean::VariantItem(Variant::Struct(ref s)) = *variant.kind {
+                let variant_id = cx.derive_id(format!(
+                    "{}.{}.fields",
+                    ItemType::Variant,
+                    variant.name.as_ref().unwrap()
+                ));
+                write!(w, "<div class=\"autohide sub-variant\" id=\"{id}\">", id = variant_id);
+                write!(
+                    w,
+                    "<h3>Fields of <b>{name}</b></h3><div>",
+                    name = variant.name.as_ref().unwrap()
+                );
+                for field in &s.fields {
+                    use crate::clean::StructFieldItem;
+                    if let StructFieldItem(ref ty) = *field.kind {
+                        let id = cx.derive_id(format!(
+                            "variant.{}.field.{}",
+                            variant.name.as_ref().unwrap(),
+                            field.name.as_ref().unwrap()
+                        ));
+                        write!(
+                            w,
+                            "<span id=\"{id}\" class=\"variant small-section-header\">\
+                                 <a href=\"#{id}\" class=\"anchor field\"></a>\
+                                 <code>{f}:&nbsp;{t}</code>\
+                             </span>",
+                            id = id,
+                            f = field.name.as_ref().unwrap(),
+                            t = ty.print(cx.cache())
+                        );
+                        document(w, cx, field, Some(variant));
+                    }
+                }
+                w.write_str("</div></div>");
+            }
+            render_stability_since(w, variant, it, cx.tcx());
+        }
+    }
+    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
+}
+
+fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) {
+    wrap_into_docblock(w, |w| {
+        highlight::render_with_highlighting(
+            &t.source,
+            w,
+            Some("macro"),
+            None,
+            None,
+            it.source.span().edition(),
+        );
+    });
+    document(w, cx, it, None)
+}
+
+fn item_proc_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, m: &clean::ProcMacro) {
+    let name = it.name.as_ref().expect("proc-macros always have names");
+    match m.kind {
+        MacroKind::Bang => {
+            w.push_str("<pre class=\"rust macro\">");
+            write!(w, "{}!() {{ /* proc-macro */ }}", name);
+            w.push_str("</pre>");
+        }
+        MacroKind::Attr => {
+            w.push_str("<pre class=\"rust attr\">");
+            write!(w, "#[{}]", name);
+            w.push_str("</pre>");
+        }
+        MacroKind::Derive => {
+            w.push_str("<pre class=\"rust derive\">");
+            write!(w, "#[derive({})]", name);
+            if !m.helpers.is_empty() {
+                w.push_str("\n{\n");
+                w.push_str("    // Attributes available to this derive:\n");
+                for attr in &m.helpers {
+                    writeln!(w, "    #[{}]", attr);
+                }
+                w.push_str("}\n");
+            }
+            w.push_str("</pre>");
+        }
+    }
+    document(w, cx, it, None)
+}
+
+fn item_primitive(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) {
+    document(w, cx, it, None);
+    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
+}
+
+fn item_constant(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, c: &clean::Constant) {
+    w.write_str("<pre class=\"rust const\">");
+    render_attributes(w, it, false);
+
+    write!(
+        w,
+        "{vis}const {name}: {typ}",
+        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        name = it.name.as_ref().unwrap(),
+        typ = c.type_.print(cx.cache()),
+    );
+
+    if c.value.is_some() || c.is_literal {
+        write!(w, " = {expr};", expr = Escape(&c.expr));
+    } else {
+        w.write_str(";");
+    }
+
+    if let Some(value) = &c.value {
+        if !c.is_literal {
+            let value_lowercase = value.to_lowercase();
+            let expr_lowercase = c.expr.to_lowercase();
+
+            if value_lowercase != expr_lowercase
+                && value_lowercase.trim_end_matches("i32") != expr_lowercase
+            {
+                write!(w, " // {value}", value = Escape(value));
+            }
+        }
+    }
+
+    w.write_str("</pre>");
+    document(w, cx, it, None)
+}
+
+fn item_struct(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) {
+    wrap_into_docblock(w, |w| {
+        w.write_str("<pre class=\"rust struct\">");
+        render_attributes(w, it, true);
+        render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true, cx);
+        w.write_str("</pre>")
+    });
+
+    document(w, cx, it, None);
+    let mut fields = s
+        .fields
+        .iter()
+        .filter_map(|f| match *f.kind {
+            clean::StructFieldItem(ref ty) => Some((f, ty)),
+            _ => None,
+        })
+        .peekable();
+    if let CtorKind::Fictive = s.struct_type {
+        if fields.peek().is_some() {
+            write!(
+                w,
+                "<h2 id=\"fields\" class=\"fields small-section-header\">
+                       Fields{}<a href=\"#fields\" class=\"anchor\"></a></h2>",
+                document_non_exhaustive_header(it)
+            );
+            document_non_exhaustive(w, it);
+            for (field, ty) in fields {
+                let id = cx.derive_id(format!(
+                    "{}.{}",
+                    ItemType::StructField,
+                    field.name.as_ref().unwrap()
+                ));
+                write!(
+                    w,
+                    "<span id=\"{id}\" class=\"{item_type} small-section-header\">\
+                         <a href=\"#{id}\" class=\"anchor field\"></a>\
+                         <code>{name}: {ty}</code>\
+                     </span>",
+                    item_type = ItemType::StructField,
+                    id = id,
+                    name = field.name.as_ref().unwrap(),
+                    ty = ty.print(cx.cache())
+                );
+                document(w, cx, field, Some(it));
+            }
+        }
+    }
+    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
+}
+
+fn item_static(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Static) {
+    w.write_str("<pre class=\"rust static\">");
+    render_attributes(w, it, false);
+    write!(
+        w,
+        "{vis}static {mutability}{name}: {typ}</pre>",
+        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        mutability = s.mutability.print_with_space(),
+        name = it.name.as_ref().unwrap(),
+        typ = s.type_.print(cx.cache())
+    );
+    document(w, cx, it, None)
+}
+
+fn item_foreign_type(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) {
+    w.write_str("<pre class=\"rust foreigntype\">extern {\n");
+    render_attributes(w, it, false);
+    write!(
+        w,
+        "    {}type {};\n}}</pre>",
+        it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        it.name.as_ref().unwrap(),
+    );
+
+    document(w, cx, it, None);
+
+    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
+}
+
+fn item_keyword(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) {
+    document(w, cx, it, None)
+}
+
+/// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order).
+crate fn compare_names(mut lhs: &str, mut rhs: &str) -> Ordering {
+    /// Takes a non-numeric and a numeric part from the given &str.
+    fn take_parts<'a>(s: &mut &'a str) -> (&'a str, &'a str) {
+        let i = s.find(|c: char| c.is_ascii_digit());
+        let (a, b) = s.split_at(i.unwrap_or(s.len()));
+        let i = b.find(|c: char| !c.is_ascii_digit());
+        let (b, c) = b.split_at(i.unwrap_or(b.len()));
+        *s = c;
+        (a, b)
+    }
+
+    while !lhs.is_empty() || !rhs.is_empty() {
+        let (la, lb) = take_parts(&mut lhs);
+        let (ra, rb) = take_parts(&mut rhs);
+        // First process the non-numeric part.
+        match la.cmp(ra) {
+            Ordering::Equal => (),
+            x => return x,
+        }
+        // Then process the numeric part, if both sides have one (and they fit in a u64).
+        if let (Ok(ln), Ok(rn)) = (lb.parse::<u64>(), rb.parse::<u64>()) {
+            match ln.cmp(&rn) {
+                Ordering::Equal => (),
+                x => return x,
+            }
+        }
+        // Then process the numeric part again, but this time as strings.
+        match lb.cmp(rb) {
+            Ordering::Equal => (),
+            x => return x,
+        }
+    }
+
+    Ordering::Equal
+}
+
+pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String {
+    let mut s = cx.current.join("::");
+    s.push_str("::");
+    s.push_str(&item.name.unwrap().as_str());
+    s
+}
+
+pub(super) fn item_path(ty: ItemType, name: &str) -> String {
+    match ty {
+        ItemType::Module => format!("{}index.html", ensure_trailing_slash(name)),
+        _ => format!("{}.{}.html", ty, name),
+    }
+}
+
+fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cache: &Cache) -> String {
+    let mut bounds = String::new();
+    if !t_bounds.is_empty() {
+        if !trait_alias {
+            bounds.push_str(": ");
+        }
+        for (i, p) in t_bounds.iter().enumerate() {
+            if i > 0 {
+                bounds.push_str(" + ");
+            }
+            bounds.push_str(&p.print(cache).to_string());
+        }
+    }
+    bounds
+}
+
+fn wrap_into_docblock<F>(w: &mut Buffer, f: F)
+where
+    F: FnOnce(&mut Buffer),
+{
+    w.write_str("<div class=\"docblock type-decl hidden-by-usual-hider\">");
+    f(w);
+    w.write_str("</div>")
+}
+
+fn render_stability_since(
+    w: &mut Buffer,
+    item: &clean::Item,
+    containing_item: &clean::Item,
+    tcx: TyCtxt<'_>,
+) {
+    render_stability_since_raw(
+        w,
+        item.stable_since(tcx).as_deref(),
+        item.const_stable_since(tcx).as_deref(),
+        containing_item.stable_since(tcx).as_deref(),
+        containing_item.const_stable_since(tcx).as_deref(),
+    )
+}
+
+fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl, cache: &Cache) -> Ordering {
+    let lhs = format!("{}", lhs.inner_impl().print(cache, false));
+    let rhs = format!("{}", rhs.inner_impl().print(cache, false));
+
+    // lhs and rhs are formatted as HTML, which may be unnecessary
+    compare_names(&lhs, &rhs)
+}
+
+fn render_implementor(
+    cx: &Context<'_>,
+    implementor: &Impl,
+    trait_: &clean::Item,
+    w: &mut Buffer,
+    implementor_dups: &FxHashMap<Symbol, (DefId, bool)>,
+    aliases: &[String],
+) {
+    // If there's already another implementor that has the same abbridged name, use the
+    // full path, for example in `std::iter::ExactSizeIterator`
+    let use_absolute = match implementor.inner_impl().for_ {
+        clean::ResolvedPath { ref path, is_generic: false, .. }
+        | clean::BorrowedRef {
+            type_: box clean::ResolvedPath { ref path, is_generic: false, .. },
+            ..
+        } => implementor_dups[&path.last()].1,
+        _ => false,
+    };
+    render_impl(
+        w,
+        cx,
+        implementor,
+        trait_,
+        AssocItemLink::Anchor(None),
+        RenderMode::Normal,
+        trait_.stable_since(cx.tcx()).as_deref(),
+        trait_.const_stable_since(cx.tcx()).as_deref(),
+        false,
+        Some(use_absolute),
+        false,
+        false,
+        aliases,
+    );
+}
+
+fn render_union(
+    w: &mut Buffer,
+    it: &clean::Item,
+    g: Option<&clean::Generics>,
+    fields: &[clean::Item],
+    tab: &str,
+    structhead: bool,
+    cx: &Context<'_>,
+) {
+    write!(
+        w,
+        "{}{}{}",
+        it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        if structhead { "union " } else { "" },
+        it.name.as_ref().unwrap()
+    );
+    if let Some(g) = g {
+        write!(w, "{}", g.print(cx.cache()));
+        write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache()));
+    }
+
+    write!(w, " {{\n{}", tab);
+    for field in fields {
+        if let clean::StructFieldItem(ref ty) = *field.kind {
+            write!(
+                w,
+                "    {}{}: {},\n{}",
+                field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()),
+                field.name.as_ref().unwrap(),
+                ty.print(cx.cache()),
+                tab
+            );
+        }
+    }
+
+    if it.has_stripped_fields().unwrap() {
+        write!(w, "    // some fields omitted\n{}", tab);
+    }
+    w.write_str("}");
+}
+
+fn render_struct(
+    w: &mut Buffer,
+    it: &clean::Item,
+    g: Option<&clean::Generics>,
+    ty: CtorKind,
+    fields: &[clean::Item],
+    tab: &str,
+    structhead: bool,
+    cx: &Context<'_>,
+) {
+    write!(
+        w,
+        "{}{}{}",
+        it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
+        if structhead { "struct " } else { "" },
+        it.name.as_ref().unwrap()
+    );
+    if let Some(g) = g {
+        write!(w, "{}", g.print(cx.cache()))
+    }
+    match ty {
+        CtorKind::Fictive => {
+            if let Some(g) = g {
+                write!(
+                    w,
+                    "{}",
+                    WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache())
+                )
+            }
+            let mut has_visible_fields = false;
+            w.write_str(" {");
+            for field in fields {
+                if let clean::StructFieldItem(ref ty) = *field.kind {
+                    write!(
+                        w,
+                        "\n{}    {}{}: {},",
+                        tab,
+                        field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()),
+                        field.name.as_ref().unwrap(),
+                        ty.print(cx.cache())
+                    );
+                    has_visible_fields = true;
+                }
+            }
+
+            if has_visible_fields {
+                if it.has_stripped_fields().unwrap() {
+                    write!(w, "\n{}    // some fields omitted", tab);
+                }
+                write!(w, "\n{}", tab);
+            } else if it.has_stripped_fields().unwrap() {
+                // If there are no visible fields we can just display
+                // `{ /* fields omitted */ }` to save space.
+                write!(w, " /* fields omitted */ ");
+            }
+            w.write_str("}");
+        }
+        CtorKind::Fn => {
+            w.write_str("(");
+            for (i, field) in fields.iter().enumerate() {
+                if i > 0 {
+                    w.write_str(", ");
+                }
+                match *field.kind {
+                    clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"),
+                    clean::StructFieldItem(ref ty) => {
+                        write!(
+                            w,
+                            "{}{}",
+                            field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()),
+                            ty.print(cx.cache())
+                        )
+                    }
+                    _ => unreachable!(),
+                }
+            }
+            w.write_str(")");
+            if let Some(g) = g {
+                write!(
+                    w,
+                    "{}",
+                    WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache())
+                )
+            }
+            w.write_str(";");
+        }
+        CtorKind::Const => {
+            // Needed for PhantomData.
+            if let Some(g) = g {
+                write!(
+                    w,
+                    "{}",
+                    WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache())
+                )
+            }
+            w.write_str(";");
+        }
+    }
+}
+
+fn document_non_exhaustive_header(item: &clean::Item) -> &str {
+    if item.is_non_exhaustive() { " (Non-exhaustive)" } else { "" }
+}
+
+fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) {
+    if item.is_non_exhaustive() {
+        write!(w, "<div class=\"docblock non-exhaustive non-exhaustive-{}\">", {
+            if item.is_struct() {
+                "struct"
+            } else if item.is_enum() {
+                "enum"
+            } else if item.is_variant() {
+                "variant"
+            } else {
+                "type"
+            }
+        });
+
+        if item.is_struct() {
+            w.write_str(
+                "Non-exhaustive structs could have additional fields added in future. \
+                 Therefore, non-exhaustive structs cannot be constructed in external crates \
+                 using the traditional <code>Struct {{ .. }}</code> syntax; cannot be \
+                 matched against without a wildcard <code>..</code>; and \
+                 struct update syntax will not work.",
+            );
+        } else if item.is_enum() {
+            w.write_str(
+                "Non-exhaustive enums could have additional variants added in future. \
+                 Therefore, when matching against variants of non-exhaustive enums, an \
+                 extra wildcard arm must be added to account for any future variants.",
+            );
+        } else if item.is_variant() {
+            w.write_str(
+                "Non-exhaustive enum variants could have additional fields added in future. \
+                 Therefore, non-exhaustive enum variants cannot be constructed in external \
+                 crates and cannot be matched against.",
+            );
+        } else {
+            w.write_str(
+                "This type will require a wildcard arm in any match statements or constructors.",
+            );
+        }
+
+        w.write_str("</div>");
+    }
+}
diff --git a/src/librustdoc/html/render/tests.rs b/src/librustdoc/html/render/tests.rs
index 224c794fb3b4a..3175fbe5666d8 100644
--- a/src/librustdoc/html/render/tests.rs
+++ b/src/librustdoc/html/render/tests.rs
@@ -1,4 +1,7 @@
-use super::*;
+use std::cmp::Ordering;
+
+use super::print_item::compare_names;
+use super::{AllTypes, Buffer};
 
 #[test]
 fn test_compare_names() {
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
new file mode 100644
index 0000000000000..cbf0f9a4927c6
--- /dev/null
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -0,0 +1,542 @@
+use std::ffi::OsStr;
+use std::fmt::Write;
+use std::fs::{self, File};
+use std::io::prelude::*;
+use std::io::{self, BufReader};
+use std::path::{Component, Path, PathBuf};
+
+use itertools::Itertools;
+use rustc_data_structures::flock;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use serde::Serialize;
+
+use super::{collect_paths_for_type, ensure_trailing_slash, Context, BASIC_KEYWORDS};
+use crate::clean::Crate;
+use crate::config::RenderOptions;
+use crate::docfs::{DocFS, PathError};
+use crate::error::Error;
+use crate::formats::FormatRenderer;
+use crate::html::{layout, static_files};
+
+pub(super) fn write_shared(
+    cx: &Context<'_>,
+    krate: &Crate,
+    search_index: String,
+    options: &RenderOptions,
+) -> Result<(), Error> {
+    // Write out the shared files. Note that these are shared among all rustdoc
+    // docs placed in the output directory, so this needs to be a synchronized
+    // operation with respect to all other rustdocs running around.
+    let lock_file = cx.dst.join(".lock");
+    let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
+
+    // Add all the static files. These may already exist, but we just
+    // overwrite them anyway to make sure that they're fresh and up-to-date.
+
+    write_minify(
+        &cx.shared.fs,
+        cx.path("rustdoc.css"),
+        static_files::RUSTDOC_CSS,
+        options.enable_minification,
+    )?;
+    write_minify(
+        &cx.shared.fs,
+        cx.path("settings.css"),
+        static_files::SETTINGS_CSS,
+        options.enable_minification,
+    )?;
+    write_minify(
+        &cx.shared.fs,
+        cx.path("noscript.css"),
+        static_files::NOSCRIPT_CSS,
+        options.enable_minification,
+    )?;
+
+    // To avoid "light.css" to be overwritten, we'll first run over the received themes and only
+    // then we'll run over the "official" styles.
+    let mut themes: FxHashSet<String> = FxHashSet::default();
+
+    for entry in &cx.shared.style_files {
+        let theme = try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path);
+        let extension =
+            try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
+
+        // Handle the official themes
+        match theme {
+            "light" => write_minify(
+                &cx.shared.fs,
+                cx.path("light.css"),
+                static_files::themes::LIGHT,
+                options.enable_minification,
+            )?,
+            "dark" => write_minify(
+                &cx.shared.fs,
+                cx.path("dark.css"),
+                static_files::themes::DARK,
+                options.enable_minification,
+            )?,
+            "ayu" => write_minify(
+                &cx.shared.fs,
+                cx.path("ayu.css"),
+                static_files::themes::AYU,
+                options.enable_minification,
+            )?,
+            _ => {
+                // Handle added third-party themes
+                let content = try_err!(fs::read(&entry.path), &entry.path);
+                cx.shared
+                    .fs
+                    .write(cx.path(&format!("{}.{}", theme, extension)), content.as_slice())?;
+            }
+        };
+
+        themes.insert(theme.to_owned());
+    }
+
+    let write = |p, c| cx.shared.fs.write(p, c);
+    if (*cx.shared).layout.logo.is_empty() {
+        write(cx.path("rust-logo.png"), static_files::RUST_LOGO)?;
+    }
+    if (*cx.shared).layout.favicon.is_empty() {
+        write(cx.path("favicon.svg"), static_files::RUST_FAVICON_SVG)?;
+        write(cx.path("favicon-16x16.png"), static_files::RUST_FAVICON_PNG_16)?;
+        write(cx.path("favicon-32x32.png"), static_files::RUST_FAVICON_PNG_32)?;
+    }
+    write(cx.path("brush.svg"), static_files::BRUSH_SVG)?;
+    write(cx.path("wheel.svg"), static_files::WHEEL_SVG)?;
+    write(cx.path("down-arrow.svg"), static_files::DOWN_ARROW_SVG)?;
+
+    let mut themes: Vec<&String> = themes.iter().collect();
+    themes.sort();
+    // To avoid theme switch latencies as much as possible, we put everything theme related
+    // at the beginning of the html files into another js file.
+    let theme_js = format!(
+        r#"var themes = document.getElementById("theme-choices");
+var themePicker = document.getElementById("theme-picker");
+
+function showThemeButtonState() {{
+    themes.style.display = "block";
+    themePicker.style.borderBottomRightRadius = "0";
+    themePicker.style.borderBottomLeftRadius = "0";
+}}
+
+function hideThemeButtonState() {{
+    themes.style.display = "none";
+    themePicker.style.borderBottomRightRadius = "3px";
+    themePicker.style.borderBottomLeftRadius = "3px";
+}}
+
+function switchThemeButtonState() {{
+    if (themes.style.display === "block") {{
+        hideThemeButtonState();
+    }} else {{
+        showThemeButtonState();
+    }}
+}};
+
+function handleThemeButtonsBlur(e) {{
+    var active = document.activeElement;
+    var related = e.relatedTarget;
+
+    if (active.id !== "theme-picker" &&
+        (!active.parentNode || active.parentNode.id !== "theme-choices") &&
+        (!related ||
+         (related.id !== "theme-picker" &&
+          (!related.parentNode || related.parentNode.id !== "theme-choices")))) {{
+        hideThemeButtonState();
+    }}
+}}
+
+themePicker.onclick = switchThemeButtonState;
+themePicker.onblur = handleThemeButtonsBlur;
+{}.forEach(function(item) {{
+    var but = document.createElement("button");
+    but.textContent = item;
+    but.onclick = function(el) {{
+        switchTheme(currentTheme, mainTheme, item, true);
+        useSystemTheme(false);
+    }};
+    but.onblur = handleThemeButtonsBlur;
+    themes.appendChild(but);
+}});"#,
+        serde_json::to_string(&themes).unwrap()
+    );
+
+    write_minify(&cx.shared.fs, cx.path("theme.js"), &theme_js, options.enable_minification)?;
+    write_minify(
+        &cx.shared.fs,
+        cx.path("main.js"),
+        static_files::MAIN_JS,
+        options.enable_minification,
+    )?;
+    write_minify(
+        &cx.shared.fs,
+        cx.path("settings.js"),
+        static_files::SETTINGS_JS,
+        options.enable_minification,
+    )?;
+    if cx.shared.include_sources {
+        write_minify(
+            &cx.shared.fs,
+            cx.path("source-script.js"),
+            static_files::sidebar::SOURCE_SCRIPT,
+            options.enable_minification,
+        )?;
+    }
+
+    {
+        write_minify(
+            &cx.shared.fs,
+            cx.path("storage.js"),
+            &format!(
+                "var resourcesSuffix = \"{}\";{}",
+                cx.shared.resource_suffix,
+                static_files::STORAGE_JS
+            ),
+            options.enable_minification,
+        )?;
+    }
+
+    if let Some(ref css) = cx.shared.layout.css_file_extension {
+        let out = cx.path("theme.css");
+        let buffer = try_err!(fs::read_to_string(css), css);
+        if !options.enable_minification {
+            cx.shared.fs.write(&out, &buffer)?;
+        } else {
+            write_minify(&cx.shared.fs, out, &buffer, options.enable_minification)?;
+        }
+    }
+    write_minify(
+        &cx.shared.fs,
+        cx.path("normalize.css"),
+        static_files::NORMALIZE_CSS,
+        options.enable_minification,
+    )?;
+    write(cx.dst.join("FiraSans-Regular.woff2"), static_files::fira_sans::REGULAR2)?;
+    write(cx.dst.join("FiraSans-Medium.woff2"), static_files::fira_sans::MEDIUM2)?;
+    write(cx.dst.join("FiraSans-Regular.woff"), static_files::fira_sans::REGULAR)?;
+    write(cx.dst.join("FiraSans-Medium.woff"), static_files::fira_sans::MEDIUM)?;
+    write(cx.dst.join("FiraSans-LICENSE.txt"), static_files::fira_sans::LICENSE)?;
+    write(cx.dst.join("SourceSerifPro-Regular.ttf.woff"), static_files::source_serif_pro::REGULAR)?;
+    write(cx.dst.join("SourceSerifPro-Bold.ttf.woff"), static_files::source_serif_pro::BOLD)?;
+    write(cx.dst.join("SourceSerifPro-It.ttf.woff"), static_files::source_serif_pro::ITALIC)?;
+    write(cx.dst.join("SourceSerifPro-LICENSE.md"), static_files::source_serif_pro::LICENSE)?;
+    write(cx.dst.join("SourceCodePro-Regular.woff"), static_files::source_code_pro::REGULAR)?;
+    write(cx.dst.join("SourceCodePro-Semibold.woff"), static_files::source_code_pro::SEMIBOLD)?;
+    write(cx.dst.join("SourceCodePro-LICENSE.txt"), static_files::source_code_pro::LICENSE)?;
+    write(cx.dst.join("LICENSE-MIT.txt"), static_files::LICENSE_MIT)?;
+    write(cx.dst.join("LICENSE-APACHE.txt"), static_files::LICENSE_APACHE)?;
+    write(cx.dst.join("COPYRIGHT.txt"), static_files::COPYRIGHT)?;
+
+    fn collect(path: &Path, krate: &str, key: &str) -> io::Result<(Vec<String>, Vec<String>)> {
+        let mut ret = Vec::new();
+        let mut krates = Vec::new();
+
+        if path.exists() {
+            let prefix = format!(r#"{}["{}"]"#, key, krate);
+            for line in BufReader::new(File::open(path)?).lines() {
+                let line = line?;
+                if !line.starts_with(key) {
+                    continue;
+                }
+                if line.starts_with(&prefix) {
+                    continue;
+                }
+                ret.push(line.to_string());
+                krates.push(
+                    line[key.len() + 2..]
+                        .split('"')
+                        .next()
+                        .map(|s| s.to_owned())
+                        .unwrap_or_else(String::new),
+                );
+            }
+        }
+        Ok((ret, krates))
+    }
+
+    fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec<String>, Vec<String>)> {
+        let mut ret = Vec::new();
+        let mut krates = Vec::new();
+
+        if path.exists() {
+            let prefix = format!("\"{}\"", krate);
+            for line in BufReader::new(File::open(path)?).lines() {
+                let line = line?;
+                if !line.starts_with('"') {
+                    continue;
+                }
+                if line.starts_with(&prefix) {
+                    continue;
+                }
+                if line.ends_with(",\\") {
+                    ret.push(line[..line.len() - 2].to_string());
+                } else {
+                    // Ends with "\\" (it's the case for the last added crate line)
+                    ret.push(line[..line.len() - 1].to_string());
+                }
+                krates.push(
+                    line.split('"')
+                        .find(|s| !s.is_empty())
+                        .map(|s| s.to_owned())
+                        .unwrap_or_else(String::new),
+                );
+            }
+        }
+        Ok((ret, krates))
+    }
+
+    use std::ffi::OsString;
+
+    #[derive(Debug)]
+    struct Hierarchy {
+        elem: OsString,
+        children: FxHashMap<OsString, Hierarchy>,
+        elems: FxHashSet<OsString>,
+    }
+
+    impl Hierarchy {
+        fn new(elem: OsString) -> Hierarchy {
+            Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() }
+        }
+
+        fn to_json_string(&self) -> String {
+            let mut subs: Vec<&Hierarchy> = self.children.values().collect();
+            subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
+            let mut files = self
+                .elems
+                .iter()
+                .map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion")))
+                .collect::<Vec<_>>();
+            files.sort_unstable();
+            let subs = subs.iter().map(|s| s.to_json_string()).collect::<Vec<_>>().join(",");
+            let dirs =
+                if subs.is_empty() { String::new() } else { format!(",\"dirs\":[{}]", subs) };
+            let files = files.join(",");
+            let files =
+                if files.is_empty() { String::new() } else { format!(",\"files\":[{}]", files) };
+            format!(
+                "{{\"name\":\"{name}\"{dirs}{files}}}",
+                name = self.elem.to_str().expect("invalid osstring conversion"),
+                dirs = dirs,
+                files = files
+            )
+        }
+    }
+
+    if cx.shared.include_sources {
+        let mut hierarchy = Hierarchy::new(OsString::new());
+        for source in cx
+            .shared
+            .local_sources
+            .iter()
+            .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
+        {
+            let mut h = &mut hierarchy;
+            let mut elems = source
+                .components()
+                .filter_map(|s| match s {
+                    Component::Normal(s) => Some(s.to_owned()),
+                    _ => None,
+                })
+                .peekable();
+            loop {
+                let cur_elem = elems.next().expect("empty file path");
+                if elems.peek().is_none() {
+                    h.elems.insert(cur_elem);
+                    break;
+                } else {
+                    let e = cur_elem.clone();
+                    h = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e));
+                }
+            }
+        }
+
+        let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix));
+        let (mut all_sources, _krates) =
+            try_err!(collect(&dst, &krate.name.as_str(), "sourcesIndex"), &dst);
+        all_sources.push(format!(
+            "sourcesIndex[\"{}\"] = {};",
+            &krate.name,
+            hierarchy.to_json_string()
+        ));
+        all_sources.sort();
+        let v = format!(
+            "var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();\n",
+            all_sources.join("\n")
+        );
+        cx.shared.fs.write(&dst, v.as_bytes())?;
+    }
+
+    // Update the search index and crate list.
+    let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix));
+    let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst);
+    all_indexes.push(search_index);
+    krates.push(krate.name.to_string());
+    krates.sort();
+
+    // Sort the indexes by crate so the file will be generated identically even
+    // with rustdoc running in parallel.
+    all_indexes.sort();
+    {
+        let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
+        v.push_str(&all_indexes.join(",\\\n"));
+        v.push_str("\\\n}');\ninitSearch(searchIndex);");
+        cx.shared.fs.write(&dst, &v)?;
+    }
+
+    let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix));
+    let crate_list =
+        format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(","));
+    cx.shared.fs.write(&crate_list_dst, &crate_list)?;
+
+    if options.enable_index_page {
+        if let Some(index_page) = options.index_page.clone() {
+            let mut md_opts = options.clone();
+            md_opts.output = cx.dst.clone();
+            md_opts.external_html = (*cx.shared).layout.external_html.clone();
+
+            crate::markdown::render(&index_page, md_opts, cx.shared.edition)
+                .map_err(|e| Error::new(e, &index_page))?;
+        } else {
+            let dst = cx.dst.join("index.html");
+            let page = layout::Page {
+                title: "Index of crates",
+                css_class: "mod",
+                root_path: "./",
+                static_root_path: cx.shared.static_root_path.as_deref(),
+                description: "List of crates",
+                keywords: BASIC_KEYWORDS,
+                resource_suffix: &cx.shared.resource_suffix,
+                extra_scripts: &[],
+                static_extra_scripts: &[],
+            };
+
+            let content = format!(
+                "<h1 class=\"fqn\">\
+                     <span class=\"in-band\">List of all crates</span>\
+                </h1><ul class=\"crate mod\">{}</ul>",
+                krates
+                    .iter()
+                    .map(|s| {
+                        format!(
+                            "<li><a class=\"crate mod\" href=\"{}index.html\">{}</a></li>",
+                            ensure_trailing_slash(s),
+                            s
+                        )
+                    })
+                    .collect::<String>()
+            );
+            let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files);
+            cx.shared.fs.write(&dst, v.as_bytes())?;
+        }
+    }
+
+    // Update the list of all implementors for traits
+    let dst = cx.dst.join("implementors");
+    for (&did, imps) in &cx.cache.implementors {
+        // Private modules can leak through to this phase of rustdoc, which
+        // could contain implementations for otherwise private types. In some
+        // rare cases we could find an implementation for an item which wasn't
+        // indexed, so we just skip this step in that case.
+        //
+        // FIXME: this is a vague explanation for why this can't be a `get`, in
+        //        theory it should be...
+        let &(ref remote_path, remote_item_type) = match cx.cache.paths.get(&did) {
+            Some(p) => p,
+            None => match cx.cache.external_paths.get(&did) {
+                Some(p) => p,
+                None => continue,
+            },
+        };
+
+        #[derive(Serialize)]
+        struct Implementor {
+            text: String,
+            synthetic: bool,
+            types: Vec<String>,
+        }
+
+        let implementors = imps
+            .iter()
+            .filter_map(|imp| {
+                // If the trait and implementation are in the same crate, then
+                // there's no need to emit information about it (there's inlining
+                // going on). If they're in different crates then the crate defining
+                // the trait will be interested in our implementation.
+                //
+                // If the implementation is from another crate then that crate
+                // should add it.
+                if imp.impl_item.def_id.krate == did.krate || !imp.impl_item.def_id.is_local() {
+                    None
+                } else {
+                    Some(Implementor {
+                        text: imp.inner_impl().print(cx.cache(), false).to_string(),
+                        synthetic: imp.inner_impl().synthetic,
+                        types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()),
+                    })
+                }
+            })
+            .collect::<Vec<_>>();
+
+        // Only create a js file if we have impls to add to it. If the trait is
+        // documented locally though we always create the file to avoid dead
+        // links.
+        if implementors.is_empty() && !cx.cache.paths.contains_key(&did) {
+            continue;
+        }
+
+        let implementors = format!(
+            r#"implementors["{}"] = {};"#,
+            krate.name,
+            serde_json::to_string(&implementors).unwrap()
+        );
+
+        let mut mydst = dst.clone();
+        for part in &remote_path[..remote_path.len() - 1] {
+            mydst.push(part);
+        }
+        cx.shared.ensure_dir(&mydst)?;
+        mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1]));
+
+        let (mut all_implementors, _) =
+            try_err!(collect(&mydst, &krate.name.as_str(), "implementors"), &mydst);
+        all_implementors.push(implementors);
+        // Sort the implementors by crate so the file will be generated
+        // identically even with rustdoc running in parallel.
+        all_implementors.sort();
+
+        let mut v = String::from("(function() {var implementors = {};\n");
+        for implementor in &all_implementors {
+            writeln!(v, "{}", *implementor).unwrap();
+        }
+        v.push_str(
+            "if (window.register_implementors) {\
+                 window.register_implementors(implementors);\
+             } else {\
+                 window.pending_implementors = implementors;\
+             }",
+        );
+        v.push_str("})()");
+        cx.shared.fs.write(&mydst, &v)?;
+    }
+    Ok(())
+}
+
+fn write_minify(
+    fs: &DocFS,
+    dst: PathBuf,
+    contents: &str,
+    enable_minification: bool,
+) -> Result<(), Error> {
+    if enable_minification {
+        if dst.extension() == Some(&OsStr::new("css")) {
+            let res = try_none!(minifier::css::minify(contents).ok(), &dst);
+            fs.write(dst, res.as_bytes())
+        } else {
+            fs.write(dst, minifier::js::minify(contents).as_bytes())
+        }
+    } else {
+        fs.write(dst, contents.as_bytes())
+    }
+}
diff --git a/src/librustdoc/passes/doc_test_lints.rs b/src/librustdoc/passes/doc_test_lints.rs
index 81104236314d9..ede9e18a511a8 100644
--- a/src/librustdoc/passes/doc_test_lints.rs
+++ b/src/librustdoc/passes/doc_test_lints.rs
@@ -9,8 +9,10 @@ use crate::clean::*;
 use crate::core::DocContext;
 use crate::fold::DocFolder;
 use crate::html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString};
+use crate::visit_ast::inherits_doc_hidden;
 use rustc_middle::lint::LintLevelSource;
 use rustc_session::lint;
+use rustc_span::symbol::sym;
 
 crate const CHECK_PRIVATE_ITEMS_DOC_TESTS: Pass = Pass {
     name: "check-private-items-doc-tests",
@@ -51,23 +53,30 @@ impl crate::doctest::Tester for Tests {
 }
 
 crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool {
-    if matches!(
-        *item.kind,
-        clean::StructFieldItem(_)
-            | clean::VariantItem(_)
-            | clean::AssocConstItem(_, _)
-            | clean::AssocTypeItem(_, _)
-            | clean::TypedefItem(_, _)
-            | clean::StaticItem(_)
-            | clean::ConstantItem(_)
-            | clean::ExternCrateItem(_, _)
-            | clean::ImportItem(_)
-            | clean::PrimitiveItem(_)
-            | clean::KeywordItem(_)
-    ) {
+    if !cx.cache.access_levels.is_public(item.def_id)
+        || matches!(
+            *item.kind,
+            clean::StructFieldItem(_)
+                | clean::VariantItem(_)
+                | clean::AssocConstItem(_, _)
+                | clean::AssocTypeItem(_, _)
+                | clean::TypedefItem(_, _)
+                | clean::StaticItem(_)
+                | clean::ConstantItem(_)
+                | clean::ExternCrateItem(_, _)
+                | clean::ImportItem(_)
+                | clean::PrimitiveItem(_)
+                | clean::KeywordItem(_)
+        )
+    {
         return false;
     }
     let hir_id = cx.tcx.hir().local_def_id_to_hir_id(item.def_id.expect_local());
+    if cx.tcx.hir().attrs(hir_id).lists(sym::doc).has_word(sym::hidden)
+        || inherits_doc_hidden(cx.tcx, hir_id)
+    {
+        return false;
+    }
     let (level, source) = cx.tcx.lint_level_at_node(crate::lint::MISSING_DOC_CODE_EXAMPLES, hir_id);
     level != lint::Level::Allow || matches!(source, LintLevelSource::Default)
 }
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index 5da7d2f1e9b84..ba6bb359b9135 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -29,6 +29,16 @@ fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<String> {
     std::iter::once(crate_name).chain(relative).collect()
 }
 
+crate fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut node: hir::HirId) -> bool {
+    while let Some(id) = tcx.hir().get_enclosing_scope(node) {
+        node = id;
+        if tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) {
+            return true;
+        }
+    }
+    false
+}
+
 // Also, is there some reason that this doesn't use the 'visit'
 // framework from syntax?.
 
@@ -158,19 +168,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
         om: &mut Module<'tcx>,
         please_inline: bool,
     ) -> bool {
-        fn inherits_doc_hidden(cx: &core::DocContext<'_>, mut node: hir::HirId) -> bool {
-            while let Some(id) = cx.tcx.hir().get_enclosing_scope(node) {
-                node = id;
-                if cx.tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) {
-                    return true;
-                }
-                if node == hir::CRATE_HIR_ID {
-                    break;
-                }
-            }
-            false
-        }
-
         debug!("maybe_inline_local res: {:?}", res);
 
         let tcx = self.cx.tcx;
@@ -212,7 +209,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
         };
 
         let is_private = !self.cx.cache.access_levels.is_public(res_did);
-        let is_hidden = inherits_doc_hidden(self.cx, res_hir_id);
+        let is_hidden = inherits_doc_hidden(self.cx.tcx, res_hir_id);
 
         // Only inline if requested or if the item would otherwise be stripped.
         if (!please_inline && !is_private && !is_hidden) || is_no_inline {
diff --git a/src/test/rustdoc-ui/lint-missing-doc-code-example.rs b/src/test/rustdoc-ui/lint-missing-doc-code-example.rs
index 8d727b0d0b550..41e8847792694 100644
--- a/src/test/rustdoc-ui/lint-missing-doc-code-example.rs
+++ b/src/test/rustdoc-ui/lint-missing-doc-code-example.rs
@@ -12,16 +12,16 @@
 /// ```
 /// println!("hello");
 /// ```
-fn test() {
+pub fn test() {
 }
 
 #[allow(missing_docs)]
-mod module1 { //~ ERROR
+pub mod module1 { //~ ERROR
 }
 
 #[allow(rustdoc::missing_doc_code_examples)]
 /// doc
-mod module2 {
+pub mod module2 {
 
   /// doc
   pub fn test() {}
@@ -63,9 +63,22 @@ pub enum Enum {
 /// Doc
 //~^ ERROR
 #[repr(C)]
-union Union {
+pub union Union {
     /// Doc, but no code example and it's fine!
     a: i32,
     /// Doc, but no code example and it's fine!
     b: f32,
 }
+
+
+#[doc(hidden)]
+pub mod foo {
+    pub fn bar() {}
+}
+
+fn babar() {}
+
+
+mod fofoo {
+    pub fn tadam() {}
+}
diff --git a/src/test/rustdoc-ui/lint-missing-doc-code-example.stderr b/src/test/rustdoc-ui/lint-missing-doc-code-example.stderr
index 370c577f85d8f..3715797854281 100644
--- a/src/test/rustdoc-ui/lint-missing-doc-code-example.stderr
+++ b/src/test/rustdoc-ui/lint-missing-doc-code-example.stderr
@@ -1,7 +1,7 @@
 error: missing code example in this documentation
   --> $DIR/lint-missing-doc-code-example.rs:19:1
    |
-LL | / mod module1 {
+LL | / pub mod module1 {
 LL | | }
    | |_^
    |
diff --git a/src/test/rustdoc/extern-links.rs b/src/test/rustdoc/extern-links.rs
index 991f869138d93..0383ccf7db666 100644
--- a/src/test/rustdoc/extern-links.rs
+++ b/src/test/rustdoc/extern-links.rs
@@ -3,7 +3,7 @@
 
 #![crate_name = "foo"]
 
-extern crate extern_links;
+pub extern crate extern_links;
 
 // @!has foo/index.html '//a' 'extern_links'
 #[doc(no_inline)]
diff --git a/src/test/rustdoc/issue-28927.rs b/src/test/rustdoc/issue-28927.rs
index 7b535f33bf7e3..38a520850b6dd 100644
--- a/src/test/rustdoc/issue-28927.rs
+++ b/src/test/rustdoc/issue-28927.rs
@@ -2,5 +2,5 @@
 // aux-build:issue-28927-1.rs
 // ignore-cross-compile
 
-extern crate issue_28927_1 as inner1;
+pub extern crate issue_28927_1 as inner1;
 pub use inner1 as foo;
diff --git a/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.rs b/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.rs
new file mode 100644
index 0000000000000..934870afc119c
--- /dev/null
+++ b/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.rs
@@ -0,0 +1,21 @@
+// Regression test for #81712.
+
+#![feature(generic_associated_types)]
+#![allow(incomplete_features)]
+
+trait A {
+    type BType: B<AType = Self>;
+}
+
+trait B {
+    type AType: A<BType = Self>;
+}
+trait C {
+    type DType<T>: D<T, CType = Self>;
+    //~^ ERROR: missing generics for associated type `C::DType` [E0107]
+}
+trait D<T> {
+    type CType: C<DType = Self>;
+}
+
+fn main() {}
diff --git a/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.stderr b/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.stderr
new file mode 100644
index 0000000000000..75f68cd314893
--- /dev/null
+++ b/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.stderr
@@ -0,0 +1,19 @@
+error[E0107]: missing generics for associated type `C::DType`
+  --> $DIR/issue-81712-cyclic-traits.rs:14:10
+   |
+LL |     type DType<T>: D<T, CType = Self>;
+   |          ^^^^^ expected 1 type argument
+   |
+note: associated type defined here, with 1 type parameter: `T`
+  --> $DIR/issue-81712-cyclic-traits.rs:14:10
+   |
+LL |     type DType<T>: D<T, CType = Self>;
+   |          ^^^^^ -
+help: use angle brackets to add missing type argument
+   |
+LL |     type DType<T><T>: D<T, CType = Self>;
+   |               ^^^
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0107`.
diff --git a/src/test/ui/pub/pub-reexport-priv-extern-crate.rs b/src/test/ui/pub/pub-reexport-priv-extern-crate.rs
index e95d6924026ca..dd5cd420fa546 100644
--- a/src/test/ui/pub/pub-reexport-priv-extern-crate.rs
+++ b/src/test/ui/pub/pub-reexport-priv-extern-crate.rs
@@ -1,5 +1,3 @@
-#![allow(unused)]
-
 extern crate core;
 pub use core as reexported_core; //~ ERROR `core` is private, and cannot be re-exported
                                  //~^ WARN this was previously accepted
@@ -9,16 +7,14 @@ mod foo1 {
 }
 
 mod foo2 {
-    use foo1::core; //~ ERROR `core` is private, and cannot be re-exported
-                    //~^ WARN this was previously accepted
+    use foo1::core; //~ ERROR crate import `core` is private
     pub mod bar {
         extern crate core;
     }
 }
 
 mod baz {
-    pub use foo2::bar::core; //~ ERROR `core` is private, and cannot be re-exported
-                             //~^ WARN this was previously accepted
+    pub use foo2::bar::core; //~ ERROR crate import `core` is private
 }
 
 fn main() {}
diff --git a/src/test/ui/pub/pub-reexport-priv-extern-crate.stderr b/src/test/ui/pub/pub-reexport-priv-extern-crate.stderr
index 0b44c5a6525f6..e4d73c6475dc4 100644
--- a/src/test/ui/pub/pub-reexport-priv-extern-crate.stderr
+++ b/src/test/ui/pub/pub-reexport-priv-extern-crate.stderr
@@ -1,30 +1,37 @@
-error: extern crate `core` is private, and cannot be re-exported (error E0365), consider declaring with `pub`
-  --> $DIR/pub-reexport-priv-extern-crate.rs:4:9
+error[E0603]: crate import `core` is private
+  --> $DIR/pub-reexport-priv-extern-crate.rs:10:15
    |
-LL | pub use core as reexported_core;
-   |         ^^^^^^^^^^^^^^^^^^^^^^^
+LL |     use foo1::core;
+   |               ^^^^ private crate import
    |
-   = note: `#[deny(pub_use_of_private_extern_crate)]` on by default
-   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
-   = note: for more information, see issue #34537 <https://github.com/rust-lang/rust/issues/34537>
+note: the crate import `core` is defined here
+  --> $DIR/pub-reexport-priv-extern-crate.rs:6:5
+   |
+LL |     extern crate core;
+   |     ^^^^^^^^^^^^^^^^^^
 
-error: extern crate `core` is private, and cannot be re-exported (error E0365), consider declaring with `pub`
-  --> $DIR/pub-reexport-priv-extern-crate.rs:12:9
+error[E0603]: crate import `core` is private
+  --> $DIR/pub-reexport-priv-extern-crate.rs:17:24
    |
-LL |     use foo1::core;
-   |         ^^^^^^^^^^
+LL |     pub use foo2::bar::core;
+   |                        ^^^^ private crate import
    |
-   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
-   = note: for more information, see issue #34537 <https://github.com/rust-lang/rust/issues/34537>
+note: the crate import `core` is defined here
+  --> $DIR/pub-reexport-priv-extern-crate.rs:12:9
+   |
+LL |         extern crate core;
+   |         ^^^^^^^^^^^^^^^^^^
 
 error: extern crate `core` is private, and cannot be re-exported (error E0365), consider declaring with `pub`
-  --> $DIR/pub-reexport-priv-extern-crate.rs:20:13
+  --> $DIR/pub-reexport-priv-extern-crate.rs:2:9
    |
-LL |     pub use foo2::bar::core;
-   |             ^^^^^^^^^^^^^^^
+LL | pub use core as reexported_core;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^
    |
+   = note: `#[deny(pub_use_of_private_extern_crate)]` on by default
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #34537 <https://github.com/rust-lang/rust/issues/34537>
 
 error: aborting due to 3 previous errors
 
+For more information about this error, try `rustc --explain E0603`.