diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js
index 8b931f74e60..f697abd0776 100644
--- a/src/librustdoc/html/static/js/externs.js
+++ b/src/librustdoc/html/static/js/externs.js
@@ -53,7 +53,7 @@ let ParsedQuery;
* parent: (Object|null|undefined),
* path: string,
* ty: (Number|null|number),
- * type: (Array>|null)
+ * type: FunctionSearchType?
* }}
*/
let Row;
@@ -135,7 +135,7 @@ let RawFunctionType;
/**
* @typedef {{
* inputs: Array,
- * outputs: Array,
+ * output: Array,
* }}
*/
let FunctionSearchType;
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 1ccfca8d0d5..25259971eff 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -1216,113 +1216,182 @@ function initSearch(rawSearchIndex) {
}
/**
- * This function checks if the object (`row`) generics match the given type (`elem`)
- * generics. If there are no generics on `row`, `defaultDistance` is returned.
+ * This function checks generics in search query `queryElem` can all be found in the
+ * search index (`fnType`),
*
- * @param {Row} row - The object to check.
- * @param {QueryElement} elem - The element from the parsed query.
+ * @param {FunctionType} fnType - The object to check.
+ * @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) {
- if (row.generics.length === 0 || elem.generics.length === 0) {
- return false;
- }
- // This function is called if the names match, but we need to make
- // sure that all generics match as well.
- //
+ function checkGenerics(fnType, queryElem) {
+ return unifyFunctionTypes(fnType.generics, queryElem.generics);
+ }
+ /**
+ * This function checks if a list of search query `queryElems` can all be found in the
+ * search index (`fnTypes`).
+ *
+ * @param {Array} fnTypes - The objects to check.
+ * @param {Array} 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
// should be no missing duplicates (generics have "bag semantics"),
// and the row is allowed to have extras.
- if (elem.generics.length > 0 && row.generics.length >= elem.generics.length) {
- const elems = new Map();
- const addEntryToElems = function addEntryToElems(entry) {
- if (entry.id === -1) {
- // Pure generic, needs to check into it.
- for (const inner_entry of entry.generics) {
- addEntryToElems(inner_entry);
- }
- return;
- }
- let currentEntryElems;
- if (elems.has(entry.id)) {
- currentEntryElems = elems.get(entry.id);
- } else {
- currentEntryElems = [];
- elems.set(entry.id, currentEntryElems);
- }
- currentEntryElems.push(entry);
- };
- for (const entry of row.generics) {
- addEntryToElems(entry);
- }
- // We need to find the type that matches the most to remove it in order
- // to move forward.
- const handleGeneric = generic => {
- if (!elems.has(generic.id)) {
- return false;
- }
- const matchElems = elems.get(generic.id);
- const matchIdx = matchElems.findIndex(tmp_elem => {
- if (generic.generics.length > 0 && !checkGenerics(tmp_elem, generic)) {
- return false;
- }
- return typePassesFilter(generic.typeFilter, tmp_elem.ty);
- });
- if (matchIdx === -1) {
- return false;
- }
- matchElems.splice(matchIdx, 1);
- if (matchElems.length === 0) {
- elems.delete(generic.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.
- for (const generic of elem.generics) {
- if (generic.typeFilter === TY_PRIMITIVE &&
- generic.id === typeNameIdOfArrayOrSlice) {
- const genericArray = {
- id: typeNameIdOfArray,
- typeFilter: TY_PRIMITIVE,
- generics: generic.generics,
- };
- const genericSlice = {
- id: typeNameIdOfSlice,
- typeFilter: TY_PRIMITIVE,
- generics: generic.generics,
- };
- if (!handleGeneric(genericArray) && !handleGeneric(genericSlice)) {
- return false;
- }
- } else if (generic.typeFilter !== -1 && !handleGeneric(generic)) {
- return false;
- }
- }
- for (const generic of elem.generics) {
- if (generic.typeFilter === -1 && !handleGeneric(generic)) {
- return false;
- }
- }
+ if (queryElems.length === 0) {
return true;
}
- return false;
+ if (!fnTypes || fnTypes.length === 0) {
+ return false;
+ }
+ /**
+ * @type Map
+ */
+ 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
+ */
+ const fnTypeSet = new Map();
+ const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) {
+ // 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);
+ }
+ return;
+ }
+ let currentFnTypeList;
+ if (fnTypeSet.has(fnType.id)) {
+ currentFnTypeList = fnTypeSet.get(fnType.id);
+ } else {
+ currentFnTypeList = [];
+ fnTypeSet.set(fnType.id, currentFnTypeList);
+ }
+ currentFnTypeList.push(fnType);
+ };
+ for (const fnType of fnTypes) {
+ addFnTypeToFnTypeSet(fnType);
+ }
+ const doHandleQueryElemList = (currentFnTypeList, queryElemList) => {
+ if (queryElemList.length === 0) {
+ return true;
+ }
+ // 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)) {
+ continue;
+ }
+ 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 queryElemSetSize === 0;
}
/**
* This function checks if the object (`row`) matches the given type (`elem`) and its
* generics (if any).
*
- * @param {Row} row
+ * @param {Array} list
* @param {QueryElement} elem - The element from the parsed query.
*
* @return {boolean} - Returns true if found, false otherwise.
*/
- function checkIfInGenerics(row, elem) {
- for (const entry of row.generics) {
+ function checkIfInList(list, elem) {
+ for (const entry of list) {
if (checkType(entry, elem)) {
return true;
}
@@ -1342,7 +1411,7 @@ function initSearch(rawSearchIndex) {
function checkType(row, elem) {
if (row.id === -1) {
// 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;
@@ -1360,59 +1429,7 @@ function initSearch(rawSearchIndex) {
// 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
- return checkIfInGenerics(row, 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} 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} 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;
+ return checkIfInList(row.generics, elem);
}
function checkPath(contains, ty, maxEditDistance) {
@@ -1613,14 +1630,14 @@ function initSearch(rawSearchIndex) {
const fullId = row.id;
const searchWord = searchWords[pos];
- const in_args = findArg(row, elem, []);
- if (in_args !== -1) {
+ const in_args = row.type && row.type.inputs && checkIfInList(row.type.inputs, elem);
+ if (in_args) {
// path_dist is 0 because no parent path information is currently stored
// in the search index
addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance);
}
- const returned = checkReturned(row, elem, []);
- if (returned !== -1) {
+ const returned = row.type && row.type.output && checkIfInList(row.type.output, elem);
+ if (returned) {
addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance);
}
@@ -1676,32 +1693,15 @@ function initSearch(rawSearchIndex) {
* @param {Object} results
*/
function handleArgs(row, pos, results) {
- if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
+ if (!row || (filterCrates !== null && row.crate !== filterCrates) || !row.type) {
return;
}
// If the result is too "bad", we return false and it ends this search.
- function checkArgs(elems, callback) {
- 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)) {
+ if (!unifyFunctionTypes(row.type.inputs, parsedQuery.elems)) {
return;
}
- if (!checkArgs(parsedQuery.returned, checkReturned)) {
+ if (!unifyFunctionTypes(row.type.output, parsedQuery.returned)) {
return;
}
@@ -1788,12 +1788,9 @@ function initSearch(rawSearchIndex) {
elem = parsedQuery.returned[0];
for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
row = searchIndex[i];
- in_returned = checkReturned(
- row,
- elem,
- []
- );
- if (in_returned !== -1) {
+ in_returned = row.type &&
+ unifyFunctionTypes(row.type.output, parsedQuery.returned);
+ if (in_returned) {
addIntoResults(
results_others,
row.id,
diff --git a/tests/rustdoc-js-std/bufread-fill-buf.js b/tests/rustdoc-js-std/bufread-fill-buf.js
new file mode 100644
index 00000000000..3828cf76026
--- /dev/null
+++ b/tests/rustdoc-js-std/bufread-fill-buf.js
@@ -0,0 +1,13 @@
+// ignore-order
+
+const EXPECTED = [
+ {
+ 'query': 'bufread -> result',
+ 'others': [
+ { 'path': 'std::io::Split', 'name': 'next' },
+ { 'path': 'std::boxed::Box', 'name': 'fill_buf' },
+ { 'path': 'std::io::Chain', 'name': 'fill_buf' },
+ { 'path': 'std::io::Take', 'name': 'fill_buf' },
+ ],
+ },
+];
diff --git a/tests/rustdoc-js/generics-match-ambiguity.js b/tests/rustdoc-js/generics-match-ambiguity.js
new file mode 100644
index 00000000000..a9932a16ca3
--- /dev/null
+++ b/tests/rustdoc-js/generics-match-ambiguity.js
@@ -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',
+ 'in_args': [
+ { 'path': 'generics_match_ambiguity', 'name': 'bar' },
+ { 'path': 'generics_match_ambiguity', 'name': 'foo' },
+ ],
+ },
+ {
+ 'query': 'Wrap, Wrap',
+ 'others': [
+ { 'path': 'generics_match_ambiguity', 'name': 'bar' },
+ { 'path': 'generics_match_ambiguity', 'name': 'foo' },
+ ],
+ },
+ {
+ 'query': 'Wrap, Wrap',
+ 'others': [
+ { 'path': 'generics_match_ambiguity', 'name': 'bar' },
+ { 'path': 'generics_match_ambiguity', 'name': 'foo' },
+ ],
+ },
+ {
+ 'query': 'W3, W3',
+ '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, W3',
+ '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, W2',
+ 'others': [
+ { 'path': 'generics_match_ambiguity', 'name': 'baag' },
+ { 'path': 'generics_match_ambiguity', 'name': 'baah' },
+ ],
+ },
+ {
+ 'query': 'W2, W2',
+ 'others': [
+ { 'path': 'generics_match_ambiguity', 'name': 'baag' },
+ { 'path': 'generics_match_ambiguity', 'name': 'baah' },
+ ],
+ },
+ {
+ 'query': 'W2, W3',
+ 'others': [
+ { 'path': 'generics_match_ambiguity', 'name': 'baac' },
+ { 'path': 'generics_match_ambiguity', 'name': 'baaf' },
+ { 'path': 'generics_match_ambiguity', 'name': 'baag' },
+ ],
+ },
+ {
+ 'query': 'W2, W2',
+ 'others': [
+ { 'path': 'generics_match_ambiguity', 'name': 'baag' },
+ { 'path': 'generics_match_ambiguity', 'name': 'baah' },
+ ],
+ },
+];
diff --git a/tests/rustdoc-js/generics-match-ambiguity.rs b/tests/rustdoc-js/generics-match-ambiguity.rs
new file mode 100644
index 00000000000..79c493856eb
--- /dev/null
+++ b/tests/rustdoc-js/generics-match-ambiguity.rs
@@ -0,0 +1,17 @@
+pub struct Wrap(pub T, pub U);
+
+pub fn foo(a: Wrap, b: Wrap) {}
+pub fn bar(a: Wrap, b: Wrap) {}
+
+pub struct W2(pub T);
+pub struct W3(pub T, pub U);
+
+pub fn baaa(a: W3, b: W3) {}
+pub fn baab(a: W3, b: W3) {}
+pub fn baac(a: W2>, b: W3) {}
+pub fn baad(a: W2>, b: W3) {}
+pub fn baae(a: W3, b: W2>) {}
+pub fn baaf(a: W3, b: W2>) {}
+pub fn baag(a: W2>, b: W2>) {}
+pub fn baah(a: W2>, b: W2>) {}
+//
diff --git a/tests/rustdoc-js/nested-unboxed.js b/tests/rustdoc-js/nested-unboxed.js
new file mode 100644
index 00000000000..44f784eb1f6
--- /dev/null
+++ b/tests/rustdoc-js/nested-unboxed.js
@@ -0,0 +1,68 @@
+// exact-check
+
+const EXPECTED = [
+ {
+ 'query': '-> Result