about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--src/librustdoc/Cargo.toml1
-rw-r--r--src/librustdoc/clean/inline.rs12
-rw-r--r--src/librustdoc/clean/mod.rs39
-rw-r--r--src/librustdoc/formats/cache.rs4
-rw-r--r--src/librustdoc/formats/item_type.rs3
-rw-r--r--src/librustdoc/html/render/context.rs53
-rw-r--r--src/librustdoc/html/render/mod.rs25
-rw-r--r--src/librustdoc/html/render/print_item.rs106
-rw-r--r--src/librustdoc/html/render/sidebar.rs70
-rw-r--r--src/librustdoc/html/render/write_shared.rs271
-rw-r--r--src/librustdoc/html/static/js/main.js213
-rw-r--r--src/librustdoc/html/templates/sidebar.html2
-rw-r--r--src/librustdoc/passes/collect_trait_impls.rs20
-rw-r--r--tests/rustdoc-gui/code-tags.goml2
-rw-r--r--tests/rustdoc-gui/item-decl-colors.goml2
-rw-r--r--tests/rustdoc-gui/no-docblock.goml2
-rw-r--r--tests/rustdoc-gui/search-tab.goml4
-rw-r--r--tests/rustdoc-gui/setting-auto-hide-content-large-items.goml2
-rw-r--r--tests/rustdoc-gui/src/test_docs/lib.rs27
-rw-r--r--tests/rustdoc-gui/trait-sidebar-item-order.goml2
-rw-r--r--tests/rustdoc-gui/type-declation-overflow.goml2
-rw-r--r--tests/rustdoc-gui/type-impls.goml86
-rw-r--r--tests/rustdoc/deref/deref-mut-methods.rs2
-rw-r--r--tests/rustdoc/deref/deref-recursive-pathbuf.rs4
-rw-r--r--tests/rustdoc/hidden-impls.rs2
-rw-r--r--tests/rustdoc/impl-parts-crosscrate.rs2
-rw-r--r--tests/rustdoc/inline_cross/implementors-js.rs10
-rw-r--r--tests/rustdoc/issue-43701.rs2
-rw-r--r--tests/rustdoc/strip-enum-variant.no-not-shown.html2
-rw-r--r--tests/rustdoc/strip-enum-variant.rs2
-rw-r--r--tests/rustdoc/type-alias/auxiliary/parent-crate-115718.rs9
-rw-r--r--tests/rustdoc/type-alias/cross-crate-115718.rs34
-rw-r--r--tests/rustdoc/type-alias/deeply-nested-112515.rs (renamed from tests/rustdoc/issue-112515-impl-ty-alias.rs)0
-rw-r--r--tests/rustdoc/type-alias/deref-32077.rs (renamed from tests/rustdoc/type-alias-impls-32077.rs)19
-rw-r--r--tests/rustdoc/type-alias/same-crate-115718.rs34
36 files changed, 920 insertions, 151 deletions
diff --git a/Cargo.lock b/Cargo.lock
index aca76c9c495..e1b0de6c7a4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4692,6 +4692,7 @@ dependencies = [
  "arrayvec",
  "askama",
  "expect-test",
+ "indexmap 2.0.0",
  "itertools",
  "minifier",
  "once_cell",
diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml
index 38935b7d1bb..0e01c100f9c 100644
--- a/src/librustdoc/Cargo.toml
+++ b/src/librustdoc/Cargo.toml
@@ -10,6 +10,7 @@ path = "lib.rs"
 arrayvec = { version = "0.7", default-features = false }
 askama = { version = "0.12", default-features = false, features = ["config"] }
 itertools = "0.10.1"
+indexmap = "2"
 minifier = "0.2.3"
 once_cell = "1.10.0"
 regex = "1"
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 89796761126..013814b1f7d 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -26,6 +26,8 @@ use crate::clean::{
 use crate::core::DocContext;
 use crate::formats::item_type::ItemType;
 
+use super::Item;
+
 /// Attempt to inline a definition into this AST.
 ///
 /// This function will fetch the definition specified, and if it is
@@ -83,7 +85,7 @@ pub(crate) fn try_inline(
         Res::Def(DefKind::TyAlias, did) => {
             record_extern_fqn(cx, did, ItemType::TypeAlias);
             build_impls(cx, did, attrs_without_docs, &mut ret);
-            clean::TypeAliasItem(build_type_alias(cx, did))
+            clean::TypeAliasItem(build_type_alias(cx, did, &mut ret))
         }
         Res::Def(DefKind::Enum, did) => {
             record_extern_fqn(cx, did, ItemType::Enum);
@@ -281,11 +283,15 @@ fn build_union(cx: &mut DocContext<'_>, did: DefId) -> clean::Union {
     clean::Union { generics, fields }
 }
 
-fn build_type_alias(cx: &mut DocContext<'_>, did: DefId) -> Box<clean::TypeAlias> {
+fn build_type_alias(
+    cx: &mut DocContext<'_>,
+    did: DefId,
+    ret: &mut Vec<Item>,
+) -> Box<clean::TypeAlias> {
     let predicates = cx.tcx.explicit_predicates_of(did);
     let ty = cx.tcx.type_of(did).instantiate_identity();
     let type_ = clean_middle_ty(ty::Binder::dummy(ty), cx, Some(did), None);
-    let inner_type = clean_ty_alias_inner_type(ty, cx);
+    let inner_type = clean_ty_alias_inner_type(ty, cx, ret);
 
     Box::new(clean::TypeAlias {
         type_,
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 125b1fecf18..f2447b877ca 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -934,11 +934,16 @@ fn clean_ty_generics<'tcx>(
 fn clean_ty_alias_inner_type<'tcx>(
     ty: Ty<'tcx>,
     cx: &mut DocContext<'tcx>,
+    ret: &mut Vec<Item>,
 ) -> Option<TypeAliasInnerType> {
     let ty::Adt(adt_def, args) = ty.kind() else {
         return None;
     };
 
+    if !adt_def.did().is_local() {
+        inline::build_impls(cx, adt_def.did(), None, ret);
+    }
+
     Some(if adt_def.is_enum() {
         let variants: rustc_index::IndexVec<_, _> = adt_def
             .variants()
@@ -946,6 +951,10 @@ fn clean_ty_alias_inner_type<'tcx>(
             .map(|variant| clean_variant_def_with_args(variant, args, cx))
             .collect();
 
+        if !adt_def.did().is_local() {
+            inline::record_extern_fqn(cx, adt_def.did(), ItemType::Enum);
+        }
+
         TypeAliasInnerType::Enum {
             variants,
             is_non_exhaustive: adt_def.is_variant_list_non_exhaustive(),
@@ -961,8 +970,14 @@ fn clean_ty_alias_inner_type<'tcx>(
             clean_variant_def_with_args(variant, args, cx).kind.inner_items().cloned().collect();
 
         if adt_def.is_struct() {
+            if !adt_def.did().is_local() {
+                inline::record_extern_fqn(cx, adt_def.did(), ItemType::Struct);
+            }
             TypeAliasInnerType::Struct { ctor_kind: variant.ctor_kind(), fields }
         } else {
+            if !adt_def.did().is_local() {
+                inline::record_extern_fqn(cx, adt_def.did(), ItemType::Union);
+            }
             TypeAliasInnerType::Union { fields }
         }
     })
@@ -2744,14 +2759,24 @@ fn clean_maybe_renamed_item<'tcx>(
                 }
 
                 let ty = cx.tcx.type_of(def_id).instantiate_identity();
-                let inner_type = clean_ty_alias_inner_type(ty, cx);
 
-                TypeAliasItem(Box::new(TypeAlias {
-                    generics,
-                    inner_type,
-                    type_: rustdoc_ty,
-                    item_type: Some(type_),
-                }))
+                let mut ret = Vec::new();
+                let inner_type = clean_ty_alias_inner_type(ty, cx, &mut ret);
+
+                ret.push(generate_item_with_correct_attrs(
+                    cx,
+                    TypeAliasItem(Box::new(TypeAlias {
+                        generics,
+                        inner_type,
+                        type_: rustdoc_ty,
+                        item_type: Some(type_),
+                    })),
+                    item.owner_id.def_id.to_def_id(),
+                    name,
+                    import_id,
+                    renamed,
+                ));
+                return ret;
             }
             ItemKind::Enum(ref def, generics) => EnumItem(Enum {
                 variants: def.variants.iter().map(|v| clean_variant(v, cx)).collect(),
diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs
index 4ccb5f2be34..abff77253ea 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -50,8 +50,8 @@ pub(crate) struct Cache {
     /// Unlike 'paths', this mapping ignores any renames that occur
     /// due to 'use' statements.
     ///
-    /// This map is used when writing out the special 'implementors'
-    /// javascript file. By using the exact path that the type
+    /// This map is used when writing out the `impl.trait` and `impl.type`
+    /// javascript files. By using the exact path that the type
     /// is declared with, we ensure that each path will be identical
     /// to the path used if the corresponding type is inlined. By
     /// doing this, we can detect duplicate impls on a trait page, and only display
diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs
index e37d16f5bd0..def3a90c8e8 100644
--- a/src/librustdoc/formats/item_type.rs
+++ b/src/librustdoc/formats/item_type.rs
@@ -180,6 +180,9 @@ impl ItemType {
     pub(crate) fn is_method(&self) -> bool {
         matches!(*self, ItemType::Method | ItemType::TyMethod)
     }
+    pub(crate) fn is_adt(&self) -> bool {
+        matches!(*self, ItemType::Struct | ItemType::Union | ItemType::Enum)
+    }
 }
 
 impl fmt::Display for ItemType {
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index bf8d1a80337..6da9e45a1da 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -7,10 +7,8 @@ use std::sync::mpsc::{channel, Receiver};
 
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_hir::def_id::{DefIdMap, LOCAL_CRATE};
-use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
 use rustc_middle::ty::TyCtxt;
 use rustc_session::Session;
-use rustc_span::def_id::DefId;
 use rustc_span::edition::Edition;
 use rustc_span::source_map::FileName;
 use rustc_span::{sym, Symbol};
@@ -25,13 +23,13 @@ use super::{
     AllTypes, LinkFromSrc, StylePath,
 };
 use crate::clean::utils::has_doc_flag;
-use crate::clean::{self, types::ExternalLocation, ExternalCrate, TypeAliasItem};
+use crate::clean::{self, types::ExternalLocation, ExternalCrate};
 use crate::config::{ModuleSorting, RenderOptions};
 use crate::docfs::{DocFS, PathError};
 use crate::error::Error;
 use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
-use crate::formats::{self, FormatRenderer};
+use crate::formats::FormatRenderer;
 use crate::html::escape::Escape;
 use crate::html::format::{join_with_double_colon, Buffer};
 use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
@@ -150,53 +148,6 @@ impl SharedContext<'_> {
     pub(crate) fn edition(&self) -> Edition {
         self.tcx.sess.edition()
     }
-
-    /// Returns a list of impls on the given type, and, if it's a type alias,
-    /// other types that it aliases.
-    pub(crate) fn all_impls_for_item<'a>(
-        &'a self,
-        it: &clean::Item,
-        did: DefId,
-    ) -> Vec<&'a formats::Impl> {
-        let tcx = self.tcx;
-        let cache = &self.cache;
-        let mut saw_impls = FxHashSet::default();
-        let mut v: Vec<&formats::Impl> = cache
-            .impls
-            .get(&did)
-            .map(Vec::as_slice)
-            .unwrap_or(&[])
-            .iter()
-            .filter(|i| saw_impls.insert(i.def_id()))
-            .collect();
-        if let TypeAliasItem(ait) = &*it.kind &&
-            let aliased_clean_type = ait.item_type.as_ref().unwrap_or(&ait.type_) &&
-            let Some(aliased_type_defid) = aliased_clean_type.def_id(cache) &&
-            let Some(av) = cache.impls.get(&aliased_type_defid) &&
-            let Some(alias_def_id) = it.item_id.as_def_id()
-        {
-            // This branch of the compiler compares types structually, but does
-            // not check trait bounds. That's probably fine, since type aliases
-            // don't normally constrain on them anyway.
-            // https://github.com/rust-lang/rust/issues/21903
-            //
-            // FIXME(lazy_type_alias): Once the feature is complete or stable, rewrite this to use type unification.
-            // Be aware of `tests/rustdoc/issue-112515-impl-ty-alias.rs` which might regress.
-            let aliased_ty = tcx.type_of(alias_def_id).skip_binder();
-            let reject_cx = DeepRejectCtxt {
-                treat_obligation_params: TreatParams::AsCandidateKey,
-            };
-            v.extend(av.iter().filter(|impl_| {
-                if let Some(impl_def_id) = impl_.impl_item.item_id.as_def_id() {
-                    reject_cx.types_may_unify(aliased_ty, tcx.type_of(impl_def_id).skip_binder())
-                        && saw_impls.insert(impl_def_id)
-                } else {
-                    false
-                }
-            }));
-        }
-        v
-    }
 }
 
 impl<'tcx> Context<'tcx> {
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index e01341acf43..d9086433608 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -1145,13 +1145,13 @@ pub(crate) fn render_all_impls(
 fn render_assoc_items<'a, 'cx: 'a>(
     cx: &'a mut Context<'cx>,
     containing_item: &'a clean::Item,
-    did: DefId,
+    it: DefId,
     what: AssocItemRender<'a>,
 ) -> impl fmt::Display + 'a + Captures<'cx> {
     let mut derefs = DefIdSet::default();
-    derefs.insert(did);
+    derefs.insert(it);
     display_fn(move |f| {
-        render_assoc_items_inner(f, cx, containing_item, did, what, &mut derefs);
+        render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs);
         Ok(())
     })
 }
@@ -1160,17 +1160,15 @@ fn render_assoc_items_inner(
     mut w: &mut dyn fmt::Write,
     cx: &mut Context<'_>,
     containing_item: &clean::Item,
-    did: DefId,
+    it: DefId,
     what: AssocItemRender<'_>,
     derefs: &mut DefIdSet,
 ) {
     info!("Documenting associated items of {:?}", containing_item.name);
     let shared = Rc::clone(&cx.shared);
-    let v = shared.all_impls_for_item(containing_item, did);
-    let v = v.as_slice();
-    let (non_trait, traits): (Vec<&Impl>, _) =
-        v.iter().partition(|i| i.inner_impl().trait_.is_none());
-    let mut saw_impls = FxHashSet::default();
+    let cache = &shared.cache;
+    let Some(v) = cache.impls.get(&it) else { return };
+    let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none());
     if !non_trait.is_empty() {
         let mut tmp_buf = Buffer::html();
         let (render_mode, id, class_html) = match what {
@@ -1199,9 +1197,6 @@ fn render_assoc_items_inner(
         };
         let mut impls_buf = Buffer::html();
         for i in &non_trait {
-            if !saw_impls.insert(i.def_id()) {
-                continue;
-            }
             render_impl(
                 &mut impls_buf,
                 cx,
@@ -1247,10 +1242,8 @@ fn render_assoc_items_inner(
 
         let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
             traits.into_iter().partition(|t| t.inner_impl().kind.is_auto());
-        let (blanket_impl, concrete): (Vec<&Impl>, _) = concrete
-            .into_iter()
-            .filter(|t| saw_impls.insert(t.def_id()))
-            .partition(|t| t.inner_impl().kind.is_blanket());
+        let (blanket_impl, concrete): (Vec<&Impl>, _) =
+            concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket());
 
         render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl);
     }
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index f6432dc61ae..fdf45569061 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -1066,6 +1066,8 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
         }
     }
 
+    // [RUSTDOCIMPL] trait.impl
+    //
     // Include implementors in crates that depend on the current crate.
     //
     // This is complicated by the way rustdoc is invoked, which is basically
@@ -1101,7 +1103,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
     // ```
     //
     // Basically, we want `C::Baz` and `A::Foo` to show the same set of
-    // impls, which is easier if they both treat `/implementors/A/trait.Foo.js`
+    // impls, which is easier if they both treat `/trait.impl/A/trait.Foo.js`
     // as the Single Source of Truth.
     //
     // We also want the `impl Baz for Quux` to be written to
@@ -1110,7 +1112,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
     // because that'll load faster, and it's better for SEO. And we don't want
     // the same impl to show up twice on the same page.
     //
-    // To make this work, the implementors JS file has a structure kinda
+    // To make this work, the trait.impl/A/trait.Foo.js JS file has a structure kinda
     // like this:
     //
     // ```js
@@ -1127,7 +1129,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
     // So C's HTML will have something like this:
     //
     // ```html
-    // <script src="/implementors/A/trait.Foo.js"
+    // <script src="/trait.impl/A/trait.Foo.js"
     //     data-ignore-extern-crates="A,B" async></script>
     // ```
     //
@@ -1137,7 +1139,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
     // [JSONP]: https://en.wikipedia.org/wiki/JSONP
     let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..")
         .take(cx.current.len())
-        .chain(std::iter::once("implementors"))
+        .chain(std::iter::once("trait.impl"))
         .collect();
     if let Some(did) = it.item_id.as_def_id() &&
         let get_extern = { || cache.external_paths.get(&did).map(|s| &s.0) } &&
@@ -1319,6 +1321,102 @@ fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &c
     // we need #14072 to make sense of the generics.
     write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
     write!(w, "{}", document_type_layout(cx, def_id));
+
+    // [RUSTDOCIMPL] type.impl
+    //
+    // Include type definitions from the alias target type.
+    //
+    // Earlier versions of this code worked by having `render_assoc_items`
+    // include this data directly. That generates *O*`(types*impls)` of HTML
+    // text, and some real crates have a lot of types and impls.
+    //
+    // To create the same UX without generating half a gigabyte of HTML for a
+    // crate that only contains 20 megabytes of actual documentation[^115718],
+    // rustdoc stashes these type-alias-inlined docs in a [JSONP]
+    // "database-lite". The file itself is generated in `write_shared.rs`,
+    // and hooks into functions provided by `main.js`.
+    //
+    // The format of `trait.impl` and `type.impl` JS files are superficially
+    // similar. Each line, except the JSONP wrapper itself, belongs to a crate,
+    // and they are otherwise separate (rustdoc should be idempotent). The
+    // "meat" of the file is HTML strings, so the frontend code is very simple.
+    // Links are relative to the doc root, though, so the frontend needs to fix
+    // that up, and inlined docs can reuse these files.
+    //
+    // However, there are a few differences, caused by the sophisticated
+    // features that type aliases have. Consider this crate graph:
+    //
+    // ```text
+    //  ---------------------------------
+    //  | crate A: struct Foo<T>        |
+    //  |          type Bar = Foo<i32>  |
+    //  |          impl X for Foo<i8>   |
+    //  |          impl Y for Foo<i32>  |
+    //  ---------------------------------
+    //      |
+    //  ----------------------------------
+    //  | crate B: type Baz = A::Foo<i8> |
+    //  |          type Xyy = A::Foo<i8> |
+    //  |          impl Z for Xyy        |
+    //  ----------------------------------
+    // ```
+    //
+    // The type.impl/A/struct.Foo.js JS file has a structure kinda like this:
+    //
+    // ```js
+    // JSONP({
+    // "A": [["impl Y for Foo<i32>", "Y", "A::Bar"]],
+    // "B": [["impl X for Foo<i8>", "X", "B::Baz", "B::Xyy"], ["impl Z for Xyy", "Z", "B::Baz"]],
+    // });
+    // ```
+    //
+    // When the type.impl file is loaded, only the current crate's docs are
+    // actually used. The main reason to bundle them together is that there's
+    // enough duplication in them for DEFLATE to remove the redundancy.
+    //
+    // The contents of a crate are a list of impl blocks, themselves
+    // represented as lists. The first item in the sublist is the HTML block,
+    // the second item is the name of the trait (which goes in the sidebar),
+    // and all others are the names of type aliases that successfully match.
+    //
+    // This way:
+    //
+    // - There's no need to generate these files for types that have no aliases
+    //   in the current crate. If a dependent crate makes a type alias, it'll
+    //   take care of generating its own docs.
+    // - There's no need to reimplement parts of the type checker in
+    //   JavaScript. The Rust backend does the checking, and includes its
+    //   results in the file.
+    // - Docs defined directly on the type alias are dropped directly in the
+    //   HTML by `render_assoc_items`, and are accessible without JavaScript.
+    //   The JSONP file will not list impl items that are known to be part
+    //   of the main HTML file already.
+    //
+    // [JSONP]: https://en.wikipedia.org/wiki/JSONP
+    // [^115718]: https://github.com/rust-lang/rust/issues/115718
+    let cloned_shared = Rc::clone(&cx.shared);
+    let cache = &cloned_shared.cache;
+    if let Some(target_did) = t.type_.def_id(cache) &&
+        let get_extern = { || cache.external_paths.get(&target_did) } &&
+        let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern) &&
+        target_type.is_adt() && // primitives cannot be inlined
+        let Some(self_did) = it.item_id.as_def_id() &&
+        let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) } &&
+        let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local)
+    {
+        let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..")
+            .take(cx.current.len())
+            .chain(std::iter::once("type.impl"))
+            .collect();
+        js_src_path.extend(target_fqp[..target_fqp.len() - 1].iter().copied());
+        js_src_path.push_fmt(format_args!("{target_type}.{}.js", target_fqp.last().unwrap()));
+        let self_path = self_fqp.iter().map(Symbol::as_str).collect::<Vec<&str>>().join("::");
+        write!(
+            w,
+            "<script src=\"{src}\" data-self-path=\"{self_path}\" async></script>",
+            src = js_src_path.finish(),
+        );
+    }
 }
 
 fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) {
diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs
index fb429f237e3..4e8d88c55b6 100644
--- a/src/librustdoc/html/render/sidebar.rs
+++ b/src/librustdoc/html/render/sidebar.rs
@@ -38,18 +38,19 @@ pub(crate) struct LinkBlock<'a> {
     /// as well as the link to it, e.g. `#implementations`.
     /// Will be rendered inside an `<h3>` tag
     heading: Link<'a>,
+    class: &'static str,
     links: Vec<Link<'a>>,
     /// Render the heading even if there are no links
     force_render: bool,
 }
 
 impl<'a> LinkBlock<'a> {
-    pub fn new(heading: Link<'a>, links: Vec<Link<'a>>) -> Self {
-        Self { heading, links, force_render: false }
+    pub fn new(heading: Link<'a>, class: &'static str, links: Vec<Link<'a>>) -> Self {
+        Self { heading, links, class, force_render: false }
     }
 
-    pub fn forced(heading: Link<'a>) -> Self {
-        Self { heading, links: vec![], force_render: true }
+    pub fn forced(heading: Link<'a>, class: &'static str) -> Self {
+        Self { heading, links: vec![], class, force_render: true }
     }
 
     pub fn should_render(&self) -> bool {
@@ -157,7 +158,7 @@ fn sidebar_struct<'a>(
     };
     let mut items = vec![];
     if let Some(name) = field_name {
-        items.push(LinkBlock::new(Link::new("fields", name), fields));
+        items.push(LinkBlock::new(Link::new("fields", name), "structfield", fields));
     }
     sidebar_assoc_items(cx, it, &mut items);
     items
@@ -214,12 +215,15 @@ fn sidebar_trait<'a>(
         ("foreign-impls", "Implementations on Foreign Types", foreign_impls),
     ]
     .into_iter()
-    .map(|(id, title, items)| LinkBlock::new(Link::new(id, title), items))
+    .map(|(id, title, items)| LinkBlock::new(Link::new(id, title), "", items))
     .collect();
     sidebar_assoc_items(cx, it, &mut blocks);
-    blocks.push(LinkBlock::forced(Link::new("implementors", "Implementors")));
+    blocks.push(LinkBlock::forced(Link::new("implementors", "Implementors"), "impl"));
     if t.is_auto(cx.tcx()) {
-        blocks.push(LinkBlock::forced(Link::new("synthetic-implementors", "Auto Implementors")));
+        blocks.push(LinkBlock::forced(
+            Link::new("synthetic-implementors", "Auto Implementors"),
+            "impl-auto",
+        ));
     }
     blocks
 }
@@ -245,7 +249,7 @@ fn sidebar_type_alias<'a>(
 ) -> Vec<LinkBlock<'a>> {
     let mut items = vec![];
     if let Some(inner_type) = &t.inner_type {
-        items.push(LinkBlock::forced(Link::new("aliased-type", "Aliased type")));
+        items.push(LinkBlock::forced(Link::new("aliased-type", "Aliased type"), "type"));
         match inner_type {
             clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive: _ } => {
                 let mut variants = variants
@@ -256,12 +260,12 @@ fn sidebar_type_alias<'a>(
                     .collect::<Vec<_>>();
                 variants.sort_unstable();
 
-                items.push(LinkBlock::new(Link::new("variants", "Variants"), variants));
+                items.push(LinkBlock::new(Link::new("variants", "Variants"), "variant", variants));
             }
             clean::TypeAliasInnerType::Union { fields }
             | clean::TypeAliasInnerType::Struct { ctor_kind: _, fields } => {
                 let fields = get_struct_fields_name(fields);
-                items.push(LinkBlock::new(Link::new("fields", "Fields"), fields));
+                items.push(LinkBlock::new(Link::new("fields", "Fields"), "field", fields));
             }
         }
     }
@@ -275,7 +279,7 @@ fn sidebar_union<'a>(
     u: &'a clean::Union,
 ) -> Vec<LinkBlock<'a>> {
     let fields = get_struct_fields_name(&u.fields);
