Skip to content

Commit f36c5af

Browse files
committed
rustdoc-search: single result for items with multiple paths
This change uses the same "exact" paths as trait implementors and type alias inlining to track items with multiple reachable paths. This way, if you search for `vec`, you get only the `std` exports of it, and not the one from `alloc`. It still includes all the items in the search index so that you can search for them by all available paths. For example, try `core::option` and `std::option`, and notice that the results page doesn't show duplicates, but still shows all the items in their respective crates.
1 parent ab5bda1 commit f36c5af

File tree

13 files changed

+313
-25
lines changed

13 files changed

+313
-25
lines changed

src/librustdoc/formats/cache.rs

+12
Original file line numberDiff line numberDiff line change
@@ -348,16 +348,28 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
348348
{
349349
let desc =
350350
short_markdown_summary(&item.doc_value(), &item.link_names(self.cache));
351+
// For searching purposes, a re-export is a duplicate if:
352+
//
353+
// - It's either an inline, or a true re-export
354+
// - It's got the same name
355+
// - Both of them have the same exact path
356+
let defid = (match &*item.kind {
357+
&clean::ItemKind::ImportItem(ref import) => import.source.did,
358+
_ => None,
359+
})
360+
.or_else(|| item.item_id.as_def_id());
351361
// In case this is a field from a tuple struct, we don't add it into
352362
// the search index because its name is something like "0", which is
353363
// not useful for rustdoc search.
354364
self.cache.search_index.push(IndexItem {
355365
ty,
366+
defid,
356367
name: s,
357368
path: join_with_double_colon(path),
358369
desc,
359370
parent,
360371
parent_idx: None,
372+
exact_path: None,
361373
impl_id: if let Some(ParentStackItem::Impl { item_id, .. }) =
362374
self.cache.parent_stack.last()
363375
{

src/librustdoc/html/render/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,13 @@ pub(crate) enum RenderMode {
111111
#[derive(Debug)]
112112
pub(crate) struct IndexItem {
113113
pub(crate) ty: ItemType,
114+
pub(crate) defid: Option<DefId>,
114115
pub(crate) name: Symbol,
115116
pub(crate) path: String,
116117
pub(crate) desc: String,
117118
pub(crate) parent: Option<DefId>,
118119
pub(crate) parent_idx: Option<isize>,
120+
pub(crate) exact_path: Option<String>,
119121
pub(crate) impl_id: Option<DefId>,
120122
pub(crate) search_type: Option<IndexItemFunctionType>,
121123
pub(crate) aliases: Box<[Symbol]>,

src/librustdoc/html/render/search_index.rs

+134-16
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,13 @@ pub(crate) fn build_index<'tcx>(
5959
cache: &mut Cache,
6060
tcx: TyCtxt<'tcx>,
6161
) -> SerializedSearchIndex {
62+
// Maps from ID to position in the `crate_paths` array.
6263
let mut itemid_to_pathid = FxHashMap::default();
6364
let mut primitives = FxHashMap::default();
6465
let mut associated_types = FxHashMap::default();
65-
let mut crate_paths = vec![];
66+
67+
// item type, display path, re-exported internal path
68+
let mut crate_paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)> = vec![];
6669

6770
// Attach all orphan items to the type's definition if the type
6871
// has since been learned.
@@ -72,11 +75,13 @@ pub(crate) fn build_index<'tcx>(
7275
let desc = short_markdown_summary(&item.doc_value(), &item.link_names(cache));
7376
cache.search_index.push(IndexItem {
7477
ty: item.type_(),
78+
defid: item.item_id.as_def_id(),
7579
name: item.name.unwrap(),
7680
path: join_with_double_colon(&fqp[..fqp.len() - 1]),
7781
desc,
7882
parent: Some(parent),
7983
parent_idx: None,
84+
exact_path: None,
8085
impl_id,
8186
search_type: get_function_type_for_search(
8287
item,
@@ -126,17 +131,22 @@ pub(crate) fn build_index<'tcx>(
126131
map: &mut FxHashMap<F, isize>,
127132
itemid: F,
128133
lastpathid: &mut isize,
129-
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
134+
crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
130135
item_type: ItemType,
131136
path: &[Symbol],
137+
exact_path: Option<&[Symbol]>,
132138
) -> RenderTypeId {
133139
match map.entry(itemid) {
134140
Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()),
135141
Entry::Vacant(entry) => {
136142
let pathid = *lastpathid;
137143
entry.insert(pathid);
138144
*lastpathid += 1;
139-
crate_paths.push((item_type, path.to_vec()));
145+
crate_paths.push((
146+
item_type,
147+
path.to_vec(),
148+
exact_path.map(|path| path.to_vec()),
149+
));
140150
RenderTypeId::Index(pathid)
141151
}
142152
}
@@ -149,21 +159,30 @@ pub(crate) fn build_index<'tcx>(
149159
primitives: &mut FxHashMap<Symbol, isize>,
150160
associated_types: &mut FxHashMap<Symbol, isize>,
151161
lastpathid: &mut isize,
152-
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
162+
crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
153163
) -> Option<RenderTypeId> {
154-
let Cache { ref paths, ref external_paths, .. } = *cache;
164+
let Cache { ref paths, ref external_paths, ref exact_paths, .. } = *cache;
155165
match id {
156166
RenderTypeId::DefId(defid) => {
157167
if let Some(&(ref fqp, item_type)) =
158168
paths.get(&defid).or_else(|| external_paths.get(&defid))
159169
{
170+
let exact_fqp = exact_paths
171+
.get(&defid)
172+
.or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp))
173+
// re-exports only count if the name is exactly the same
174+
// this is a size optimization, as well as a DWIM attempt
175+
// since if the names are not the same, the intent probably
176+
// isn't, either
177+
.filter(|fqp| fqp.last() == fqp.last());
160178
Some(insert_into_map(
161179
itemid_to_pathid,
162180
ItemId::DefId(defid),
163181
lastpathid,
164182
crate_paths,
165183
item_type,
166184
fqp,
185+
exact_fqp.map(|x| &x[..]).filter(|exact_fqp| exact_fqp != fqp),
167186
))
168187
} else {
169188
None
@@ -178,6 +197,7 @@ pub(crate) fn build_index<'tcx>(
178197
crate_paths,
179198
ItemType::Primitive,
180199
&[sym],
200+
None,
181201
))
182202
}
183203
RenderTypeId::Index(_) => Some(id),
@@ -188,6 +208,7 @@ pub(crate) fn build_index<'tcx>(
188208
crate_paths,
189209
ItemType::AssocType,
190210
&[sym],
211+
None,
191212
)),
192213
}
193214
}
@@ -199,7 +220,7 @@ pub(crate) fn build_index<'tcx>(
199220
primitives: &mut FxHashMap<Symbol, isize>,
200221
associated_types: &mut FxHashMap<Symbol, isize>,
201222
lastpathid: &mut isize,
202-
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
223+
crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
203224
) {
204225
if let Some(generics) = &mut ty.generics {
205226
for item in generics {
@@ -296,7 +317,7 @@ pub(crate) fn build_index<'tcx>(
296317
}
297318
}
298319

299-
let Cache { ref paths, .. } = *cache;
320+
let Cache { ref paths, ref exact_paths, ref external_paths, .. } = *cache;
300321

301322
// Then, on parent modules
302323
let crate_items: Vec<&IndexItem> = search_index
@@ -311,14 +332,54 @@ pub(crate) fn build_index<'tcx>(
311332
lastpathid += 1;
312333

313334
if let Some(&(ref fqp, short)) = paths.get(&defid) {
314-
crate_paths.push((short, fqp.clone()));
335+
let exact_fqp = exact_paths
336+
.get(&defid)
337+
.or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp))
338+
.filter(|exact_fqp| {
339+
exact_fqp.last() == Some(&item.name) && *exact_fqp != fqp
340+
});
341+
crate_paths.push((short, fqp.clone(), exact_fqp.cloned()));
315342
Some(pathid)
316343
} else {
317344
None
318345
}
319346
}
320347
});
321348

