1
Fork 0

Auto merge of #119912 - notriddle:notriddle/reexport-dedup, r=GuillaumeGomez

rustdoc-search: single result for items with multiple paths

Part of #15723

Preview: https://notriddle.com/rustdoc-html-demo-9/reexport-dup/std/index.html?search=hashmap

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.
This commit is contained in:
bors 2024-04-18 21:23:15 +00:00
commit e3181b091e
14 changed files with 330 additions and 31 deletions

View file

@ -346,16 +346,28 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
{ {
let desc = let desc =
short_markdown_summary(&item.doc_value(), &item.link_names(self.cache)); short_markdown_summary(&item.doc_value(), &item.link_names(self.cache));
// For searching purposes, a re-export is a duplicate if:
//
// - It's either an inline, or a true re-export
// - It's got the same name
// - Both of them have the same exact path
let defid = (match &*item.kind {
&clean::ItemKind::ImportItem(ref import) => import.source.did,
_ => None,
})
.or_else(|| item.item_id.as_def_id());
// In case this is a field from a tuple struct, we don't add it into // 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 // the search index because its name is something like "0", which is
// not useful for rustdoc search. // not useful for rustdoc search.
self.cache.search_index.push(IndexItem { self.cache.search_index.push(IndexItem {
ty, ty,
defid,
name: s, name: s,
path: join_with_double_colon(path), path: join_with_double_colon(path),
desc, desc,
parent, parent,
parent_idx: None, parent_idx: None,
exact_path: None,
impl_id: if let Some(ParentStackItem::Impl { item_id, .. }) = impl_id: if let Some(ParentStackItem::Impl { item_id, .. }) =
self.cache.parent_stack.last() self.cache.parent_stack.last()
{ {

View file

@ -111,11 +111,13 @@ pub(crate) enum RenderMode {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct IndexItem { pub(crate) struct IndexItem {
pub(crate) ty: ItemType, pub(crate) ty: ItemType,
pub(crate) defid: Option<DefId>,
pub(crate) name: Symbol, pub(crate) name: Symbol,
pub(crate) path: String, pub(crate) path: String,
pub(crate) desc: String, pub(crate) desc: String,
pub(crate) parent: Option<DefId>, pub(crate) parent: Option<DefId>,
pub(crate) parent_idx: Option<isize>, pub(crate) parent_idx: Option<isize>,
pub(crate) exact_path: Option<String>,
pub(crate) impl_id: Option<DefId>, pub(crate) impl_id: Option<DefId>,
pub(crate) search_type: Option<IndexItemFunctionType>, pub(crate) search_type: Option<IndexItemFunctionType>,
pub(crate) aliases: Box<[Symbol]>, pub(crate) aliases: Box<[Symbol]>,

View file

@ -59,10 +59,13 @@ pub(crate) fn build_index<'tcx>(
cache: &mut Cache, cache: &mut Cache,
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
) -> SerializedSearchIndex { ) -> SerializedSearchIndex {
// Maps from ID to position in the `crate_paths` array.
let mut itemid_to_pathid = FxHashMap::default(); let mut itemid_to_pathid = FxHashMap::default();
let mut primitives = FxHashMap::default(); let mut primitives = FxHashMap::default();
let mut associated_types = FxHashMap::default(); let mut associated_types = FxHashMap::default();
let mut crate_paths = vec![];
// item type, display path, re-exported internal path
let mut crate_paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)> = vec![];
// Attach all orphan items to the type's definition if the type // Attach all orphan items to the type's definition if the type
// has since been learned. // has since been learned.
@ -72,11 +75,13 @@ pub(crate) fn build_index<'tcx>(
let desc = short_markdown_summary(&item.doc_value(), &item.link_names(cache)); let desc = short_markdown_summary(&item.doc_value(), &item.link_names(cache));
cache.search_index.push(IndexItem { cache.search_index.push(IndexItem {
ty: item.type_(), ty: item.type_(),
defid: item.item_id.as_def_id(),
name: item.name.unwrap(), name: item.name.unwrap(),
path: join_with_double_colon(&fqp[..fqp.len() - 1]), path: join_with_double_colon(&fqp[..fqp.len() - 1]),
desc, desc,
parent: Some(parent), parent: Some(parent),
parent_idx: None, parent_idx: None,
exact_path: None,
impl_id, impl_id,
search_type: get_function_type_for_search( search_type: get_function_type_for_search(
item, item,
@ -126,9 +131,10 @@ pub(crate) fn build_index<'tcx>(
map: &mut FxHashMap<F, isize>, map: &mut FxHashMap<F, isize>,
itemid: F, itemid: F,
lastpathid: &mut isize, lastpathid: &mut isize,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>, crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
item_type: ItemType, item_type: ItemType,
path: &[Symbol], path: &[Symbol],
exact_path: Option<&[Symbol]>,
) -> RenderTypeId { ) -> RenderTypeId {
match map.entry(itemid) { match map.entry(itemid) {
Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()), Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()),
@ -136,7 +142,11 @@ pub(crate) fn build_index<'tcx>(
let pathid = *lastpathid; let pathid = *lastpathid;
entry.insert(pathid); entry.insert(pathid);
*lastpathid += 1; *lastpathid += 1;
crate_paths.push((item_type, path.to_vec())); crate_paths.push((
item_type,
path.to_vec(),
exact_path.map(|path| path.to_vec()),
));
RenderTypeId::Index(pathid) RenderTypeId::Index(pathid)
} }
} }
@ -149,14 +159,24 @@ pub(crate) fn build_index<'tcx>(
primitives: &mut FxHashMap<Symbol, isize>, primitives: &mut FxHashMap<Symbol, isize>,
associated_types: &mut FxHashMap<Symbol, isize>, associated_types: &mut FxHashMap<Symbol, isize>,
lastpathid: &mut isize, lastpathid: &mut isize,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>, crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
) -> Option<RenderTypeId> { ) -> Option<RenderTypeId> {
let Cache { ref paths, ref external_paths, .. } = *cache; let Cache { ref paths, ref external_paths, ref exact_paths, .. } = *cache;
match id { match id {
RenderTypeId::DefId(defid) => { RenderTypeId::DefId(defid) => {
if let Some(&(ref fqp, item_type)) = if let Some(&(ref fqp, item_type)) =
paths.get(&defid).or_else(|| external_paths.get(&defid)) paths.get(&defid).or_else(|| external_paths.get(&defid))
{ {
let exact_fqp = exact_paths
.get(&defid)
.or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp))
// Re-exports only count if the name is exactly the same.
// This is a size optimization, since it means we only need
// to store the name once (and the path is re-used for everything
// exported from this same module). It's also likely to Do
// What I Mean, since if a re-export changes the name, it might
// also be a change in semantic meaning.
.filter(|fqp| fqp.last() == fqp.last());
Some(insert_into_map( Some(insert_into_map(
itemid_to_pathid, itemid_to_pathid,
ItemId::DefId(defid), ItemId::DefId(defid),
@ -164,6 +184,7 @@ pub(crate) fn build_index<'tcx>(
crate_paths, crate_paths,
item_type, item_type,
fqp, fqp,
exact_fqp.map(|x| &x[..]).filter(|exact_fqp| exact_fqp != fqp),
)) ))
} else { } else {
None None
@ -178,6 +199,7 @@ pub(crate) fn build_index<'tcx>(
crate_paths, crate_paths,
ItemType::Primitive, ItemType::Primitive,
&[sym], &[sym],
None,
)) ))
} }
RenderTypeId::Index(_) => Some(id), RenderTypeId::Index(_) => Some(id),
@ -188,6 +210,7 @@ pub(crate) fn build_index<'tcx>(
crate_paths, crate_paths,
ItemType::AssocType, ItemType::AssocType,
&[sym], &[sym],
None,
)), )),
} }
} }
@ -199,7 +222,7 @@ pub(crate) fn build_index<'tcx>(
primitives: &mut FxHashMap<Symbol, isize>, primitives: &mut FxHashMap<Symbol, isize>,
associated_types: &mut FxHashMap<Symbol, isize>, associated_types: &mut FxHashMap<Symbol, isize>,
lastpathid: &mut isize, lastpathid: &mut isize,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>, crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
) { ) {
if let Some(generics) = &mut ty.generics { if let Some(generics) = &mut ty.generics {
for item in generics { for item in generics {
@ -296,7 +319,7 @@ pub(crate) fn build_index<'tcx>(
} }
} }
let Cache { ref paths, .. } = *cache; let Cache { ref paths, ref exact_paths, ref external_paths, .. } = *cache;
// Then, on parent modules // Then, on parent modules
let crate_items: Vec<&IndexItem> = search_index let crate_items: Vec<&IndexItem> = search_index
@ -311,7 +334,13 @@ pub(crate) fn build_index<'tcx>(
lastpathid += 1; lastpathid += 1;
if let Some(&(ref fqp, short)) = paths.get(&defid) { if let Some(&(ref fqp, short)) = paths.get(&defid) {
crate_paths.push((short, fqp.clone())); let exact_fqp = exact_paths
.get(&defid)
.or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp))
.filter(|exact_fqp| {
exact_fqp.last() == Some(&item.name) && *exact_fqp != fqp
});
crate_paths.push((short, fqp.clone(), exact_fqp.cloned()));
Some(pathid) Some(pathid)
} else { } else {
None None
@ -319,6 +348,42 @@ pub(crate) fn build_index<'tcx>(
} }
}); });
if let Some(defid) = item.defid
&& item.parent_idx.is_none()
{
// If this is a re-export, retain the original path.
// Associated items don't use this.
// Their parent carries the exact fqp instead.
let exact_fqp = exact_paths
.get(&defid)
.or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp));
item.exact_path = exact_fqp.and_then(|fqp| {
// Re-exports only count if the name is exactly the same.
// This is a size optimization, since it means we only need
// to store the name once (and the path is re-used for everything
// exported from this same module). It's also likely to Do
// What I Mean, since if a re-export changes the name, it might
// also be a change in semantic meaning.
if fqp.last() != Some(&item.name) {
return None;
}
let path =
if item.ty == ItemType::Macro && tcx.has_attr(defid, sym::macro_export) {
// `#[macro_export]` always exports to the crate root.
tcx.crate_name(defid.krate).to_string()
} else {
if fqp.len() < 2 {
return None;
}
join_with_double_colon(&fqp[..fqp.len() - 1])
};
if path == item.path {
return None;
}
Some(path)
});
}
// Omit the parent path if it is same to that of the prior item. // Omit the parent path if it is same to that of the prior item.
if lastpath == &item.path { if lastpath == &item.path {
item.path.clear(); item.path.clear();
@ -356,7 +421,7 @@ pub(crate) fn build_index<'tcx>(
struct CrateData<'a> { struct CrateData<'a> {
items: Vec<&'a IndexItem>, items: Vec<&'a IndexItem>,
paths: Vec<(ItemType, Vec<Symbol>)>, paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
// The String is alias name and the vec is the list of the elements with this alias. // The String is alias name and the vec is the list of the elements with this alias.
// //
// To be noted: the `usize` elements are indexes to `items`. // To be noted: the `usize` elements are indexes to `items`.
@ -374,6 +439,7 @@ pub(crate) fn build_index<'tcx>(
ty: ItemType, ty: ItemType,
name: Symbol, name: Symbol,
path: Option<usize>, path: Option<usize>,
exact_path: Option<usize>,
} }
impl Serialize for Paths { impl Serialize for Paths {
@ -387,6 +453,10 @@ pub(crate) fn build_index<'tcx>(
if let Some(ref path) = self.path { if let Some(ref path) = self.path {
seq.serialize_element(path)?; seq.serialize_element(path)?;
} }
if let Some(ref path) = self.exact_path {
assert!(self.path.is_some());
seq.serialize_element(path)?;
}
seq.end() seq.end()
} }
} }
@ -409,14 +479,39 @@ pub(crate) fn build_index<'tcx>(
mod_paths.insert(&item.path, index); mod_paths.insert(&item.path, index);
} }
let mut paths = Vec::with_capacity(self.paths.len()); let mut paths = Vec::with_capacity(self.paths.len());
for (ty, path) in &self.paths { for (ty, path, exact) in &self.paths {
if path.len() < 2 { if path.len() < 2 {
paths.push(Paths { ty: *ty, name: path[0], path: None }); paths.push(Paths { ty: *ty, name: path[0], path: None, exact_path: None });
continue; continue;
} }
let full_path = join_with_double_colon(&path[..path.len() - 1]); let full_path = join_with_double_colon(&path[..path.len() - 1]);
let full_exact_path = exact
.as_ref()
.filter(|exact| exact.last() == path.last() && exact.len() >= 2)
.map(|exact| join_with_double_colon(&exact[..exact.len() - 1]));
let exact_path = extra_paths.len() + self.items.len();
let exact_path = full_exact_path.as_ref().map(|full_exact_path| match extra_paths
.entry(full_exact_path.clone())
{
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
if let Some(index) = mod_paths.get(&full_exact_path) {
return *index;
}
entry.insert(exact_path);
if !revert_extra_paths.contains_key(&exact_path) {
revert_extra_paths.insert(exact_path, full_exact_path.clone());
}
exact_path
}
});
if let Some(index) = mod_paths.get(&full_path) { if let Some(index) = mod_paths.get(&full_path) {
paths.push(Paths { ty: *ty, name: *path.last().unwrap(), path: Some(*index) }); paths.push(Paths {
ty: *ty,
name: *path.last().unwrap(),
path: Some(*index),
exact_path,
});
continue; continue;
} }
// It means it comes from an external crate so the item and its path will be // It means it comes from an external crate so the item and its path will be
@ -424,28 +519,54 @@ pub(crate) fn build_index<'tcx>(
// //
// `index` is put after the last `mod_paths` // `index` is put after the last `mod_paths`
let index = extra_paths.len() + self.items.len(); let index = extra_paths.len() + self.items.len();
if !revert_extra_paths.contains_key(&index) { match extra_paths.entry(full_path.clone()) {
revert_extra_paths.insert(index, full_path.clone());
}
match extra_paths.entry(full_path) {
Entry::Occupied(entry) => { Entry::Occupied(entry) => {
paths.push(Paths { paths.push(Paths {
ty: *ty, ty: *ty,
name: *path.last().unwrap(), name: *path.last().unwrap(),
path: Some(*entry.get()), path: Some(*entry.get()),
exact_path,
}); });
} }
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
entry.insert(index); entry.insert(index);
if !revert_extra_paths.contains_key(&index) {
revert_extra_paths.insert(index, full_path);
}
paths.push(Paths { paths.push(Paths {
ty: *ty, ty: *ty,
name: *path.last().unwrap(), name: *path.last().unwrap(),
path: Some(index), path: Some(index),
exact_path,
}); });
} }
} }
} }
// Direct exports use adjacent arrays for the current crate's items,
// but re-exported exact paths don't.
let mut re_exports = Vec::new();
for (item_index, item) in self.items.iter().enumerate() {
if let Some(exact_path) = item.exact_path.as_ref() {
if let Some(path_index) = mod_paths.get(&exact_path) {
re_exports.push((item_index, *path_index));
} else {
let path_index = extra_paths.len() + self.items.len();
let path_index = match extra_paths.entry(exact_path.clone()) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
entry.insert(path_index);
if !revert_extra_paths.contains_key(&path_index) {
revert_extra_paths.insert(path_index, exact_path.clone());
}
path_index
}
};
re_exports.push((item_index, path_index));
}
}
}
let mut names = Vec::with_capacity(self.items.len()); let mut names = Vec::with_capacity(self.items.len());
let mut types = String::with_capacity(self.items.len()); let mut types = String::with_capacity(self.items.len());
let mut full_paths = Vec::with_capacity(self.items.len()); let mut full_paths = Vec::with_capacity(self.items.len());
@ -501,6 +622,7 @@ pub(crate) fn build_index<'tcx>(
crate_data.serialize_field("f", &functions)?; crate_data.serialize_field("f", &functions)?;
crate_data.serialize_field("D", &self.desc_index)?; crate_data.serialize_field("D", &self.desc_index)?;
crate_data.serialize_field("p", &paths)?; crate_data.serialize_field("p", &paths)?;
crate_data.serialize_field("r", &re_exports)?;
crate_data.serialize_field("b", &self.associated_item_disambiguators)?; crate_data.serialize_field("b", &self.associated_item_disambiguators)?;
crate_data.serialize_field("c", &bitmap_to_string(&deprecated))?; crate_data.serialize_field("c", &bitmap_to_string(&deprecated))?;
crate_data.serialize_field("e", &bitmap_to_string(&self.empty_desc))?; crate_data.serialize_field("e", &bitmap_to_string(&self.empty_desc))?;

