rustdoc-search: fix order-independence bug

This commit is contained in:
Michael Howell 2023-06-08 23:12:36 -07:00
parent c897deddb8
commit 94badbe599
4 changed files with 212 additions and 77 deletions

View File

@ -1208,13 +1208,62 @@ function initSearch(rawSearchIndex) {
if (!fnTypes || fnTypes.length === 0) {
return false;
}
/**
* @type Map<integer, QueryElement[]>
*/
const queryElemSet = new Map();
const addQueryElemToQueryElemSet = function addQueryElemToQueryElemSet(queryElem) {
let currentQueryElemList;
if (queryElemSet.has(queryElem.id)) {
currentQueryElemList = queryElemSet.get(queryElem.id);
} else {
currentQueryElemList = [];
queryElemSet.set(queryElem.id, currentQueryElemList);
}
currentQueryElemList.push(queryElem);
};
for (const queryElem of queryElems) {
addQueryElemToQueryElemSet(queryElem);
}
/**
* @type Map<integer, FunctionType[]>
*/
const fnTypeSet = new Map();
const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) {
if (fnType.id === -1) {
// Pure generic, needs to check into it.
// Pure generic, or an item that's not matched by any query elems.
// Try [unboxing] it.
//
// [unboxing]:
// http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice);
if (fnType.id === -1 || !(
queryElemSet.has(fnType.id) ||
(fnType.id === typeNameIdOfSlice && queryContainsArrayOrSliceElem) ||
(fnType.id === typeNameIdOfArray && queryContainsArrayOrSliceElem)
)) {
for (const innerFnType of fnType.generics) {
addFnTypeToFnTypeSet(innerFnType);
}
return;
}
let currentQueryElemList = queryElemSet.get(fnType.id) || [];
let matchIdx = currentQueryElemList.findIndex(queryElem => {
return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
checkGenerics(fnType, queryElem);
});
if (matchIdx === -1 &&
(fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) &&
queryContainsArrayOrSliceElem
) {
currentQueryElemList = queryElemSet.get(typeNameIdOfArrayOrSlice) || [];
matchIdx = currentQueryElemList.findIndex(queryElem => {
return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
checkGenerics(fnType, queryElem);
});
}
// None of the query elems match the function type.
// Try [unboxing] it.
if (matchIdx === -1) {
for (const innerFnType of fnType.generics) {
addFnTypeToFnTypeSet(innerFnType);
}
@ -1232,85 +1281,66 @@ function initSearch(rawSearchIndex) {
for (const fnType of fnTypes) {
addFnTypeToFnTypeSet(fnType);
}
// We need to find the type that matches the most to remove it in order
// to move forward.
const handleQueryElem = queryElem => {
if (!fnTypeSet.has(queryElem.id)) {
return false;
const doHandleQueryElemList = (currentFnTypeList, queryElemList) => {
if (queryElemList.length === 0) {
return true;
}
const currentFnTypeList = fnTypeSet.get(queryElem.id);
const matchIdx = currentFnTypeList.findIndex(fnType => {
// Multiple items in one list might match multiple items in another.
// Since an item with fewer generics can match an item with more, we
// need to check all combinations for a potential match.
const queryElem = queryElemList.pop();
const l = currentFnTypeList.length;
for (let i = 0; i < l; i += 1) {
const fnType = currentFnTypeList[i];
if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
return false;
continue;
}
return queryElem.generics.length === 0 || checkGenerics(fnType, queryElem);
});
if (matchIdx === -1) {
return false;
}
currentFnTypeList.splice(matchIdx, 1);
if (currentFnTypeList.length === 0) {
fnTypeSet.delete(queryElem.id);
}
return true;
};
// To do the right thing with type filters, we first process generics
// that have them, removing matching ones from the "bag," then do the
// ones with no type filter, which can match any entry regardless of its
// own type.
const needsUnboxed = [];
for (const queryElem of queryElems) {
if (queryElem.typeFilter === TY_PRIMITIVE &&
queryElem.id === typeNameIdOfArrayOrSlice) {
const queryElemArray = {
id: typeNameIdOfArray,
typeFilter: TY_PRIMITIVE,
generics: queryElem.generics,
};
const queryElemSlice = {
id: typeNameIdOfSlice,
typeFilter: TY_PRIMITIVE,
generics: queryElem.generics,
};
if (!handleQueryElem(queryElemArray) && !handleQueryElem(queryElemSlice)) {
needsUnboxed.push(queryElem);
}
} else if (queryElem.typeFilter !== -1 && !handleQueryElem(queryElem)) {
needsUnboxed.push(queryElem);
}
}
for (const queryElem of queryElems) {
if (queryElem.typeFilter === -1 && !handleQueryElem(queryElem)) {
needsUnboxed.push(queryElem);
}
}
// If the current item does not match, try [unboxing] the generic.
// [unboxing]:
// https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
unboxing: while (needsUnboxed.length !== 0) {
for (const [i, queryElem] of needsUnboxed.entries()) {
if (handleQueryElem(queryElem)) {
needsUnboxed.splice(i, 1);
continue unboxing;
}
}
for (const [id, fnTypeList] of fnTypeSet) {
for (const [i, fnType] of fnTypeList.entries()) {
if (fnType.generics.length !== 0) {
fnTypeList.splice(i, 1);
for (const innerFnType of fnType.generics) {
addFnTypeToFnTypeSet(innerFnType);
}
if (fnTypeList.length === 0) {
fnTypeSet.delete(id);
}
continue unboxing;
if (queryElem.generics.length === 0 || checkGenerics(fnType, queryElem)) {
currentFnTypeList.splice(i, 1);
const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
if (result) {
return true;
}
currentFnTypeList.splice(i, 0, fnType);
}
}
return false;
};
const handleQueryElemList = (id, queryElemList) => {
if (!fnTypeSet.has(id)) {
if (id === typeNameIdOfArrayOrSlice) {
return handleQueryElemList(typeNameIdOfSlice, queryElemList) ||
handleQueryElemList(typeNameIdOfArray, queryElemList);
}
return false;
}
const currentFnTypeList = fnTypeSet.get(id);
if (currentFnTypeList.length < queryElemList.length) {
// It's not possible for all the query elems to find a match.
return false;
}
const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
if (result) {
// Found a solution.
// Any items that weren't used for it can be unboxed, and might form
// part of the solution for another item.
for (const innerFnType of currentFnTypeList) {
addFnTypeToFnTypeSet(innerFnType);
}
fnTypeSet.delete(id);
}
return result;
};
let queryElemSetSize = -1;
while (queryElemSetSize !== queryElemSet.size) {
queryElemSetSize = queryElemSet.size;
for (const [id, queryElemList] of queryElemSet) {
if (handleQueryElemList(id, queryElemList)) {
queryElemSet.delete(id);
}
}
}
return true;
return queryElemSetSize === 0;
}
/**

View File

@ -1,11 +1,8 @@
// ignore-order
const QUERY = [
'bufread -> result<u8>',
];
const EXPECTED = [
{
'query': 'bufread -> result<u8>',
'others': [
{ 'path': 'std::io::Split', 'name': 'next' },
{ 'path': 'std::boxed::Box', 'name': 'fill_buf' },

View File

@ -0,0 +1,91 @@
// ignore-order
// exact-check
// Make sure that results are order-agnostic, even when there's search items that only differ
// by generics.
const EXPECTED = [
{
'query': 'Wrap',
'in_args': [
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
],
},
{
'query': 'Wrap<i32>',
'in_args': [
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
],
},
{
'query': 'Wrap<i32>, Wrap<i32, u32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
],
},
{
'query': 'Wrap<i32, u32>, Wrap<i32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
],
},
{
'query': 'W3<i32>, W3<i32, u32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'baaa' },
{ 'path': 'generics_match_ambiguity', 'name': 'baab' },
{ 'path': 'generics_match_ambiguity', 'name': 'baac' },
{ 'path': 'generics_match_ambiguity', 'name': 'baad' },
{ 'path': 'generics_match_ambiguity', 'name': 'baae' },
{ 'path': 'generics_match_ambiguity', 'name': 'baaf' },
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
],
},
{
'query': 'W3<i32, u32>, W3<i32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'baaa' },
{ 'path': 'generics_match_ambiguity', 'name': 'baab' },
{ 'path': 'generics_match_ambiguity', 'name': 'baac' },
{ 'path': 'generics_match_ambiguity', 'name': 'baad' },
{ 'path': 'generics_match_ambiguity', 'name': 'baae' },
{ 'path': 'generics_match_ambiguity', 'name': 'baaf' },
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
],
},
{
'query': 'W2<i32>, W2<i32, u32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
],
},
{
'query': 'W2<i32, u32>, W2<i32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
],
},
{
'query': 'W2<i32>, W3<i32, u32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'baac' },
{ 'path': 'generics_match_ambiguity', 'name': 'baaf' },
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
],
},
{
'query': 'W2<i32>, W2<i32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
],
},
];

View File

@ -0,0 +1,17 @@
pub struct Wrap<T, U = ()>(pub T, pub U);
pub fn foo(a: Wrap<i32>, b: Wrap<i32, u32>) {}
pub fn bar(a: Wrap<i32, u32>, b: Wrap<i32>) {}
pub struct W2<T>(pub T);
pub struct W3<T, U = ()>(pub T, pub U);
pub fn baaa(a: W3<i32>, b: W3<i32, u32>) {}
pub fn baab(a: W3<i32, u32>, b: W3<i32>) {}
pub fn baac(a: W2<W3<i32>>, b: W3<i32, u32>) {}
pub fn baad(a: W2<W3<i32, u32>>, b: W3<i32>) {}
pub fn baae(a: W3<i32>, b: W2<W3<i32, u32>>) {}
pub fn baaf(a: W3<i32, u32>, b: W2<W3<i32>>) {}
pub fn baag(a: W2<W3<i32>>, b: W2<W3<i32, u32>>) {}
pub fn baah(a: W2<W3<i32, u32>>, b: W2<W3<i32>>) {}
//