From 46fdeb24fd16156f73d95272b48604ab967c81db Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 6 Oct 2023 23:31:16 -0700 Subject: [PATCH] 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 51cc836cd15..3cf166a3e43 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -55,7 +55,6 @@ 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 4054281f346..3e58dd96ed9 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -530,7 +530,11 @@ fn serialize(&self, serializer: S) -> Result .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 @@ fn serialize(&self, serializer: S) -> Result 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 6f0bcb8e6d0..7c052606aba 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 5b6d5435b35..138a1b302fd 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 cc18b5de475..870a9cbe53f 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 740ed4cec70..186ebb1a632 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 {}'