View file

@ -239,20 +239,27 @@ let FunctionType;
* `doc` contains the description of the crate. * `doc` contains the description of the crate.
* *
* `p` is a list of path/type pairs. It is used for parents and function parameters. * `p` is a list of path/type pairs. It is used for parents and function parameters.
* The first item is the type, the second is the name, the third is the visible path (if any) and
* the fourth is the canonical path used for deduplication (if any).
*
* `r` is the canonical path used for deduplication of re-exported items.
* It is not used for associated items like methods (that's the fourth element
* of `p`) but is used for modules items like free functions.
* *
* `c` is an array of item indices that are deprecated. * `c` is an array of item indices that are deprecated.
* @typedef {{ * @typedef {{
* doc: string, * doc: string,
* a: Object, * a: Object,
* n: Array<string>, * n: Array<string>,
* t: String, * t: string,
* d: Array<string>, * d: Array<string>,
* q: Array<[Number, string]>, * q: Array<[number, string]>,
* i: Array<Number>, * i: Array<number>,
* f: string, * f: string,
* p: Array<Object>, * p: Array<[number, string] | [number, string, number] | [number, string, number, number]>,
* b: Array<[Number, String]>, * b: Array<[number, String]>,
* c: Array<Number> * c: Array<number>,
* r: Array<[number, number]>,
* }} * }}
*/ */
let RawSearchIndexCrate; let RawSearchIndexCrate;

