diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index ba5d384336003..56edc015049a1 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -1208,13 +1208,62 @@ function initSearch(rawSearchIndex) {
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) {
- 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;
+ }
+ 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 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;
+ return false;
};
- // 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);
+ const handleQueryElemList = (id, queryElemList) => {
+ if (!fnTypeSet.has(id)) {
+ if (id === typeNameIdOfArrayOrSlice) {
+ return handleQueryElemList(typeNameIdOfSlice, queryElemList) ||
+ handleQueryElemList(typeNameIdOfArray, queryElemList);
}
- } else if (queryElem.typeFilter !== -1 && !handleQueryElem(queryElem)) {
- needsUnboxed.push(queryElem);
+ return false;
}
- }
- for (const queryElem of queryElems) {
- if (queryElem.typeFilter === -1 && !handleQueryElem(queryElem)) {
- needsUnboxed.push(queryElem);
+ 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;
}
- }
- // 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;
+ 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);
}
- 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 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 false;
}
- return true;
+ return queryElemSetSize === 0;
}
/**
diff --git a/tests/rustdoc-js-std/bufread-fill-buf.js b/tests/rustdoc-js-std/bufread-fill-buf.js
index a079f32b7c0f1..3828cf76026ea 100644
--- a/tests/rustdoc-js-std/bufread-fill-buf.js
+++ b/tests/rustdoc-js-std/bufread-fill-buf.js
@@ -1,11 +1,8 @@
// ignore-order
-const QUERY = [
- 'bufread -> result',
-];
-
const EXPECTED = [
{
+ 'query': 'bufread -> result',
'others': [
{ 'path': 'std::io::Split', 'name': 'next' },
{ 'path': 'std::boxed::Box', '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 0000000000000..a9932a16ca38b
--- /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 0000000000000..79c493856ebc7
--- /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>) {}
+//