349+
if let Some(defid) = item.defid
350+
&& item.parent_idx.is_none()
351+
{
352+
// If this is a re-export, retain the original path.
353+
// Associated items don't use this.
354+
// Their parent carries the exact fqp instead.
355+
let exact_fqp = exact_paths
356+
.get(&defid)
357+
.or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp));
358+
item.exact_path = exact_fqp.and_then(|fqp| {
359+
// re-exports only count if the name is exactly the same
360+
// this is a size optimization, as well as a DWIM attempt
361+
// since if the names are not the same, the intent probably
362+
// isn't, either
363+
if fqp.last() != Some(&item.name) {
364+
return None;
365+
}
366+
let path =
367+
if item.ty == ItemType::Macro && tcx.has_attr(defid, sym::macro_export) {
368+
// `#[macro_export]` always exports to the crate root.
369+
tcx.crate_name(defid.krate).to_string()
370+
} else {
371+
if fqp.len() < 2 {
372+
return None;
373+
}
374+
join_with_double_colon(&fqp[..fqp.len() - 1])
375+
};
376+
if path == item.path {
377+
return None;
378+
}
379+
Some(path)
380+
});
381+
}
382+
322383
// Omit the parent path if it is same to that of the prior item.
323384
if lastpath == &item.path {
324385
item.path.clear();
@@ -356,7 +417,7 @@ pub(crate) fn build_index<'tcx>(
356417

357418
struct CrateData<'a> {
358419
items: Vec<&'a IndexItem>,
359-
paths: Vec<(ItemType, Vec<Symbol>)>,
420+
paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
360421
// The String is alias name and the vec is the list of the elements with this alias.
361422
//
362423
// To be noted: the `usize` elements are indexes to `items`.
@@ -374,6 +435,7 @@ pub(crate) fn build_index<'tcx>(
374435
ty: ItemType,
375436
name: Symbol,
376437
path: Option<usize>,
438+
exact_path: Option<usize>,
377439
}
378440

