Skip to content

Commit 17c865c

Browse files
committed
rustdoc-search: add impl disambiguator to duplicate assoc items
Helps with rust-lang#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<i8> -> simd<i8>` and `simd<i64> -> simd<i64>` 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<T,+A>/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.
1 parent d755cdf commit 17c865c

File tree

11 files changed

+292
-20
lines changed

11 files changed

+292
-20
lines changed

src/librustdoc/formats/cache.rs

+13
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,11 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
350350
desc,
351351
parent,
352352
parent_idx: None,
353+
impl_id: if let Some(ParentStackItem::Impl { item_id, .. }) = self.cache.parent_stack.last() {
354+
item_id.as_def_id()
355+
} else {
356+
None
357+
},
353358
search_type: get_function_type_for_search(
354359
&item,
355360
self.tcx,
@@ -369,6 +374,13 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
369374
parent,
370375
item: item.clone(),
371376
impl_generics,
377+
impl_id: if let Some(ParentStackItem::Impl { item_id, .. }) =
378+
self.cache.parent_stack.last()
379+
{
380+
item_id.as_def_id()
381+
} else {
382+
None
383+
},
372384
});
373385
}
374386
_ => {}
@@ -539,6 +551,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
539551

540552
pub(crate) struct OrphanImplItem {
541553
pub(crate) parent: DefId,
554+
pub(crate) impl_id: Option<DefId>,
542555
pub(crate) item: clean::Item,
543556
pub(crate) impl_generics: Option<(clean::Type, clean::Generics)>,
544557
}

src/librustdoc/html/render/mod.rs

