From ade7ecf90943a7b865a6281112f8b2bbda55dba2 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 5 Oct 2023 18:44:40 -0700 Subject: [PATCH 01/12] rustdoc: rename `/implementors` to `/impl.trait` This is shorter, avoids potential conflicts with a crate named `implementors`[^1], and will be less confusing when JS include files are added for type aliases. [^1]: AFAIK, this couldn't actually cause any problems right now, but it's simpler just to make it impossible than relying on never having a file named `trait.Foo.js` in the crate data area. --- src/librustdoc/html/render/print_item.rs | 8 ++++---- src/librustdoc/html/render/write_shared.rs | 2 +- tests/rustdoc-gui/code-tags.goml | 2 +- tests/rustdoc-gui/item-decl-colors.goml | 2 +- tests/rustdoc-gui/no-docblock.goml | 2 +- .../setting-auto-hide-content-large-items.goml | 2 +- tests/rustdoc-gui/trait-sidebar-item-order.goml | 2 +- tests/rustdoc-gui/type-declation-overflow.goml | 2 +- tests/rustdoc/hidden-impls.rs | 2 +- tests/rustdoc/impl-parts-crosscrate.rs | 2 +- tests/rustdoc/inline_cross/implementors-js.rs | 10 +++++----- tests/rustdoc/issue-43701.rs | 2 +- 12 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index f6432dc61ae06..b553954845b41 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1101,7 +1101,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 +1110,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 +1127,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 - // // ``` // @@ -1137,7 +1137,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) } && diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index e68d5ab2fbdb5..b162ea99d8f21 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -362,7 +362,7 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; } // Update the list of all implementors for traits - let dst = cx.dst.join("implementors"); + let dst = cx.dst.join("trait.impl"); let cache = cx.cache(); for (&did, imps) in &cache.implementors { // Private modules can leak through to this phase of rustdoc, which diff --git a/tests/rustdoc-gui/code-tags.goml b/tests/rustdoc-gui/code-tags.goml index 3405d3295e69d..577f932e576bf 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

 
-// 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 a777842735c24..7bbd20c4ee006 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 1b4638ef067c5..2115b6f5390c3 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/setting-auto-hide-content-large-items.goml b/tests/rustdoc-gui/setting-auto-hide-content-large-items.goml
index 6cd725043f4ad..b55a1cfd92bc5 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/trait-sidebar-item-order.goml b/tests/rustdoc-gui/trait-sidebar-item-order.goml
index 9330ef040ec9e..73e362ca81349 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 f212781e9b340..5780f5c88f82c 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 
 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/hidden-impls.rs b/tests/rustdoc/hidden-impls.rs
index 26e2e0e066008..3283fbfecce81 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 34733f1f8ccb8..da109ea709001 100644
--- a/tests/rustdoc/impl-parts-crosscrate.rs
+++ b/tests/rustdoc/impl-parts-crosscrate.rs
@@ -12,7 +12,7 @@ pub struct Bar { 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 c79f05d8d3c9b..c17d52d0f4147 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 44335e961f90d..de772881e7323 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

From aa76a59bf024f7b721e7e0da6d39b30a3f7adbc5 Mon Sep 17 00:00:00 2001
From: Michael Howell 
Date: Thu, 5 Oct 2023 18:44:42 -0700
Subject: [PATCH 02/12] Revert "rustdoc: filter before storing in vec"

This reverts commit c79b960747487f6724ebe5b163a22c82a2b636d3.
---
 src/librustdoc/html/render/context.rs | 14 ++++----------
 1 file changed, 4 insertions(+), 10 deletions(-)

diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index bf8d1a80337e8..7e432ecedc563 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -160,15 +160,8 @@ impl SharedContext<'_> {
     ) -> 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();
+        let mut v: Vec<&formats::Impl> =
+            cache.impls.get(&did).map(Vec::as_slice).unwrap_or(&[]).iter().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) &&
@@ -189,12 +182,13 @@ impl SharedContext<'_> {
             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
                 }
             }));
         }
+        let mut saw_impls = FxHashSet::default();
+        v.retain(|i| saw_impls.insert(i.def_id()));
         v
     }
 }

From ab125a2e0b52601a5de1b85a937a4eea2309669c Mon Sep 17 00:00:00 2001
From: Michael Howell 
Date: Thu, 5 Oct 2023 18:44:43 -0700
Subject: [PATCH 03/12] Revert "rustdoc: factor all-impls-for-item out into its
 own method"