-    let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), fields)];
+    let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), "structfield", fields)];
     sidebar_assoc_items(cx, it, &mut items);
     items
 }
@@ -287,12 +291,11 @@ fn sidebar_assoc_items<'a>(
     links: &mut Vec<LinkBlock<'a>>,
 ) {
     let did = it.item_id.expect_def_id();
-    let v = cx.shared.all_impls_for_item(it, it.item_id.expect_def_id());
-    let v = v.as_slice();
+    let cache = cx.cache();
 
     let mut assoc_consts = Vec::new();
     let mut methods = Vec::new();
-    if !v.is_empty() {
+    if let Some(v) = cache.impls.get(&did) {
         let mut used_links = FxHashSet::default();
         let mut id_map = IdMap::new();
 
@@ -328,7 +331,7 @@ fn sidebar_assoc_items<'a>(
                     cx,
                     &mut deref_methods,
                     impl_,
-                    v.iter().copied(),
+                    v,
                     &mut derefs,
                     &mut used_links,
                 );
@@ -341,12 +344,16 @@ fn sidebar_assoc_items<'a>(
 
             sidebar_render_assoc_items(cx, &mut id_map, concrete, synthetic, blanket_impl)
         } else {
-            std::array::from_fn(|_| LinkBlock::new(Link::empty(), vec![]))
+            std::array::from_fn(|_| LinkBlock::new(Link::empty(), "", vec![]))
         };
 
         let mut blocks = vec![
-            LinkBlock::new(Link::new("implementations", "Associated Constants"), assoc_consts),
-            LinkBlock::new(Link::new("implementations", "Methods"), methods),
+            LinkBlock::new(
+                Link::new("implementations", "Associated Constants"),
+                "associatedconstant",
+                assoc_consts,
+            ),
+            LinkBlock::new(Link::new("implementations", "Methods"), "method", methods),
         ];
         blocks.append(&mut deref_methods);
         blocks.extend([concrete, synthetic, blanket]);
@@ -358,7 +365,7 @@ fn sidebar_deref_methods<'a>(
     cx: &'a Context<'_>,
     out: &mut Vec<LinkBlock<'a>>,
     impl_: &Impl,
-    v: impl Iterator<Item = &'a Impl>,
+    v: &[Impl],
     derefs: &mut DefIdSet,
     used_links: &mut FxHashSet<String>,
 ) {
@@ -383,7 +390,7 @@ fn sidebar_deref_methods<'a>(
             // Avoid infinite cycles
             return;
         }