View file

@ -79,6 +79,7 @@ const longItemTypes = [
// used for special search precedence // used for special search precedence
const TY_GENERIC = itemTypes.indexOf("generic"); const TY_GENERIC = itemTypes.indexOf("generic");
const TY_IMPORT = itemTypes.indexOf("import");
const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../"; const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
// Hard limit on how deep to recurse into generics when doing type-driven search. // Hard limit on how deep to recurse into generics when doing type-driven search.
@ -1324,14 +1325,23 @@ function initSearch(rawSearchIndex) {
obj.dist = result.dist; obj.dist = result.dist;
const res = buildHrefAndPath(obj); const res = buildHrefAndPath(obj);
obj.displayPath = pathSplitter(res[0]); obj.displayPath = pathSplitter(res[0]);
obj.fullPath = obj.displayPath + obj.name;
// To be sure than it some items aren't considered as duplicate.
obj.fullPath += "|" + obj.ty;
// To be sure than it some items aren't considered as duplicate.
obj.fullPath = res[2] + "|" + obj.ty;
if (duplicates.has(obj.fullPath)) { if (duplicates.has(obj.fullPath)) {
continue; continue;
} }
// Exports are specifically not shown if the items they point at
// are already in the results.
if (obj.ty === TY_IMPORT && duplicates.has(res[2])) {
continue;
}
if (duplicates.has(res[2] + "|" + TY_IMPORT)) {
continue;
}
duplicates.add(obj.fullPath); duplicates.add(obj.fullPath);
duplicates.add(res[2]);
obj.href = res[1]; obj.href = res[1];
out.push(obj); out.push(obj);
@ -2085,6 +2095,7 @@ function initSearch(rawSearchIndex) {
path: item.path, path: item.path,
descShard: item.descShard, descShard: item.descShard,
descIndex: item.descIndex, descIndex: item.descIndex,
exactPath: item.exactPath,
ty: item.ty, ty: item.ty,
parent: item.parent, parent: item.parent,
type: item.type, type: item.type,
@ -2538,6 +2549,7 @@ function initSearch(rawSearchIndex) {
const type = itemTypes[item.ty]; const type = itemTypes[item.ty];
const name = item.name; const name = item.name;
let path = item.path; let path = item.path;
let exactPath = item.exactPath;
if (type === "mod") { if (type === "mod") {
displayPath = path + "::"; displayPath = path + "::";
@ -2559,6 +2571,7 @@ function initSearch(rawSearchIndex) {
const parentType = itemTypes[myparent.ty]; const parentType = itemTypes[myparent.ty];
let pageType = parentType; let pageType = parentType;
let pageName = myparent.name; let pageName = myparent.name;
exactPath = `${myparent.exactPath}::${myparent.name}`;
if (parentType === "primitive") { if (parentType === "primitive") {
displayPath = myparent.name + "::"; displayPath = myparent.name + "::";
@ -2587,7 +2600,7 @@ function initSearch(rawSearchIndex) {
href = ROOT_PATH + item.path.replace(/::/g, "/") + href = ROOT_PATH + item.path.replace(/::/g, "/") +
"/" + type + "." + name + ".html"; "/" + type + "." + name + ".html";
} }
return [displayPath, href]; return [displayPath, href, `${exactPath}::${name}`];
} }
function pathSplitter(path) { function pathSplitter(path) {
@ -2980,6 +2993,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
id: pathIndex, id: pathIndex,
ty: TY_GENERIC, ty: TY_GENERIC,
path: null, path: null,
exactPath: null,
generics, generics,
bindings, bindings,
}; };
@ -2989,6 +3003,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
id: null, id: null,
ty: null, ty: null,
path: null, path: null,
exactPath: null,
generics, generics,
bindings, bindings,
}; };
@ -2998,6 +3013,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
id: buildTypeMapIndex(item.name, isAssocType), id: buildTypeMapIndex(item.name, isAssocType),
ty: item.ty, ty: item.ty,
path: item.path, path: item.path,
exactPath: item.exactPath,
generics, generics,
bindings, bindings,
}; };
@ -3453,6 +3469,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\
path: "", path: "",
descShard, descShard,
descIndex, descIndex,
exactPath: "",
desc: crateCorpus.doc,
parent: undefined, parent: undefined,
type: null, type: null,
id, id,
@ -3478,6 +3496,9 @@ ${item.displayPath}<span class="${type}">${name}</span>\
// i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present, // i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present,
// 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11 // 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11
const itemPaths = new Map(crateCorpus.q); const itemPaths = new Map(crateCorpus.q);
// An array of [(Number) item index, (Number) path index]
// Used to de-duplicate inlined and re-exported stuff
const itemReexports = new Map(crateCorpus.r);
// an array of (Number) the parent path index + 1 to `paths`, or 0 if none // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
const itemParentIdxs = crateCorpus.i; const itemParentIdxs = crateCorpus.i;
// a map Number, string for impl disambiguators // a map Number, string for impl disambiguators
@ -3511,9 +3532,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
path = itemPaths.has(elem[2]) ? itemPaths.get(elem[2]) : lastPath; path = itemPaths.has(elem[2]) ? itemPaths.get(elem[2]) : lastPath;
lastPath = path; lastPath = path;
} }
const exactPath = elem.length > 3 ? itemPaths.get(elem[3]) : path;
lowercasePaths.push({ty: ty, name: name.toLowerCase(), path: path}); lowercasePaths.push({ty, name: name.toLowerCase(), path, exactPath});
paths[i] = {ty: ty, name: name, path: path}; paths[i] = {ty, name, path, exactPath};
} }
// convert `item*` into an object form, and construct word indices. // convert `item*` into an object form, and construct word indices.
@ -3572,6 +3594,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
path, path,
descShard, descShard,
descIndex, descIndex,
exactPath: itemReexports.has(i) ? itemPaths.get(itemReexports.get(i)) : path,
parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined, parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
type, type,
id, id,