This reverts commit c3e5ad448b87be31e570c048cf7ba3b1e7daec44.
---
 src/librustdoc/html/render/context.rs | 47 ++------------------------
 src/librustdoc/html/render/mod.rs     | 48 ++++++++++++++++++++++-----
 src/librustdoc/html/render/sidebar.rs | 38 +++++++++++++++++++--
 3 files changed, 78 insertions(+), 55 deletions(-)

diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 7e432ecedc563..6da9e45a1da40 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,47 +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 v: Vec<&formats::Impl> =
-            cache.impls.get(&did).map(Vec::as_slice).unwrap_or(&[]).iter().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())
-                } else {
-                    false
-                }
-            }));
-        }
-        let mut saw_impls = FxHashSet::default();
-        v.retain(|i| saw_impls.insert(i.def_id()));
-        v
-    }
 }
 
 impl<'tcx> Context<'tcx> {
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 89e29d8b59b2f..515dedbb85fb5 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -55,6 +55,7 @@ use rustc_hir::def_id::{DefId, DefIdSet};
 use rustc_hir::Mutability;
 use rustc_middle::middle::stability;
 use rustc_middle::ty::{self, TyCtxt};
+use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
 use rustc_span::{
     symbol::{sym, Symbol},
     BytePos, FileName, RealFileName,
@@ -62,6 +63,7 @@ use rustc_span::{
 use serde::ser::{SerializeMap, SerializeSeq};
 use serde::{Serialize, Serializer};
 
+use crate::clean::types::TypeAliasItem;
 use crate::clean::{self, ItemId, RenderedLink, SelfTy};
 use crate::error::Error;
 use crate::formats::cache::Cache;
@@ -1132,13 +1134,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(())
     })
 }
@@ -1147,16 +1149,46 @@ 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 cache = &shared.cache;
+    let tcx = cx.tcx();
+    let av = if let TypeAliasItem(ait) = &*containing_item.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(mut av) = cache.impls.get(&aliased_type_defid).cloned() &&
+        let Some(alias_def_id) = containing_item.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,
+        };
+        av.retain(|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())
+            } else {
+                false
+            }
+        });
+        av
+    } else {
+        Vec::new()
+    };
+    let blank = Vec::new();
+    let v = cache.impls.get(&it).unwrap_or(&blank);
+    let (non_trait, traits): (Vec<_>, _) =
+        v.iter().chain(&av[..]).partition(|i| i.inner_impl().trait_.is_none());
     let mut saw_impls = FxHashSet::default();
     if !non_trait.is_empty() {
         let mut tmp_buf = Buffer::html();
diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs
index fb429f237e3db..8ff95477dd810 100644
--- a/src/librustdoc/html/render/sidebar.rs
+++ b/src/librustdoc/html/render/sidebar.rs
@@ -3,10 +3,12 @@ use std::{borrow::Cow, rc::Rc};
 use askama::Template;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::{def::CtorKind, def_id::DefIdSet};
+use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
 use rustc_middle::ty::{self, TyCtxt};
 
 use crate::{
     clean,
+    clean::types::TypeAliasItem,
     formats::{item_type::ItemType, Impl},
     html::{format::Buffer, markdown::IdMap},
 };
@@ -287,8 +289,40 @@ fn sidebar_assoc_items<'a>(
     links: &mut Vec>,
 ) {
     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 tcx = cx.tcx();
+    let mut v: Vec<&Impl> =
+        cache.impls.get(&did).map(Vec::as_slice).unwrap_or(&[]).iter().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())
+            } else {
+                false
+            }
+        }));
+    }
+    let v = {
+        let mut saw_impls = FxHashSet::default();
+        v.retain(|i| saw_impls.insert(i.def_id()));
+        v.as_slice()
+    };
 
     let mut assoc_consts = Vec::new();
     let mut methods = Vec::new();

From 36b8d58b41c6071bff0f5640b0e01f83cae8cd21 Mon Sep 17 00:00:00 2001
From: Michael Howell 
Date: Thu, 5 Oct 2023 18:44:44 -0700
Subject: [PATCH 04/12] Revert "rustdoc: add impl items from aliased type into
 sidebar"

This reverts commit d882b2118e505d86a9f770ef862fb1ee6e91ced8.
---
 src/librustdoc/html/render/sidebar.rs   | 45 +++----------------------
 tests/rustdoc/type-alias-impls-32077.rs |  5 ---
 2 files changed, 5 insertions(+), 45 deletions(-)

diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs
index 8ff95477dd810..9f7744b45d36e 100644
--- a/src/librustdoc/html/render/sidebar.rs
+++ b/src/librustdoc/html/render/sidebar.rs
@@ -3,12 +3,10 @@ use std::{borrow::Cow, rc::Rc};
 use askama::Template;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::{def::CtorKind, def_id::DefIdSet};