-        let deref_mut = { v }.any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait());
+        let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait());
         let inner_impl = target
             .def_id(c)
             .or_else(|| {
@@ -415,7 +422,7 @@ fn sidebar_deref_methods<'a>(
                 );
                 // We want links' order to be reproducible so we don't use unstable sort.
                 ret.sort();
-                out.push(LinkBlock::new(Link::new(id, title), ret));
+                out.push(LinkBlock::new(Link::new(id, title), "deref-methods", ret));
             }
         }
 
@@ -434,7 +441,7 @@ fn sidebar_deref_methods<'a>(
                 cx,
                 out,
                 target_deref_impl,
-                target_impls.iter(),
+                target_impls,
                 derefs,
                 used_links,
             );
@@ -454,7 +461,7 @@ fn sidebar_enum<'a>(
         .collect::<Vec<_>>();
     variants.sort_unstable();
 
-    let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), variants)];
+    let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), "variant", variants)];
     sidebar_assoc_items(cx, it, &mut items);
     items
 }
@@ -468,7 +475,7 @@ pub(crate) fn sidebar_module_like(
         .filter(|sec| item_sections_in_use.contains(sec))
         .map(|sec| Link::new(sec.id(), sec.name()))
         .collect();
-    LinkBlock::new(Link::empty(), item_sections)
+    LinkBlock::new(Link::empty(), "", item_sections)
 }
 
 fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> {
@@ -529,12 +536,21 @@ fn sidebar_render_assoc_items(
     let synthetic = format_impls(synthetic, id_map);
     let blanket = format_impls(blanket_impl, id_map);
     [
-        LinkBlock::new(Link::new("trait-implementations", "Trait Implementations"), concrete),
+        LinkBlock::new(
+            Link::new("trait-implementations", "Trait Implementations"),
+            "trait-implementation",
+            concrete,
+        ),
         LinkBlock::new(
             Link::new("synthetic-implementations", "Auto Trait Implementations"),
+            "synthetic-implementation",
             synthetic,
         ),
-        LinkBlock::new(Link::new("blanket-implementations", "Blanket Implementations"), blanket),
+        LinkBlock::new(
+            Link::new("blanket-implementations", "Blanket Implementations"),
+            "blanket-implementation",
+            blanket,
+        ),
     ]
 }
 
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index e68d5ab2fbd..3e58dd96ed9 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -5,18 +5,28 @@ use std::io::{self, BufReader};
 use std::path::{Component, Path};
 use std::rc::{Rc, Weak};
 
+use indexmap::IndexMap;
 use itertools::Itertools;
 use rustc_data_structures::flock;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
+use rustc_span::def_id::DefId;
+use rustc_span::Symbol;
 use serde::ser::SerializeSeq;
 use serde::{Serialize, Serializer};
 
 use super::{collect_paths_for_type, ensure_trailing_slash, Context};
-use crate::clean::Crate;
+use crate::clean::{Crate, Item, ItemId, ItemKind};
 use crate::config::{EmitType, RenderOptions};
 use crate::docfs::PathError;
 use crate::error::Error;
+use crate::formats::cache::Cache;
+use crate::formats::item_type::ItemType;
+use crate::formats::{Impl, RenderMode};
+use crate::html::format::Buffer;
+use crate::html::render::{AssocItemLink, ImplRenderingParameters};
 use crate::html::{layout, static_files};
+use crate::visit::DocVisitor;
 use crate::{try_err, try_none};
 
 /// Rustdoc writes out two kinds of shared files:
@@ -361,9 +371,264 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
         }
     }
 