View file

@ -3,17 +3,47 @@ const EXPECTED = [
'query': 'Vec::new', 'query': 'Vec::new',
'others': [ 'others': [
{ 'path': 'std::vec::Vec', 'name': 'new' }, { 'path': 'std::vec::Vec', 'name': 'new' },
{ 'path': 'alloc::vec::Vec', 'name': 'new' },
{ 'path': 'std::vec::Vec', 'name': 'new_in' }, { 'path': 'std::vec::Vec', 'name': 'new_in' },
{ 'path': 'alloc::vec::Vec', 'name': 'new_in' }, ],
},
{
'query': 'prelude::vec',
'others': [
{ 'path': 'std::prelude::rust_2024', 'name': 'Vec' },
], ],
}, },
{ {
'query': 'Vec new', 'query': 'Vec new',
'others': [ 'others': [
{ 'path': 'std::vec::Vec', 'name': 'new' }, { 'path': 'std::vec::Vec', 'name': 'new' },
{ 'path': 'alloc::vec::Vec', 'name': 'new' },
{ 'path': 'std::vec::Vec', 'name': 'new_in' }, { 'path': 'std::vec::Vec', 'name': 'new_in' },
],
},
{
'query': 'std::Vec::new',
'others': [
{ 'path': 'std::vec::Vec', 'name': 'new' },
{ 'path': 'std::vec::Vec', 'name': 'new_in' },
],
},
{
'query': 'std Vec new',
'others': [
{ 'path': 'std::vec::Vec', 'name': 'new' },
{ 'path': 'std::vec::Vec', 'name': 'new_in' },
],
},
{
'query': 'alloc::Vec::new',
'others': [
{ 'path': 'alloc::vec::Vec', 'name': 'new' },
{ 'path': 'alloc::vec::Vec', 'name': 'new_in' },
],
},
{
'query': 'alloc Vec new',
'others': [
{ 'path': 'alloc::vec::Vec', 'name': 'new' },
{ 'path': 'alloc::vec::Vec', 'name': 'new_in' }, { 'path': 'alloc::vec::Vec', 'name': 'new_in' },
], ],
}, },