-use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
 use rustc_middle::ty::{self, TyCtxt};
 
 use crate::{
     clean,
-    clean::types::TypeAliasItem,
     formats::{item_type::ItemType, Impl},
     html::{format::Buffer, markdown::IdMap},
 };
@@ -290,43 +288,10 @@ fn sidebar_assoc_items<'a>(
 ) {
     let did = it.item_id.expect_def_id();
     let cache = cx.cache();
-    let tcx = cx.tcx();
-    let mut v: Vec<&Impl> =
-        cache.impls.get(&did).map(Vec::as_slice).unwrap_or(&[]).iter().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())
-            } else {
-                false
-            }
-        }));
-    }
-    let v = {
-        let mut saw_impls = FxHashSet::default();
-        v.retain(|i| saw_impls.insert(i.def_id()));
-        v.as_slice()
-    };
 
     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();
 
@@ -362,7 +327,7 @@ fn sidebar_assoc_items<'a>(
                     cx,
                     &mut deref_methods,
                     impl_,
-                    v.iter().copied(),
+                    v,
                     &mut derefs,
                     &mut used_links,
                 );
@@ -392,7 +357,7 @@ fn sidebar_deref_methods<'a>(
     cx: &'a Context<'_>,
     out: &mut Vec>,
     impl_: &Impl,
