diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index e2b4cc50dd580..58898247a9f40 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -328,10 +328,11 @@ details:not(.toggle) summary { margin-bottom: .6em; } -code, pre, a.test-arrow, .code-header { +code, pre, a.test-arrow, .code-header, .search-results .type-signature { font-family: "Source Code Pro", monospace; } -.docblock code, .docblock-short code { +.docblock code, .docblock-short code, +.search-results .type-signature strong { border-radius: 3px; padding: 0 0.125em; } @@ -681,7 +682,8 @@ ul.block, .block li { } .docblock code, .docblock-short code, -pre, .rustdoc.src .example-wrap { +pre, .rustdoc.src .example-wrap, +.search-results .type-signature strong { background-color: var(--code-block-background-color); } diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 48c9a53a28310..a2878be2817df 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -262,6 +262,14 @@ function initSearch(rawSearchIndex) { * Special type name IDs for searching by both array and slice (`[]` syntax). */ let typeNameIdOfArrayOrSlice; + /** + * Special type name IDs for searching never (the `!` type). + */ + let typeNameIdOfNever; + /** + * Special type name IDs for searching tuple (the `()` type). + */ + let typeNameIdOfTuple; /** * Add an item to the type Name->ID map, or, if one already exists, use it. @@ -467,12 +475,13 @@ function initSearch(rawSearchIndex) { * @param {ParsedQuery} query * @param {ParserState} parserState * @param {string} name - Name of the query element. + * @param {string} normalizedName - Name of the query element will be used for searching. * @param {Array} generics - List of generics of this query element. * * @return {QueryElement} - The newly created `QueryElement`. */ - function createQueryElement(query, parserState, name, generics, isInGenerics) { - const path = name.trim(); + function createQueryElement(query, parserState, name, normalizedName, generics, isInGenerics) { + const path = normalizedName.trim(); if (path.length === 0 && generics.length === 0) { throw ["Unexpected ", parserState.userQuery[parserState.pos]]; } else if (path === "*") { @@ -483,7 +492,7 @@ function initSearch(rawSearchIndex) { } const typeFilter = parserState.typeFilter; parserState.typeFilter = null; - if (name === "!") { + if (normalizedName === "!") { if (typeFilter !== null && typeFilter !== "primitive") { throw [ "Invalid search type: primitive never type ", @@ -502,6 +511,7 @@ function initSearch(rawSearchIndex) { } return { name: "never", + normalizedName: "never", id: null, fullPath: ["never"], pathWithoutLast: [], @@ -544,6 +554,7 @@ function initSearch(rawSearchIndex) { } return { name: name.trim(), + normalizedName: normalizedName.trim(), id: null, fullPath: pathSegments, pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1), @@ -673,6 +684,7 @@ function initSearch(rawSearchIndex) { } elems.push({ name: "[]", + normalizedName: "[]", id: null, fullPath: ["[]"], pathWithoutLast: [], @@ -709,6 +721,7 @@ function initSearch(rawSearchIndex) { createQueryElement( query, parserState, + parserState.original.slice(start, end), parserState.userQuery.slice(start, end), generics, isInGenerics @@ -771,7 +784,7 @@ function initSearch(rawSearchIndex) { // The type filter doesn't count as an element since it's a modifier. const typeFilterElem = elems.pop(); checkExtraTypeFilterCharacters(start, parserState); - parserState.typeFilter = typeFilterElem.name; + parserState.typeFilter = typeFilterElem.normalizedName; parserState.pos += 1; parserState.totalElems -= 1; query.literalSearch = false; @@ -894,7 +907,7 @@ function initSearch(rawSearchIndex) { // The type filter doesn't count as an element since it's a modifier. const typeFilterElem = query.elems.pop(); checkExtraTypeFilterCharacters(start, parserState); - parserState.typeFilter = typeFilterElem.name; + parserState.typeFilter = typeFilterElem.normalizedName; parserState.pos += 1; parserState.totalElems -= 1; query.literalSearch = false; @@ -1063,6 +1076,7 @@ function initSearch(rawSearchIndex) { totalElems: 0, genericsElems: 0, typeFilter: null, + original: userQuery, userQuery: userQuery.toLowerCase(), }; let query = newParsedQuery(userQuery); @@ -1129,29 +1143,35 @@ function initSearch(rawSearchIndex) { * marked for removal. * * @param {[ResultObject]} results + * @param {boolean} isType - True will include function signature string * @returns {[ResultObject]} */ - function transformResults(results) { + function transformResults(results, isType) { const duplicates = new Set(); const out = []; for (const result of results) { if (result.id !== -1) { const obj = searchIndex[result.id]; - obj.dist = result.dist; const res = buildHrefAndPath(obj); - obj.displayPath = pathSplitter(res[0]); - obj.fullPath = obj.displayPath + obj.name; + const displayPath = pathSplitter(res[0]); + let fullPath = displayPath + obj.name; // To be sure than it some items aren't considered as duplicate. - obj.fullPath += "|" + obj.ty; + fullPath += "|" + obj.ty; - if (duplicates.has(obj.fullPath)) { + if (!isType && duplicates.has(fullPath)) { continue; } - duplicates.add(obj.fullPath); - - obj.href = res[1]; - out.push(obj); + duplicates.add(fullPath); + + out.push(Object.assign({ + dist: result.dist, + index: result.index, + displayTypeSignature: result.displayTypeSignature, + displayPath, + fullPath, + href: res[1], + }, obj)); if (out.length >= MAX_RESULTS) { break; } @@ -1160,6 +1180,119 @@ function initSearch(rawSearchIndex) { return out; } + /** + * Convert a type signature to a readable string. + * + * @param {FunctionSearchType?} results + * + * @returns string? + */ + function formatResultTypeSignature(type) { + if (type === null) { + return null; + } + + let result = []; + + function pushNotHighlighted(text) { + if (result.length % 2 === 0) { + result.push(""); + } + result[result.length - 1] = `${result[result.length - 1]}${text}`; + } + function pushHighlighted(text) { + if (result.length === 0) { + result.push(""); + } + if (result.length % 2 !== 0) { + result.push(""); + } + result[result.length - 1] = `${result[result.length - 1]}${text}`; + } + function push(text, highlighted) { + if (highlighted) { + pushHighlighted(text); + } else { + pushNotHighlighted(text); + } + } + + function isTransitivelyHighlighted(fnType) { + // Caching the flag is necessary to avoid quadratic complexity + // in this function. + if (Object.prototype.hasOwnProperty.call(fnType, "isTransitivelyHighlighted")) { + return fnType.isTransitivelyHighlighted; + } + const isHighlighted = fnType.highlighted || + (fnType.generics && fnType.generics.length > 0 && + fnType.generics.some(isTransitivelyHighlighted)) || + (type.where_clause && type.where_clause[(-fnType.id) - 1] && + type.where_clause[(-fnType.id) - 1].some(isTransitivelyHighlighted)); + fnType.isTransitivelyHighlighted = isHighlighted; + return isHighlighted; + } + + function formatTypeList(fnTypes, delim) { + if (!fnTypes) { + return; + } + let first = true; + for (const fnType of fnTypes) { + if (first) { + first = false; + } else { + pushNotHighlighted(delim); + } + if (fnType.ty === TY_PRIMITIVE && + (fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) + ) { + push("[", fnType.highlighted); + if (fnType.generics && fnType.generics.some(isTransitivelyHighlighted)) { + formatTypeList(fnType.generics, ", "); + } + push("]", fnType.highlighted); + } else if (fnType.ty === TY_PRIMITIVE && fnType.id === typeNameIdOfNever) { + push("!", fnType.highlighted); + } else if (fnType.name === null) { + if (fnType.generics && fnType.generics.some(t => t.name !== null)) { + push("impl ", fnType.highlighted); + formatTypeList(fnType.generics, " + "); + } else if (type.where_clause && + type.where_clause[(-fnType.id) - 1].some(isTransitivelyHighlighted) + ) { + push("impl ", fnType.highlighted); + formatTypeList(type.where_clause[(-fnType.id) - 1], " + "); + } else { + push("_", fnType.highlighted); + } + } else { + push(fnType.name, fnType.highlighted); + if (fnType.generics && fnType.generics.some(isTransitivelyHighlighted)) { + pushNotHighlighted("<"); + formatTypeList(fnType.generics, ", "); + pushNotHighlighted(">"); + } + } + } + } + + formatTypeList(type.inputs, ", "); + if (result.every(f => f === "")) { + result = []; + } + const returnsUnit = type.output.length === 1 && + type.output[0].id === typeNameIdOfTuple && + type.output[0].generics.length === 0; + if (type.output.length !== 0 && !returnsUnit) { + pushNotHighlighted(result.length === 0 ? "-> " : " -> "); + formatTypeList(type.output, ", "); + } + if (result.every(f => f === "")) { + return null; + } + return result; + } + /** * This function takes a result map, and sorts it by various criteria, including edit * distance, substring match, and the crate it comes from. @@ -1305,39 +1438,37 @@ function initSearch(rawSearchIndex) { result.id = -1; } } - return transformResults(result_list); + return transformResults(result_list, isType); } /** - * This function checks generics in search query `queryElem` can all be found in the - * search index (`fnType`), - * - * This function returns `true` if it matches, and also writes the results to mgensInout. - * It returns `false` if no match is found, and leaves mgensInout untouched. + * The type tree on each function is annotated with information about which + * node to highlight. This is then used to generate the formatted type + * signature for the result. * - * @param {FunctionType} fnType - The object to check. - * @param {QueryElement} queryElem - The element from the parsed query. - * @param {[FunctionType]} whereClause - Trait bounds for generic items. - * @param {Map|null} mgensInout - Map functions generics to query generics. - * - * @return {boolean} - Returns true if a match, false otherwise. + * Between each result, it gets cleared out. This function clears it. */ - function checkGenerics(fnType, queryElem, whereClause, mgensInout) { - return unifyFunctionTypes( - fnType.generics, - queryElem.generics, - whereClause, - mgensInout, - mgens => { - if (mgensInout) { - for (const [fid, qid] of mgens.entries()) { - mgensInout.set(fid, qid); - } - } - return true; + function clearHighlighted(fnType) { + function doClearHighlighted(fnTypeList) { + if (!fnTypeList) { + return; } - ); + for (const fnType of fnTypeList) { + fnType.highlighted = false; + delete fnType.isTransitivelyHighlighted; + delete fnType.matchedGenerics; + doClearHighlighted(fnType.generics); + } + } + doClearHighlighted(fnType.inputs); + doClearHighlighted(fnType.output); + if (fnType.where_clause) { + for (const where of fnType.where_clause) { + doClearHighlighted(where); + } + } } + /** * This function checks if a list of search query `queryElems` can all be found in the * search index (`fnTypes`). @@ -1353,9 +1484,10 @@ function initSearch(rawSearchIndex) { * @param {[FunctionType]} whereClause - Trait bounds for generic items. * @param {Map|null} mgensIn * - Map functions generics to query generics (never modified). - * @param {null|Map -> bool} solutionCb - Called for each `mgens` solution. + * @param {(null|Map, Array) -> bool} solutionCb + * - Called for each `mgens` solution. * - * @return {boolean} - Returns true if a match, false otherwise. + * @return {Array|false} - Returns array of matched parts, false otherwise. */ function unifyFunctionTypes(fnTypesIn, queryElems, whereClause, mgensIn, solutionCb) { /** @@ -1363,7 +1495,7 @@ function initSearch(rawSearchIndex) { */ let mgens = new Map(mgensIn); if (queryElems.length === 0) { - return !solutionCb || solutionCb(mgens); + return !solutionCb || solutionCb(mgens, []); } if (!fnTypesIn || fnTypesIn.length === 0) { return false; @@ -1411,6 +1543,7 @@ function initSearch(rawSearchIndex) { queryElemsOffset, fnTypesOffset, unbox, + matchedGenerics, } = backtracking.pop(); mgens = new Map(mgensScratch); const fnType = fnTypesScratch[fnTypesOffset]; @@ -1440,6 +1573,7 @@ function initSearch(rawSearchIndex) { fl = fnTypes.length; const tmp = fnTypes[queryElemsOffset]; fnTypes[queryElemsOffset] = fnTypes[fnTypesOffset]; + fnTypes[queryElemsOffset].matchedGenerics = matchedGenerics; fnTypes[fnTypesOffset] = tmp; // this is known as a good match; go to the next one i = queryElemsOffset; @@ -1470,10 +1604,11 @@ function initSearch(rawSearchIndex) { queryElem.generics, whereClause, mgens, - mgensScratch => { + (mgensScratch, matchedGenerics) => { matchCandidates.push({ fnTypesScratch, mgensScratch, + matchedGenerics, queryElemsOffset: i, fnTypesOffset: j, unbox: false, @@ -1492,6 +1627,7 @@ function initSearch(rawSearchIndex) { backtracking.push({ fnTypesScratch, mgensScratch, + matchedGenerics: [], queryElemsOffset: i, fnTypesOffset: j, unbox: true, @@ -1506,7 +1642,8 @@ function initSearch(rawSearchIndex) { } } // use the current candidate - const {fnTypesOffset: candidate, mgensScratch: mgensNew} = matchCandidates.pop(); + const {fnTypesOffset: candidate, mgensScratch: mgensNew, matchedGenerics} + = matchCandidates.shift(); if (fnTypes[candidate].id < 0 && queryElems[i].id < 0) { mgens.set(fnTypes[candidate].id, queryElems[i].id); } @@ -1523,9 +1660,22 @@ function initSearch(rawSearchIndex) { for (const otherCandidate of matchCandidates) { backtracking.push(otherCandidate); } + fnTypes[i].matchedGenerics = matchedGenerics; // If we're on the last item, check the solution with the callback // backtrack if the callback says its unsuitable - while (i === (ql - 1) && solutionCb && !solutionCb(mgens)) { + while (i === (ql - 1) && solutionCb) { + // The above loop partitions the list into "matched" and "unmatched" parts + // by swapping things into place when they match. + let highlightable = fnTypes.slice(0, ql); + for (let j = 0; j < highlightable.length; ++j) { + if (highlightable[j].matchedGenerics.length === 0) { + continue; + } + highlightable = [...highlightable, ...highlightable[j].matchedGenerics]; + } + if (solutionCb(mgens, highlightable)) { + break; + } if (!backtrack()) { return false; } @@ -1649,38 +1799,15 @@ function initSearch(rawSearchIndex) { * @return {boolean} - Returns true if the type matches, false otherwise. */ function checkType(row, elem, whereClause) { - if (row.id === null) { - // This is a pure "generic" search, no need to run other checks. - return row.generics.length > 0 - ? checkIfInList(row.generics, elem, whereClause) - : false; - } - - if (row.id < 0 && elem.id >= 0) { - const gid = (-row.id) - 1; - return checkIfInList(whereClause[gid], elem, whereClause); - } - - if (row.id < 0 && elem.id < 0) { - return true; - } - - const matchesExact = row.id === elem.id; - const matchesArrayOrSlice = elem.id === typeNameIdOfArrayOrSlice && - (row.id === typeNameIdOfSlice || row.id === typeNameIdOfArray); - - if ((matchesExact || matchesArrayOrSlice) && - typePassesFilter(elem.typeFilter, row.ty)) { - if (elem.generics.length > 0) { - return checkGenerics(row, elem, whereClause, new Map()); - } - return true; + if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 && + row.generics.length === 0 && elem.generics.length === 0 && + // special case + elem.id !== typeNameIdOfArrayOrSlice + ) { + return row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty); + } else { + return unifyFunctionTypes([row], [elem], whereClause); } - - // 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 checkIfInList(row.generics, elem, whereClause); } function checkPath(contains, ty, maxEditDistance) { @@ -1831,8 +1958,19 @@ function initSearch(rawSearchIndex) { * @param {integer} index * @param {integer} dist * @param {integer} path_dist + * @param {integer} maxEditDistance + * @param {boolean} isType */ - function addIntoResults(results, fullId, id, index, dist, path_dist, maxEditDistance) { + function addIntoResults( + results, + fullId, + id, + index, + dist, + path_dist, + maxEditDistance, + isType + ) { const inBounds = dist <= maxEditDistance || index !== -1; if (dist === 0 || (!parsedQuery.literalSearch && inBounds)) { if (results.has(fullId)) { @@ -1841,12 +1979,16 @@ function initSearch(rawSearchIndex) { return; } } + const displayTypeSignature = isType ? + formatResultTypeSignature(searchIndex[id].type) : + null; results.set(fullId, { id: id, index: index, dontValidate: parsedQuery.literalSearch, dist: dist, path_dist: path_dist, + displayTypeSignature, }); } } @@ -1885,14 +2027,46 @@ function initSearch(rawSearchIndex) { const in_args = row.type && row.type.inputs && checkIfInList(row.type.inputs, elem, row.type.where_clause); if (in_args) { + // Generate type signature information for search result display. + clearHighlighted(row.type); + unifyFunctionTypes( + row.type.inputs, + parsedQuery.elems, + row.type.where_clause, + null, + (mgens, parts) => { + writeNamesToTypeParameters(mgens, row.type, parsedQuery); + for (const part of parts) { + part.highlighted = true; + } + return true; + } + ); // 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); + addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance, true); } const returned = row.type && row.type.output && checkIfInList(row.type.output, elem, row.type.where_clause); if (returned) { - addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance); + // Generate type signature information for search result display. + if (!in_args) { + clearHighlighted(row.type); + unifyFunctionTypes( + row.type.output, + parsedQuery.elems, + row.type.where_clause, + null, + (mgens, parts) => { + writeNamesToTypeParameters(mgens, row.type, parsedQuery); + for (const part of parts) { + part.highlighted = true; + } + return true; + } + ); + } + addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance, true); } if (!typePassesFilter(elem.typeFilter, row.ty)) { @@ -1922,8 +2096,17 @@ function initSearch(rawSearchIndex) { } if (parsedQuery.literalSearch) { - if (searchWord === elem.name) { - addIntoResults(results_others, fullId, pos, index, 0, path_dist); + if (searchWord === elem.normalizedName) { + addIntoResults( + results_others, + fullId, + pos, + index, + 0, + path_dist, + -1, + false + ); } return; } @@ -1934,7 +2117,16 @@ function initSearch(rawSearchIndex) { return; } - addIntoResults(results_others, fullId, pos, index, dist, path_dist, maxEditDistance); + addIntoResults( + results_others, + fullId, + pos, + index, + dist, + path_dist, + maxEditDistance, + false + ); } /** @@ -1951,25 +2143,80 @@ function initSearch(rawSearchIndex) { return; } - // If the result is too "bad", we return false and it ends this search. if (!unifyFunctionTypes( row.type.inputs, parsedQuery.elems, row.type.where_clause, null, - mgens => { + (mgens, matchedPartsParams) => { return unifyFunctionTypes( row.type.output, parsedQuery.returned, row.type.where_clause, - mgens + mgens, + (completeMgens, matchedPartsReturn) => { + writeNamesToTypeParameters(completeMgens, row.type, parsedQuery); + clearHighlighted(row.type); + for (const part of matchedPartsParams) { + part.highlighted = true; + } + for (const part of matchedPartsReturn) { + part.highlighted = true; + } + return true; + } ); } )) { return; } - addIntoResults(results, row.id, pos, 0, 0, 0, Number.MAX_VALUE); + addIntoResults(results, row.id, pos, 0, 0, 0, Number.MAX_VALUE, true); + } + + function writeNamesToTypeParameters(mgens, rowType, parsedQuery) { + const queryTypeToName = new Map(); + function getQueryTypeFromList(list) { + if (!list) { + return; + } + for (const queryElem of list) { + if (queryElem.generics && queryElem.generics.length > 0) { + getQueryTypeFromList(queryElem.generics); + } + if (!queryTypeToName.has(queryElem.id)) { + queryTypeToName.set(queryElem.id, queryElem.name); + } + } + } + getQueryTypeFromList(parsedQuery.elems); + getQueryTypeFromList(parsedQuery.returned); + function assignFunctionNameFromList(list) { + if (!list) { + return; + } + for (const fnType of list) { + if (fnType.id < 0) { + assignFunctionNameFromList(rowType.where_clause[(-fnType.id) - 1]); + if (mgens.has(fnType.id)) { + const queryId = mgens.get(fnType.id); + if (queryTypeToName.has(queryId) && queryId < 0) { + const queryName = queryTypeToName.get(queryId); + fnType.name = queryName; + } else { + fnType.name = null; + } + } else { + fnType.name = null; + } + } + if (fnType.generics && fnType.generics.length > 0) { + assignFunctionNameFromList(fnType.generics); + } + } + } + assignFunctionNameFromList(rowType.inputs); + assignFunctionNameFromList(rowType.output); } function innerRunQuery() { @@ -1977,10 +2224,10 @@ function initSearch(rawSearchIndex) { let queryLen = 0; for (const elem of parsedQuery.elems) { - queryLen += elem.name.length; + queryLen += elem.normalizedName.length; } for (const elem of parsedQuery.returned) { - queryLen += elem.name.length; + queryLen += elem.normalizedName.length; } const maxEditDistance = Math.floor(queryLen / 3); @@ -2027,21 +2274,21 @@ function initSearch(rawSearchIndex) { if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1 && elem.generics.length === 0) || elem.typeFilter === TY_GENERIC) { - if (genericSymbols.has(elem.name)) { - elem.id = genericSymbols.get(elem.name); + if (genericSymbols.has(elem.normalizedName)) { + elem.id = genericSymbols.get(elem.normalizedName); } else { elem.id = -(genericSymbols.size + 1); - genericSymbols.set(elem.name, elem.id); + genericSymbols.set(elem.normalizedName, elem.id); } - if (elem.typeFilter === -1 && elem.name.length >= 3) { + if (elem.typeFilter === -1 && elem.normalizedName.length >= 3) { // Silly heuristic to catch if the user probably meant // to not write a generic parameter. We don't use it, // just bring it up. - const maxPartDistance = Math.floor(elem.name.length / 3); + const maxPartDistance = Math.floor(elem.normalizedName.length / 3); let matchDist = maxPartDistance + 1; let matchName = ""; for (const name of typeNameIdMap.keys()) { - const dist = editDistance(name, elem.name, maxPartDistance); + const dist = editDistance(name, elem.normalizedName, maxPartDistance); if (dist <= matchDist && dist <= maxPartDistance) { if (dist === matchDist && matchName > name) { continue; @@ -2051,7 +2298,7 @@ function initSearch(rawSearchIndex) { } } if (matchName !== "") { - parsedQuery.proposeCorrectionFrom = elem.name; + parsedQuery.proposeCorrectionFrom = elem.normalizedName; parsedQuery.proposeCorrectionTo = matchName; } } @@ -2061,7 +2308,7 @@ function initSearch(rawSearchIndex) { // Rust does not have HKT parsedQuery.error = [ "Generic type parameter ", - elem.name, + elem.normalizedName, " does not accept generic parameters", ]; } @@ -2098,10 +2345,22 @@ function initSearch(rawSearchIndex) { elem = parsedQuery.returned[0]; for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) { row = searchIndex[i]; - in_returned = row.type && unifyFunctionTypes( + if (!row.type) { + continue; + } + in_returned = unifyFunctionTypes( row.type.output, parsedQuery.returned, - row.type.where_clause + row.type.where_clause, + null, + (mgens, parts) => { + writeNamesToTypeParameters(mgens, row.type, parsedQuery); + clearHighlighted(row.type); + for (const part of parts) { + part.highlighted = true; + } + return true; + } ); if (in_returned) { addIntoResults( @@ -2110,7 +2369,9 @@ function initSearch(rawSearchIndex) { i, -1, 0, - Number.MAX_VALUE + Number.MAX_VALUE, + undefined, + true ); } } @@ -2126,10 +2387,11 @@ function initSearch(rawSearchIndex) { innerRunQuery(); } + const isOthersTypeSearch = parsedQuery.foundElems > 1 || parsedQuery.returned.length > 0; const ret = createQueryResults( sortResults(results_in_args, true, currentCrate), sortResults(results_returned, true, currentCrate), - sortResults(results_others, false, currentCrate), + sortResults(results_others, isOthersTypeSearch, currentCrate), parsedQuery); handleAliases(ret, parsedQuery.original.replace(/"/g, ""), filterCrates, currentCrate); if (parsedQuery.error !== null && ret.others.length !== 0) { @@ -2264,11 +2526,12 @@ function initSearch(rawSearchIndex) { /** * Render a set of search results for a single tab. - * @param {Array} array - The search results for this tab + * @param {Array} array - The search results for this tab * @param {ParsedQuery} query * @param {boolean} display - True if this is the active tab + * @param {boolean} isType - True shows the function signature */ - function addTab(array, query, display) { + function addTab(array, query, display, isType) { let extraClass = ""; if (display === true) { extraClass = " active"; @@ -2313,6 +2576,20 @@ ${item.displayPath}${name}\ const description = document.createElement("div"); description.className = "desc"; + if (isType) { + const displayTypeSignature = document.createElement("div"); + item.displayTypeSignature.forEach((value, index) => { + if (index % 2 !== 0) { + const highlight = document.createElement("strong"); + highlight.appendChild(document.createTextNode(value)); + displayTypeSignature.appendChild(highlight); + } else { + displayTypeSignature.appendChild(document.createTextNode(value)); + } + }); + displayTypeSignature.className = "type-signature"; + description.appendChild(displayTypeSignature); + } description.insertAdjacentHTML("beforeend", item.desc); link.appendChild(description); @@ -2392,9 +2669,24 @@ ${item.displayPath}${name}\ currentResults = results.query.userQuery; - const ret_others = addTab(results.others, results.query, true); - const ret_in_args = addTab(results.in_args, results.query, false); - const ret_returned = addTab(results.returned, results.query, false); + const ret_others = addTab( + results.others, + results.query, + true, + results.query.foundElems > 1 || results.query.returned.length > 0 + ); + const ret_in_args = addTab( + results.in_args, + results.query, + false, + true + ); + const ret_returned = addTab( + results.returned, + results.query, + false, + true + ); // Navigate to the relevant tab if the current tab is empty, like in case users search // for "-> String". If they had selected another tab previously, they have to click on @@ -2426,6 +2718,9 @@ ${item.displayPath}${name}\ let output = `

Results${crates}

`; if (results.query.error !== null) { const error = results.query.error; + if (!error.forEach) { + throw error; + } error.forEach((value, index) => { value = value.split("<").join("<").split(">").join(">"); if (index % 2 !== 0) { @@ -2569,19 +2864,22 @@ ${item.displayPath}${name}\ * * @param {null|Array} types * @param {Array<{name: string, ty: number}>} lowercasePaths + * @param {Array<{name: string, ty: number}>} paths * * @return {Array} */ - function buildItemSearchTypeAll(types, lowercasePaths) { - return types.map(type => buildItemSearchType(type, lowercasePaths)); + function buildItemSearchTypeAll(types, lowercasePaths, paths) { + return types.map(type => buildItemSearchType(type, lowercasePaths, paths)); } /** * Converts a single type. * * @param {RawFunctionType} type + * @param {Array<{name: string, ty: number}>} lowercasePaths + * @param {Array<{name: string, ty: number}>} paths */ - function buildItemSearchType(type, lowercasePaths) { + function buildItemSearchType(type, lowercasePaths, paths) { const PATH_INDEX_DATA = 0; const GENERICS_DATA = 1; let pathIndex, generics; @@ -2592,7 +2890,8 @@ ${item.displayPath}${name}\ pathIndex = type[PATH_INDEX_DATA]; generics = buildItemSearchTypeAll( type[GENERICS_DATA], - lowercasePaths + lowercasePaths, + paths ); } if (pathIndex < 0) { @@ -2600,6 +2899,7 @@ ${item.displayPath}${name}\ // the actual names of generic parameters aren't stored, since they aren't API return { id: pathIndex, + name: null, ty: TY_GENERIC, path: null, generics, @@ -2609,6 +2909,7 @@ ${item.displayPath}${name}\ // `0` is used as a sentinel because it's fewer bytes than `null` return { id: null, + name: null, ty: null, path: null, generics, @@ -2617,6 +2918,7 @@ ${item.displayPath}${name}\ const item = lowercasePaths[pathIndex - 1]; return { id: buildTypeMapIndex(item.name), + name: paths[pathIndex - 1].name, ty: item.ty, path: item.path, generics, @@ -2635,11 +2937,11 @@ ${item.displayPath}${name}\ * * @param {RawFunctionSearchType} functionSearchType * @param {Array<{name: string, ty: number}>} lowercasePaths - * @param {Map} + * @param {Array<{name: string, ty: number}>} paths * * @return {null|FunctionSearchType} */ - function buildFunctionSearchType(functionSearchType, lowercasePaths) { + function buildFunctionSearchType(functionSearchType, lowercasePaths, paths) { const INPUTS_DATA = 0; const OUTPUT_DATA = 1; // `0` is used as a sentinel because it's fewer bytes than `null` @@ -2648,20 +2950,26 @@ ${item.displayPath}${name}\ } let inputs, output; if (typeof functionSearchType[INPUTS_DATA] === "number") { - inputs = [buildItemSearchType(functionSearchType[INPUTS_DATA], lowercasePaths)]; + inputs = [buildItemSearchType(functionSearchType[INPUTS_DATA], lowercasePaths, paths)]; } else { inputs = buildItemSearchTypeAll( functionSearchType[INPUTS_DATA], - lowercasePaths + lowercasePaths, + paths ); } if (functionSearchType.length > 1) { if (typeof functionSearchType[OUTPUT_DATA] === "number") { - output = [buildItemSearchType(functionSearchType[OUTPUT_DATA], lowercasePaths)]; + output = [buildItemSearchType( + functionSearchType[OUTPUT_DATA], + lowercasePaths, + paths + )]; } else { output = buildItemSearchTypeAll( functionSearchType[OUTPUT_DATA], - lowercasePaths + lowercasePaths, + paths ); } } else { @@ -2671,8 +2979,8 @@ ${item.displayPath}${name}\ const l = functionSearchType.length; for (let i = 2; i < l; ++i) { where_clause.push(typeof functionSearchType[i] === "number" - ? [buildItemSearchType(functionSearchType[i], lowercasePaths)] - : buildItemSearchTypeAll(functionSearchType[i], lowercasePaths)); + ? [buildItemSearchType(functionSearchType[i], lowercasePaths, paths)] + : buildItemSearchTypeAll(functionSearchType[i], lowercasePaths, paths)); } return { inputs, output, where_clause, @@ -2697,6 +3005,8 @@ ${item.displayPath}${name}\ typeNameIdOfArray = buildTypeMapIndex("array"); typeNameIdOfSlice = buildTypeMapIndex("slice"); typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]"); + typeNameIdOfNever = buildTypeMapIndex("never"); + typeNameIdOfTuple = buildTypeMapIndex("tuple"); for (const crate in rawSearchIndex) { if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) { @@ -2856,7 +3166,8 @@ ${item.displayPath}${name}\ parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined, type: buildFunctionSearchType( itemFunctionSearchTypes[i], - lowercasePaths + lowercasePaths, + paths ), id: id, normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""), diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js index c7e6dd3615e94..9d979067d6a63 100644 --- a/src/tools/rustdoc-js/tester.js +++ b/src/tools/rustdoc-js/tester.js @@ -373,8 +373,35 @@ function loadSearchJS(doc_folder, resource_suffix) { return { doSearch: function(queryStr, filterCrate, currentCrate) { - return searchModule.execQuery(searchModule.parseQuery(queryStr), searchWords, + const results = searchModule.execQuery(searchModule.parseQuery(queryStr), searchWords, filterCrate, currentCrate); + for (const key in results) { + if (results[key]) { + for (const resultKey in results[key]) { + if (!Object.prototype.hasOwnProperty.call(results[key], resultKey)) { + continue; + } + const entry = results[key][resultKey]; + if (!entry) { + continue; + } + if (Object.prototype.hasOwnProperty.call(entry, "displayTypeSignature") && + entry.displayTypeSignature !== null && + entry.displayTypeSignature instanceof Array + ) { + entry.displayTypeSignature.forEach((value, index) => { + if (index % 2 === 1) { + entry.displayTypeSignature[index] = "*" + value + "*"; + } else { + entry.displayTypeSignature[index] = value; + } + }); + entry.displayTypeSignature = entry.displayTypeSignature.join(""); + } + } + } + } + return results; }, getCorrections: function(queryStr, filterCrate, currentCrate) { const parsedQuery = searchModule.parseQuery(queryStr); diff --git a/tests/rustdoc-js/generics-impl.js b/tests/rustdoc-js/generics-impl.js index 5e33e224876fe..1e9d57dcf6e85 100644 --- a/tests/rustdoc-js/generics-impl.js +++ b/tests/rustdoc-js/generics-impl.js @@ -4,33 +4,61 @@ const EXPECTED = [ { 'query': 'Aaaaaaa -> u32', 'others': [ - { 'path': 'generics_impl::Aaaaaaa', 'name': 'bbbbbbb' }, + { + 'path': 'generics_impl::Aaaaaaa', + 'name': 'bbbbbbb', + 'displayTypeSignature': '*Aaaaaaa* -> *u32*' + }, ], }, { 'query': 'Aaaaaaa -> bool', 'others': [ - { 'path': 'generics_impl::Aaaaaaa', 'name': 'ccccccc' }, + { + 'path': 'generics_impl::Aaaaaaa', + 'name': 'ccccccc', + 'displayTypeSignature': '*Aaaaaaa* -> *bool*' + }, ], }, { 'query': 'Aaaaaaa -> usize', 'others': [ - { 'path': 'generics_impl::Aaaaaaa', 'name': 'read' }, + { + 'path': 'generics_impl::Aaaaaaa', + 'name': 'read', + 'displayTypeSignature': '*Aaaaaaa*, [] -> Result<*usize*>' + }, ], }, { 'query': 'Read -> u64', 'others': [ - { 'path': 'generics_impl::Ddddddd', 'name': 'eeeeeee' }, - { 'path': 'generics_impl::Ddddddd', 'name': 'ggggggg' }, + { + 'path': 'generics_impl::Ddddddd', + 'name': 'eeeeeee', + 'displayTypeSignature': 'impl *Read* -> *u64*' + }, + { + 'path': 'generics_impl::Ddddddd', + 'name': 'ggggggg', + 'displayTypeSignature': 'Ddddddd -> *u64*' + }, ], }, { 'query': 'trait:Read -> u64', 'others': [ - { 'path': 'generics_impl::Ddddddd', 'name': 'eeeeeee' }, - { 'path': 'generics_impl::Ddddddd', 'name': 'ggggggg' }, + { + 'path': 'generics_impl::Ddddddd', + 'name': 'eeeeeee', + 'displayTypeSignature': 'impl *Read* -> *u64*' + }, + { + 'path': 'generics_impl::Ddddddd', + 'name': 'ggggggg', + 'displayTypeSignature': 'Ddddddd -> *u64*' + }, ], }, { @@ -40,19 +68,31 @@ const EXPECTED = [ { 'query': 'bool -> u64', 'others': [ - { 'path': 'generics_impl::Ddddddd', 'name': 'fffffff' }, + { + 'path': 'generics_impl::Ddddddd', + 'name': 'fffffff', + 'displayTypeSignature': '*bool* -> *u64*' + }, ], }, { 'query': 'Ddddddd -> u64', 'others': [ - { 'path': 'generics_impl::Ddddddd', 'name': 'ggggggg' }, + { + 'path': 'generics_impl::Ddddddd', + 'name': 'ggggggg', + 'displayTypeSignature': '*Ddddddd* -> *u64*' + }, ], }, { 'query': '-> Ddddddd', 'others': [ - { 'path': 'generics_impl::Ddddddd', 'name': 'hhhhhhh' }, + { + 'path': 'generics_impl::Ddddddd', + 'name': 'hhhhhhh', + 'displayTypeSignature': '-> *Ddddddd*' + }, ], }, ]; diff --git a/tests/rustdoc-js/generics-match-ambiguity.js b/tests/rustdoc-js/generics-match-ambiguity.js index edce4268c5ac7..247e66697d5d9 100644 --- a/tests/rustdoc-js/generics-match-ambiguity.js +++ b/tests/rustdoc-js/generics-match-ambiguity.js @@ -8,15 +8,31 @@ const EXPECTED = [ { 'query': 'Wrap', 'in_args': [ - { 'path': 'generics_match_ambiguity', 'name': 'bar' }, - { 'path': 'generics_match_ambiguity', 'name': 'foo' }, + { + 'path': 'generics_match_ambiguity', + 'name': 'bar', + 'displayTypeSignature': '*Wrap*, Wrap' + }, + { + 'path': 'generics_match_ambiguity', + 'name': 'foo', + 'displayTypeSignature': '*Wrap*, Wrap' + }, ], }, { 'query': 'Wrap', 'in_args': [ - { 'path': 'generics_match_ambiguity', 'name': 'bar' }, - { 'path': 'generics_match_ambiguity', 'name': 'foo' }, + { + 'path': 'generics_match_ambiguity', + 'name': 'bar', + 'displayTypeSignature': '*Wrap*<*i32*, u32>, Wrap' + }, + { + 'path': 'generics_match_ambiguity', + 'name': 'foo', + 'displayTypeSignature': '*Wrap*<*i32*>, Wrap' + }, ], }, { diff --git a/tests/rustdoc-js/type-parameters.js b/tests/rustdoc-js/type-parameters.js index e695f189bb672..e637cb4fb254e 100644 --- a/tests/rustdoc-js/type-parameters.js +++ b/tests/rustdoc-js/type-parameters.js @@ -5,79 +5,79 @@ const EXPECTED = [ { query: '-> trait:Some', others: [ - { path: 'foo', name: 'alef' }, - { path: 'foo', name: 'alpha' }, + { path: 'foo', name: 'alef', displayTypeSignature: '-> impl *Some*' }, + { path: 'foo', name: 'alpha', displayTypeSignature: '-> impl *Some*' }, ], }, { query: '-> generic:T', others: [ - { path: 'foo', name: 'bet' }, - { path: 'foo', name: 'alef' }, - { path: 'foo', name: 'beta' }, + { path: 'foo', name: 'bet', displayTypeSignature: '_ -> *T*' }, + { path: 'foo', name: 'alef', displayTypeSignature: '-> *T*' }, + { path: 'foo', name: 'beta', displayTypeSignature: 'T -> *T*' }, ], }, { query: 'A -> B', others: [ - { path: 'foo', name: 'bet' }, + { path: 'foo', name: 'bet', displayTypeSignature: '*A* -> *B*' }, ], }, { query: 'A -> A', others: [ - { path: 'foo', name: 'beta' }, + { path: 'foo', name: 'beta', displayTypeSignature: '*A* -> *A*' }, ], }, { query: 'A, A', others: [ - { path: 'foo', name: 'alternate' }, + { path: 'foo', name: 'alternate', displayTypeSignature: '*A*, *A*' }, ], }, { query: 'A, B', others: [ - { path: 'foo', name: 'other' }, + { path: 'foo', name: 'other', displayTypeSignature: '*A*, *B*' }, ], }, { query: 'Other, Other', others: [ - { path: 'foo', name: 'other' }, - { path: 'foo', name: 'alternate' }, + { path: 'foo', name: 'other', displayTypeSignature: 'impl *Other*, impl *Other*' }, + { path: 'foo', name: 'alternate', displayTypeSignature: 'impl *Other*, impl *Other*' }, ], }, { query: 'generic:T', in_args: [ - { path: 'foo', name: 'bet' }, - { path: 'foo', name: 'beta' }, - { path: 'foo', name: 'other' }, - { path: 'foo', name: 'alternate' }, + { path: 'foo', name: 'bet', displayTypeSignature: '*T* -> _' }, + { path: 'foo', name: 'beta', displayTypeSignature: '*T* -> T' }, + { path: 'foo', name: 'other', displayTypeSignature: '*T*, _' }, + { path: 'foo', name: 'alternate', displayTypeSignature: '*T*, T' }, ], }, { query: 'generic:Other', in_args: [ - { path: 'foo', name: 'bet' }, - { path: 'foo', name: 'beta' }, - { path: 'foo', name: 'other' }, - { path: 'foo', name: 'alternate' }, + { path: 'foo', name: 'bet', displayTypeSignature: '*Other* -> _' }, + { path: 'foo', name: 'beta', displayTypeSignature: '*Other* -> Other' }, + { path: 'foo', name: 'other', displayTypeSignature: '*Other*, _' }, + { path: 'foo', name: 'alternate', displayTypeSignature: '*Other*, Other' }, ], }, { query: 'trait:Other', in_args: [ - { path: 'foo', name: 'other' }, - { path: 'foo', name: 'alternate' }, + { path: 'foo', name: 'other', displayTypeSignature: '_, impl *Other*' }, + { path: 'foo', name: 'alternate', displayTypeSignature: 'impl *Other*, impl *Other*' }, ], }, { query: 'Other', in_args: [ - { path: 'foo', name: 'other' }, - { path: 'foo', name: 'alternate' }, + { path: 'foo', name: 'other', displayTypeSignature: '_, impl *Other*' }, + { path: 'foo', name: 'alternate', displayTypeSignature: 'impl *Other*, impl *Other*' }, ], }, {