View file

@ -0,0 +1,7 @@
#[macro_use]
mod hidden_macro_module {
#[macro_export]
macro_rules! vec {
() => {};
}
}

View file

@ -0,0 +1,11 @@
// exact-check
const EXPECTED = [
{
'query': 'vec',
'others': [
{ 'path': 'foo', 'name': 'vec', 'exactPath': 'macro_in_module' },
{ 'path': 'foo', 'name': 'myspecialvec', 'exactPath': 'foo' },
],
},
];

View file

@ -0,0 +1,15 @@
//@ aux-crate: macro_in_module=macro-in-module.rs
#![crate_name="foo"]
extern crate macro_in_module;
// Test case based on the relationship between alloc and std.
#[doc(inline)]
pub use macro_in_module::vec;
#[macro_use]
mod hidden_macro_module {
#[macro_export]
macro_rules! myspecialvec {
() => {};
}
}

View file

@ -0,0 +1,16 @@
// exact-check
const EXPECTED = [
{
'query': 'Subscriber dostuff',
'others': [
{ 'path': 'foo::fmt::Subscriber', 'name': 'dostuff' },
],
},
{
'query': 'AnotherOne dostuff',
'others': [
{ 'path': 'foo::AnotherOne', 'name': 'dostuff' },
],
},
];