-    v: impl Iterator,
+    v: &[Impl],
     derefs: &mut DefIdSet,
     used_links: &mut FxHashSet,
 ) {
@@ -417,7 +382,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(|| {
@@ -468,7 +433,7 @@ fn sidebar_deref_methods<'a>(
                 cx,
                 out,
                 target_deref_impl,
-                target_impls.iter(),
+                target_impls,
                 derefs,
                 used_links,
             );
diff --git a/tests/rustdoc/type-alias-impls-32077.rs b/tests/rustdoc/type-alias-impls-32077.rs
index 7bb763f86afb4..2108749d1a6e4 100644
--- a/tests/rustdoc/type-alias-impls-32077.rs
+++ b/tests/rustdoc/type-alias-impls-32077.rs
@@ -32,11 +32,6 @@ impl Bar for GenericStruct {}
 // @!has - '//h3' 'impl Bar for GenericStruct {}'
 // Same goes for the `Deref` impl.
 // @!has - '//h2' 'Methods from Deref'
-// @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' 'Bar'
-// @!has - '//nav[@class="sidebar"]//a' 'on_u32'
 pub type TypedefStruct = GenericStruct;
 
 impl TypedefStruct {

From 77da7c655ee2583f336f23f61246bc0885beaf5d Mon Sep 17 00:00:00 2001
From: Michael Howell 
Date: Thu, 5 Oct 2023 18:44:46 -0700
Subject: [PATCH 05/12] Revert "Add note about lazy_type_alias"

This reverts commit b3686c2fd6ad57912e1b0e778bedb0b9a05c73fa.
---
 src/librustdoc/html/render/mod.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 515dedbb85fb5..029ad750f5b95 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -1168,8 +1168,8 @@ fn render_assoc_items_inner(
         // 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.
+        // If that changes, then this will need to check them with type
+        // unification.
         let aliased_ty = tcx.type_of(alias_def_id).skip_binder();
         let reject_cx = DeepRejectCtxt {
             treat_obligation_params: TreatParams::AsCandidateKey,

From e701e64d59b8666161ca526a3c1bf3048019c8d9 Mon Sep 17 00:00:00 2001
From: Michael Howell 
Date: Thu, 5 Oct 2023 18:44:48 -0700
Subject: [PATCH 06/12] Revert "rustdoc: list matching impls on type aliases"

This reverts commit 19edb3ce808ee2b1190026b9d56cc6187e1ad9b1.
---
 src/librustdoc/html/render/mod.rs       | 46 ++----------------
 tests/rustdoc/type-alias-impls-32077.rs | 62 -------------------------
 2 files changed, 4 insertions(+), 104 deletions(-)
 delete mode 100644 tests/rustdoc/type-alias-impls-32077.rs

diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 029ad750f5b95..51cc836cd157d 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -63,7 +63,6 @@ use rustc_span::{
 use serde::ser::{SerializeMap, SerializeSeq};
 use serde::{Serialize, Serializer};
 
-use crate::clean::types::TypeAliasItem;
 use crate::clean::{self, ItemId, RenderedLink, SelfTy};
 use crate::error::Error;
 use crate::formats::cache::Cache;
@@ -1156,40 +1155,8 @@ fn render_assoc_items_inner(
     info!("Documenting associated items of {:?}", containing_item.name);
     let shared = Rc::clone(&cx.shared);
     let cache = &shared.cache;
-    let tcx = cx.tcx();
-    let av = if let TypeAliasItem(ait) = &*containing_item.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(mut av) = cache.impls.get(&aliased_type_defid).cloned() &&
-        let Some(alias_def_id) = containing_item.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
-        //
-        // If that changes, then this will need to check them with type
-        // unification.
-        let aliased_ty = tcx.type_of(alias_def_id).skip_binder();
-        let reject_cx = DeepRejectCtxt {
-            treat_obligation_params: TreatParams::AsCandidateKey,
-        };
-        av.retain(|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())
-            } else {
-                false
-            }
-        });
-        av
-    } else {
-        Vec::new()
-    };
-    let blank = Vec::new();
-    let v = cache.impls.get(&it).unwrap_or(&blank);
-    let (non_trait, traits): (Vec<_>, _) =
-        v.iter().chain(&av[..]).partition(|i| i.inner_impl().trait_.is_none());
-    let mut saw_impls = FxHashSet::default();
+    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 {
@@ -1218,9 +1185,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,
@@ -1266,10 +1230,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/tests/rustdoc/type-alias-impls-32077.rs b/tests/rustdoc/type-alias-impls-32077.rs
deleted file mode 100644
index 2108749d1a6e4..0000000000000
--- a/tests/rustdoc/type-alias-impls-32077.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-// Regression test for .
-
-// https://github.com/rust-lang/rust/issues/32077
-#![crate_name = "foo"]
-
-pub struct GenericStruct(T);
-
-impl GenericStruct {
-    pub fn on_gen(arg: T) {}
-}
-
-impl GenericStruct {
-    pub fn on_u32(arg: u32) {}
-}
-
-pub trait Foo {}
-pub trait Bar {}
-
-impl Foo for GenericStruct {}
-impl Bar for GenericStruct {}
-
-// @has 'foo/type.TypedefStruct.html'
-// We check that "Aliased type" is also present as a title in the sidebar.
-// @has - '//*[@class="sidebar-elems"]//h3/a[@href="#aliased-type"]' 'Aliased type'
-// 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 GenericStruct'
-// @has - '//*[@id="method.on_gen"]/h4' 'pub fn on_gen(arg: T)'
-// @has - '//*[@id="impl-Foo-for-GenericStruct%3CT%3E"]/h3' 'impl Foo for GenericStruct'
-// This trait implementation doesn't match the type alias parameters so shouldn't appear in docs.
-// @!has - '//h3' 'impl Bar for GenericStruct {}'
-// Same goes for the `Deref` impl.
-// @!has - '//h2' 'Methods from Deref'
-pub type TypedefStruct = GenericStruct;
-
-impl TypedefStruct {
-    pub fn on_alias() {}
-}
-
-impl std::ops::Deref for GenericStruct {
-    type Target = u32;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-pub struct Wrap(GenericStruct);
-
-// @has 'foo/type.Alias.html'
-// @has - '//h2' 'Methods from Deref'
-// @has - '//*[@id="impl-Deref-for-Wrap%3CT%3E"]/h3' 'impl Deref for Wrap'
-pub type Alias = Wrap;
-
-impl std::ops::Deref for Wrap {
-    type Target = GenericStruct;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}

From 4dfd82713353a40058a674d7df3a3c05cf3dcfd6 Mon Sep 17 00:00:00 2001
From: Michael Howell 
Date: Thu, 5 Oct 2023 18:44:49 -0700
Subject: [PATCH 07/12] Added a new item, need to bump this count

---
 tests/rustdoc-gui/search-tab.goml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/rustdoc-gui/search-tab.goml b/tests/rustdoc-gui/search-tab.goml
index 427201e1b5da4..1c0c9e79e4050 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|})

From fa10e4d667aea7ca869eb53f9af925a5fa120c84 Mon Sep 17 00:00:00 2001
From: Michael Howell 
Date: Thu, 5 Oct 2023 18:44:52 -0700
Subject: [PATCH 08/12] rustdoc: use JS to inline target type impl docs into
 alias

This is an attempt to balance three problems, each of which would
be violated by a simpler implementation:

- A type alias should show all the `impl` blocks for the target
  type, and vice versa, if they're applicable. If nothing was
  done, and rustdoc continues to match them up in HIR, this
  would not work.

- Copying the target type's docs into its aliases' HTML pages
  directly causes far too much redundant HTML text to be generated
  when a crate has large numbers of methods and large numbers
  of type aliases.

- Using JavaScript exclusively for type alias impl docs would
  be a functional regression, and could make some docs very hard
  to find for non-JS readers.

- Making sure that only applicable docs are show in the
  resulting page requires a type checkers. Do not reimplement
  the type checker in JavaScript.

So, to make it work, rustdoc stashes these type-alias-inlined docs
in a JSONP "database-lite". The file is generated in `write_shared.rs`,
included in a `",
+            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 9f7744b45d36e..4e8d88c55b64f 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 `

` tag heading: Link<'a>, + class: &'static str, links: Vec>, /// Render the heading even if there are no links force_render: bool, } impl<'a> LinkBlock<'a> { - pub fn new(heading: Link<'a>, links: Vec>) -> Self { - Self { heading, links, force_render: false } + pub fn new(heading: Link<'a>, class: &'static str, links: Vec>) -> 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> { 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::>(); 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> { 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 } @@ -340,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]); @@ -414,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)); } } @@ -453,7 +461,7 @@ fn sidebar_enum<'a>( .collect::>(); 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 } @@ -467,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> { @@ -528,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 b162ea99d8f21..4054281f34681 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,247 @@ 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. + // + // + // 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>, + visited_aliases: FxHashSet, + 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>, + } + // 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 = 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, + aliases: Vec, + } + impl Serialize for AliasSerializableImpl { + fn serialize(&self, serializer: S) -> Result + 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(|path| path.last().to_string()); + // 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(); + super::render_impl( + &mut buf, + cx, + *impl_, + &type_alias_item, + AssocItemLink::Anchor(None), + 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::>(); + 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("trait.impl"); - let cache = cx.cache(); 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 2e9897ef82ba2..6cd57c8c598ee 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -549,6 +549,7 @@ function preLoadCss(cssUrl) { } } + // 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,177 @@ function preLoadCss(cssUrl) { window.register_implementors(window.pending_implementors); } + // + 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"); + + // 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 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 outputListH = document.createElement("h2"); + outputListH.id = outputListHeaderId; + outputListH.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; + sidebarSection.appendChild(h); + sidebarTraitList = document.createElement("ul"); + sidebarTraitList.className = "block trait-implementation"; + sidebarSection.appendChild(sidebarTraitList); + const mainContent = document.querySelector("#main-content"); + mainContent.appendChild(outputListH); + mainContent.appendChild(outputList); + } else { + implementations = outputList; + if (trait_implementations) { + document.insertBefore(outputListH, trait_implementations); + document.insertBefore(outputList, trait_implementations); + } else { + const mainContent = document.querySelector("#main-content"); + mainContent.appendChild(outputListH); + 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) { + el.id = `${el.id}-${i}`; + } + 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 blockH = document.createElement("h3"); + const blockA = document.createElement("a"); + blockA.href = "#implementations"; + blockA.innerText = blockTitle; + blockH.appendChild(blockA); + block = document.createElement("ul"); + block.className = `block ${blockClass}`; + const insertionReference = methods || sidebarTraitList; + if (insertionReference) { + const insertionReferenceH = insertionReference.previousElementSibling; + sidebarSection.insertBefore(blockH, insertionReferenceH); + sidebarSection.insertBefore(block, insertionReferenceH); + } else { + sidebarSection.appendChild(blockH); + 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 a99198141e281..4318d680276cc 100644 --- a/src/librustdoc/html/templates/sidebar.html +++ b/src/librustdoc/html/templates/sidebar.html @@ -18,7 +18,11 @@

{# #}

{{block.heading.name}}

{% endif %} {% if !block.links.is_empty() %} -
    +
      {% for link in block.links %}
    • {{link.name}}
    • {% endfor %} diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index ff89d4e088735..a57321b5822e4 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 = 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, + 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/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs index 5c91bcbb4eecb..5b6d5435b3575 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -160,6 +160,13 @@ 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) {} +} + pub mod huge_amount_of_consts { include!(concat!(env!("OUT_DIR"), "/huge_amount_of_consts.rs")); } diff --git a/tests/rustdoc-gui/type-impls.goml b/tests/rustdoc-gui/type-impls.goml new file mode 100644 index 0000000000000..c7c9f54cad6c8 --- /dev/null +++ b/tests/rustdoc-gui/type-impls.goml @@ -0,0 +1,40 @@ +// 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.must_use']" +assert: "//*[@id='method.warning1']" +assert: "//*[@id='method.warning2']" + +// sidebar items +assert: "//*[@class='sidebar-elems']//li/a[@href='#method.as_ref']" +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)", 'as_ref') +assert-text: (".block.method li:nth-child(2)", 'must_use') +assert-text: (".block.method li:nth-child(3)", 'some_other_method_directly') +assert-text: (".block.method li:nth-child(4)", 'warning1') +assert-text: (".block.method li:nth-child(5)", 'warning2') + +/////////////////////////////////////////////////////////////////////////// +// Now, if JavaScript is disabled, only the first method will be present // +/////////////////////////////////////////////////////////////////////////// +javascript: false +reload: + +// 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']" diff --git a/tests/rustdoc/deref/deref-mut-methods.rs b/tests/rustdoc/deref/deref-mut-methods.rs index fdf8434224f83..65681f8124506 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 be2b42b5ac611..7aee3147ba815 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' // @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' -// @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' -// @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/strip-enum-variant.no-not-shown.html b/tests/rustdoc/strip-enum-variant.no-not-shown.html index 782198956a0f3..e072335297d6b 100644 --- a/tests/rustdoc/strip-enum-variant.no-not-shown.html +++ b/tests/rustdoc/strip-enum-variant.no-not-shown.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/tests/rustdoc/strip-enum-variant.rs b/tests/rustdoc/strip-enum-variant.rs index 8753a7dc613a8..2512fa34b3965 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 0000000000000..3607612c27ae5 --- /dev/null +++ b/tests/rustdoc/type-alias/auxiliary/parent-crate-115718.rs @@ -0,0 +1,9 @@ +pub struct MyStruct(T); + +pub trait MyTrait1 { + fn method_trait_1(); +} + +impl MyTrait1 for MyStruct { + 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 0000000000000..372e62e421333 --- /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 { + 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; diff --git a/tests/rustdoc/issue-112515-impl-ty-alias.rs b/tests/rustdoc/type-alias/deeply-nested-112515.rs similarity index 100% rename from tests/rustdoc/issue-112515-impl-ty-alias.rs rename to tests/rustdoc/type-alias/deeply-nested-112515.rs diff --git a/tests/rustdoc/type-alias/deref-32077.rs b/tests/rustdoc/type-alias/deref-32077.rs new file mode 100644 index 0000000000000..740ed4cec702c --- /dev/null +++ b/tests/rustdoc/type-alias/deref-32077.rs @@ -0,0 +1,70 @@ +// Regression test for . + +#![crate_name = "foo"] + +pub struct GenericStruct(T); + +impl GenericStruct { + pub fn on_gen(arg: T) {} +} + +impl GenericStruct { + pub fn on_u32(arg: u32) {} +} + +pub trait Foo {} +pub trait Bar {} + +impl Foo for GenericStruct {} +impl Bar for GenericStruct {} + +// @has 'foo/type.TypedefStruct.html' +// We check that "Aliased type" is also present as a title in the sidebar. +// @has - '//*[@class="sidebar-elems"]//h3/a[@href="#aliased-type"]' 'Aliased type' +// We check that we have the implementation of the type alias itself. +// @has - '//*[@id="impl-TypedefStruct"]/h3' 'impl TypedefStruct' +// @has - '//*[@id="method.on_alias"]/h4' 'pub fn on_alias()' +// This trait implementation doesn't match the type alias parameters so shouldn't appear in docs. +// @!has - '//h3' 'impl Bar for GenericStruct {}' +// Same goes for the `Deref` impl. +// @!has - '//h2' 'Methods from Deref' +// @count - '//nav[@class="sidebar"]//a' 'on_alias' 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; + +impl TypedefStruct { + pub fn on_alias() {} +} + +impl std::ops::Deref for GenericStruct { + type Target = u32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub struct Wrap(GenericStruct); + +// @has 'foo/type.Alias.html' +// @!has - '//h2' 'Methods from Deref' +// @!has - '//*[@id="impl-Deref-for-Wrap%3CT%3E"]/h3' 'impl Deref for Wrap' +// @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; + +impl std::ops::Deref for Wrap { + type Target = GenericStruct; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} 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 0000000000000..26e5db85cd60b --- /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); + +impl MyStruct { + pub fn method_u32() {} +} + +impl MyStruct { + pub fn method_u16() {} +} + +impl MyTrait1 for MyStruct { + fn method_trait_1() {} +} + +impl MyTrait2 for MyStruct { + 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; From d8afa673ccbcdd4f8f947e4f7eda8d1839bed277 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 6 Oct 2023 07:58:11 -0700 Subject: [PATCH 09/12] rustdoc: remove as_ref from method sidebar test I fixed this in the code, but forgot to fix it in the test. --- tests/rustdoc-gui/type-impls.goml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/rustdoc-gui/type-impls.goml b/tests/rustdoc-gui/type-impls.goml index c7c9f54cad6c8..cc18b5de47590 100644 --- a/tests/rustdoc-gui/type-impls.goml +++ b/tests/rustdoc-gui/type-impls.goml @@ -6,12 +6,12 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/type.SomeOtherTypeWithMethodsAndInli 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.as_ref']" 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']" @@ -19,11 +19,10 @@ 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)", 'as_ref') -assert-text: (".block.method li:nth-child(2)", 'must_use') -assert-text: (".block.method li:nth-child(3)", 'some_other_method_directly') -assert-text: (".block.method li:nth-child(4)", 'warning1') -assert-text: (".block.method li:nth-child(5)", 'warning2') +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, if JavaScript is disabled, only the first method will be present // From fd9ab34785f2991f71896940cbb5c547da9d08b2 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 6 Oct 2023 11:30:48 -0700 Subject: [PATCH 10/12] rustdoc: clean up sidebar.html block class This line is longer than 100 characters, but apparently, [tidy's list of checked extensions] doesn't include html, so the line length doesn't matter. [tidy's list of checked extensions]: https://github.com/rust-lang/rust/blob/31be8cc41148983e742fea8f559aacca0f6647db/src/tools/tidy/src/style.rs#L245 --- src/librustdoc/html/templates/sidebar.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html index 4318d680276cc..d982134181c52 100644 --- a/src/librustdoc/html/templates/sidebar.html +++ b/src/librustdoc/html/templates/sidebar.html @@ -18,11 +18,7 @@

      {# #}

      {{block.heading.name}}

      {% endif %} {% if !block.links.is_empty() %} -
        +
          {% for link in block.links %}
        • {{link.name}}
        • {% endfor %} From 62c67a6438aac23f7c757322be880ca45f2b6b63 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 6 Oct 2023 12:06:52 -0700 Subject: [PATCH 11/12] rustdoc: clean up and comment main.js `register_type_impls` --- src/librustdoc/html/static/js/main.js | 58 ++++++++++++++++++++------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 6cd57c8c598ee..6f0bcb8e6d0b3 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -640,7 +640,37 @@ function preLoadCss(cssUrl) { window.register_implementors(window.pending_implementors); } - // + /** + * + * + * [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>}} impl + */ window.register_type_impls = imp => { if (!imp || !imp[window.currentCrate]) { return; @@ -677,9 +707,9 @@ function preLoadCss(cssUrl) { "trait-implementations-list" : "implementations-list"; const outputListHeaderId = isTrait ? "trait-implementations" : "implementations"; - const outputListH = document.createElement("h2"); - outputListH.id = outputListHeaderId; - outputListH.innerText = outputListName; + const outputListHeader = document.createElement("h2"); + outputListHeader.id = outputListHeaderId; + outputListHeader.innerText = outputListName; outputList = document.createElement("div"); outputList.id = outputListId; if (isTrait) { @@ -694,16 +724,16 @@ function preLoadCss(cssUrl) { sidebarTraitList.className = "block trait-implementation"; sidebarSection.appendChild(sidebarTraitList); const mainContent = document.querySelector("#main-content"); - mainContent.appendChild(outputListH); + mainContent.appendChild(outputListHeader); mainContent.appendChild(outputList); } else { implementations = outputList; if (trait_implementations) { - document.insertBefore(outputListH, trait_implementations); + document.insertBefore(outputListHeader, trait_implementations); document.insertBefore(outputList, trait_implementations); } else { const mainContent = document.querySelector("#main-content"); - mainContent.appendChild(outputListH); + mainContent.appendChild(outputListHeader); mainContent.appendChild(outputList); } } @@ -757,20 +787,20 @@ function preLoadCss(cssUrl) { const blockClass = hasClass(item, "associatedtype") ? "associatedtype" : ( hasClass(item, "associatedconstant") ? "associatedconstant" : ( "method")); - const blockH = document.createElement("h3"); - const blockA = document.createElement("a"); - blockA.href = "#implementations"; - blockA.innerText = blockTitle; - blockH.appendChild(blockA); + 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(blockH, insertionReferenceH); + sidebarSection.insertBefore(blockHeader, insertionReferenceH); sidebarSection.insertBefore(block, insertionReferenceH); } else { - sidebarSection.appendChild(blockH); + sidebarSection.appendChild(blockHeader); sidebarSection.appendChild(block); } if (hasClass(item, "associatedtype")) { From 46fdeb24fd16156f73d95272b48604ab967c81db Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 6 Oct 2023 23:31:16 -0700 Subject: [PATCH 12/12] rustdoc: make JS trait impls act more like HTML --- src/librustdoc/html/render/mod.rs | 1 - src/librustdoc/html/render/write_shared.rs | 21 +++++++++- src/librustdoc/html/static/js/main.js | 15 +++++-- tests/rustdoc-gui/src/test_docs/lib.rs | 20 +++++++++ tests/rustdoc-gui/type-impls.goml | 49 +++++++++++++++++++++- tests/rustdoc/type-alias/deref-32077.rs | 2 +- 6 files changed, 100 insertions(+), 8 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 51cc836cd157d..3cf166a3e43b9 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -55,7 +55,6 @@ use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::Mutability; use rustc_middle::middle::stability; use rustc_middle::ty::{self, TyCtxt}; -use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_span::{ symbol::{sym, Symbol}, BytePos, FileName, RealFileName, diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 4054281f34681..3e58dd96ed92a 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -530,7 +530,11 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; .values() .flat_map(|AliasedTypeImpl { impl_, type_aliases }| { let mut ret = Vec::new(); - let trait_ = impl_.inner_impl().trait_.as_ref().map(|path| path.last().to_string()); + 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 @@ -538,12 +542,25 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; 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, - AssocItemLink::Anchor(None), + assoc_link, RenderMode::Normal, None, &[], diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 6f0bcb8e6d0b3..7c052606abacb 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -680,12 +680,14 @@ function preLoadCss(cssUrl) { 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"); @@ -719,18 +721,18 @@ function preLoadCss(cssUrl) { 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); - const mainContent = document.querySelector("#main-content"); mainContent.appendChild(outputListHeader); mainContent.appendChild(outputList); } else { implementations = outputList; if (trait_implementations) { - document.insertBefore(outputListHeader, trait_implementations); - document.insertBefore(outputList, trait_implementations); + mainContent.insertBefore(outputListHeader, trait_implementations_header); + mainContent.insertBefore(outputList, trait_implementations_header); } else { const mainContent = document.querySelector("#main-content"); mainContent.appendChild(outputListHeader); @@ -762,7 +764,14 @@ function preLoadCss(cssUrl) { } } 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); }); diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs index 5b6d5435b3575..138a1b302fd77 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -167,6 +167,26 @@ 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 for UnderlyingFooBarBaz { + fn as_ref(&self) -> &str { + "hello" + } +} + +impl UnderlyingFooBarBaz { + pub fn inherent_fn(&self) {} +} + +impl AsRef 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/type-impls.goml b/tests/rustdoc-gui/type-impls.goml index cc18b5de47590..870a9cbe53fc9 100644 --- a/tests/rustdoc-gui/type-impls.goml +++ b/tests/rustdoc-gui/type-impls.goml @@ -24,11 +24,39 @@ 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") +assert-text: ( + "//*[@class='sidebar-elems']//li/a[@href='#impl-AsRef%3Cu8%3E-for-UnderlyingFooBarBaz']", + "AsRef" +) +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 -reload: +go-to: "file://" + |DOC_PATH| + "/test_docs/type.SomeOtherTypeWithMethodsAndInlining.html" // method directly on type alias wait-for: "//*[@id='method.some_other_method_directly']" @@ -37,3 +65,22 @@ wait-for: "//*[@id='method.some_other_method_directly']" 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/type-alias/deref-32077.rs b/tests/rustdoc/type-alias/deref-32077.rs index 740ed4cec702c..186ebb1a632e3 100644 --- a/tests/rustdoc/type-alias/deref-32077.rs +++ b/tests/rustdoc/type-alias/deref-32077.rs @@ -22,7 +22,7 @@ impl Bar for GenericStruct {} // We check that "Aliased type" is also present as a title in the sidebar. // @has - '//*[@class="sidebar-elems"]//h3/a[@href="#aliased-type"]' 'Aliased type' // We check that we have the implementation of the type alias itself. -// @has - '//*[@id="impl-TypedefStruct"]/h3' 'impl TypedefStruct' +// @has - '//*[@id="impl-GenericStruct%3Cu8%3E"]/h3' 'impl TypedefStruct' // @has - '//*[@id="method.on_alias"]/h4' 'pub fn on_alias()' // This trait implementation doesn't match the type alias parameters so shouldn't appear in docs. // @!has - '//h3' 'impl Bar for GenericStruct {}'