Skip to content

[rustdoc] Make aliases search support partial matching #143988

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/librustdoc/html/render/search_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub(crate) fn build_index(
// Set up alias indexes.
for (i, item) in cache.search_index.iter().enumerate() {
for alias in &item.aliases[..] {
aliases.entry(alias.as_str().to_lowercase()).or_default().push(i);
aliases.entry(alias.to_string()).or_default().push(i);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is no longer any transformation taking place, and since aliases doesn't seem to escape the function (based on the fact CrateData is a struct defined within this function and it borrows the aliases variable), couldn't aliases have its type changed to BTreeMap<&str, Vec<usize>> to save a bunch of allocations?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem is with serde and borrow checker here. If I use &str, we can't do:

    let mut search_index = std::mem::take(&mut cache.search_index);

just below. Other solution would be to keep Symbol instead, but Serialize is not implemented on it, so not possible either. Fixing this would require a lot of code, so better keep it for later optimization.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this worth a FIXME comment or is this too small of an optimization?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's worth it. Also I expect you will gove it a try soon enough. 😄

}
}

Expand Down
7 changes: 7 additions & 0 deletions src/librustdoc/html/static/js/rustdoc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ declare namespace rustdoc {
crate: string,
descShard: SearchDescShard,
id: number,
// This is the name of the item. For doc aliases, if you want the name of the aliased
// item, take a look at `Row.original.name`.
name: string,
normalizedName: string,
word: string,
Expand All @@ -227,6 +229,11 @@ declare namespace rustdoc {
path: string,
ty: number,
type: FunctionSearchType | null,
descIndex: number,
bitIndex: number,
implDisambiguator: String | null,
is_alias?: boolean,
original?: Row,
}

/**
Expand Down
150 changes: 77 additions & 73 deletions src/librustdoc/html/static/js/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,7 @@ function createQueryElement(query, parserState, name, generics, isInGenerics) {
*/
function makePrimitiveElement(name, extra) {
return Object.assign({
name: name,
name,
id: null,
fullPath: [name],
pathWithoutLast: [],
Expand Down Expand Up @@ -1483,6 +1483,7 @@ class DocSearch {
*/
this.assocTypeIdNameMap = new Map();
this.ALIASES = new Map();
this.FOUND_ALIASES = new Set();
this.rootPath = rootPath;
this.searchState = searchState;

Expand Down Expand Up @@ -2030,6 +2031,8 @@ class DocSearch {
// normalized names, type signature objects and fingerprints, and aliases.
id = 0;

/** @type {Array<[string, { [key: string]: Array<number> }, number]>} */
const allAliases = [];
for (const [crate, crateCorpus] of rawSearchIndex) {
// a string representing the lengths of each description shard
// a string representing the list of function types
Expand Down Expand Up @@ -2178,10 +2181,10 @@ class DocSearch {
paths[i] = { ty, name, path, exactPath, unboxFlag };
}

// convert `item*` into an object form, and construct word indices.
// Convert `item*` into an object form, and construct word indices.
//
// before any analysis is performed lets gather the search terms to
// search against apart from the rest of the data. This is a quick
// Before any analysis is performed, let's gather the search terms to
// search against apart from the rest of the data. This is a quick
// operation that is cached for the life of the page state so that
// all other search operations have access to this cached data for
// faster analysis operations
Expand Down Expand Up @@ -2269,29 +2272,58 @@ class DocSearch {
}

if (aliases) {
const currentCrateAliases = new Map();
this.ALIASES.set(crate, currentCrateAliases);
for (const alias_name in aliases) {
if (!Object.prototype.hasOwnProperty.call(aliases, alias_name)) {
continue;
}

/** @type{number[]} */
let currentNameAliases;
if (currentCrateAliases.has(alias_name)) {
currentNameAliases = currentCrateAliases.get(alias_name);
} else {
currentNameAliases = [];
currentCrateAliases.set(alias_name, currentNameAliases);
}
for (const local_alias of aliases[alias_name]) {
currentNameAliases.push(local_alias + currentIndex);
}
}
// We need to add the aliases in `searchIndex` after we finished filling it
// to not mess up indexes.
allAliases.push([crate, aliases, currentIndex]);
}
currentIndex += itemTypes.length;
this.searchState.descShards.set(crate, descShardList);
}

for (const [crate, aliases, index] of allAliases) {
for (const [alias_name, alias_refs] of Object.entries(aliases)) {
if (!this.ALIASES.has(crate)) {
this.ALIASES.set(crate, new Map());
}
const word = alias_name.toLowerCase();
const crate_alias_map = this.ALIASES.get(crate);
if (!crate_alias_map.has(word)) {
crate_alias_map.set(word, []);
}
const aliases_map = crate_alias_map.get(word);

const normalizedName = word.indexOf("_") === -1 ? word : word.replace(/_/g, "");
for (const alias of alias_refs) {
const originalIndex = alias + index;
const original = searchIndex[originalIndex];
/** @type {rustdoc.Row} */
const row = {
crate,
name: alias_name,
normalizedName,
is_alias: true,
ty: original.ty,
type: original.type,
paramNames: [],
word,
id,
parent: undefined,
original,
path: "",
implDisambiguator: original.implDisambiguator,
// Needed to load the description of the original item.
// @ts-ignore
descShard: original.descShard,
descIndex: original.descIndex,
bitIndex: original.bitIndex,
};
aliases_map.push(row);
this.nameTrie.insert(normalizedName, id, this.tailTable);
id += 1;
searchIndex.push(row);
}
}
}
// Drop the (rather large) hash table used for reusing function items
this.TYPES_POOL = new Map();
return searchIndex;
Expand Down Expand Up @@ -2536,6 +2568,8 @@ class DocSearch {
parsedQuery.elems.reduce((acc, next) => acc + next.pathLast.length, 0) +
parsedQuery.returned.reduce((acc, next) => acc + next.pathLast.length, 0);
const maxEditDistance = Math.floor(queryLen / 3);
// We reinitialize the `FOUND_ALIASES` map.
this.FOUND_ALIASES.clear();

/**
* @type {Map<string, number>}
Expand Down Expand Up @@ -2695,6 +2729,10 @@ class DocSearch {
const buildHrefAndPath = item => {
let displayPath;
let href;
if (item.is_alias) {
this.FOUND_ALIASES.add(item.word);
item = item.original;
}
const type = itemTypes[item.ty];
const name = item.name;
let path = item.path;
Expand Down Expand Up @@ -3198,8 +3236,7 @@ class DocSearch {
result.item = this.searchIndex[result.id];
result.word = this.searchIndex[result.id].word;
if (isReturnTypeQuery) {
// we are doing a return-type based search,
// deprioritize "clone-like" results,
// We are doing a return-type based search, deprioritize "clone-like" results,
// ie. functions that also take the queried type as an argument.
const resultItemType = result.item && result.item.type;
if (!resultItemType) {
Expand Down Expand Up @@ -4259,28 +4296,13 @@ class DocSearch {
return false;
}

// this does not yet have a type in `rustdoc.d.ts`.
// @ts-expect-error
function createAliasFromItem(item) {
return {
crate: item.crate,
name: item.name,
path: item.path,
descShard: item.descShard,
descIndex: item.descIndex,
exactPath: item.exactPath,
ty: item.ty,
parent: item.parent,
type: item.type,
is_alias: true,
bitIndex: item.bitIndex,
implDisambiguator: item.implDisambiguator,
};
}

// @ts-expect-error
const handleAliases = async(ret, query, filterCrates, currentCrate) => {
const lowerQuery = query.toLowerCase();
if (this.FOUND_ALIASES.has(lowerQuery)) {
return;
}
this.FOUND_ALIASES.add(lowerQuery);
// We separate aliases and crate aliases because we want to have current crate
// aliases to be before the others in the displayed results.
// @ts-expect-error
Expand All @@ -4292,7 +4314,7 @@ class DocSearch {
&& this.ALIASES.get(filterCrates).has(lowerQuery)) {
const query_aliases = this.ALIASES.get(filterCrates).get(lowerQuery);
for (const alias of query_aliases) {
aliases.push(createAliasFromItem(this.searchIndex[alias]));
aliases.push(alias);
}
}
} else {
Expand All @@ -4302,17 +4324,17 @@ class DocSearch {
const pushTo = crate === currentCrate ? crateAliases : aliases;
const query_aliases = crateAliasesIndex.get(lowerQuery);
for (const alias of query_aliases) {
pushTo.push(createAliasFromItem(this.searchIndex[alias]));
pushTo.push(alias);
}
}
}
}

// @ts-expect-error
const sortFunc = (aaa, bbb) => {
if (aaa.path < bbb.path) {
if (aaa.original.path < bbb.original.path) {
return 1;
} else if (aaa.path === bbb.path) {
} else if (aaa.original.path === bbb.original.path) {
return 0;
}
return -1;
Expand All @@ -4321,21 +4343,10 @@ class DocSearch {
crateAliases.sort(sortFunc);
aliases.sort(sortFunc);

// @ts-expect-error
const fetchDesc = alias => {
// @ts-expect-error
return this.searchIndexEmptyDesc.get(alias.crate).contains(alias.bitIndex) ?
"" : this.searchState.loadDesc(alias);
};
const [crateDescs, descs] = await Promise.all([
// @ts-expect-error
Promise.all(crateAliases.map(fetchDesc)),
Promise.all(aliases.map(fetchDesc)),
]);

// @ts-expect-error
const pushFunc = alias => {
alias.alias = query;
// Cloning `alias` to prevent its fields to be updated.
alias = {...alias};
const res = buildHrefAndPath(alias);
alias.displayPath = pathSplitter(res[0]);
alias.fullPath = alias.displayPath + alias.name;
Expand All @@ -4347,16 +4358,8 @@ class DocSearch {
}
};

aliases.forEach((alias, i) => {
// @ts-expect-error
alias.desc = descs[i];
});
aliases.forEach(pushFunc);
// @ts-expect-error
crateAliases.forEach((alias, i) => {
alias.desc = crateDescs[i];
});
// @ts-expect-error
crateAliases.forEach(pushFunc);
};

Expand Down Expand Up @@ -4802,7 +4805,7 @@ async function addTab(array, query, display) {
output.className = "search-results " + extraClass;

const lis = Promise.all(array.map(async item => {
const name = item.name;
const name = item.is_alias ? item.original.name : item.name;
const type = itemTypes[item.ty];
const longType = longItemTypes[item.ty];
const typeName = longType.length !== 0 ? `${longType}` : "?";
Expand All @@ -4822,7 +4825,7 @@ async function addTab(array, query, display) {
let alias = " ";
if (item.is_alias) {
alias = ` <div class="alias">\
<b>${item.alias}</b><i class="grey">&nbsp;- see&nbsp;</i>\
<b>${item.name}</b><i class="grey">&nbsp;- see&nbsp;</i>\
</div>`;
}
resultName.insertAdjacentHTML(
Expand Down Expand Up @@ -5201,6 +5204,7 @@ function registerSearchEvents() {
if (searchState.input.value.length === 0) {
searchState.hideResults();
} else {
// @ts-ignore
searchState.timeout = setTimeout(search, 500);
}
};
Expand Down Expand Up @@ -5842,8 +5846,8 @@ Lev1TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new In
// be called ONLY when the whole file has been parsed and loaded.

// @ts-expect-error
function initSearch(searchIndx) {
rawSearchIndex = searchIndx;
function initSearch(searchIndex) {
rawSearchIndex = searchIndex;
if (typeof window !== "undefined") {
// @ts-expect-error
docSearch = new DocSearch(rawSearchIndex, ROOT_PATH, searchState);
Expand Down
Loading
Loading