+    let cloned_shared = Rc::clone(&cx.shared);
+    let cache = &cloned_shared.cache;
+
+    // Collect the list of aliased types and their aliases.
+    // <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+type.impl&type=code>
+    //
+    // The clean AST has type aliases that point at their types, but
+    // this visitor works to reverse that: `aliased_types` is a map
+    // from target to the aliases that reference it, and each one
+    // will generate one file.
+    struct TypeImplCollector<'cx, 'cache> {
+        // Map from DefId-of-aliased-type to its data.
+        aliased_types: IndexMap<DefId, AliasedType<'cache>>,
+        visited_aliases: FxHashSet<DefId>,
+        cache: &'cache Cache,
+        cx: &'cache mut Context<'cx>,
+    }
+    // Data for an aliased type.
+    //
+    // In the final file, the format will be roughly:
+    //
+    // ```json
+    // // type.impl/CRATE/TYPENAME.js
+    // JSONP(
+    // "CRATE": [
+    //   ["IMPL1 HTML", "ALIAS1", "ALIAS2", ...],
+    //   ["IMPL2 HTML", "ALIAS3", "ALIAS4", ...],
+    //    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ struct AliasedType
+    //   ...
+    // ]
+    // )
+    // ```
+    struct AliasedType<'cache> {
+        // This is used to generate the actual filename of this aliased type.
+        target_fqp: &'cache [Symbol],
+        target_type: ItemType,
+        // This is the data stored inside the file.
+        // ItemId is used to deduplicate impls.
+        impl_: IndexMap<ItemId, AliasedTypeImpl<'cache>>,
+    }
+    // The `impl_` contains data that's used to figure out if an alias will work,
+    // and to generate the HTML at the end.
+    //
+    // The `type_aliases` list is built up with each type alias that matches.
+    struct AliasedTypeImpl<'cache> {
+        impl_: &'cache Impl,
+        type_aliases: Vec<(&'cache [Symbol], Item)>,
+    }
+    impl<'cx, 'cache> DocVisitor for TypeImplCollector<'cx, 'cache> {
+        fn visit_item(&mut self, it: &Item) {
+            self.visit_item_recur(it);
+            let cache = self.cache;
+            let ItemKind::TypeAliasItem(ref t) = *it.kind else { return };
+            let Some(self_did) = it.item_id.as_def_id() else { return };
+            if !self.visited_aliases.insert(self_did) {
+                return;
+            }
+            let Some(target_did) = t.type_.def_id(cache) else { return };
+            let get_extern = { || cache.external_paths.get(&target_did) };
+            let Some(&(ref target_fqp, target_type)) =
+                cache.paths.get(&target_did).or_else(get_extern)
+            else {
+                return;
+            };
+            let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| {
+                let impl_ = cache
+                    .impls
+                    .get(&target_did)
+                    .map(|v| &v[..])
+                    .unwrap_or_default()
+                    .iter()
+                    .map(|impl_| {
+                        (
+                            impl_.impl_item.item_id,
+                            AliasedTypeImpl { impl_, type_aliases: Vec::new() },
+                        )
+                    })
+                    .collect();
+                AliasedType { target_fqp: &target_fqp[..], target_type, impl_ }
+            });
+            let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) };
+            let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else {
+                return;
+            };
+            let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder();
+            // Exclude impls that are directly on this type. They're already in the HTML.
+            // Some inlining scenarios can cause there to be two versions of the same
+            // impl: one on the type alias and one on the underlying target type.
+            let mut seen_impls: FxHashSet<ItemId> = cache
+                .impls
+                .get(&self_did)
+                .map(|s| &s[..])
+                .unwrap_or_default()
+                .iter()
+                .map(|i| i.impl_item.item_id)
+                .collect();
+            for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ {
+                // Only include this impl if it actually unifies with this alias.
+                // Synthetic impls are not included; those are also included in the HTML.
+                //
+                // FIXME(lazy_type_alias): Once the feature is complete or stable, rewrite this
+                // to use type unification.
+                // Be aware of `tests/rustdoc/type-alias/deeply-nested-112515.rs` which might regress.
+                let Some(impl_did) = impl_item_id.as_def_id() else { continue };
+                let for_ty = self.cx.tcx().type_of(impl_did).skip_binder();
+                let reject_cx =
+                    DeepRejectCtxt { treat_obligation_params: TreatParams::AsCandidateKey };
+                if !reject_cx.types_may_unify(aliased_ty, for_ty) {
+                    continue;
+                }
+                // Avoid duplicates
+                if !seen_impls.insert(*impl_item_id) {
+                    continue;
+                }
+                // This impl was not found in the set of rejected impls
+                aliased_type_impl.type_aliases.push((&self_fqp[..], it.clone()));
+            }
+        }
+    }
+    let mut type_impl_collector = TypeImplCollector {
+        aliased_types: IndexMap::default(),
+        visited_aliases: FxHashSet::default(),
+        cache,
+        cx,
+    };
+    DocVisitor::visit_crate(&mut type_impl_collector, &krate);
+    // Final serialized form of the alias impl
+    struct AliasSerializableImpl {
+        text: String,
+        trait_: Option<String>,
+        aliases: Vec<String>,
+    }
+    impl Serialize for AliasSerializableImpl {
+        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: Serializer,
+        {
+            let mut seq = serializer.serialize_seq(None)?;
+            seq.serialize_element(&self.text)?;
+            if let Some(trait_) = &self.trait_ {
+                seq.serialize_element(trait_)?;
+            } else {
+                seq.serialize_element(&0)?;
+            }
+            for type_ in &self.aliases {
+                seq.serialize_element(type_)?;
+            }
+            seq.end()
+        }
+    }
+    let cx = type_impl_collector.cx;
+    let dst = cx.dst.join("type.impl");
+    let aliased_types = type_impl_collector.aliased_types;
+    for aliased_type in aliased_types.values() {
+        let impls = aliased_type
+            .impl_
+            .values()
+            .flat_map(|AliasedTypeImpl { impl_, type_aliases }| {
+                let mut ret = Vec::new();
+                let trait_ = impl_
+                    .inner_impl()
+                    .trait_
+                    .as_ref()
+                    .map(|trait_| format!("{:#}", trait_.print(cx)));
+                // render_impl will filter out "impossible-to-call" methods
+                // to make that functionality work here, it needs to be called with
+                // each type alias, and if it gives a different result, split the impl
+                for &(type_alias_fqp, ref type_alias_item) in type_aliases {
+                    let mut buf = Buffer::html();
+                    cx.id_map = Default::default();
+                    cx.deref_id_map = Default::default();
+                    let target_did = impl_
+                        .inner_impl()
+                        .trait_
+                        .as_ref()
+                        .map(|trait_| trait_.def_id())
+                        .or_else(|| impl_.inner_impl().for_.def_id(cache));
+                    let provided_methods;
+                    let assoc_link = if let Some(target_did) = target_did {
+                        provided_methods = impl_.inner_impl().provided_trait_methods(cx.tcx());
+                        AssocItemLink::GotoSource(ItemId::DefId(target_did), &provided_methods)
+                    } else {
+                        AssocItemLink::Anchor(None)
+                    };
+                    super::render_impl(
+                        &mut buf,
+                        cx,
+                        *impl_,
+                        &type_alias_item,
+                        assoc_link,
+                        RenderMode::Normal,
+                        None,
+                        &[],
+                        ImplRenderingParameters {
+                            show_def_docs: true,
+                            show_default_items: true,
+                            show_non_assoc_items: true,
+                            toggle_open_by_default: true,
+                        },
+                    );
+                    let text = buf.into_inner();
+                    let type_alias_fqp = (*type_alias_fqp).iter().join("::");
+                    if Some(&text) == ret.last().map(|s: &AliasSerializableImpl| &s.text) {
+                        ret.last_mut()
+                            .expect("already established that ret.last() is Some()")
+                            .aliases
+                            .push(type_alias_fqp);
+                    } else {
+                        ret.push(AliasSerializableImpl {
+                            text,
+                            trait_: trait_.clone(),
+                            aliases: vec![type_alias_fqp],
+                        })
+                    }
+                }
+                ret
+            })
+            .collect::<Vec<_>>();
+        let impls = format!(
+            r#""{}":{}"#,
+            krate.name(cx.tcx()),
+            serde_json::to_string(&impls).expect("failed serde conversion"),
+        );
+
+        let mut mydst = dst.clone();
+        for part in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] {
+            mydst.push(part.to_string());
+        }
+        cx.shared.ensure_dir(&mydst)?;
+        let aliased_item_type = aliased_type.target_type;
+        mydst.push(&format!(
+            "{aliased_item_type}.{}.js",
+            aliased_type.target_fqp[aliased_type.target_fqp.len() - 1]
+        ));
+
+        let (mut all_impls, _) = try_err!(collect(&mydst, krate.name(cx.tcx()).as_str()), &mydst);
+        all_impls.push(impls);
+        // Sort the implementors by crate so the file will be generated
+        // identically even with rustdoc running in parallel.
+        all_impls.sort();
+
+        let mut v = String::from("(function() {var type_impls = {\n");
+        v.push_str(&all_impls.join(",\n"));
+        v.push_str("\n};");
+        v.push_str(
+            "if (window.register_type_impls) {\
+                 window.register_type_impls(type_impls);\
+             } else {\
+                 window.pending_type_impls = type_impls;\
+             }",
+        );
+        v.push_str("})()");
+        cx.shared.fs.write(mydst, v)?;
+    }
+
     // Update the list of all implementors for traits
