|null
*/
const mgens = mgensIn === null ? null : new Map(mgensIn);
if (queryElems.length === 0) {
- return !solutionCb || solutionCb(mgens);
+ return (!solutionCb || solutionCb(mgens)) ? fnTypesIn : null;
}
if (!fnTypesIn || fnTypesIn.length === 0) {
- return false;
+ return null;
}
const ql = queryElems.length;
const fl = fnTypesIn.length;
@@ -2260,7 +2652,7 @@ class DocSearch {
if (ql === 1 && queryElems[0].generics.length === 0
&& queryElems[0].bindings.size === 0) {
const queryElem = queryElems[0];
- for (const fnType of fnTypesIn) {
+ for (const [i, fnType] of fnTypesIn.entries()) {
if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, mgens)) {
continue;
}
@@ -2272,14 +2664,33 @@ class DocSearch {
const mgensScratch = new Map(mgens);
mgensScratch.set(fnType.id, queryElem.id);
if (!solutionCb || solutionCb(mgensScratch)) {
- return true;
+ const highlighted = [...fnTypesIn];
+ highlighted[i] = Object.assign({
+ highlighted: true,
+ }, fnType, {
+ generics: whereClause[-1 - fnType.id],
+ });
+ return highlighted;
}
} else if (!solutionCb || solutionCb(mgens ? new Map(mgens) : null)) {
// unifyFunctionTypeIsMatchCandidate already checks that ids match
- return true;
+ const highlighted = [...fnTypesIn];
+ highlighted[i] = Object.assign({
+ highlighted: true,
+ }, fnType, {
+ generics: unifyFunctionTypes(
+ fnType.generics,
+ queryElem.generics,
+ whereClause,
+ mgens ? new Map(mgens) : null,
+ solutionCb,
+ unboxingDepth,
+ ) || fnType.generics,
+ });
+ return highlighted;
}
}
- for (const fnType of fnTypesIn) {
+ for (const [i, fnType] of fnTypesIn.entries()) {
if (!unifyFunctionTypeIsUnboxCandidate(
fnType,
queryElem,
@@ -2296,25 +2707,42 @@ class DocSearch {
}
const mgensScratch = new Map(mgens);
mgensScratch.set(fnType.id, 0);
- if (unifyFunctionTypes(
+ const highlightedGenerics = unifyFunctionTypes(
whereClause[(-fnType.id) - 1],
queryElems,
whereClause,
mgensScratch,
solutionCb,
unboxingDepth + 1,
- )) {
- return true;
+ );
+ if (highlightedGenerics) {
+ const highlighted = [...fnTypesIn];
+ highlighted[i] = Object.assign({
+ highlighted: true,
+ }, fnType, {
+ generics: highlightedGenerics,
+ });
+ return highlighted;
+ }
+ } else {
+ const highlightedGenerics = unifyFunctionTypes(
+ [...Array.from(fnType.bindings.values()).flat(), ...fnType.generics],
+ queryElems,
+ whereClause,
+ mgens ? new Map(mgens) : null,
+ solutionCb,
+ unboxingDepth + 1,
+ );
+ if (highlightedGenerics) {
+ const highlighted = [...fnTypesIn];
+ highlighted[i] = Object.assign({}, fnType, {
+ generics: highlightedGenerics,
+ bindings: new Map([...fnType.bindings.entries()].map(([k, v]) => {
+ return [k, highlightedGenerics.splice(0, v.length)];
+ })),
+ });
+ return highlighted;
}
- } else if (unifyFunctionTypes(
- [...fnType.generics, ...Array.from(fnType.bindings.values()).flat()],
- queryElems,
- whereClause,
- mgens ? new Map(mgens) : null,
- solutionCb,
- unboxingDepth + 1,
- )) {
- return true;
}
}
return false;
@@ -2371,6 +2799,8 @@ class DocSearch {
if (!queryElemsTmp) {
queryElemsTmp = queryElems.slice(0, qlast);
}
+ let unifiedGenerics = [];
+ let unifiedGenericsMgens = null;
const passesUnification = unifyFunctionTypes(
fnTypes,
queryElemsTmp,
@@ -2393,7 +2823,7 @@ class DocSearch {
}
const simplifiedGenerics = solution.simplifiedGenerics;
for (const simplifiedMgens of solution.mgens) {
- const passesUnification = unifyFunctionTypes(
+ unifiedGenerics = unifyFunctionTypes(
simplifiedGenerics,
queryElem.generics,
whereClause,
@@ -2401,7 +2831,8 @@ class DocSearch {
solutionCb,
unboxingDepth,
);
- if (passesUnification) {
+ if (unifiedGenerics) {
+ unifiedGenericsMgens = simplifiedMgens;
return true;
}
}
@@ -2410,7 +2841,23 @@ class DocSearch {
unboxingDepth,
);
if (passesUnification) {
- return true;
+ passesUnification.length = fl;
+ passesUnification[flast] = passesUnification[i];
+ passesUnification[i] = Object.assign({}, fnType, {
+ highlighted: true,
+ generics: unifiedGenerics,
+ bindings: new Map([...fnType.bindings.entries()].map(([k, v]) => {
+ return [k, queryElem.bindings.has(k) ? unifyFunctionTypes(
+ v,
+ queryElem.bindings.get(k),
+ whereClause,
+ unifiedGenericsMgens,
+ solutionCb,
+ unboxingDepth,
+ ) : unifiedGenerics.splice(0, v.length)];
+ })),
+ });
+ return passesUnification;
}
// backtrack
fnTypes[flast] = fnTypes[i];
@@ -2445,7 +2892,7 @@ class DocSearch {
Array.from(fnType.bindings.values()).flat() :
[];
const passesUnification = unifyFunctionTypes(
- fnTypes.toSpliced(i, 1, ...generics, ...bindings),
+ fnTypes.toSpliced(i, 1, ...bindings, ...generics),
queryElems,
whereClause,
mgensScratch,
@@ -2453,10 +2900,24 @@ class DocSearch {
unboxingDepth + 1,
);
if (passesUnification) {
- return true;
+ const highlightedGenerics = passesUnification.slice(
+ i,
+ i + generics.length + bindings.length,
+ );
+ const highlightedFnType = Object.assign({}, fnType, {
+ generics: highlightedGenerics,
+ bindings: new Map([...fnType.bindings.entries()].map(([k, v]) => {
+ return [k, highlightedGenerics.splice(0, v.length)];
+ })),
+ });
+ return passesUnification.toSpliced(
+ i,
+ generics.length + bindings.length,
+ highlightedFnType,
+ );
}
}
- return false;
+ return null;
}
/**
* Check if this function is a match candidate.
@@ -2627,7 +3088,7 @@ class DocSearch {
}
});
if (simplifiedGenerics.length > 0) {
- simplifiedGenerics = [...simplifiedGenerics, ...binds];
+ simplifiedGenerics = [...binds, ...simplifiedGenerics];
} else {
simplifiedGenerics = binds;
}
@@ -3285,10 +3746,11 @@ class DocSearch {
innerRunQuery();
}
+ const isType = parsedQuery.foundElems !== 1 || parsedQuery.hasReturnArrow;
const [sorted_in_args, sorted_returned, sorted_others] = await Promise.all([
- sortResults(results_in_args, true, currentCrate),
- sortResults(results_returned, true, currentCrate),
- sortResults(results_others, false, currentCrate),
+ sortResults(results_in_args, "elems", currentCrate),
+ sortResults(results_returned, "returned", currentCrate),
+ sortResults(results_others, (isType ? "query" : null), currentCrate),
]);
const ret = createQueryResults(
sorted_in_args,
@@ -3315,6 +3777,7 @@ class DocSearch {
}
}
+
// ==================== Core search logic end ====================
let rawSearchIndex;
@@ -3446,15 +3909,18 @@ function focusSearchResult() {
* @param {Array>} array - The search results for this tab
* @param {ParsedQuery} query
* @param {boolean} display - True if this is the active tab
+ * @param {"sig"|"elems"|"returned"|null} typeInfo
*/
async function addTab(array, query, display) {
const extraClass = display ? " active" : "";
- const output = document.createElement("div");
+ const output = document.createElement(
+ array.length === 0 && query.error === null ? "div" : "ul",
+ );
if (array.length > 0) {
output.className = "search-results " + extraClass;
- for (const item of array) {
+ const lis = Promise.all(array.map(async item => {
const name = item.name;
const type = itemTypes[item.ty];
const longType = longItemTypes[item.ty];
@@ -3464,7 +3930,7 @@ async function addTab(array, query, display) {
link.className = "result-" + type;
link.href = item.href;
- const resultName = document.createElement("div");
+ const resultName = document.createElement("span");
resultName.className = "result-name";
resultName.insertAdjacentHTML(
@@ -3487,10 +3953,73 @@ ${item.displayPath}${name}\
const description = document.createElement("div");
description.className = "desc";
description.insertAdjacentHTML("beforeend", item.desc);
+ if (item.displayTypeSignature) {
+ const {type, mappedNames, whereClause} = await item.displayTypeSignature;
+ const displayType = document.createElement("div");
+ type.forEach((value, index) => {
+ if (index % 2 !== 0) {
+ const highlight = document.createElement("strong");
+ highlight.appendChild(document.createTextNode(value));
+ displayType.appendChild(highlight);
+ } else {
+ displayType.appendChild(document.createTextNode(value));
+ }
+ });
+ if (mappedNames.size > 0 || whereClause.size > 0) {
+ let addWhereLineFn = () => {
+ const line = document.createElement("div");
+ line.className = "where";
+ line.appendChild(document.createTextNode("where"));
+ displayType.appendChild(line);
+ addWhereLineFn = () => {};
+ };
+ for (const [name, qname] of mappedNames) {
+ // don't care unless the generic name is different
+ if (name === qname) {
+ continue;
+ }
+ addWhereLineFn();
+ const line = document.createElement("div");
+ line.className = "where";
+ line.appendChild(document.createTextNode(` ${qname} matches `));
+ const lineStrong = document.createElement("strong");
+ lineStrong.appendChild(document.createTextNode(name));
+ line.appendChild(lineStrong);
+ displayType.appendChild(line);
+ }
+ for (const [name, innerType] of whereClause) {
+ // don't care unless there's at least one highlighted entry
+ if (innerType.length <= 1) {
+ continue;
+ }
+ addWhereLineFn();
+ const line = document.createElement("div");
+ line.className = "where";
+ line.appendChild(document.createTextNode(` ${name}: `));
+ innerType.forEach((value, index) => {
+ if (index % 2 !== 0) {
+ const highlight = document.createElement("strong");
+ highlight.appendChild(document.createTextNode(value));
+ line.appendChild(highlight);
+ } else {
+ line.appendChild(document.createTextNode(value));
+ }
+ });
+ displayType.appendChild(line);
+ }
+ }
+ displayType.className = "type-signature";
+ link.appendChild(displayType);
+ }
link.appendChild(description);
- output.appendChild(link);
- }
+ return link;
+ }));
+ lis.then(lis => {
+ for (const li of lis) {
+ output.appendChild(li);
+ }
+ });
} else if (query.error === null) {
output.className = "search-failed" + extraClass;
output.innerHTML = "No results :(
" +
@@ -3507,7 +4036,7 @@ ${item.displayPath}${name}\
"href=\"https://docs.rs\">Docs.rs for documentation of crates released on" +
" crates.io.";
}
- return [output, array.length];
+ return output;
}
function makeTabHeader(tabNb, text, nbElems) {
@@ -3564,24 +4093,18 @@ async function showResults(results, go_to_first, filterCrates) {
currentResults = results.query.userQuery;
- const [ret_others, ret_in_args, ret_returned] = await Promise.all([
- addTab(results.others, results.query, true),
- addTab(results.in_args, results.query, false),
- addTab(results.returned, results.query, false),
- ]);
-
// 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
// it again.
let currentTab = searchState.currentTab;
- if ((currentTab === 0 && ret_others[1] === 0) ||
- (currentTab === 1 && ret_in_args[1] === 0) ||
- (currentTab === 2 && ret_returned[1] === 0)) {
- if (ret_others[1] !== 0) {
+ if ((currentTab === 0 && results.others.length === 0) ||
+ (currentTab === 1 && results.in_args.length === 0) ||
+ (currentTab === 2 && results.returned.length === 0)) {
+ if (results.others.length !== 0) {
currentTab = 0;
- } else if (ret_in_args[1] !== 0) {
+ } else if (results.in_args.length) {
currentTab = 1;
- } else if (ret_returned[1] !== 0) {
+ } else if (results.returned.length) {
currentTab = 2;
}
}
@@ -3610,14 +4133,14 @@ async function showResults(results, go_to_first, filterCrates) {
});
output += `Query parser error: "${error.join("")}".
`;
output += "" +
- makeTabHeader(0, "In Names", ret_others[1]) +
+ makeTabHeader(0, "In Names", results.others.length) +
"
";
currentTab = 0;
} else if (results.query.foundElems <= 1 && results.query.returned.length === 0) {
output += "" +
- makeTabHeader(0, "In Names", ret_others[1]) +
- makeTabHeader(1, "In Parameters", ret_in_args[1]) +
- makeTabHeader(2, "In Return Types", ret_returned[1]) +
+ makeTabHeader(0, "In Names", results.others.length) +
+ makeTabHeader(1, "In Parameters", results.in_args.length) +
+ makeTabHeader(2, "In Return Types", results.returned.length) +
"
";
} else {
const signatureTabTitle =
@@ -3625,7 +4148,7 @@ async function showResults(results, go_to_first, filterCrates) {
results.query.returned.length === 0 ? "In Function Parameters" :
"In Function Signatures";
output += "" +
- makeTabHeader(0, signatureTabTitle, ret_others[1]) +
+ makeTabHeader(0, signatureTabTitle, results.others.length) +
"
";
currentTab = 0;
}
@@ -3647,11 +4170,17 @@ async function showResults(results, go_to_first, filterCrates) {
`Consider searching for "${targ}" instead.`;
}
+ const [ret_others, ret_in_args, ret_returned] = await Promise.all([
+ addTab(results.others, results.query, currentTab === 0),
+ addTab(results.in_args, results.query, currentTab === 1),
+ addTab(results.returned, results.query, currentTab === 2),
+ ]);
+
const resultsElem = document.createElement("div");
resultsElem.id = "results";
- resultsElem.appendChild(ret_others[0]);
- resultsElem.appendChild(ret_in_args[0]);
- resultsElem.appendChild(ret_returned[0]);
+ resultsElem.appendChild(ret_others);
+ resultsElem.appendChild(ret_in_args);
+ resultsElem.appendChild(ret_returned);
search.innerHTML = output;
if (searchState.rustdocToolbar) {
@@ -3933,4 +4462,3 @@ if (typeof window !== "undefined") {
// exports.
initSearch(new Map());
}
-})();
diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js
index 63cda4111e6..7aa5e102e6d 100644
--- a/src/tools/rustdoc-js/tester.js
+++ b/src/tools/rustdoc-js/tester.js
@@ -2,6 +2,14 @@
const fs = require("fs");
const path = require("path");
+
+function arrayToCode(array) {
+ return array.map((value, index) => {
+ value = value.split(" ").join(" ");
+ return (index % 2 === 1) ? ("`" + value + "`") : value;
+ }).join("");
+}
+
function loadContent(content) {
const Module = module.constructor;
const m = new Module();
@@ -180,15 +188,7 @@ function valueCheck(fullPath, expected, result, error_text, queryName) {
if (!result_v.forEach) {
throw result_v;
}
- result_v.forEach((value, index) => {
- value = value.split(" ").join(" ");
- if (index % 2 === 1) {
- result_v[index] = "`" + value + "`";
- } else {
- result_v[index] = value;
- }
- });
- result_v = result_v.join("");
+ result_v = arrayToCode(result_v);
}
const obj_path = fullPath + (fullPath.length > 0 ? "." : "") + key;
valueCheck(obj_path, expected[key], result_v, error_text, queryName);
@@ -436,9 +436,41 @@ function loadSearchJS(doc_folder, resource_suffix) {
searchModule.initSearch(searchIndex.searchIndex);
const docSearch = searchModule.docSearch;
return {
- doSearch: function(queryStr, filterCrate, currentCrate) {
- return docSearch.execQuery(searchModule.parseQuery(queryStr),
+ doSearch: async function(queryStr, filterCrate, currentCrate) {
+ const result = await docSearch.execQuery(searchModule.parseQuery(queryStr),
filterCrate, currentCrate);
+ for (const tab in result) {
+ if (!Object.prototype.hasOwnProperty.call(result, tab)) {
+ continue;
+ }
+ if (!(result[tab] instanceof Array)) {
+ continue;
+ }
+ for (const entry of result[tab]) {
+ for (const key in entry) {
+ if (!Object.prototype.hasOwnProperty.call(entry, key)) {
+ continue;
+ }
+ if (key === "displayTypeSignature") {
+ const {type, mappedNames, whereClause} =
+ await entry.displayTypeSignature;
+ entry.displayType = arrayToCode(type);
+ entry.displayMappedNames = [...mappedNames.entries()]
+ .map(([name, qname]) => {
+ return `${name} = ${qname}`;
+ }).join(", ");
+ entry.displayWhereClause = [...whereClause.entries()]
+ .flatMap(([name, value]) => {
+ if (value.length === 0) {
+ return [];
+ }
+ return [`${name}: ${arrayToCode(value)}`];
+ }).join(", ");
+ }
+ }
+ }
+ }
+ return result;
},
getCorrections: function(queryStr, filterCrate, currentCrate) {
const parsedQuery = searchModule.parseQuery(queryStr);
diff --git a/tests/rustdoc-gui/search-about-this-result.goml b/tests/rustdoc-gui/search-about-this-result.goml
new file mode 100644
index 00000000000..62780d01ed7
--- /dev/null
+++ b/tests/rustdoc-gui/search-about-this-result.goml
@@ -0,0 +1,42 @@
+// Check the "About this Result" popover.
+// Try a complex result.
+go-to: "file://" + |DOC_PATH| + "/lib2/index.html?search=scroll_traits::Iterator,(T->bool)->(Extend,Extend)"
+
+// These two commands are used to be sure the search will be run.
+focus: ".search-input"
+press-key: "Enter"
+
+wait-for: "#search-tabs"
+assert-count: ("#search-tabs button", 1)
+assert-count: (".search-results > a", 1)
+
+assert: "//div[@class='type-signature']/strong[text()='Iterator']"
+assert: "//div[@class='type-signature']/strong[text()='(']"
+assert: "//div[@class='type-signature']/strong[text()=')']"
+
+assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='FnMut']"
+assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='Iterator::Item']"
+assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='bool']"
+assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='Extend']"
+
+assert-text: ("div.type-signature div.where:nth-child(4)", "where")
+assert-text: ("div.type-signature div.where:nth-child(5)", " T matches Iterator::Item")
+assert-text: ("div.type-signature div.where:nth-child(6)", " F: FnMut (&Iterator::Item) -> bool")
+assert-text: ("div.type-signature div.where:nth-child(7)", " B: Default + Extend")
+
+// Try a simple result that *won't* give an info box.
+go-to: "file://" + |DOC_PATH| + "/lib2/index.html?search=F->lib2::WhereWhitespace"
+
+// These two commands are used to be sure the search will be run.
+focus: ".search-input"
+press-key: "Enter"
+
+wait-for: "#search-tabs"
+assert-text: ("//div[@class='type-signature']", "F -> WhereWhitespace")
+assert-count: ("#search-tabs button", 1)
+assert-count: (".search-results > a", 1)
+assert-count: ("//div[@class='type-signature']/div[@class='where']", 0)
+
+assert: "//div[@class='type-signature']/strong[text()='F']"
+assert: "//div[@class='type-signature']/strong[text()='WhereWhitespace']"
+assert: "//div[@class='type-signature']/strong[text()='T']"
diff --git a/tests/rustdoc-js-std/option-type-signatures.js b/tests/rustdoc-js-std/option-type-signatures.js
index e154fa707ab..1690d5dc8b5 100644
--- a/tests/rustdoc-js-std/option-type-signatures.js
+++ b/tests/rustdoc-js-std/option-type-signatures.js
@@ -6,79 +6,217 @@ const EXPECTED = [
{
'query': 'option, fnonce -> option',
'others': [
- { 'path': 'std::option::Option', 'name': 'map' },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'map',
+ 'displayType': '`Option`, F -> `Option`',
+ 'displayWhereClause': "F: `FnOnce` (T) -> U",
+ },
+ ],
+ },
+ {
+ 'query': 'option, fnonce -> option',
+ 'others': [
+ {
+ 'path': 'std::option::Option',
+ 'name': 'map',
+ 'displayType': '`Option`<`T`>, F -> `Option`',
+ 'displayWhereClause': "F: `FnOnce` (T) -> U",
+ },
],
},
{
'query': 'option -> default',
'others': [
- { 'path': 'std::option::Option', 'name': 'unwrap_or_default' },
- { 'path': 'std::option::Option', 'name': 'get_or_insert_default' },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'unwrap_or_default',
+ 'displayType': '`Option` -> `T`',
+ 'displayWhereClause': "T: `Default`",
+ },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'get_or_insert_default',
+ 'displayType': '&mut `Option` -> &mut `T`',
+ 'displayWhereClause': "T: `Default`",
+ },
],
},
{
'query': 'option -> []',
'others': [
- { 'path': 'std::option::Option', 'name': 'as_slice' },
- { 'path': 'std::option::Option', 'name': 'as_mut_slice' },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'as_slice',
+ 'displayType': '&`Option` -> &`[`T`]`',
+ },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'as_mut_slice',
+ 'displayType': '&mut `Option` -> &mut `[`T`]`',
+ },
],
},
{
'query': 'option, option -> option',
'others': [
- { 'path': 'std::option::Option', 'name': 'or' },
- { 'path': 'std::option::Option', 'name': 'xor' },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'or',
+ 'displayType': '`Option`<`T`>, `Option`<`T`> -> `Option`<`T`>',
+ },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'xor',
+ 'displayType': '`Option`<`T`>, `Option`<`T`> -> `Option`<`T`>',
+ },
],
},
{
'query': 'option, option -> option',
'others': [
- { 'path': 'std::option::Option', 'name': 'and' },
- { 'path': 'std::option::Option', 'name': 'zip' },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'and',
+ 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<`U`>',
+ },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'zip',
+ 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<(T, `U`)>',
+ },
],
},
{
'query': 'option, option -> option',
'others': [
- { 'path': 'std::option::Option', 'name': 'and' },
- { 'path': 'std::option::Option', 'name': 'zip' },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'and',
+ 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<`U`>',
+ },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'zip',
+ 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<(`T`, U)>',
+ },
],
},
{
'query': 'option, option -> option',
'others': [
- { 'path': 'std::option::Option', 'name': 'zip' },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'zip',
+ 'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<(`T`, `U`)>',
+ },
],
},
{
'query': 'option, e -> result',
'others': [
- { 'path': 'std::option::Option', 'name': 'ok_or' },
- { 'path': 'std::result::Result', 'name': 'transpose' },
+ {
+ 'path': 'std::option::Option',
+ 'name': 'ok_or',
+ 'displayType': '`Option`<`T`>, `E` -> `Result`<`T`, `E`>',
+ },
+ {
+ 'path': 'std::result::Result',
+ 'name': 'transpose',
+ 'displayType': 'Result<`Option`<`T`>, `E`> -> Option<`Result`<`T`, `E`>>',
+ },
],
},
{
'query': 'result