+28-13
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ use rustc_hir::def_id::{DefId, DefIdSet};
5555
use rustc_hir::Mutability;
5656
use rustc_middle::middle::stability;
5757
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
58-
use rustc_middle::ty::TyCtxt;
58+
use rustc_middle::ty::{self, TyCtxt};
5959
use rustc_span::{
6060
symbol::{sym, Symbol},
6161
BytePos, FileName, RealFileName,
@@ -104,6 +104,7 @@ pub(crate) struct IndexItem {
104104
pub(crate) desc: String,
105105
pub(crate) parent: Option<DefId>,
106106
pub(crate) parent_idx: Option<usize>,
107+
pub(crate) impl_id: Option<DefId>,
107108
pub(crate) search_type: Option<IndexItemFunctionType>,
108109
pub(crate) aliases: Box<[Symbol]>,
109110
pub(crate) deprecation: Option<Deprecation>,
@@ -1893,7 +1894,7 @@ pub(crate) fn render_impl_summary(
18931894
aliases: &[String],
18941895
) {
18951896
let inner_impl = i.inner_impl();
1896-
let id = cx.derive_id(get_id_for_impl(&inner_impl.for_, inner_impl.trait_.as_ref(), cx));
1897+
let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id));
18971898
let aliases = if aliases.is_empty() {
18981899
String::new()
18991900
} else {
@@ -2010,21 +2011,35 @@ pub(crate) fn small_url_encode(s: String) -> String {
20102011
}
20112012
}
20122013

2013-
fn get_id_for_impl(for_: &clean::Type, trait_: Option<&clean::Path>, cx: &Context<'_>) -> String {
2014-
match trait_ {
2015-
Some(t) => small_url_encode(format!("impl-{:#}-for-{:#}", t.print(cx), for_.print(cx))),
2016-
None => small_url_encode(format!("impl-{:#}", for_.print(cx))),
2017-
}
2014+
fn get_id_for_impl<'tcx>(tcx: TyCtxt<'tcx>, impl_id: ItemId) -> String {
2015+
let (type_, trait_) = match impl_id {
2016+
ItemId::Auto { trait_, for_ } => {
2017+
let ty = tcx.type_of(for_).skip_binder();
2018+
(ty, Some(ty::TraitRef::new(tcx, trait_, [ty])))
2019+
}
2020+
ItemId::Blanket { impl_id, .. } => (
2021+
tcx.type_of(impl_id).skip_binder(),
2022+
tcx.impl_trait_ref(impl_id).map(|x| x.skip_binder()),
2023+
),
2024+
ItemId::DefId(impl_id) => (
2025+
tcx.type_of(impl_id).skip_binder(),
2026+
tcx.impl_trait_ref(impl_id).map(|x| x.skip_binder()),
2027+
),
2028+
};
2029+
use rustc_middle::ty::print::with_forced_trimmed_paths;
2030+
with_forced_trimmed_paths!(small_url_encode(if let Some(trait_) = trait_ {
2031+
format!("impl-{trait_}-for-{type_}", trait_ = trait_.print_only_trait_path())
2032+
} else {
2033+
format!("impl-{type_}")
2034+
}))
20182035
}
20192036

20202037
fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> {
20212038
match *item.kind {
2022-
clean::ItemKind::ImplItem(ref i) => {
2023-
i.trait_.as_ref().map(|trait_| {
2024-
// Alternative format produces no URLs,
2025-
// so this parameter does nothing.
2026-
(format!("{:#}", i.for_.print(cx)), get_id_for_impl(&i.for_, Some(trait_), cx))
2027-
})
2039+
clean::ItemKind::ImplItem(ref i) if i.trait_.is_some() => {
2040+
// Alternative format produces no URLs,
2041+
// so this parameter does nothing.
2042+
Some((format!("{:#}", i.for_.print(cx)), get_id_for_impl(cx.tcx(), item.item_id)))
20282043
}
20292044
_ => None,
20302045
}

src/librustdoc/html/render/search_index.rs

+31-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::formats::cache::{Cache, OrphanImplItem};
1212
use crate::formats::item_type::ItemType;
1313
use crate::html::format::join_with_double_colon;
1414
use crate::html::markdown::short_markdown_summary;
15-
use crate::html::render::{IndexItem, IndexItemFunctionType, RenderType, RenderTypeId};
15+
use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId};
1616

1717
/// Builds the search index from the collected metadata
1818
pub(crate) fn build_index<'tcx>(
@@ -26,7 +26,8 @@ pub(crate) fn build_index<'tcx>(
2626

2727
// Attach all orphan items to the type's definition if the type
2828
// has since been learned.
29-
for &OrphanImplItem { parent, ref item, ref impl_generics } in &cache.orphan_impl_items {
29+
for &OrphanImplItem { impl_id, parent, ref item, ref impl_generics } in &cache.orphan_impl_items
30+
{
3031
if let Some((fqp, _)) = cache.paths.get(&parent) {
3132
let desc = short_markdown_summary(&item.doc_value(), &item.link_names(cache));
3233
cache.search_index.push(IndexItem {
@@ -36,6 +37,7 @@ pub(crate) fn build_index<'tcx>(
3637
desc,
3738
parent: Some(parent),
3839
parent_idx: None,
40+
impl_id,
3941
search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache),
4042
aliases: item.attrs.get_doc_aliases(),
4143
deprecation: item.deprecation(tcx),
@@ -210,6 +212,29 @@ pub(crate) fn build_index<'tcx>(
210212
})
211213
.collect();
212214

215+
// Find associated items that need disambiguators
216+
let mut associated_item_duplicates = FxHashMap::<(usize, ItemType, Symbol), usize>::default();
217+
218+
for &item in &crate_items {
219+
if item.impl_id.is_some() && let Some(parent_idx) = item.parent_idx {
220+
let count = associated_item_duplicates
221+
.entry((parent_idx, item.ty, item.name))
222+
.or_insert(0);
223+
*count += 1;
224+
}
225+
}
226+
227+
let associated_item_disambiguators = crate_items
228+
.iter()
229+
.enumerate()
230+
.filter_map(|(index, item)| {
231+
let impl_id = ItemId::DefId(item.impl_id?);
232+
let parent_idx = item.parent_idx?;
233+
let count = *associated_item_duplicates.get(&(parent_idx, item.ty, item.name))?;
234+
if count > 1 { Some((index, render::get_id_for_impl(tcx, impl_id))) } else { None }
235+
})
236+
.collect::<Vec<_>>();
237+
213238
struct CrateData<'a> {
214239
doc: String,
215240
items: Vec<&'a IndexItem>,
@@ -218,6 +243,8 @@ pub(crate) fn build_index<'tcx>(
218243
//
219244
// To be noted: the `usize` elements are indexes to `items`.
220245
aliases: &'a BTreeMap<String, Vec<usize>>,
246+
// Used when a type has more than one impl with an associated item with the same name.
247+
associated_item_disambiguators: &'a Vec<(usize, String)>,
221248
}
222249

223250
struct Paths {
@@ -370,6 +397,7 @@ pub(crate) fn build_index<'tcx>(
370397
crate_data.serialize_field("f", &functions)?;
371398
crate_data.serialize_field("c", &deprecated)?;
372399
crate_data.serialize_field("p", &paths)?;
400+
crate_data.serialize_field("b", &self.associated_item_disambiguators)?;
373401
if has_aliases {
374402
crate_data.serialize_field("a", &self.aliases)?;
375403
}
@@ -386,6 +414,7 @@ pub(crate) fn build_index<'tcx>(
386414
items: crate_items,
387415
paths: crate_paths,
388416
aliases: &aliases,
417+
associated_item_disambiguators: &associated_item_disambiguators,
389418
})
390419
.expect("failed serde conversion")
391420
// All these `replace` calls are because we have to go through JS string for JSON content.

src/librustdoc/html/render/sidebar.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,7 @@ fn sidebar_render_assoc_items(
501501
.iter()
502502
.filter_map(|it| {
503503
let trait_ = it.inner_impl().trait_.as_ref()?;
504-
let encoded =
505-
id_map.derive(super::get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx));
504+
let encoded = id_map.derive(super::get_id_for_impl(cx.tcx(), it.impl_item.item_id));
506505

507506
let prefix = match it.inner_impl().polarity {
508507
ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "",

src/librustdoc/html/static/js/main.js

+24
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,30 @@ function preLoadCss(cssUrl) {
363363
expandSection(pageId);
364364
}
365365
}
366+
if (savedHash.startsWith("#impl-")) {
367+
// impl-disambiguated links, used by the search engine
368+
// format: impl-X[-for-Y]/method.WHATEVER
369+
// turn this into method.WHATEVER[-NUMBER]
370+
const splitAt = savedHash.indexOf("/");
371+
if (splitAt !== -1) {
372+
const implId = savedHash.slice(1, splitAt);
373+
const assocId = savedHash.slice(splitAt + 1);
374+
const implElem = document.getElementById(implId);
375+
if (implElem && implElem.parentElement.tagName === "SUMMARY" &&
376+
implElem.parentElement.parentElement.tagName === "DETAILS") {
377+
onEachLazy(implElem.parentElement.parentElement.querySelectorAll(
378+
`[id^="${assocId}"]`),
379+
item => {
380+
const numbered = /([^-]+)-([0-9]+)/.exec(item.id);
381+
if (item.id === assocId || (numbered && numbered[1] === assocId)) {
382+
expandSection(item.id);
383+
window.location = "#" + item.id;
384+
}
385+
}
386+
);
387+
}
388+
}
389+
}
366390
}
367391

368392
function onHashChange(ev) {

src/librustdoc/html/static/js/search.js

+16-3
Original file line numberDiff line numberDiff line change
@@ -1656,6 +1656,7 @@ function initSearch(rawSearchIndex) {
16561656
type: item.type,
16571657
is_alias: true,
16581658
deprecated: item.deprecated,
1659+
implDisambiguator: item.implDisambiguator,
16591660
};
16601661
}
16611662

@@ -2061,7 +2062,7 @@ function initSearch(rawSearchIndex) {
20612062
href = ROOT_PATH + name + "/index.html";
20622063
} else if (item.parent !== undefined) {
20632064
const myparent = item.parent;
2064-
let anchor = "#" + type + "." + name;
2065+
let anchor = type + "." + name;
20652066
const parentType = itemTypes[myparent.ty];
20662067
let pageType = parentType;
20672068
let pageName = myparent.name;
@@ -2075,16 +2076,19 @@ function initSearch(rawSearchIndex) {
20752076
const enumName = item.path.substr(enumNameIdx + 2);
20762077
path = item.path.substr(0, enumNameIdx);
20772078
displayPath = path + "::" + enumName + "::" + myparent.name + "::";
2078-
anchor = "#variant." + myparent.name + ".field." + name;
2079+
anchor = "variant." + myparent.name + ".field." + name;
20792080
pageType = "enum";
20802081
pageName = enumName;
20812082
} else {
20822083
displayPath = path + "::" + myparent.name + "::";
20832084
}
2085+
if (item.implDisambiguator !== null) {
2086+
anchor = item.implDisambiguator + "/" + anchor;
2087+
}
20842088
href = ROOT_PATH + path.replace(/::/g, "/") +
20852089
"/" + pageType +
20862090
"." + pageName +
2087-
".html" + anchor;
2091+
".html#" + anchor;
20882092
} else {
20892093
displayPath = item.path + "::";
20902094
href = ROOT_PATH + item.path.replace(/::/g, "/") +
@@ -2562,6 +2566,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
25622566
* Types are also represented as arrays; the first item is an index into the `p`
25632567
* array, while the second is a list of types representing any generic parameters.
25642568
*
2569+
* b[i] contains an item's impl disambiguator. This is only present if an item
2570+
* is defined in an impl block and, the impl block's type has more than one associated
2571+
* item with the same name.
2572+
*
25652573
* `a` defines aliases with an Array of pairs: [name, offset], where `offset`
25662574
* points into the n/t/d/q/i/f arrays.
25672575
*
@@ -2581,6 +2589,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
25812589
* i: Array<Number>,
25822590
* f: Array<RawFunctionSearchType>,
25832591
* p: Array<Object>,
2592+
* b: Array<[Number, String]>,
25842593
* c: Array<Number>
25852594
* }}
25862595
*/
@@ -2601,6 +2610,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
26012610
id: id,
26022611
normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""),
26032612
deprecated: null,
2613+
implDisambiguator: null,
26042614
};
26052615
id += 1;
26062616
searchIndex.push(crateRow);
@@ -2624,6 +2634,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\
26242634
const itemFunctionSearchTypes = crateCorpus.f;
26252635
// an array of (Number) indices for the deprecated items
26262636
const deprecatedItems = new Set(crateCorpus.c);
2637+
// an array of (Number) indices for the deprecated items
2638+
const implDisambiguator = new Map(crateCorpus.b);
26272639
// an array of [(Number) item type,
26282640
// (String) name]
26292641
const paths = crateCorpus.p;
@@ -2684,6 +2696,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
26842696
id: id,
26852697
normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
26862698
deprecated: deprecatedItems.has(i),
2699+
implDisambiguator: implDisambiguator.has(i) ? implDisambiguator.get(i) : null,
26872700
};
26882701
id += 1;
26892702
searchIndex.push(row);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// ignore-tidy-linelength
2+
3+
// Checks that, if a type has two methods with the same name, they both get
4+
// linked correctly.
5+
goto: "file://" + |DOC_PATH| + "/test_docs/index.html"
6+
7+
// This should link to the inherent impl
8+
write: (".search-input", "ZyxwvutMethodDisambiguation -> bool")
9+
// To be SURE that the search will be run.
10+
press-key: 'Enter'
11+
// Waiting for the search results to appear...
12+
wait-for: "#search-tabs"
13+
// Check the disambiguated link.
14+
assert-count: ("a.result-method", 1)
15+
assert-attribute: ("a.result-method", {
16+
"href": "../test_docs/struct.ZyxwvutMethodDisambiguation.html#impl-ZyxwvutMethodDisambiguation/method.method_impl_disambiguation"
17+
})
18+
click: "a.result-method"
19+
wait-for: "#impl-ZyxwvutMethodDisambiguation"
20+
assert-document-property: ({
21+
"URL": "struct.ZyxwvutMethodDisambiguation.html#method.method_impl_disambiguation"
22+
}, ENDS_WITH)
23+
24+
goto: "file://" + |DOC_PATH| + "/test_docs/index.html"
25+
26+
// This should link to the trait impl
27+
write: (".search-input", "ZyxwvutMethodDisambiguation, usize -> usize")
28+
// To be SURE that the search will be run.
29+
press-key: 'Enter'
30+
// Waiting for the search results to appear...
31+
wait-for: "#search-tabs"
32+
// Check the disambiguated link.
33+
assert-count: ("a.result-method", 1)
34+
assert-attribute: ("a.result-method", {
35+
"href": "../test_docs/struct.ZyxwvutMethodDisambiguation.html#impl-ZyxwvutTrait-for-ZyxwvutMethodDisambiguation/method.method_impl_disambiguation"
36+
})
37+
click: "a.result-method"
38+
wait-for: "#impl-ZyxwvutMethodDisambiguation"
39+
assert-document-property: ({
40+
"URL": "struct.ZyxwvutMethodDisambiguation.html#method.method_impl_disambiguation-1"
41+
}, ENDS_WITH)

tests/rustdoc-gui/src/test_docs/lib.rs

+18
Original file line numberDiff line numberDiff line change
@@ -529,3 +529,21 @@ pub mod cfgs {
529529
/// Some docs.
530530
pub mod cfgs {}
531531
}
532+
533+
pub struct ZyxwvutMethodDisambiguation;
534+
535+
impl ZyxwvutMethodDisambiguation {
536+
pub fn method_impl_disambiguation(&self) -> bool {
537+
true
538+
}
539+
}
540+
541+
pub trait ZyxwvutTrait {
542+
fn method_impl_disambiguation(&self, x: usize) -> usize;
543+
}
544+
545+
impl ZyxwvutTrait for ZyxwvutMethodDisambiguation {
546+
fn method_impl_disambiguation(&self, x: usize) -> usize {
547+
x
548+
}
549+
}

0 commit comments

Comments
 (0)