379441
impl Serialize for Paths {
@@ -387,6 +449,10 @@ pub(crate) fn build_index<'tcx>(
387449
if let Some(ref path) = self.path {
388450
seq.serialize_element(path)?;
389451
}
452+
if let Some(ref path) = self.exact_path {
453+
assert!(self.path.is_some());
454+
seq.serialize_element(path)?;
455+
}
390456
seq.end()
391457
}
392458
}
@@ -409,43 +475,94 @@ pub(crate) fn build_index<'tcx>(
409475
mod_paths.insert(&item.path, index);
410476
}
411477
let mut paths = Vec::with_capacity(self.paths.len());
412-
for (ty, path) in &self.paths {
478+
for (ty, path, exact) in &self.paths {
413479
if path.len() < 2 {
414-
paths.push(Paths { ty: *ty, name: path[0], path: None });
480+
paths.push(Paths { ty: *ty, name: path[0], path: None, exact_path: None });
415481
continue;
416482
}
417483
let full_path = join_with_double_colon(&path[..path.len() - 1]);
484+
let full_exact_path = exact
485+
.as_ref()
486+
.filter(|exact| exact.last() == path.last() && exact.len() >= 2)
487+
.map(|exact| join_with_double_colon(&exact[..exact.len() - 1]));
488+
let exact_path = extra_paths.len() + self.items.len();
489+
let exact_path = full_exact_path.as_ref().map(|full_exact_path| match extra_paths
490+
.entry(full_exact_path.clone())
491+
{
492+
Entry::Occupied(entry) => *entry.get(),
493+
Entry::Vacant(entry) => {
494+
if let Some(index) = mod_paths.get(&full_exact_path) {
495+
return *index;
496+
}
497+
entry.insert(exact_path);
498+
if !revert_extra_paths.contains_key(&exact_path) {
499+
revert_extra_paths.insert(exact_path, full_exact_path.clone());
500+
}
501+
exact_path
502+
}
503+
});
418504
if let Some(index) = mod_paths.get(&full_path) {
419-
paths.push(Paths { ty: *ty, name: *path.last().unwrap(), path: Some(*index) });
505+
paths.push(Paths {
506+
ty: *ty,
507+
name: *path.last().unwrap(),
508+
path: Some(*index),
509+
exact_path,
510+
});
420511
continue;
421512
}
422513
// It means it comes from an external crate so the item and its path will be
423514
// stored into another array.
424515
//
425516
// `index` is put after the last `mod_paths`
426517
let index = extra_paths.len() + self.items.len();
427-
if !revert_extra_paths.contains_key(&index) {
428-
revert_extra_paths.insert(index, full_path.clone());
429-
}
430-
match extra_paths.entry(full_path) {
518+
match extra_paths.entry(full_path.clone()) {
431519
Entry::Occupied(entry) => {
432520
paths.push(Paths {
433521
ty: *ty,
434522
name: *path.last().unwrap(),
435523
path: Some(*entry.get()),
524+
exact_path,
436525
});
437526
}
438527
Entry::Vacant(entry) => {
439528
entry.insert(index);
529+
if !revert_extra_paths.contains_key(&index) {
530+
revert_extra_paths.insert(index, full_path);
531+
}
440532
paths.push(Paths {
441533
ty: *ty,
442534
name: *path.last().unwrap(),
443535
path: Some(index),
536+
exact_path,
444537
});
445538
}
446539
}
447540
}
448541

542+
// Direct exports use adjacent arrays for the current crate's items,
543+
// but re-exported exact paths don't.
544+
let mut re_exports = Vec::new();
545+
for (item_index, item) in self.items.iter().enumerate() {
546+
if let Some(exact_path) = item.exact_path.as_ref() {
547+
if let Some(path_index) = mod_paths.get(&exact_path) {
548+
re_exports.push((item_index, *path_index));
549+
} else {
550+
let path_index = extra_paths.len() + self.items.len();
551+
let path_index = match extra_paths.entry(exact_path.clone()) {
552+
Entry::Occupied(entry) => *entry.get(),
553+
Entry::Vacant(entry) => {
554+
entry.insert(path_index);
555+
if !revert_extra_paths.contains_key(&path_index) {
556+
revert_extra_paths.insert(path_index, exact_path.clone());
557+
}
558+
path_index
559+
}
560+
};
561+
re_exports.push((item_index, path_index));
562+
}
563+
}
564+
}
565+
449566
let mut names = Vec::with_capacity(self.items.len());
450567
let mut types = String::with_capacity(self.items.len());
451568
let mut full_paths = Vec::with_capacity(self.items.len());
@@ -501,6 +618,7 @@ pub(crate) fn build_index<'tcx>(
501618
crate_data.serialize_field("f", &functions)?;
502619
crate_data.serialize_field("D", &self.desc_index)?;
503620
crate_data.serialize_field("p", &paths)?;
621+
crate_data.serialize_field("r", &re_exports)?;
504622
crate_data.serialize_field("b", &self.associated_item_disambiguators)?;
505623
crate_data.serialize_field("c", &bitmap_to_string(&deprecated))?;
506624
crate_data.serialize_field("e", &bitmap_to_string(&self.empty_desc))?;

0 commit comments

Comments
 (0)