From 9683f8a9656580aae49f7664a5134d061b795b3f Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 20 Mar 2023 11:31:17 -0700 Subject: [PATCH 1/5] rustdoc: use let chain in `CacheBuilder::fold_item` --- src/librustdoc/formats/cache.rs | 60 ++++++++++++++++----------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 4c6e7dfb9873b..898aaa3e1962e 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -223,17 +223,16 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { // If the impl is from a masked crate or references something from a // masked crate then remove it completely. - if let clean::ImplItem(ref i) = *item.kind { - if self.cache.masked_crates.contains(&item.item_id.krate()) + if let clean::ImplItem(ref i) = *item.kind && + (self.cache.masked_crates.contains(&item.item_id.krate()) || i.trait_ .as_ref() .map_or(false, |t| self.cache.masked_crates.contains(&t.def_id().krate)) || i.for_ .def_id(self.cache) - .map_or(false, |d| self.cache.masked_crates.contains(&d.krate)) - { - return None; - } + .map_or(false, |d| self.cache.masked_crates.contains(&d.krate))) + { + return None; } // Propagate a trait method's documentation to all implementors of the @@ -334,33 +333,32 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { // A crate has a module at its root, containing all items, // which should not be indexed. The crate-item itself is // inserted later on when serializing the search-index. - if item.item_id.as_def_id().map_or(false, |idx| !idx.is_crate_root()) { + if item.item_id.as_def_id().map_or(false, |idx| !idx.is_crate_root()) + && let ty = item.type_() + && (ty != ItemType::StructField + || u16::from_str_radix(s.as_str(), 10).is_err()) + { let desc = short_markdown_summary(&item.doc_value(), &item.link_names(self.cache)); - let ty = item.type_(); - if ty != ItemType::StructField - || u16::from_str_radix(s.as_str(), 10).is_err() - { - // In case this is a field from a tuple struct, we don't add it into - // the search index because its name is something like "0", which is - // not useful for rustdoc search. - self.cache.search_index.push(IndexItem { - ty, - name: s, - path: join_with_double_colon(path), - desc, - parent, - parent_idx: None, - search_type: get_function_type_for_search( - &item, - self.tcx, - clean_impl_generics(self.cache.parent_stack.last()).as_ref(), - self.cache, - ), - aliases: item.attrs.get_doc_aliases(), - deprecation: item.deprecation(self.tcx), - }); - } + // In case this is a field from a tuple struct, we don't add it into + // the search index because its name is something like "0", which is + // not useful for rustdoc search. + self.cache.search_index.push(IndexItem { + ty, + name: s, + path: join_with_double_colon(path), + desc, + parent, + parent_idx: None, + search_type: get_function_type_for_search( + &item, + self.tcx, + clean_impl_generics(self.cache.parent_stack.last()).as_ref(), + self.cache, + ), + aliases: item.attrs.get_doc_aliases(), + deprecation: item.deprecation(self.tcx), + }); } } (Some(parent), None) if is_inherent_impl_item => { From 3fbfe2bca5e0227e2b9c9363558dc6a5dec54351 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 20 Mar 2023 16:02:51 -0700 Subject: [PATCH 2/5] rustdoc-search: add impl disambiguator to duplicate assoc items Helps with #90929 This changes the search results, specifically, when there's more than one impl with an associated item with the same name. For example, the search queries `simd -> simd` and `simd -> simd` don't link to the same function, but most of the functions have the same names. This change should probably be FCP-ed, especially since it adds a new anchor link format for `main.js` to handle, so that URLs like `struct.Vec.html#impl-AsMut<[T]>-for-Vec/method.as_mut` redirect to `struct.Vec.html#method.as_mut-2`. It's a strange design, but there are a few reasons for it: * I'd like to avoid making the HTML bigger. Obviously, fixing this bug is going to add at least a little more data to the search index, but adding more HTML penalises viewers for the benefit of searchers. * Breaking `struct.Vec.html#method.len` would also be a disappointment. On the other hand: * The path-style anchors might be less prone to link rot than the numbered anchors. It's definitely less likely to have URLs that appear to "work", but silently point at the wrong thing. * This commit arranges the path-style anchor to redirect to the numbered anchor. Nothing stops rustdoc from doing the opposite, making path-style anchors the default and redirecting the "legacy" numbered ones. --- src/librustdoc/formats/cache.rs | 13 ++++ src/librustdoc/html/render/mod.rs | 41 +++++++---- src/librustdoc/html/render/search_index.rs | 33 ++++++++- src/librustdoc/html/render/sidebar.rs | 3 +- src/librustdoc/html/static/js/main.js | 24 +++++++ src/librustdoc/html/static/js/search.js | 19 ++++- .../search-result-impl-disambiguation.goml | 41 +++++++++++ tests/rustdoc-gui/src/test_docs/lib.rs | 18 +++++ tests/rustdoc-js-std/simd-type-signatures.js | 70 +++++++++++++++++++ .../rustdoc-js/search-method-disambiguate.js | 28 ++++++++ .../rustdoc-js/search-method-disambiguate.rs | 22 ++++++ tests/rustdoc/issue-32077-type-alias-impls.rs | 2 +- 12 files changed, 293 insertions(+), 21 deletions(-) create mode 100644 tests/rustdoc-gui/search-result-impl-disambiguation.goml create mode 100644 tests/rustdoc-js-std/simd-type-signatures.js create mode 100644 tests/rustdoc-js/search-method-disambiguate.js create mode 100644 tests/rustdoc-js/search-method-disambiguate.rs diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 898aaa3e1962e..b916baecc1407 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -350,6 +350,11 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { desc, parent, parent_idx: None, + impl_id: if let Some(ParentStackItem::Impl { item_id, .. }) = self.cache.parent_stack.last() { + item_id.as_def_id() + } else { + None + }, search_type: get_function_type_for_search( &item, self.tcx, @@ -369,6 +374,13 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { parent, item: item.clone(), impl_generics, + impl_id: if let Some(ParentStackItem::Impl { item_id, .. }) = + self.cache.parent_stack.last() + { + item_id.as_def_id() + } else { + None + }, }); } _ => {} @@ -539,6 +551,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { pub(crate) struct OrphanImplItem { pub(crate) parent: DefId, + pub(crate) impl_id: Option, pub(crate) item: clean::Item, pub(crate) impl_generics: Option<(clean::Type, clean::Generics)>, } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 3e671a64b54f7..b0ce475850cf8 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -54,7 +54,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::Mutability; use rustc_middle::middle::stability; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{self, TyCtxt}; use rustc_span::{ symbol::{sym, Symbol}, BytePos, FileName, RealFileName, @@ -102,6 +102,7 @@ pub(crate) struct IndexItem { pub(crate) desc: String, pub(crate) parent: Option, pub(crate) parent_idx: Option, + pub(crate) impl_id: Option, pub(crate) search_type: Option, pub(crate) aliases: Box<[Symbol]>, pub(crate) deprecation: Option, @@ -1877,7 +1878,7 @@ pub(crate) fn render_impl_summary( aliases: &[String], ) { let inner_impl = i.inner_impl(); - let id = cx.derive_id(get_id_for_impl(&inner_impl.for_, inner_impl.trait_.as_ref(), cx)); + let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id)); let aliases = if aliases.is_empty() { String::new() } else { @@ -1994,21 +1995,35 @@ pub(crate) fn small_url_encode(s: String) -> String { } } -fn get_id_for_impl(for_: &clean::Type, trait_: Option<&clean::Path>, cx: &Context<'_>) -> String { - match trait_ { - Some(t) => small_url_encode(format!("impl-{:#}-for-{:#}", t.print(cx), for_.print(cx))), - None => small_url_encode(format!("impl-{:#}", for_.print(cx))), - } +fn get_id_for_impl<'tcx>(tcx: TyCtxt<'tcx>, impl_id: ItemId) -> String { + use rustc_middle::ty::print::with_forced_trimmed_paths; + let (type_, trait_) = match impl_id { + ItemId::Auto { trait_, for_ } => { + let ty = tcx.type_of(for_).skip_binder(); + (ty, Some(ty::TraitRef::new(tcx, trait_, [ty]))) + } + ItemId::Blanket { impl_id, .. } | ItemId::DefId(impl_id) => { + match tcx.impl_subject(impl_id).skip_binder() { + ty::ImplSubject::Trait(trait_ref) => { + (trait_ref.args[0].expect_ty(), Some(trait_ref)) + } + ty::ImplSubject::Inherent(ty) => (ty, None), + } + } + }; + with_forced_trimmed_paths!(small_url_encode(if let Some(trait_) = trait_ { + format!("impl-{trait_}-for-{type_}", trait_ = trait_.print_only_trait_path()) + } else { + format!("impl-{type_}") + })) } fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> { match *item.kind { - clean::ItemKind::ImplItem(ref i) => { - i.trait_.as_ref().map(|trait_| { - // Alternative format produces no URLs, - // so this parameter does nothing. - (format!("{:#}", i.for_.print(cx)), get_id_for_impl(&i.for_, Some(trait_), cx)) - }) + clean::ItemKind::ImplItem(ref i) if i.trait_.is_some() => { + // Alternative format produces no URLs, + // so this parameter does nothing. + Some((format!("{:#}", i.for_.print(cx)), get_id_for_impl(cx.tcx(), item.item_id))) } _ => None, } diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 78c443b2257d0..af1dab5949613 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -12,7 +12,7 @@ use crate::formats::cache::{Cache, OrphanImplItem}; use crate::formats::item_type::ItemType; use crate::html::format::join_with_double_colon; use crate::html::markdown::short_markdown_summary; -use crate::html::render::{IndexItem, IndexItemFunctionType, RenderType, RenderTypeId}; +use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId}; /// Builds the search index from the collected metadata pub(crate) fn build_index<'tcx>( @@ -26,7 +26,8 @@ pub(crate) fn build_index<'tcx>( // Attach all orphan items to the type's definition if the type // has since been learned. - for &OrphanImplItem { parent, ref item, ref impl_generics } in &cache.orphan_impl_items { + for &OrphanImplItem { impl_id, parent, ref item, ref impl_generics } in &cache.orphan_impl_items + { if let Some((fqp, _)) = cache.paths.get(&parent) { let desc = short_markdown_summary(&item.doc_value(), &item.link_names(cache)); cache.search_index.push(IndexItem { @@ -36,6 +37,7 @@ pub(crate) fn build_index<'tcx>( desc, parent: Some(parent), parent_idx: None, + impl_id, search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache), aliases: item.attrs.get_doc_aliases(), deprecation: item.deprecation(tcx), @@ -222,6 +224,29 @@ pub(crate) fn build_index<'tcx>( }) .collect(); + // Find associated items that need disambiguators + let mut associated_item_duplicates = FxHashMap::<(isize, ItemType, Symbol), usize>::default(); + + for &item in &crate_items { + if item.impl_id.is_some() && let Some(parent_idx) = item.parent_idx { + let count = associated_item_duplicates + .entry((parent_idx, item.ty, item.name)) + .or_insert(0); + *count += 1; + } + } + + let associated_item_disambiguators = crate_items + .iter() + .enumerate() + .filter_map(|(index, item)| { + let impl_id = ItemId::DefId(item.impl_id?); + let parent_idx = item.parent_idx?; + let count = *associated_item_duplicates.get(&(parent_idx, item.ty, item.name))?; + if count > 1 { Some((index, render::get_id_for_impl(tcx, impl_id))) } else { None } + }) + .collect::>(); + struct CrateData<'a> { doc: String, items: Vec<&'a IndexItem>, @@ -230,6 +255,8 @@ pub(crate) fn build_index<'tcx>( // // To be noted: the `usize` elements are indexes to `items`. aliases: &'a BTreeMap>, + // Used when a type has more than one impl with an associated item with the same name. + associated_item_disambiguators: &'a Vec<(usize, String)>, } struct Paths { @@ -382,6 +409,7 @@ pub(crate) fn build_index<'tcx>( crate_data.serialize_field("f", &functions)?; crate_data.serialize_field("c", &deprecated)?; crate_data.serialize_field("p", &paths)?; + crate_data.serialize_field("b", &self.associated_item_disambiguators)?; if has_aliases { crate_data.serialize_field("a", &self.aliases)?; } @@ -398,6 +426,7 @@ pub(crate) fn build_index<'tcx>( items: crate_items, paths: crate_paths, aliases: &aliases, + associated_item_disambiguators: &associated_item_disambiguators, }) .expect("failed serde conversion") // All these `replace` calls are because we have to go through JS string for JSON content. diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 76f63c6f63e36..3f8b22050c8e1 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -503,8 +503,7 @@ fn sidebar_render_assoc_items( .iter() .filter_map(|it| { let trait_ = it.inner_impl().trait_.as_ref()?; - let encoded = - id_map.derive(super::get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx)); + let encoded = id_map.derive(super::get_id_for_impl(cx.tcx(), it.impl_item.item_id)); let prefix = match it.inner_impl().polarity { ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "", diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index eb256455b0878..970c2f2d45d15 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -354,6 +354,30 @@ function preLoadCss(cssUrl) { expandSection(pageId); } } + if (savedHash.startsWith("#impl-")) { + // impl-disambiguated links, used by the search engine + // format: impl-X[-for-Y]/method.WHATEVER + // turn this into method.WHATEVER[-NUMBER] + const splitAt = savedHash.indexOf("/"); + if (splitAt !== -1) { + const implId = savedHash.slice(1, splitAt); + const assocId = savedHash.slice(splitAt + 1); + const implElem = document.getElementById(implId); + if (implElem && implElem.parentElement.tagName === "SUMMARY" && + implElem.parentElement.parentElement.tagName === "DETAILS") { + onEachLazy(implElem.parentElement.parentElement.querySelectorAll( + `[id^="${assocId}"]`), + item => { + const numbered = /([^-]+)-([0-9]+)/.exec(item.id); + if (item.id === assocId || (numbered && numbered[1] === assocId)) { + expandSection(item.id); + window.location = "#" + item.id; + } + } + ); + } + } + } } function onHashChange(ev) { diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 2f0cae0a48e21..6cc0707a552dd 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1752,6 +1752,7 @@ function initSearch(rawSearchIndex) { type: item.type, is_alias: true, deprecated: item.deprecated, + implDisambiguator: item.implDisambiguator, }; } @@ -2218,7 +2219,7 @@ function initSearch(rawSearchIndex) { href = ROOT_PATH + name + "/index.html"; } else if (item.parent !== undefined) { const myparent = item.parent; - let anchor = "#" + type + "." + name; + let anchor = type + "." + name; const parentType = itemTypes[myparent.ty]; let pageType = parentType; let pageName = myparent.name; @@ -2232,16 +2233,19 @@ function initSearch(rawSearchIndex) { const enumName = item.path.substr(enumNameIdx + 2); path = item.path.substr(0, enumNameIdx); displayPath = path + "::" + enumName + "::" + myparent.name + "::"; - anchor = "#variant." + myparent.name + ".field." + name; + anchor = "variant." + myparent.name + ".field." + name; pageType = "enum"; pageName = enumName; } else { displayPath = path + "::" + myparent.name + "::"; } + if (item.implDisambiguator !== null) { + anchor = item.implDisambiguator + "/" + anchor; + } href = ROOT_PATH + path.replace(/::/g, "/") + "/" + pageType + "." + pageName + - ".html" + anchor; + ".html#" + anchor; } else { displayPath = item.path + "::"; href = ROOT_PATH + item.path.replace(/::/g, "/") + @@ -2727,6 +2731,10 @@ ${item.displayPath}${name}\ * Types are also represented as arrays; the first item is an index into the `p` * array, while the second is a list of types representing any generic parameters. * + * b[i] contains an item's impl disambiguator. This is only present if an item + * is defined in an impl block and, the impl block's type has more than one associated + * item with the same name. + * * `a` defines aliases with an Array of pairs: [name, offset], where `offset` * points into the n/t/d/q/i/f arrays. * @@ -2746,6 +2754,7 @@ ${item.displayPath}${name}\ * i: Array, * f: Array, * p: Array, + * b: Array<[Number, String]>, * c: Array * }} */ @@ -2766,6 +2775,7 @@ ${item.displayPath}${name}\ id: id, normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""), deprecated: null, + implDisambiguator: null, }; id += 1; searchIndex.push(crateRow); @@ -2789,6 +2799,8 @@ ${item.displayPath}${name}\ const itemFunctionSearchTypes = crateCorpus.f; // an array of (Number) indices for the deprecated items const deprecatedItems = new Set(crateCorpus.c); + // an array of (Number) indices for the deprecated items + const implDisambiguator = new Map(crateCorpus.b); // an array of [(Number) item type, // (String) name] const paths = crateCorpus.p; @@ -2849,6 +2861,7 @@ ${item.displayPath}${name}\ id: id, normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""), deprecated: deprecatedItems.has(i), + implDisambiguator: implDisambiguator.has(i) ? implDisambiguator.get(i) : null, }; id += 1; searchIndex.push(row); diff --git a/tests/rustdoc-gui/search-result-impl-disambiguation.goml b/tests/rustdoc-gui/search-result-impl-disambiguation.goml new file mode 100644 index 0000000000000..98a2cd9577397 --- /dev/null +++ b/tests/rustdoc-gui/search-result-impl-disambiguation.goml @@ -0,0 +1,41 @@ +// ignore-tidy-linelength + +// Checks that, if a type has two methods with the same name, they both get +// linked correctly. +goto: "file://" + |DOC_PATH| + "/test_docs/index.html" + +// This should link to the inherent impl +write: (".search-input", "ZyxwvutMethodDisambiguation -> bool") +// To be SURE that the search will be run. +press-key: 'Enter' +// Waiting for the search results to appear... +wait-for: "#search-tabs" +// Check the disambiguated link. +assert-count: ("a.result-method", 1) +assert-attribute: ("a.result-method", { + "href": "../test_docs/struct.ZyxwvutMethodDisambiguation.html#impl-ZyxwvutMethodDisambiguation/method.method_impl_disambiguation" +}) +click: "a.result-method" +wait-for: "#impl-ZyxwvutMethodDisambiguation" +assert-document-property: ({ + "URL": "struct.ZyxwvutMethodDisambiguation.html#method.method_impl_disambiguation" +}, ENDS_WITH) + +goto: "file://" + |DOC_PATH| + "/test_docs/index.html" + +// This should link to the trait impl +write: (".search-input", "ZyxwvutMethodDisambiguation, usize -> usize") +// To be SURE that the search will be run. +press-key: 'Enter' +// Waiting for the search results to appear... +wait-for: "#search-tabs" +// Check the disambiguated link. +assert-count: ("a.result-method", 1) +assert-attribute: ("a.result-method", { + "href": "../test_docs/struct.ZyxwvutMethodDisambiguation.html#impl-ZyxwvutTrait-for-ZyxwvutMethodDisambiguation/method.method_impl_disambiguation" +}) +click: "a.result-method" +wait-for: "#impl-ZyxwvutMethodDisambiguation" +assert-document-property: ({ + "URL": "struct.ZyxwvutMethodDisambiguation.html#method.method_impl_disambiguation-1" +}, ENDS_WITH) diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs index 38180aef762b4..5c91bcbb4eecb 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -529,3 +529,21 @@ pub mod cfgs { /// Some docs. pub mod cfgs {} } + +pub struct ZyxwvutMethodDisambiguation; + +impl ZyxwvutMethodDisambiguation { + pub fn method_impl_disambiguation(&self) -> bool { + true + } +} + +pub trait ZyxwvutTrait { + fn method_impl_disambiguation(&self, x: usize) -> usize; +} + +impl ZyxwvutTrait for ZyxwvutMethodDisambiguation { + fn method_impl_disambiguation(&self, x: usize) -> usize { + x + } +} diff --git a/tests/rustdoc-js-std/simd-type-signatures.js b/tests/rustdoc-js-std/simd-type-signatures.js new file mode 100644 index 0000000000000..bd16827095736 --- /dev/null +++ b/tests/rustdoc-js-std/simd-type-signatures.js @@ -0,0 +1,70 @@ +// exact-check +// ignore-order +// ignore-tidy-linelength + +// This test case verifies that the href points at the correct impl + +const FILTER_CRATE = "std"; + +const EXPECTED = [ + { + 'query': 'simd, simd -> simd', + 'others': [ + { + 'path': 'std::simd::Simd', + 'name': 'simd_max', + 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci16,+LANES%3E/method.simd_max' + }, + { + 'path': 'std::simd::Simd', + 'name': 'simd_min', + 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci16,+LANES%3E/method.simd_min' + }, + { + 'path': 'std::simd::Simd', + 'name': 'simd_clamp', + 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci16,+LANES%3E/method.simd_clamp' + }, + { + 'path': 'std::simd::Simd', + 'name': 'saturating_add', + 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdInt-for-core::simd::Simd%3Ci16,+LANES%3E/method.saturating_add' + }, + { + 'path': 'std::simd::Simd', + 'name': 'saturating_sub', + 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdInt-for-core::simd::Simd%3Ci16,+LANES%3E/method.saturating_sub' + }, + ], + }, + { + 'query': 'simd, simd -> simd', + 'others': [ + { + 'path': 'std::simd::Simd', + 'name': 'simd_max', + 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci8,+LANES%3E/method.simd_max' + }, + { + 'path': 'std::simd::Simd', + 'name': 'simd_min', + 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci8,+LANES%3E/method.simd_min' + }, + { + 'path': 'std::simd::Simd', + 'name': 'simd_clamp', + 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci8,+LANES%3E/method.simd_clamp' + }, + { + 'path': 'std::simd::Simd', + 'name': 'saturating_add', + 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdInt-for-core::simd::Simd%3Ci8,+LANES%3E/method.saturating_add' + }, + { + 'path': 'std::simd::Simd', + 'name': 'saturating_sub', + 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdInt-for-core::simd::Simd%3Ci8,+LANES%3E/method.saturating_sub' + }, + ], + }, +]; diff --git a/tests/rustdoc-js/search-method-disambiguate.js b/tests/rustdoc-js/search-method-disambiguate.js new file mode 100644 index 0000000000000..70aa895f99458 --- /dev/null +++ b/tests/rustdoc-js/search-method-disambiguate.js @@ -0,0 +1,28 @@ +// exact-check +// ignore-order +// ignore-tidy-linelength + +const FILTER_CRATE = "search_method_disambiguate"; + +const EXPECTED = [ + { + 'query': 'MyTy -> bool', + 'others': [ + { + 'path': 'search_method_disambiguate::MyTy', + 'name': 'my_method', + 'href': '../search_method_disambiguate/struct.MyTy.html#impl-X-for-MyTy%3Cbool%3E/method.my_method' + }, + ], + }, + { + 'query': 'MyTy -> u8', + 'others': [ + { + 'path': 'search_method_disambiguate::MyTy', + 'name': 'my_method', + 'href': '../search_method_disambiguate/struct.MyTy.html#impl-X-for-MyTy%3Cu8%3E/method.my_method' + }, + ], + } +]; diff --git a/tests/rustdoc-js/search-method-disambiguate.rs b/tests/rustdoc-js/search-method-disambiguate.rs new file mode 100644 index 0000000000000..ae884447a92e7 --- /dev/null +++ b/tests/rustdoc-js/search-method-disambiguate.rs @@ -0,0 +1,22 @@ +pub trait X { + type InnerType; + fn my_method(&self) -> Self::InnerType; +} + +pub struct MyTy { + pub t: T, +} + +impl X for MyTy { + type InnerType = bool; + fn my_method(&self) -> bool { + self.t + } +} + +impl X for MyTy { + type InnerType = u8; + fn my_method(&self) -> u8 { + self.t + } +} diff --git a/tests/rustdoc/issue-32077-type-alias-impls.rs b/tests/rustdoc/issue-32077-type-alias-impls.rs index ac486c36ad044..664b678093e90 100644 --- a/tests/rustdoc/issue-32077-type-alias-impls.rs +++ b/tests/rustdoc/issue-32077-type-alias-impls.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()' // @has - '//*[@id="impl-GenericStruct%3CT%3E"]/h3' 'impl GenericStruct' // @has - '//*[@id="method.on_gen"]/h4' 'pub fn on_gen(arg: T)' From 3583e86674749c279e7edd96641255bbf8595de1 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 20 Mar 2023 17:56:45 -0700 Subject: [PATCH 3/5] rustdoc: update test cases for changes to the printing style This whole thing changes it so that the JS and the UI both use rustc's own path printing to handle the impl IDs. This results in the format changing a little bit; full paths are used in spots where they aren't strictly necessary, and the path sometimes uses generics where the old system used the trait's own name, but it shouldn't matter since the orphan rules will prevent it anyway. --- tests/rustdoc-js-std/simd-type-signatures.js | 40 +++++++++---------- tests/rustdoc/blanket-reexport-item.rs | 2 +- tests/rustdoc/const-generics/const-impl.rs | 2 +- tests/rustdoc/generic-impl.rs | 4 +- tests/rustdoc/issue-78701.rs | 2 +- .../primitive/primitive-generic-impl.rs | 2 +- .../rustdoc/sidebar-links-to-foreign-impl.rs | 4 +- tests/rustdoc/src-links-auto-impls.rs | 4 +- tests/rustdoc/where-clause-order.rs | 13 +++++- 9 files changed, 42 insertions(+), 31 deletions(-) diff --git a/tests/rustdoc-js-std/simd-type-signatures.js b/tests/rustdoc-js-std/simd-type-signatures.js index bd16827095736..5c7cf372bce14 100644 --- a/tests/rustdoc-js-std/simd-type-signatures.js +++ b/tests/rustdoc-js-std/simd-type-signatures.js @@ -11,29 +11,29 @@ const EXPECTED = [ 'query': 'simd, simd -> simd', 'others': [ { - 'path': 'std::simd::Simd', + 'path': 'std::simd::prelude::Simd', 'name': 'simd_max', - 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci16,+LANES%3E/method.simd_max' + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci16,+LANES%3E/method.simd_max' }, { - 'path': 'std::simd::Simd', + 'path': 'std::simd::prelude::Simd', 'name': 'simd_min', - 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci16,+LANES%3E/method.simd_min' + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci16,+LANES%3E/method.simd_min' }, { - 'path': 'std::simd::Simd', + 'path': 'std::simd::prelude::Simd', 'name': 'simd_clamp', - 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci16,+LANES%3E/method.simd_clamp' + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci16,+LANES%3E/method.simd_clamp' }, { - 'path': 'std::simd::Simd', + 'path': 'std::simd::prelude::Simd', 'name': 'saturating_add', - 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdInt-for-core::simd::Simd%3Ci16,+LANES%3E/method.saturating_add' + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdInt-for-Simd%3Ci16,+LANES%3E/method.saturating_add' }, { - 'path': 'std::simd::Simd', + 'path': 'std::simd::prelude::Simd', 'name': 'saturating_sub', - 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdInt-for-core::simd::Simd%3Ci16,+LANES%3E/method.saturating_sub' + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdInt-for-Simd%3Ci16,+LANES%3E/method.saturating_sub' }, ], }, @@ -41,29 +41,29 @@ const EXPECTED = [ 'query': 'simd, simd -> simd', 'others': [ { - 'path': 'std::simd::Simd', + 'path': 'std::simd::prelude::Simd', 'name': 'simd_max', - 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci8,+LANES%3E/method.simd_max' + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci8,+LANES%3E/method.simd_max' }, { - 'path': 'std::simd::Simd', + 'path': 'std::simd::prelude::Simd', 'name': 'simd_min', - 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci8,+LANES%3E/method.simd_min' + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci8,+LANES%3E/method.simd_min' }, { - 'path': 'std::simd::Simd', + 'path': 'std::simd::prelude::Simd', 'name': 'simd_clamp', - 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdOrd-for-core::simd::Simd%3Ci8,+LANES%3E/method.simd_clamp' + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdOrd-for-Simd%3Ci8,+LANES%3E/method.simd_clamp' }, { - 'path': 'std::simd::Simd', + 'path': 'std::simd::prelude::Simd', 'name': 'saturating_add', - 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdInt-for-core::simd::Simd%3Ci8,+LANES%3E/method.saturating_add' + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdInt-for-Simd%3Ci8,+LANES%3E/method.saturating_add' }, { - 'path': 'std::simd::Simd', + 'path': 'std::simd::prelude::Simd', 'name': 'saturating_sub', - 'href': '../std/simd/struct.Simd.html#impl-core::simd::SimdInt-for-core::simd::Simd%3Ci8,+LANES%3E/method.saturating_sub' + 'href': '../std/simd/prelude/struct.Simd.html#impl-SimdInt-for-Simd%3Ci8,+LANES%3E/method.saturating_sub' }, ], }, diff --git a/tests/rustdoc/blanket-reexport-item.rs b/tests/rustdoc/blanket-reexport-item.rs index 437f0001fcfc4..315a38c30c54f 100644 --- a/tests/rustdoc/blanket-reexport-item.rs +++ b/tests/rustdoc/blanket-reexport-item.rs @@ -1,6 +1,6 @@ #![crate_name = "foo"] -// @has foo/struct.S.html '//*[@id="impl-Into%3CU%3E-for-S"]//h3[@class="code-header"]' 'impl Into for T' +// @has foo/struct.S.html '//*[@id="impl-Into%3CU%3E-for-T"]//h3[@class="code-header"]' 'impl Into for T' pub struct S2 {} mod m { pub struct S {} diff --git a/tests/rustdoc/const-generics/const-impl.rs b/tests/rustdoc/const-generics/const-impl.rs index 152b643bf4bd8..b424ea4b33cec 100644 --- a/tests/rustdoc/const-generics/const-impl.rs +++ b/tests/rustdoc/const-generics/const-impl.rs @@ -31,7 +31,7 @@ impl VSet { pub struct Escape; -// @has foo/struct.Escape.html '//*[@id="impl-Escape%3Cr%23%22%3Cscript%3Ealert(%22Escape%22);%3C/script%3E%22%23%3E"]/h3[@class="code-header"]' 'impl Escapealert("Escape");"#>' +// @has foo/struct.Escape.html '//*[@id="impl-Escape%3C%22%3Cscript%3Ealert(%5C%22Escape%5C%22);%3C/script%3E%22%3E"]/h3[@class="code-header"]' 'impl Escapealert("Escape");"#>' impl Escapealert("Escape");"#> { pub fn f() {} } diff --git a/tests/rustdoc/generic-impl.rs b/tests/rustdoc/generic-impl.rs index 6f68b1574992b..f62540c6bf963 100644 --- a/tests/rustdoc/generic-impl.rs +++ b/tests/rustdoc/generic-impl.rs @@ -5,9 +5,9 @@ use std::fmt; // @!has foo/struct.Bar.html '//*[@id="impl-ToString-for-Bar"]' '' pub struct Bar; -// @has foo/struct.Foo.html '//*[@id="impl-ToString-for-Foo"]//h3[@class="code-header"]' 'impl ToString for T' +// @has foo/struct.Foo.html '//*[@id="impl-ToString-for-T"]//h3[@class="code-header"]' 'impl ToString for T' pub struct Foo; -// @has foo/struct.Foo.html '//*[@class="sidebar-elems"]//section//a[@href="#impl-ToString-for-Foo"]' 'ToString' +// @has foo/struct.Foo.html '//*[@class="sidebar-elems"]//section//a[@href="#impl-ToString-for-T"]' 'ToString' impl fmt::Display for Foo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/tests/rustdoc/issue-78701.rs b/tests/rustdoc/issue-78701.rs index e3e46468f3840..3f1638d5ffc4e 100644 --- a/tests/rustdoc/issue-78701.rs +++ b/tests/rustdoc/issue-78701.rs @@ -6,7 +6,7 @@ // @has 'foo/struct.AnotherStruct.html' // @count - '//*[@class="sidebar"]//a[@href="#impl-AnAmazingTrait-for-AnotherStruct%3C()%3E"]' 1 -// @count - '//*[@class="sidebar"]//a[@href="#impl-AnAmazingTrait-for-AnotherStruct%3CT%3E"]' 1 +// @count - '//*[@class="sidebar"]//a[@href="#impl-AnAmazingTrait-for-T"]' 1 pub trait Something {} diff --git a/tests/rustdoc/primitive/primitive-generic-impl.rs b/tests/rustdoc/primitive/primitive-generic-impl.rs index 2da8ae6ff38da..558336d731629 100644 --- a/tests/rustdoc/primitive/primitive-generic-impl.rs +++ b/tests/rustdoc/primitive/primitive-generic-impl.rs @@ -1,7 +1,7 @@ #![feature(rustc_attrs)] #![crate_name = "foo"] -// @has foo/primitive.i32.html '//*[@id="impl-ToString-for-i32"]//h3[@class="code-header"]' 'impl ToString for T' +// @has foo/primitive.i32.html '//*[@id="impl-ToString-for-T"]//h3[@class="code-header"]' 'impl ToString for T' #[rustc_doc_primitive = "i32"] /// Some useless docs, wouhou! diff --git a/tests/rustdoc/sidebar-links-to-foreign-impl.rs b/tests/rustdoc/sidebar-links-to-foreign-impl.rs index caa17dfbb1c73..733a18ad94a45 100644 --- a/tests/rustdoc/sidebar-links-to-foreign-impl.rs +++ b/tests/rustdoc/sidebar-links-to-foreign-impl.rs @@ -7,8 +7,8 @@ // @has - '//h2[@id="foreign-impls"]' 'Implementations on Foreign Types' // @has - '//*[@class="sidebar-elems"]//section//a[@href="#impl-Foo-for-u32"]' 'u32' // @has - '//*[@id="impl-Foo-for-u32"]//h3[@class="code-header"]' 'impl Foo for u32' -// @has - "//*[@class=\"sidebar-elems\"]//section//a[@href=\"#impl-Foo-for-%26'a+str\"]" "&'a str" -// @has - "//*[@id=\"impl-Foo-for-%26'a+str\"]//h3[@class=\"code-header\"]" "impl<'a> Foo for &'a str" +// @has - "//*[@class=\"sidebar-elems\"]//section//a[@href=\"#impl-Foo-for-%26str\"]" "&'a str" +// @has - "//*[@id=\"impl-Foo-for-%26str\"]//h3[@class=\"code-header\"]" "impl<'a> Foo for &'a str" pub trait Foo {} impl Foo for u32 {} diff --git a/tests/rustdoc/src-links-auto-impls.rs b/tests/rustdoc/src-links-auto-impls.rs index 1c8d157319254..08a497d4cf5e0 100644 --- a/tests/rustdoc/src-links-auto-impls.rs +++ b/tests/rustdoc/src-links-auto-impls.rs @@ -5,8 +5,8 @@ // @!has - '//*[@id="impl-Sized-for-Unsized"]//a[@class="src"]' 'source' // @has - '//*[@id="impl-Sync-for-Unsized"]/h3[@class="code-header"]' 'impl Sync for Unsized' // @!has - '//*[@id="impl-Sync-for-Unsized"]//a[@class="src"]' 'source' -// @has - '//*[@id="impl-Any-for-Unsized"]/h3[@class="code-header"]' 'impl Any for T' -// @has - '//*[@id="impl-Any-for-Unsized"]//a[@class="src rightside"]' 'source' +// @has - '//*[@id="impl-Any-for-T"]/h3[@class="code-header"]' 'impl Any for T' +// @has - '//*[@id="impl-Any-for-T"]//a[@class="src rightside"]' 'source' pub struct Unsized { data: [u8], } diff --git a/tests/rustdoc/where-clause-order.rs b/tests/rustdoc/where-clause-order.rs index b10f8f6856e8c..7261dfa7dd912 100644 --- a/tests/rustdoc/where-clause-order.rs +++ b/tests/rustdoc/where-clause-order.rs @@ -7,7 +7,7 @@ where } // @has 'foo/trait.SomeTrait.html' -// @has - "//*[@id='impl-SomeTrait%3C(A,+B,+C,+D,+E)%3E-for-(A,+B,+C,+D,+E)']/h3" "impl SomeTrait<(A, B, C, D, E)> for (A, B, C, D, E)where A: PartialOrd + PartialEq, B: PartialOrd + PartialEq, C: PartialOrd + PartialEq, D: PartialOrd + PartialEq, E: PartialOrd + PartialEq + ?Sized, " +// @has - "//*[@id='impl-SomeTrait-for-(A,+B,+C,+D,+E)']/h3" "impl SomeTrait<(A, B, C, D, E)> for (A, B, C, D, E)where A: PartialOrd + PartialEq, B: PartialOrd + PartialEq, C: PartialOrd + PartialEq, D: PartialOrd + PartialEq, E: PartialOrd + PartialEq + ?Sized, " impl SomeTrait<(A, B, C, D, E)> for (A, B, C, D, E) where A: PartialOrd + PartialEq, @@ -17,3 +17,14 @@ where E: PartialOrd + PartialEq + ?Sized, { } + +// @has - "//*[@id='impl-SomeTrait%3C(A,+B,+C,+D)%3E-for-(A,+B,+C,+D,+E)']/h3" "impl SomeTrait<(A, B, C, D)> for (A, B, C, D, E)where A: PartialOrd + PartialEq, B: PartialOrd + PartialEq, C: PartialOrd + PartialEq, D: PartialOrd + PartialEq, E: PartialOrd + PartialEq + ?Sized, " +impl SomeTrait<(A, B, C, D)> for (A, B, C, D, E) +where + A: PartialOrd + PartialEq, + B: PartialOrd + PartialEq, + C: PartialOrd + PartialEq, + D: PartialOrd + PartialEq, + E: PartialOrd + PartialEq + ?Sized, +{ +} From 20b93b951aaa75bd32ec1c1c63eac5dbccc35156 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 21 Mar 2023 10:38:24 -0700 Subject: [PATCH 4/5] rustdoc: wait for section to open before trying to highlight This fixes a problem where hash rewriting doesn't work with `:target` CSS rules. --- src/librustdoc/html/static/js/main.js | 8 ++++++-- tests/rustdoc-gui/search-result-impl-disambiguation.goml | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 970c2f2d45d15..aa8fd7162eed1 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -370,8 +370,12 @@ function preLoadCss(cssUrl) { item => { const numbered = /([^-]+)-([0-9]+)/.exec(item.id); if (item.id === assocId || (numbered && numbered[1] === assocId)) { - expandSection(item.id); - window.location = "#" + item.id; + openParentDetails(item); + item.scrollIntoView(); + // Let the section expand itself before trying to highlight + setTimeout(() => { + window.location.replace("#" + item.id); + }, 0); } } ); diff --git a/tests/rustdoc-gui/search-result-impl-disambiguation.goml b/tests/rustdoc-gui/search-result-impl-disambiguation.goml index 98a2cd9577397..1596a3c4c6e7e 100644 --- a/tests/rustdoc-gui/search-result-impl-disambiguation.goml +++ b/tests/rustdoc-gui/search-result-impl-disambiguation.goml @@ -20,6 +20,7 @@ wait-for: "#impl-ZyxwvutMethodDisambiguation" assert-document-property: ({ "URL": "struct.ZyxwvutMethodDisambiguation.html#method.method_impl_disambiguation" }, ENDS_WITH) +assert: "section:target" goto: "file://" + |DOC_PATH| + "/test_docs/index.html" @@ -39,3 +40,4 @@ wait-for: "#impl-ZyxwvutMethodDisambiguation" assert-document-property: ({ "URL": "struct.ZyxwvutMethodDisambiguation.html#method.method_impl_disambiguation-1" }, ENDS_WITH) +assert: "section:target" From 2a4c9d07562c42950699609e32e77fbe9ceaa4e9 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 13 Jul 2023 10:53:21 -0700 Subject: [PATCH 5/5] Update search-result-impl-disambiguation.goml --- src/librustdoc/html/static/js/main.js | 4 ++-- tests/rustdoc-gui/search-result-impl-disambiguation.goml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index aa8fd7162eed1..43c4f2b6ff5bb 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -354,13 +354,13 @@ function preLoadCss(cssUrl) { expandSection(pageId); } } - if (savedHash.startsWith("#impl-")) { + if (savedHash.startsWith("impl-")) { // impl-disambiguated links, used by the search engine // format: impl-X[-for-Y]/method.WHATEVER // turn this into method.WHATEVER[-NUMBER] const splitAt = savedHash.indexOf("/"); if (splitAt !== -1) { - const implId = savedHash.slice(1, splitAt); + const implId = savedHash.slice(0, splitAt); const assocId = savedHash.slice(splitAt + 1); const implElem = document.getElementById(implId); if (implElem && implElem.parentElement.tagName === "SUMMARY" && diff --git a/tests/rustdoc-gui/search-result-impl-disambiguation.goml b/tests/rustdoc-gui/search-result-impl-disambiguation.goml index 1596a3c4c6e7e..6d12032e891b6 100644 --- a/tests/rustdoc-gui/search-result-impl-disambiguation.goml +++ b/tests/rustdoc-gui/search-result-impl-disambiguation.goml @@ -2,7 +2,7 @@ // Checks that, if a type has two methods with the same name, they both get // linked correctly. -goto: "file://" + |DOC_PATH| + "/test_docs/index.html" +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" // This should link to the inherent impl write: (".search-input", "ZyxwvutMethodDisambiguation -> bool") @@ -22,7 +22,7 @@ assert-document-property: ({ }, ENDS_WITH) assert: "section:target" -goto: "file://" + |DOC_PATH| + "/test_docs/index.html" +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" // This should link to the trait impl write: (".search-input", "ZyxwvutMethodDisambiguation, usize -> usize")