View file

@ -0,0 +1,18 @@
// This test enforces that the (renamed) reexports are present in the search results.
#![crate_name="foo"]
pub mod fmt {
pub struct Subscriber;
impl Subscriber {
pub fn dostuff(&self) {}
}
}
mod foo {
pub struct AnotherOne;
impl AnotherOne {
pub fn dostuff(&self) {}
}
}
pub use foo::AnotherOne;
pub use fmt::Subscriber;

View file

@ -0,0 +1,22 @@
// exact-check
const EXPECTED = [
{
'query': 'Subscriber',
'others': [
{ 'path': 'foo', 'name': 'Subscriber' },
],
},
{
'query': 'fmt Subscriber',
'others': [
{ 'path': 'foo::fmt', 'name': 'Subscriber' },
],
},
{
'query': 'AnotherOne',
'others': [
{ 'path': 'foo', 'name': 'AnotherOne' },
],
},
];

View file

@ -0,0 +1,12 @@
// This test enforces that the (renamed) reexports are present in the search results.
#![crate_name="foo"]
pub mod fmt {
pub struct Subscriber;
}
mod foo {
pub struct AnotherOne;
}
pub use foo::AnotherOne;
pub use fmt::Subscriber;

View file

@ -1,4 +1,6 @@
// This test enforces that the (renamed) reexports are present in the search results. // This test enforces that the (renamed) reexports are present in the search results.
// This is a DWIM case, since renaming the export probably means the intent is also different.
// For the de-duplication case of exactly the same name, see reexport-dedup
pub mod fmt { pub mod fmt {
pub struct Subscriber; pub struct Subscriber;