1
Fork 0

rustdoc-search: build args, return, and generics on one unifier

This enhances generics with the "unboxing" behavior where A<T>
matches T. It makes this unboxing transitive over generics.
This commit is contained in:
Michael Howell 2023-06-02 19:58:44 -07:00
parent 04f4493722
commit 9946d67579
4 changed files with 178 additions and 138 deletions

View file

@ -53,7 +53,7 @@ let ParsedQuery;
* parent: (Object|null|undefined), * parent: (Object|null|undefined),
* path: string, * path: string,
* ty: (Number|null|number), * ty: (Number|null|number),
* type: (Array<?>|null) * type: FunctionSearchType?
* }} * }}
*/ */
let Row; let Row;
@ -135,7 +135,7 @@ let RawFunctionType;
/** /**
* @typedef {{ * @typedef {{
* inputs: Array<FunctionType>, * inputs: Array<FunctionType>,
* outputs: Array<FunctionType>, * output: Array<FunctionType>,
* }} * }}
*/ */
let FunctionSearchType; let FunctionSearchType;

View file

@ -1178,67 +1178,79 @@ function initSearch(rawSearchIndex) {
} }
/** /**
* This function checks if the object (`row`) generics match the given type (`elem`) * This function checks generics in search query `queryElem` can all be found in the
* generics. * search index (`fnType`),
* *
* @param {Row} row - The object to check. * @param {FunctionType} fnType - The object to check.
* @param {QueryElement} elem - The element from the parsed query. * @param {QueryElement} queryElem - The element from the parsed query.
* *
* @return {boolean} - Returns true if a match, false otherwise. * @return {boolean} - Returns true if a match, false otherwise.
*/ */
function checkGenerics(row, elem) { function checkGenerics(fnType, queryElem) {
if (row.generics.length === 0 || elem.generics.length === 0) { return unifyFunctionTypes(fnType.generics, queryElem.generics);
return false; }
} /**
// This function is called if the names match, but we need to make * This function checks if a list of search query `queryElems` can all be found in the
// sure that all generics match as well. * search index (`fnTypes`).
// *
* @param {Array<FunctionType>} fnTypes - The objects to check.
* @param {Array<QueryElement>} queryElems - The elements from the parsed query.
*
* @return {boolean} - Returns true if a match, false otherwise.
*/
function unifyFunctionTypes(fnTypes, queryElems) {
// This search engine implements order-agnostic unification. There // This search engine implements order-agnostic unification. There
// should be no missing duplicates (generics have "bag semantics"), // should be no missing duplicates (generics have "bag semantics"),
// and the row is allowed to have extras. // and the row is allowed to have extras.
if (elem.generics.length <= 0 || row.generics.length < elem.generics.length) { if (queryElems.length === 0) {
return true;
}
if (!fnTypes || fnTypes.length === 0) {
return false; return false;
} }
const elems = new Map(); /**
const addEntryToElems = function addEntryToElems(entry) { * @type Map<integer, FunctionType[]>
if (entry.id === -1) { */
const fnTypeSet = new Map();
const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) {
if (fnType.id === -1) {
// Pure generic, needs to check into it. // Pure generic, needs to check into it.
for (const inner_entry of entry.generics) { for (const innerFnType of fnType.generics) {
addEntryToElems(inner_entry); addFnTypeToFnTypeSet(innerFnType);
} }
return; return;
} }
let currentEntryElems; let currentFnTypeList;
if (elems.has(entry.id)) { if (fnTypeSet.has(fnType.id)) {
currentEntryElems = elems.get(entry.id); currentFnTypeList = fnTypeSet.get(fnType.id);
} else { } else {
currentEntryElems = []; currentFnTypeList = [];
elems.set(entry.id, currentEntryElems); fnTypeSet.set(fnType.id, currentFnTypeList);
} }
currentEntryElems.push(entry); currentFnTypeList.push(fnType);
}; };
for (const entry of row.generics) { for (const fnType of fnTypes) {
addEntryToElems(entry); addFnTypeToFnTypeSet(fnType);
} }
// We need to find the type that matches the most to remove it in order // We need to find the type that matches the most to remove it in order
// to move forward. // to move forward.
const handleGeneric = generic => { const handleQueryElem = queryElem => {
if (!elems.has(generic.id)) { if (!fnTypeSet.has(queryElem.id)) {
return false; return false;
} }
const matchElems = elems.get(generic.id); const currentFnTypeList = fnTypeSet.get(queryElem.id);
const matchIdx = matchElems.findIndex(tmp_elem => { const matchIdx = currentFnTypeList.findIndex(fnType => {
if (generic.generics.length > 0 && !checkGenerics(tmp_elem, generic)) { if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
return false; return false;
} }
return typePassesFilter(generic.typeFilter, tmp_elem.ty); return queryElem.generics.length === 0 || checkGenerics(fnType, queryElem);
}); });
if (matchIdx === -1) { if (matchIdx === -1) {
return false; return false;
} }
matchElems.splice(matchIdx, 1); currentFnTypeList.splice(matchIdx, 1);
if (matchElems.length === 0) { if (currentFnTypeList.length === 0) {
elems.delete(generic.id); fnTypeSet.delete(queryElem.id);
} }
return true; return true;
}; };
@ -1246,31 +1258,58 @@ function initSearch(rawSearchIndex) {
// that have them, removing matching ones from the "bag," then do the // 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 // ones with no type filter, which can match any entry regardless of its
// own type. // own type.
for (const generic of elem.generics) { const needsUnboxed = [];
if (generic.typeFilter === TY_PRIMITIVE && for (const queryElem of queryElems) {
generic.id === typeNameIdOfArrayOrSlice) { if (queryElem.typeFilter === TY_PRIMITIVE &&
const genericArray = { queryElem.id === typeNameIdOfArrayOrSlice) {
const queryElemArray = {
id: typeNameIdOfArray, id: typeNameIdOfArray,
typeFilter: TY_PRIMITIVE, typeFilter: TY_PRIMITIVE,
generics: generic.generics, generics: queryElem.generics,
}; };
const genericSlice = { const queryElemSlice = {
id: typeNameIdOfSlice, id: typeNameIdOfSlice,
typeFilter: TY_PRIMITIVE, typeFilter: TY_PRIMITIVE,
generics: generic.generics, generics: queryElem.generics,
}; };
if (!handleGeneric(genericArray) && !handleGeneric(genericSlice)) { if (!handleQueryElem(queryElemArray) && !handleQueryElem(queryElemSlice)) {
return false; needsUnboxed.push(queryElem);
} }
} else if (generic.typeFilter !== -1 && !handleGeneric(generic)) { } else if (queryElem.typeFilter !== -1 && !handleQueryElem(queryElem)) {
return false; needsUnboxed.push(queryElem);
} }
} }
for (const generic of elem.generics) { for (const queryElem of queryElems) {
if (generic.typeFilter === -1 && !handleGeneric(generic)) { if (queryElem.typeFilter === -1 && !handleQueryElem(queryElem)) {
return false; 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;
}
}
}
return false;
}
return true; return true;
} }
@ -1278,13 +1317,13 @@ function initSearch(rawSearchIndex) {
* This function checks if the object (`row`) matches the given type (`elem`) and its * This function checks if the object (`row`) matches the given type (`elem`) and its
* generics (if any). * generics (if any).
* *
* @param {Row} row * @param {Array<FunctionType>} list
* @param {QueryElement} elem - The element from the parsed query. * @param {QueryElement} elem - The element from the parsed query.
* *
* @return {boolean} - Returns true if found, false otherwise. * @return {boolean} - Returns true if found, false otherwise.
*/ */
function checkIfInGenerics(row, elem) { function checkIfInList(list, elem) {
for (const entry of row.generics) { for (const entry of list) {
if (checkType(entry, elem)) { if (checkType(entry, elem)) {
return true; return true;
} }
@ -1304,7 +1343,7 @@ function initSearch(rawSearchIndex) {
function checkType(row, elem) { function checkType(row, elem) {
if (row.id === -1) { if (row.id === -1) {
// This is a pure "generic" search, no need to run other checks. // This is a pure "generic" search, no need to run other checks.
return row.generics.length > 0 ? checkIfInGenerics(row, elem) : false; return row.generics.length > 0 ? checkIfInList(row.generics, elem) : false;
} }
const matchesExact = row.id === elem.id; const matchesExact = row.id === elem.id;
@ -1322,59 +1361,7 @@ function initSearch(rawSearchIndex) {
// If the current item does not match, try [unboxing] the generic. // If the current item does not match, try [unboxing] the generic.
// [unboxing]: // [unboxing]:
// https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf // https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
return checkIfInGenerics(row, elem); return checkIfInList(row.generics, elem);
}
/**
* This function checks if the object (`row`) has an argument with the given type (`elem`).
*
* @param {Row} row
* @param {QueryElement} elem - The element from the parsed query.
* @param {Array<integer>} skipPositions - Do not return one of these positions.
*
* @return {integer} - Returns the position of the match, or -1 if none.
*/
function findArg(row, elem, skipPositions) {
if (row && row.type && row.type.inputs && row.type.inputs.length > 0) {
let i = 0;
for (const input of row.type.inputs) {
if (skipPositions.indexOf(i) !== -1) {
i += 1;
continue;
}
if (checkType(input, elem)) {
return i;
}
i += 1;
}
}
return -1;
}
/**
* This function checks if the object (`row`) returns the given type (`elem`).
*
* @param {Row} row
* @param {QueryElement} elem - The element from the parsed query.
* @param {Array<integer>} skipPositions - Do not return one of these positions.
*
* @return {integer} - Returns the position of the matching item, or -1 if none.
*/
function checkReturned(row, elem, skipPositions) {
if (row && row.type && row.type.output.length > 0) {
let i = 0;
for (const ret_ty of row.type.output) {
if (skipPositions.indexOf(i) !== -1) {
i += 1;
continue;
}
if (checkType(ret_ty, elem)) {
return i;
}
i += 1;
}
}
return -1;
} }
function checkPath(contains, ty, maxEditDistance) { function checkPath(contains, ty, maxEditDistance) {
@ -1575,14 +1562,14 @@ function initSearch(rawSearchIndex) {
const fullId = row.id; const fullId = row.id;
const searchWord = searchWords[pos]; const searchWord = searchWords[pos];
const in_args = findArg(row, elem, []); const in_args = row.type && row.type.inputs && checkIfInList(row.type.inputs, elem);
if (in_args !== -1) { if (in_args) {
// path_dist is 0 because no parent path information is currently stored // path_dist is 0 because no parent path information is currently stored
// in the search index // in the search index
addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance); addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance);
} }
const returned = checkReturned(row, elem, []); const returned = row.type && row.type.output && checkIfInList(row.type.output, elem);
if (returned !== -1) { if (returned) {
addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance); addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance);
} }
@ -1638,32 +1625,15 @@ function initSearch(rawSearchIndex) {
* @param {Object} results * @param {Object} results
*/ */
function handleArgs(row, pos, results) { function handleArgs(row, pos, results) {
if (!row || (filterCrates !== null && row.crate !== filterCrates)) { if (!row || (filterCrates !== null && row.crate !== filterCrates) || !row.type) {
return; return;
} }
// If the result is too "bad", we return false and it ends this search. // If the result is too "bad", we return false and it ends this search.
function checkArgs(elems, callback) { if (!unifyFunctionTypes(row.type.inputs, parsedQuery.elems)) {
const skipPositions = [];
for (const elem of elems) {
// There is more than one parameter to the query so all checks should be "exact"
const position = callback(
row,
elem,
skipPositions
);
if (position !== -1) {
skipPositions.push(position);
} else {
return false;
}
}
return true;
}
if (!checkArgs(parsedQuery.elems, findArg)) {
return; return;
} }
if (!checkArgs(parsedQuery.returned, checkReturned)) { if (!unifyFunctionTypes(row.type.output, parsedQuery.returned)) {
return; return;
} }
@ -1750,12 +1720,9 @@ function initSearch(rawSearchIndex) {
elem = parsedQuery.returned[0]; elem = parsedQuery.returned[0];
for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) { for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
row = searchIndex[i]; row = searchIndex[i];
in_returned = checkReturned( in_returned = row.type &&
row, unifyFunctionTypes(row.type.output, parsedQuery.returned);
elem, if (in_returned) {
[]
);
if (in_returned !== -1) {
addIntoResults( addIntoResults(
results_others, results_others,
row.id, row.id,

View file

@ -0,0 +1,68 @@
// exact-check
const EXPECTED = [
{
'query': '-> Result<Object, bool>',
'others': [
{ 'path': 'nested_unboxed', 'name': 'something' },
],
},
{
'query': '-> Result<Object<i32, u32>, bool>',
'others': [
{ 'path': 'nested_unboxed', 'name': 'something' },
],
},
{
'query': '-> Object, bool',
'others': [
{ 'path': 'nested_unboxed', 'name': 'something' },
],
},
{
'query': '-> Object<i32, u32>, bool',
'others': [
{ 'path': 'nested_unboxed', 'name': 'something' },
],
},
{
'query': '-> i32, u32, bool',
'others': [
{ 'path': 'nested_unboxed', 'name': 'something' },
],
},
{
'query': '-> Result<i32, u32, bool>',
'others': [
{ 'path': 'nested_unboxed', 'name': 'something' },
],
},
{
'query': '-> Result<Object<i32>, bool>',
'others': [
{ 'path': 'nested_unboxed', 'name': 'something' },
],
},
{
'query': '-> Result<Object<u32>, bool>',
'others': [
{ 'path': 'nested_unboxed', 'name': 'something' },
],
},
{
'query': '-> Result<Object<i32>, u32, bool>',
'others': [],
},
{
'query': '-> Result<i32, Object<u32>, bool>',
'others': [],
},
{
'query': '-> Result<i32, u32, Object<bool>>',
'others': [],
},
{
'query': '-> Result<Object<i32>, Object<u32>, bool>',
'others': [],
},
];

View file

@ -0,0 +1,5 @@
pub struct Object<T, U>(T, U);
pub fn something() -> Result<Object<i32, u32>, bool> {
loop {}
}