diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index ab0d663e26d7..a951a4e2f8ca 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1389,29 +1389,35 @@ function initSearch(rawSearchIndex) { * @return {boolean} - Returns true if a match, false otherwise. */ function checkGenerics(fnType, queryElem, whereClause, mgensInout) { - const simplifiedGenerics = unifyFunctionTypeCheckBindings( + const solutions = unifyFunctionTypeCheckBindings( fnType, queryElem, whereClause, mgensInout ); - if (!simplifiedGenerics) { + if (!solutions) { return false; } - return unifyFunctionTypes( - simplifiedGenerics, - queryElem.generics, - whereClause, - mgensInout, - mgens => { - if (mgensInout) { - for (const [fid, qid] of mgens.entries()) { - mgensInout.set(fid, qid); + const simplifiedGenerics = solutions.simplifiedGenerics; + for (const mgens of solutions.mgens) { + if (unifyFunctionTypes( + simplifiedGenerics, + queryElem.generics, + whereClause, + mgens, + mgens => { + if (mgensInout) { + for (const [fid, qid] of mgens.entries()) { + mgensInout.set(fid, qid); + } } + return true; } + )) { return true; } - ); + } + return false; } /** * This function checks if a list of search query `queryElems` can all be found in the @@ -1545,33 +1551,36 @@ function initSearch(rawSearchIndex) { for (j = i; j !== fl; ++j) { const fnType = fnTypes[j]; if (unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) { - const mgensScratch = new Map(mgens); - const simplifiedGenerics = unifyFunctionTypeCheckBindings( + const solution = unifyFunctionTypeCheckBindings( fnType, queryElem, whereClause, - mgensScratch + mgens ); - if (simplifiedGenerics) { + if (solution) { if (!fnTypesScratch) { fnTypesScratch = fnTypes.slice(); } - unifyFunctionTypes( - simplifiedGenerics, - queryElem.generics, - whereClause, - mgensScratch, - mgensScratch => { - matchCandidates.push({ - fnTypesScratch, - mgensScratch, - queryElemsOffset: i, - fnTypesOffset: j, - unbox: false, - }); - return false; // "reject" all candidates to gather all of them - } - ); + const simplifiedGenerics = solution.simplifiedGenerics; + for (const solutionMgens of solution.mgens) { + unifyFunctionTypes( + simplifiedGenerics, + queryElem.generics, + whereClause, + solutionMgens, + mgensScratch => { + matchCandidates.push({ + fnTypesScratch, + mgensScratch, + queryElemsOffset: i, + fnTypesOffset: j, + unbox: false, + }); + // "reject" all candidates to gather all of them + return false; + } + ); + } } } if (unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) { @@ -1726,41 +1735,44 @@ function initSearch(rawSearchIndex) { * @param {FunctionType} fnType * @param {QueryElement} queryElem * @param {[FunctionType]} whereClause - Trait bounds for generic items. - * @param {Map|null} mgensInout - Map functions generics to query generics. - * Written on success. - * @returns {boolean|FunctionType[]} + * @param {Map} mgensIn - Map functions generics to query generics. + * Never modified. + * @returns {false|{mgens: [Map], simplifiedGenerics: [FunctionType]}} */ - function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensInout) { - // Simplify generics now - let simplifiedGenerics = fnType.generics; - if (!simplifiedGenerics) { - simplifiedGenerics = []; - } + function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensIn) { if (fnType.bindings.size < queryElem.bindings.size) { return false; } + let simplifiedGenerics = fnType.generics || []; if (fnType.bindings.size > 0) { - const mgensResults = new Map(mgensInout); + let mgensSolutionSet = [mgensIn]; for (const [name, constraints] of queryElem.bindings.entries()) { - if (!fnType.bindings.has(name)) { + if (mgensSolutionSet.length === 0) { return false; } - // Since both items must have exactly one entry per name, - // we don't need to backtrack here, but do need to write mgens. - if (!unifyFunctionTypes( - fnType.bindings.get(name), - constraints, - whereClause, - mgensResults, - mgens => { - for (const [fid, qid] of mgens.entries()) { - mgensResults.set(fid, qid); - } - return true; - } - )) { + if (!fnType.bindings.has(name)) { return false; } + const fnTypeBindings = fnType.bindings.get(name); + mgensSolutionSet = mgensSolutionSet.flatMap(mgens => { + const newSolutions = []; + unifyFunctionTypes( + fnTypeBindings, + constraints, + whereClause, + mgens, + newMgens => { + newSolutions.push(newMgens); + // return `false` makes unifyFunctionTypes return the full set of + // possible solutions + return false; + } + ); + return newSolutions; + }); + } + if (mgensSolutionSet.length === 0) { + return false; } const binds = Array.from(fnType.bindings.entries()).flatMap(entry => { const [name, constraints] = entry; @@ -1775,13 +1787,9 @@ function initSearch(rawSearchIndex) { } else { simplifiedGenerics = binds; } - if (mgensInout) { - for (const [fid, qid] of mgensResults.entries()) { - mgensInout.set(fid, qid); - } - } + return { simplifiedGenerics, mgens: mgensSolutionSet }; } - return simplifiedGenerics; + return { simplifiedGenerics, mgens: [mgensIn] }; } /** * @param {FunctionType} fnType @@ -1805,7 +1813,7 @@ function initSearch(rawSearchIndex) { // `fn read_all(R) -> Result` // generic `R` is considered "unboxed" return checkIfInList(whereClause[(-fnType.id) - 1], queryElem, whereClause); - } else if (fnType.generics.length > 0 || fnType.bindings.length > 0) { + } else if (fnType.generics.length > 0 || fnType.bindings.size > 0) { const simplifiedGenerics = [ ...fnType.generics, ...Array.from(fnType.bindings.values()).flat(), diff --git a/tests/rustdoc-js/assoc-type-backtrack.js b/tests/rustdoc-js/assoc-type-backtrack.js index 6560cac7ca45..493e1a9910d1 100644 --- a/tests/rustdoc-js/assoc-type-backtrack.js +++ b/tests/rustdoc-js/assoc-type-backtrack.js @@ -63,4 +63,101 @@ const EXPECTED = [ { 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' }, ], }, + // The first two define the base case. + { + 'query': 'myintofuture> -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' }, + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + { + 'query': 'myintofuture>, myintofuture> -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + // Unboxings of the one-argument case. + { + 'query': 'myfuture -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' }, + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + { + 'query': 'myintofuture> -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' }, + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + // Invalid unboxing of the one-argument case. + // If you unbox one of the myfutures, you need to unbox both of them. + { + 'query': 'myintofuture -> myfuture', + 'correction': null, + 'others': [], + }, + // Unboxings of the two-argument case. + { + 'query': 'myintofuture, myintofuture -> t', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + { + 'query': 'myintofuture, myintofuture -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + { + 'query': 'myintofuture, myintofuture -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + { + 'query': 'myfuture, myfuture -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + // Invalid unboxings of the two-argument case. + // If you unbox one of the myfutures, you need to unbox all of them. + { + 'query': 'myintofuture, myintofuture> -> myfuture', + 'correction': null, + 'others': [], + }, + { + 'query': 'myintofuture>, myintofuture -> myfuture', + 'correction': null, + 'others': [], + }, + { + 'query': 'myintofuture>, myintofuture> -> t', + 'correction': null, + 'others': [], + }, + // different generics don't match up either + { + 'query': 'myintofuture>, myintofuture> -> myfuture', + 'correction': null, + 'others': [], + }, + { + 'query': 'myintofuture -> myfuture', + 'correction': null, + 'others': [], + }, ]; diff --git a/tests/rustdoc-js/assoc-type-backtrack.rs b/tests/rustdoc-js/assoc-type-backtrack.rs index 8e167ad8e7b7..c3cdd78c6e1c 100644 --- a/tests/rustdoc-js/assoc-type-backtrack.rs +++ b/tests/rustdoc-js/assoc-type-backtrack.rs @@ -25,3 +25,14 @@ impl<'a, T, I> MyTrait for Cloned where loop {} } } + +pub trait MyFuture { + type Output; +} + +pub trait MyIntoFuture { + type Output; + type Fut: MyFuture; + fn into_future(self) -> Self::Fut; + fn into_future_2(self, other: Self) -> Self::Fut; +}