-    let dst = cx.dst.join("implementors");
-    let cache = cx.cache();
+    // <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+trait.impl&type=code>
+    let dst = cx.dst.join("trait.impl");
     for (&did, imps) in &cache.implementors {
         // Private modules can leak through to this phase of rustdoc, which
         // could contain implementations for otherwise private types. In some
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index 2e9897ef82b..7c052606aba 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -549,6 +549,7 @@ function preLoadCss(cssUrl) {
         }
     }
 
+    // <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+trait.impl&type=code>
     window.register_implementors = imp => {
         const implementors = document.getElementById("implementors-list");
         const synthetic_implementors = document.getElementById("synthetic-implementors-list");
@@ -615,7 +616,7 @@ function preLoadCss(cssUrl) {
                 onEachLazy(code.getElementsByTagName("a"), elem => {
                     const href = elem.getAttribute("href");
 
-                    if (href && !/^(?:[a-z+]+:)?\/\//.test(href)) {
+                    if (href && !href.startsWith("#") && !/^(?:[a-z+]+:)?\/\//.test(href)) {
                         elem.setAttribute("href", window.rootPath + href);
                     }
                 });
@@ -639,6 +640,216 @@ function preLoadCss(cssUrl) {
         window.register_implementors(window.pending_implementors);
     }
 
+    /**
+     * <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+type.impl&type=code>
+     *
+     * [RUSTDOCIMPL] type.impl
+     *
+     * This code inlines implementations into the type alias docs at runtime. It's done at
+     * runtime because some crates have many type aliases and many methods, and we don't want
+     * to generate *O*`(types*methods)` HTML text. The data inside is mostly HTML fragments,
+     * wrapped in JSON.
+     *
+     * - It only includes docs generated for the current crate. This function accepts an
+     *   object mapping crate names to the set of impls.
+     *
+     * - It filters down to the set of applicable impls. The Rust type checker is used to
+     *   tag each HTML blob with the set of type aliases that can actually use it, so the
+     *   JS only needs to consult the attached list of type aliases.
+     *
+     * - It renames the ID attributes, to avoid conflicting IDs in the resulting DOM.
+     *
+     * - It adds the necessary items to the sidebar. If it's an inherent impl, that means
+     *   adding methods, associated types, and associated constants. If it's a trait impl,
+     *   that means adding it to the trait impl sidebar list.
+     *
+     * - It adds the HTML block itself. If it's an inherent impl, it goes after the type
+     *   alias's own inherent impls. If it's a trait impl, it goes in the Trait
+     *   Implementations section.
+     *
+     * - After processing all of the impls, it sorts the sidebar items by name.
+     *
+     * @param {{[cratename: string]: Array<Array<string|0>>}} impl
+     */
+    window.register_type_impls = imp => {
+        if (!imp || !imp[window.currentCrate]) {
+            return;
+        }
+        window.pending_type_impls = null;
+        const idMap = new Map();
+
+        let implementations = document.getElementById("implementations-list");
+        let trait_implementations = document.getElementById("trait-implementations-list");
+        let trait_implementations_header = document.getElementById("trait-implementations");
+
+        // We want to include the current type alias's impls, and no others.
+        const script = document.querySelector("script[data-self-path]");
+        const selfPath = script ? script.getAttribute("data-self-path") : null;
+
+        // These sidebar blocks need filled in, too.
+        const mainContent = document.querySelector("#main-content");
+        const sidebarSection = document.querySelector(".sidebar section");
+        let methods = document.querySelector(".sidebar .block.method");
+        let associatedTypes = document.querySelector(".sidebar .block.associatedtype");
+        let associatedConstants = document.querySelector(".sidebar .block.associatedconstant");
+        let sidebarTraitList = document.querySelector(".sidebar .block.trait-implementation");
+
+        for (const impList of imp[window.currentCrate]) {
+            const types = impList.slice(2);
+            const text = impList[0];
+            const isTrait = impList[1] !== 0;
+            const traitName = impList[1];
+            if (types.indexOf(selfPath) === -1) {
+                continue;
+            }
+            let outputList = isTrait ? trait_implementations : implementations;
+            if (outputList === null) {
+                const outputListName = isTrait ? "Trait Implementations" : "Implementations";
+                const outputListId = isTrait ?
+                    "trait-implementations-list" :
+                    "implementations-list";
+                const outputListHeaderId = isTrait ? "trait-implementations" : "implementations";
+                const outputListHeader = document.createElement("h2");
+                outputListHeader.id = outputListHeaderId;
+                outputListHeader.innerText = outputListName;
+                outputList = document.createElement("div");
+                outputList.id = outputListId;
+                if (isTrait) {
+                    const link = document.createElement("a");
+                    link.href = `#${outputListHeaderId}`;
+                    link.innerText = "Trait Implementations";
+                    const h = document.createElement("h3");
+                    h.appendChild(link);
+                    trait_implementations = outputList;
+                    trait_implementations_header = outputListHeader;
+                    sidebarSection.appendChild(h);
+                    sidebarTraitList = document.createElement("ul");
+                    sidebarTraitList.className = "block trait-implementation";
+                    sidebarSection.appendChild(sidebarTraitList);
+                    mainContent.appendChild(outputListHeader);
+                    mainContent.appendChild(outputList);
+                } else {
+                    implementations = outputList;
+                    if (trait_implementations) {
+                        mainContent.insertBefore(outputListHeader, trait_implementations_header);
+                        mainContent.insertBefore(outputList, trait_implementations_header);
+                    } else {
+                        const mainContent = document.querySelector("#main-content");
+                        mainContent.appendChild(outputListHeader);
+                        mainContent.appendChild(outputList);
+                    }
+                }
+            }
+            const template = document.createElement("template");
+            template.innerHTML = text;
+
+            onEachLazy(template.content.querySelectorAll("a"), elem => {
+                const href = elem.getAttribute("href");
+
+                if (href && !href.startsWith("#") && !/^(?:[a-z+]+:)?\/\//.test(href)) {
+                    elem.setAttribute("href", window.rootPath + href);
+                }
+            });
+            onEachLazy(template.content.querySelectorAll("[id]"), el => {
+                let i = 0;
+                if (idMap.has(el.id)) {
+                    i = idMap.get(el.id);
+                } else if (document.getElementById(el.id)) {
+                    i = 1;
+                    while (document.getElementById(`${el.id}-${2 * i}`)) {
+                        i = 2 * i;
+                    }
+                    while (document.getElementById(`${el.id}-${i}`)) {
+                        i += 1;
+                    }
+                }
+                if (i !== 0) {
+                    const oldHref = `#${el.id}`;
+                    const newHref = `#${el.id}-${i}`;
+                    el.id = `${el.id}-${i}`;
+                    onEachLazy(template.content.querySelectorAll("a[href]"), link => {
+                        if (link.getAttribute("href") === oldHref) {
+                            link.href = newHref;
+                        }
+                    });
+                }
+                idMap.set(el.id, i + 1);
+            });
+            const templateAssocItems = template.content.querySelectorAll("section.tymethod, " +
+                "section.method, section.associatedtype, section.associatedconstant");
+            if (isTrait) {
+                const li = document.createElement("li");
+                const a = document.createElement("a");
+                a.href = `#${template.content.querySelector(".impl").id}`;
+                a.textContent = traitName;
+                li.appendChild(a);
+                sidebarTraitList.append(li);
+            } else {
+                onEachLazy(templateAssocItems, item => {
+                    let block = hasClass(item, "associatedtype") ? associatedTypes : (
+                        hasClass(item, "associatedconstant") ? associatedConstants : (
+                        methods));
+                    if (!block) {
+                        const blockTitle = hasClass(item, "associatedtype") ? "Associated Types" : (
+                            hasClass(item, "associatedconstant") ? "Associated Constants" : (
+                            "Methods"));
+                        const blockClass = hasClass(item, "associatedtype") ? "associatedtype" : (
+                            hasClass(item, "associatedconstant") ? "associatedconstant" : (
+                            "method"));
+                        const blockHeader = document.createElement("h3");
+                        const blockLink = document.createElement("a");
+                        blockLink.href = "#implementations";
+                        blockLink.innerText = blockTitle;
+                        blockHeader.appendChild(blockLink);
+                        block = document.createElement("ul");
+                        block.className = `block ${blockClass}`;
+                        const insertionReference = methods || sidebarTraitList;
+                        if (insertionReference) {
+                            const insertionReferenceH = insertionReference.previousElementSibling;
+                            sidebarSection.insertBefore(blockHeader, insertionReferenceH);
+                            sidebarSection.insertBefore(block, insertionReferenceH);
+                        } else {
+                            sidebarSection.appendChild(blockHeader);
+                            sidebarSection.appendChild(block);
+                        }
+                        if (hasClass(item, "associatedtype")) {
+                            associatedTypes = block;
+                        } else if (hasClass(item, "associatedconstant")) {
+                            associatedConstants = block;
+                        } else {
+                            methods = block;
+                        }
+                    }
+                    const li = document.createElement("li");
+                    const a = document.createElement("a");
+                    a.innerText = item.id.split("-")[0].split(".")[1];
+                    a.href = `#${item.id}`;
+                    li.appendChild(a);
+                    block.appendChild(li);
+                });
+            }
+            outputList.appendChild(template.content);
+        }
+
+        for (const list of [methods, associatedTypes, associatedConstants, sidebarTraitList]) {
+            if (!list) {
+                continue;
+            }
+            const newChildren = Array.prototype.slice.call(list.children);
+            newChildren.sort((a, b) => {
+                const aI = a.innerText;
+                const bI = b.innerText;
+                return aI < bI ? -1 :
+                    aI > bI ? 1 :
+                    0;
+            });
+            list.replaceChildren(...newChildren);
+        }
+    };
+    if (window.pending_type_impls) {
+        window.register_type_impls(window.pending_type_impls);
+    }
+
     function addSidebarCrates() {
         if (!window.ALL_CRATES) {
             return;
diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html
index a99198141e2..d982134181c 100644
--- a/src/librustdoc/html/templates/sidebar.html
+++ b/src/librustdoc/html/templates/sidebar.html
@@ -18,7 +18,7 @@
                         <h3><a href="#{{block.heading.href|safe}}">{{block.heading.name}}</a></h3>
                     {% endif %}
                     {% if !block.links.is_empty() %}
-                        <ul class="block">
+                        <ul class="block{% if !block.class.is_empty() +%} {{+block.class}}{% endif %}">
                             {% for link in block.links %}
                                 <li><a href="#{{link.href|safe}}">{{link.name}}</a></li>
                             {% endfor %}
diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs
index ff89d4e0887..a57321b5822 100644
--- a/src/librustdoc/passes/collect_trait_impls.rs
+++ b/src/librustdoc/passes/collect_trait_impls.rs
@@ -36,7 +36,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
     let prims: FxHashSet<PrimitiveType> = local_crate.primitives(tcx).iter().map(|p| p.1).collect();
 
     let crate_items = {
-        let mut coll = ItemCollector::new();
+        let mut coll = ItemAndAliasCollector::new(&cx.cache);
         cx.sess().time("collect_items_for_trait_impls", || coll.visit_crate(&krate));
         coll.items
     };
@@ -235,21 +235,27 @@ impl<'a, 'tcx> DocVisitor for SyntheticImplCollector<'a, 'tcx> {
     }
 }
 
-#[derive(Default)]
-struct ItemCollector {
+struct ItemAndAliasCollector<'cache> {
     items: FxHashSet<ItemId>,
+    cache: &'cache Cache,
 }
 
-impl ItemCollector {
-    fn new() -> Self {
-        Self::default()
+impl<'cache> ItemAndAliasCollector<'cache> {
+    fn new(cache: &'cache Cache) -> Self {
+        ItemAndAliasCollector { items: FxHashSet::default(), cache }
     }
 }
 
-impl DocVisitor for ItemCollector {
+impl<'cache> DocVisitor for ItemAndAliasCollector<'cache> {
     fn visit_item(&mut self, i: &Item) {
         self.items.insert(i.item_id);
 
+        if let TypeAliasItem(alias) = &*i.kind &&
+            let Some(did) = alias.type_.def_id(self.cache)
+        {
+            self.items.insert(ItemId::DefId(did));
+        }
+
         self.visit_item_recur(i)
     }
 }
diff --git a/tests/rustdoc-gui/code-tags.goml b/tests/rustdoc-gui/code-tags.goml
index 3405d3295e6..577f932e576 100644
--- a/tests/rustdoc-gui/code-tags.goml
+++ b/tests/rustdoc-gui/code-tags.goml
@@ -1,6 +1,6 @@
 // This test ensures that items and documentation code blocks are wrapped in <pre><code>
 
-// We need to disable this check because `implementors/test_docs/trait.AnotherOne.js`
+// We need to disable this check because `trait.impl/test_docs/trait.AnotherOne.js`
 // doesn't exist.
 fail-on-request-error: false
 go-to: "file://" + |DOC_PATH| + "/test_docs/fn.foo.html"
diff --git a/tests/rustdoc-gui/item-decl-colors.goml b/tests/rustdoc-gui/item-decl-colors.goml
index a777842735c..7bbd20c4ee0 100644
--- a/tests/rustdoc-gui/item-decl-colors.goml
+++ b/tests/rustdoc-gui/item-decl-colors.goml
@@ -1,6 +1,6 @@
 // This test ensures that the color of the items in the type decl are working as expected.
 
-// We need to disable this check because `implementors/test_docs/trait.TraitWithoutGenerics.js`
+// We need to disable this check because `trait.impl/test_docs/trait.TraitWithoutGenerics.js`
 // doesn't exist.
 fail-on-request-error: false
 
diff --git a/tests/rustdoc-gui/no-docblock.goml b/tests/rustdoc-gui/no-docblock.goml
index 1b4638ef067..2115b6f5390 100644
--- a/tests/rustdoc-gui/no-docblock.goml
+++ b/tests/rustdoc-gui/no-docblock.goml
@@ -1,6 +1,6 @@
 // This test checks that there are margins applied to methods with no docblocks.
 
-// We need to disable this check because `implementors/test_docs/trait.TraitWithNoDocblock.js`
+// We need to disable this check because `trait.impl/test_docs/trait.TraitWithNoDocblock.js`
 // doesn't exist.
 fail-on-request-error: false
 
diff --git a/tests/rustdoc-gui/search-tab.goml b/tests/rustdoc-gui/search-tab.goml
index 427201e1b5d..1c0c9e79e40 100644
--- a/tests/rustdoc-gui/search-tab.goml
+++ b/tests/rustdoc-gui/search-tab.goml
@@ -79,8 +79,8 @@ call-function: ("check-colors", {
 set-window-size: (851, 600)
 
 // Check the size and count in tabs
-assert-text: ("#search-tabs > button:nth-child(1) > .count", " (23) ")
-assert-text: ("#search-tabs > button:nth-child(2) > .count", " (4)  ")
+assert-text: ("#search-tabs > button:nth-child(1) > .count", " (24) ")
+assert-text: ("#search-tabs > button:nth-child(2) > .count", " (5)  ")
 assert-text: ("#search-tabs > button:nth-child(3) > .count", " (0)  ")
 store-property: ("#search-tabs > button:nth-child(1)", {"offsetWidth": buttonWidth})
 assert-property: ("#search-tabs > button:nth-child(2)", {"offsetWidth": |buttonWidth|})
diff --git a/tests/rustdoc-gui/setting-auto-hide-content-large-items.goml b/tests/rustdoc-gui/setting-auto-hide-content-large-items.goml
index 6cd725043f4..b55a1cfd92b 100644
--- a/tests/rustdoc-gui/setting-auto-hide-content-large-items.goml
+++ b/tests/rustdoc-gui/setting-auto-hide-content-large-items.goml
@@ -1,7 +1,7 @@
 // This test ensures that the "Auto-hide item contents for large items" setting is working as
 // expected.
 
-// We need to disable this check because `implementors/test_docs/trait.Iterator.js` doesn't exist.
+// We need to disable this check because `trait.impl/test_docs/trait.Iterator.js` doesn't exist.
 fail-on-request-error: false
 
 define-function: (
diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs
index 5c91bcbb4ee..138a1b302fd 100644
--- a/tests/rustdoc-gui/src/test_docs/lib.rs
+++ b/tests/rustdoc-gui/src/test_docs/lib.rs
@@ -160,6 +160,33 @@ pub mod keyword {}
 /// Just some type alias.
 pub type SomeType = u32;
 
+/// Another type alias, this time with methods.
+pub type SomeOtherTypeWithMethodsAndInlining = Foo;
+
+impl SomeOtherTypeWithMethodsAndInlining {
+    pub fn some_other_method_directly(&self) {}
+}
+
+/// Another type alias, this time with methods.
+pub struct UnderlyingFooBarBaz;
+pub type SomeOtherTypeWithMethodsAndInliningAndTraits = UnderlyingFooBarBaz;
+
+impl AsRef<str> for UnderlyingFooBarBaz {
+    fn as_ref(&self) -> &str {
+        "hello"
+    }
+}
+
+impl UnderlyingFooBarBaz {
+    pub fn inherent_fn(&self) {}
+}
+
+impl AsRef<u8> for SomeOtherTypeWithMethodsAndInliningAndTraits {
+    fn as_ref(&self) -> &u8 {
+        b"hello"
+    }
+}
+
 pub mod huge_amount_of_consts {
     include!(concat!(env!("OUT_DIR"), "/huge_amount_of_consts.rs"));
 }
diff --git a/tests/rustdoc-gui/trait-sidebar-item-order.goml b/tests/rustdoc-gui/trait-sidebar-item-order.goml
index 9330ef040ec..73e362ca813 100644
--- a/tests/rustdoc-gui/trait-sidebar-item-order.goml
+++ b/tests/rustdoc-gui/trait-sidebar-item-order.goml
@@ -1,6 +1,6 @@
 // Checks that the elements in the sidebar are alphabetically sorted.
 
-// We need to disable this check because `implementors/test_docs/trait.AnotherOne.js`
+// We need to disable this check because `trait.impl/test_docs/trait.AnotherOne.js`
 // doesn't exist.
 fail-on-request-error: false
 
diff --git a/tests/rustdoc-gui/type-declation-overflow.goml b/tests/rustdoc-gui/type-declation-overflow.goml
index f212781e9b3..5780f5c88f8 100644
--- a/tests/rustdoc-gui/type-declation-overflow.goml
+++ b/tests/rustdoc-gui/type-declation-overflow.goml
@@ -2,7 +2,7 @@
 // This test ensures that the items declaration content overflow is handled inside the <pre> directly.
 
 // We need to disable this check because
-// `implementors/test_docs/trait.ALongNameBecauseItHelpsTestingTheCurrentProblem.js`
+// `trait.impl/test_docs/trait.ALongNameBecauseItHelpsTestingTheCurrentProblem.js`
 // doesn't exist.
 fail-on-request-error: false
 
diff --git a/tests/rustdoc-gui/type-impls.goml b/tests/rustdoc-gui/type-impls.goml
new file mode 100644
index 00000000000..870a9cbe53f
--- /dev/null
+++ b/tests/rustdoc-gui/type-impls.goml
@@ -0,0 +1,86 @@
+// The goal of this test is to check that the inlined type alias impls, generated with JS,
+// have the same display than the "local" ones.
+go-to: "file://" + |DOC_PATH| + "/test_docs/type.SomeOtherTypeWithMethodsAndInlining.html"
+
+// method directly on type alias
+wait-for: "//*[@id='method.some_other_method_directly']"
+
+// methods on foo
+assert: "//*[@id='method.as_ref']"
+assert: "//*[@id='method.must_use']"
+assert: "//*[@id='method.warning1']"
+assert: "//*[@id='method.warning2']"
+
+// sidebar items
+assert: "//*[@class='sidebar-elems']//li/a[@href='#method.must_use']"
+assert: "//*[@class='sidebar-elems']//li/a[@href='#method.some_other_method_directly']"
+assert: "//*[@class='sidebar-elems']//li/a[@href='#method.warning1']"
+assert: "//*[@class='sidebar-elems']//li/a[@href='#method.warning2']"
+assert: "//*[@class='sidebar-elems']//li/a[@href='#impl-AsRef%3Cstr%3E-for-Foo']"
+
+// sorting
+assert-text: (".block.method li:nth-child(1)", 'must_use')
+assert-text: (".block.method li:nth-child(2)", 'some_other_method_directly')
+assert-text: (".block.method li:nth-child(3)", 'warning1')
+assert-text: (".block.method li:nth-child(4)", 'warning2')
+
+// Now try trait implementation merging and duplicate renumbering
+go-to: "file://" + |DOC_PATH| + "/test_docs/type.SomeOtherTypeWithMethodsAndInliningAndTraits.html"
+
+// method directly on type alias
+assert: "//*[@id='method.as_ref']"
+assert-count: ("//*[@id='method.as_ref']", 1)
+// method on underlying type
+assert: "//*[@id='method.as_ref-1']"
+
+// sidebar items
+assert-count: (
+    "//*[@class='sidebar-elems']//h3/a[@href='#trait-implementations']",
+    1
+)
+assert-text: ("//*[@class='sidebar-elems']//li/a[@href='#impl-AsRef%3Cstr%3E-for-UnderlyingFooBarBaz']", "AsRef<str>")
+assert-text: (
+    "//*[@class='sidebar-elems']//li/a[@href='#impl-AsRef%3Cu8%3E-for-UnderlyingFooBarBaz']",
+    "AsRef<u8>"
+)
+assert-count: ("#trait-implementations-list", 1)
+assert-count: ("#trait-implementations-list > details", 2)
+// Both links point at the underlying trait
+store-property: ("//*[@id='method.as_ref']//a[@class='fn']", {"href": href})
+assert-property: ("//*[@id='method.as_ref-1']//a[@class='fn']", {"href": |href|})
+// Both links have a self-anchor
+assert: "//*[@id='method.as_ref']//a[@class='anchor'][@href='#method.as_ref']"
+assert: "//*[@id='method.as_ref-1']//a[@class='anchor'][@href='#method.as_ref-1']"
+
+///////////////////////////////////////////////////////////////////////////
+// Now, if JavaScript is disabled, only the first method will be present //
+///////////////////////////////////////////////////////////////////////////
+javascript: false
+go-to: "file://" + |DOC_PATH| + "/test_docs/type.SomeOtherTypeWithMethodsAndInlining.html"
+
+// method directly on type alias
+wait-for: "//*[@id='method.some_other_method_directly']"
+
+// methods on foo
+assert-false: "//*[@id='method.must_use']"
+assert-false: "//*[@id='method.warning1']"
+assert-false: "//*[@id='method.warning2']"
+
+// Now try trait implementation merging and duplicate renumbering
+go-to: "file://" + |DOC_PATH| + "/test_docs/type.SomeOtherTypeWithMethodsAndInliningAndTraits.html"
+
+// methods directly on type alias
+assert: "//*[@id='method.as_ref']"
+assert-count: ("//*[@id='method.as_ref']", 1)
+// method on target type
+assert-false: "//*[@id='method.as_ref-1']"
+
+// sidebar items
+assert-count: (
+    "//*[@class='sidebar-elems']//h3/a[@href='#trait-implementations']",
+    1
+)
+assert-false: "//a[@href='#impl-AsRef%3Cstr%3E-for-UnderlyingFooBarBaz']"
+assert: "//a[@href='#impl-AsRef%3Cu8%3E-for-UnderlyingFooBarBaz']"
+assert-count: ("#trait-implementations-list", 1)
+assert-count: ("#trait-implementations-list > details", 1)
diff --git a/tests/rustdoc/deref/deref-mut-methods.rs b/tests/rustdoc/deref/deref-mut-methods.rs
index fdf8434224f..65681f81245 100644
--- a/tests/rustdoc/deref/deref-mut-methods.rs
+++ b/tests/rustdoc/deref/deref-mut-methods.rs
@@ -9,7 +9,7 @@ impl Foo {
 }
 
 // @has foo/struct.Bar.html
-// @has - '//*[@class="sidebar-elems"]//*[@class="block"]//a[@href="#method.foo"]' 'foo'
+// @has - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.foo"]' 'foo'
 pub struct Bar {
     foo: Foo,
 }
diff --git a/tests/rustdoc/deref/deref-recursive-pathbuf.rs b/tests/rustdoc/deref/deref-recursive-pathbuf.rs
index be2b42b5ac6..7aee3147ba8 100644
--- a/tests/rustdoc/deref/deref-recursive-pathbuf.rs
+++ b/tests/rustdoc/deref/deref-recursive-pathbuf.rs
@@ -8,9 +8,9 @@
 // @has '-' '//*[@id="deref-methods-Path"]' 'Methods from Deref<Target = Path>'
 // @has '-' '//*[@class="impl-items"]//*[@id="method.exists"]' 'pub fn exists(&self)'
 // @has '-' '//div[@class="sidebar-elems"]//h3/a[@href="#deref-methods-PathBuf"]' 'Methods from Deref<Target=PathBuf>'
-// @has '-' '//*[@class="sidebar-elems"]//*[@class="block"]//a[@href="#method.as_path"]' 'as_path'
+// @has '-' '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.as_path"]' 'as_path'
 // @has '-' '//div[@class="sidebar-elems"]//h3/a[@href="#deref-methods-Path"]' 'Methods from Deref<Target=Path>'
-// @has '-' '//*[@class="sidebar-elems"]//*[@class="block"]//a[@href="#method.exists"]' 'exists'
+// @has '-' '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.exists"]' 'exists'
 
 #![crate_name = "foo"]
 
diff --git a/tests/rustdoc/hidden-impls.rs b/tests/rustdoc/hidden-impls.rs
index 26e2e0e0660..3283fbfecce 100644
--- a/tests/rustdoc/hidden-impls.rs
+++ b/tests/rustdoc/hidden-impls.rs
@@ -12,6 +12,6 @@ pub mod __hidden {
 
 // @has foo/trait.Clone.html
 // @!hasraw - 'Foo'
-// @has implementors/core/clone/trait.Clone.js
+// @has trait.impl/core/clone/trait.Clone.js
 // @!hasraw - 'Foo'
 pub use std::clone::Clone;
diff --git a/tests/rustdoc/impl-parts-crosscrate.rs b/tests/rustdoc/impl-parts-crosscrate.rs
index 34733f1f8cc..da109ea7090 100644
--- a/tests/rustdoc/impl-parts-crosscrate.rs
+++ b/tests/rustdoc/impl-parts-crosscrate.rs
@@ -12,7 +12,7 @@ pub struct Bar<T> { t: T }
 // full impl string.  Instead, just make sure something from each part
 // is mentioned.
 
-// @hasraw implementors/rustdoc_impl_parts_crosscrate/trait.AnAutoTrait.js Bar
+// @hasraw trait.impl/rustdoc_impl_parts_crosscrate/trait.AnAutoTrait.js Bar
 // @hasraw - Send
 // @hasraw - !AnAutoTrait
 // @hasraw - Copy
diff --git a/tests/rustdoc/inline_cross/implementors-js.rs b/tests/rustdoc/inline_cross/implementors-js.rs
index c79f05d8d3c..c17d52d0f41 100644
--- a/tests/rustdoc/inline_cross/implementors-js.rs
+++ b/tests/rustdoc/inline_cross/implementors-js.rs
@@ -4,13 +4,13 @@
 
 extern crate implementors_inline;
 
-// @!has implementors/implementors_js/trait.MyTrait.js
-// @has implementors/implementors_inline/my_trait/trait.MyTrait.js
-// @!has implementors/implementors_inline/prelude/trait.MyTrait.js
+// @!has trait.impl/implementors_js/trait.MyTrait.js
+// @has trait.impl/implementors_inline/my_trait/trait.MyTrait.js
+// @!has trait.impl/implementors_inline/prelude/trait.MyTrait.js
 // @has implementors_inline/my_trait/trait.MyTrait.html
-// @has - '//script/@src' '../../implementors/implementors_inline/my_trait/trait.MyTrait.js'
+// @has - '//script/@src' '../../trait.impl/implementors_inline/my_trait/trait.MyTrait.js'
 // @has implementors_js/trait.MyTrait.html
-// @has - '//script/@src' '../implementors/implementors_inline/my_trait/trait.MyTrait.js'
+// @has - '//script/@src' '../trait.impl/implementors_inline/my_trait/trait.MyTrait.js'
 /// When re-exporting this trait, the HTML will be inlined,
 /// but, vitally, the JavaScript will be located only at the
 /// one canonical path.
diff --git a/tests/rustdoc/issue-43701.rs b/tests/rustdoc/issue-43701.rs
index 44335e961f9..de772881e73 100644
--- a/tests/rustdoc/issue-43701.rs
+++ b/tests/rustdoc/issue-43701.rs
@@ -2,4 +2,4 @@
 
 pub use std::vec::Vec;
 
-// @!has implementors/core/clone/trait.Clone.js
+// @!has trait.impl/core/clone/trait.Clone.js
diff --git a/tests/rustdoc/strip-enum-variant.no-not-shown.html b/tests/rustdoc/strip-enum-variant.no-not-shown.html
index 782198956a0..e072335297d 100644
--- a/tests/rustdoc/strip-enum-variant.no-not-shown.html
+++ b/tests/rustdoc/strip-enum-variant.no-not-shown.html
@@ -1 +1 @@
-<ul class="block"><li><a href="#variant.Shown">Shown</a></li></ul>
\ No newline at end of file
+<ul class="block variant"><li><a href="#variant.Shown">Shown</a></li></ul>
\ No newline at end of file
diff --git a/tests/rustdoc/strip-enum-variant.rs b/tests/rustdoc/strip-enum-variant.rs
index 8753a7dc613..2512fa34b39 100644
--- a/tests/rustdoc/strip-enum-variant.rs
+++ b/tests/rustdoc/strip-enum-variant.rs
@@ -3,7 +3,7 @@
 // @!has - '//code' 'NotShown'
 // @has - '//code' '// some variants omitted'
 // Also check that `NotShown` isn't displayed in the sidebar.
-// @snapshot no-not-shown - '//*[@class="sidebar-elems"]/section/*[@class="block"][1]'
+// @snapshot no-not-shown - '//*[@class="sidebar-elems"]/section/*[@class="block variant"]'
 pub enum MyThing {
     Shown,
     #[doc(hidden)]
diff --git a/tests/rustdoc/type-alias/auxiliary/parent-crate-115718.rs b/tests/rustdoc/type-alias/auxiliary/parent-crate-115718.rs
new file mode 100644
index 00000000000..3607612c27a
--- /dev/null
+++ b/tests/rustdoc/type-alias/auxiliary/parent-crate-115718.rs
@@ -0,0 +1,9 @@
+pub struct MyStruct<T>(T);
+
+pub trait MyTrait1 {
+    fn method_trait_1();
+}
+
+impl MyTrait1 for MyStruct<u16> {
+    fn method_trait_1() {}
+}
diff --git a/tests/rustdoc/type-alias/cross-crate-115718.rs b/tests/rustdoc/type-alias/cross-crate-115718.rs
new file mode 100644
index 00000000000..372e62e4213
--- /dev/null
+++ b/tests/rustdoc/type-alias/cross-crate-115718.rs
@@ -0,0 +1,34 @@
+// aux-build: parent-crate-115718.rs
+
+// https://github.com/rust-lang/rust/issues/115718
+#![crate_name = "foo"]
+
+extern crate parent_crate_115718;
+
+use parent_crate_115718::MyStruct;
+
+pub trait MyTrait2 {
+    fn method_trait_2();
+}
+
+impl MyTrait2 for MyStruct<u16> {
+    fn method_trait_2() {}
+}
+
+pub trait MyTrait3 {
+    fn method_trait_3();
+}
+
+impl MyTrait3 for MyType {
+    fn method_trait_3() {}
+}
+
+// @hasraw 'type.impl/parent_crate_115718/struct.MyStruct.js' 'method_trait_1'
+// @hasraw 'type.impl/parent_crate_115718/struct.MyStruct.js' 'method_trait_2'
+// Avoid duplicating these docs.
+// @!hasraw 'foo/type.MyType.html' 'method_trait_1'
+// @!hasraw 'foo/type.MyType.html' 'method_trait_2'
+// The one made directly on the type alias should be attached to the HTML instead.
+// @!hasraw 'type.impl/parent_crate_115718/struct.MyStruct.js' 'method_trait_3'
+// @hasraw 'foo/type.MyType.html' 'method_trait_3'
+pub type MyType = MyStruct<u16>;
diff --git a/tests/rustdoc/issue-112515-impl-ty-alias.rs b/tests/rustdoc/type-alias/deeply-nested-112515.rs
index 161188ee576..161188ee576 100644
--- a/tests/rustdoc/issue-112515-impl-ty-alias.rs
+++ b/tests/rustdoc/type-alias/deeply-nested-112515.rs
diff --git a/tests/rustdoc/type-alias-impls-32077.rs b/tests/rustdoc/type-alias/deref-32077.rs
index 7bb763f86af..186ebb1a632 100644
--- a/tests/rustdoc/type-alias-impls-32077.rs
+++ b/tests/rustdoc/type-alias/deref-32077.rs
@@ -1,6 +1,5 @@
 // Regression test for <https://github.com/rust-lang/rust/issues/32077>.
 
-// https://github.com/rust-lang/rust/issues/32077
 #![crate_name = "foo"]
 
 pub struct GenericStruct<T>(T);
@@ -25,18 +24,19 @@ impl Bar for GenericStruct<u32> {}
 // We check that we have the implementation of the type alias itself.
 // @has - '//*[@id="impl-GenericStruct%3Cu8%3E"]/h3' 'impl TypedefStruct'
 // @has - '//*[@id="method.on_alias"]/h4' 'pub fn on_alias()'
-// @has - '//*[@id="impl-GenericStruct%3CT%3E"]/h3' 'impl<T> GenericStruct<T>'
-// @has - '//*[@id="method.on_gen"]/h4' 'pub fn on_gen(arg: T)'
-// @has - '//*[@id="impl-Foo-for-GenericStruct%3CT%3E"]/h3' 'impl<T> Foo for GenericStruct<T>'
 // This trait implementation doesn't match the type alias parameters so shouldn't appear in docs.
 // @!has - '//h3' 'impl Bar for GenericStruct<u32> {}'
 // Same goes for the `Deref` impl.
 // @!has - '//h2' 'Methods from Deref<Target = u32>'
 // @count - '//nav[@class="sidebar"]//a' 'on_alias' 1
-// @count - '//nav[@class="sidebar"]//a' 'on_gen' 1
-// @count - '//nav[@class="sidebar"]//a' 'Foo' 1
+// @!has - '//nav[@class="sidebar"]//a' 'on_gen'
+// @!has - '//nav[@class="sidebar"]//a' 'Foo'
 // @!has - '//nav[@class="sidebar"]//a' 'Bar'
 // @!has - '//nav[@class="sidebar"]//a' 'on_u32'
+// TypedefStruct inlined to GenericStruct
+// @hasraw 'type.impl/foo/struct.GenericStruct.js' 'TypedefStruct'
+// @hasraw 'type.impl/foo/struct.GenericStruct.js' 'method.on_gen'
+// @hasraw 'type.impl/foo/struct.GenericStruct.js' 'Foo'
 pub type TypedefStruct = GenericStruct<u8>;
 
 impl TypedefStruct {
@@ -54,8 +54,11 @@ impl std::ops::Deref for GenericStruct<u32> {
 pub struct Wrap<T>(GenericStruct<T>);
 
 // @has 'foo/type.Alias.html'
-// @has - '//h2' 'Methods from Deref<Target = u32>'
-// @has - '//*[@id="impl-Deref-for-Wrap%3CT%3E"]/h3' 'impl<T> Deref for Wrap<T>'
+// @!has - '//h2' 'Methods from Deref<Target = u32>'
+// @!has - '//*[@id="impl-Deref-for-Wrap%3CT%3E"]/h3' 'impl<T> Deref for Wrap<T>'
+// @hasraw 'type.impl/foo/struct.Wrap.js' 'impl-Deref-for-Wrap%3CT%3E'
+// Deref Methods aren't gathered for type aliases, though the actual impl is.
+// @!hasraw 'type.impl/foo/struct.Wrap.js' 'BITS'
 pub type Alias = Wrap<u32>;
 
 impl<T> std::ops::Deref for Wrap<T> {
diff --git a/tests/rustdoc/type-alias/same-crate-115718.rs b/tests/rustdoc/type-alias/same-crate-115718.rs
new file mode 100644
index 00000000000..26e5db85cd6
--- /dev/null
+++ b/tests/rustdoc/type-alias/same-crate-115718.rs
@@ -0,0 +1,34 @@
+// https://github.com/rust-lang/rust/issues/115718
+#![crate_name = "foo"]
+
+pub trait MyTrait1 {
+    fn method_trait_1();
+}
+
+pub trait MyTrait2 {
+    fn method_trait_2();
+}
+
+pub struct MyStruct<T>(T);
+
+impl MyStruct<u32> {
+    pub fn method_u32() {}
+}
+
+impl MyStruct<u16> {
+    pub fn method_u16() {}
+}
+
+impl MyTrait1 for MyStruct<u32> {
+    fn method_trait_1() {}
+}
+
+impl MyTrait2 for MyStruct<u16> {
+    fn method_trait_2() {}
+}
+
+// @hasraw 'type.impl/foo/struct.MyStruct.js' 'method_u16'
+// @!hasraw 'type.impl/foo/struct.MyStruct.js' 'method_u32'
+// @!hasraw 'type.impl/foo/struct.MyStruct.js' 'method_trait_1'
+// @hasraw 'type.impl/foo/struct.MyStruct.js' 'method_trait_2'
+pub type MyType = MyStruct<u16>;