rustdoc-search: show types signatures in results

This commit is contained in:
Michael Howell
2024-09-24 14:31:44 -07:00
parent 5973005d93
commit 20a4b4fea1
10 changed files with 995 additions and 126 deletions
+16 -5
View File
@@ -264,6 +264,7 @@ a.anchor,
.mobile-topbar h2 a,
h1 a,
.search-results a,
.search-results li,
.stab,
.result-name i {
color: var(--main-color);
@@ -379,7 +380,7 @@ details:not(.toggle) summary {
margin-bottom: .6em;
}
code, pre, .code-header {
code, pre, .code-header, .type-signature {
font-family: "Source Code Pro", monospace;
}
.docblock code, .docblock-short code {
@@ -1205,22 +1206,28 @@ so that we can apply CSS-filters to change the arrow color in themes */
.search-results.active {
display: block;
margin: 0;
padding: 0;
}
.search-results > a {
display: flex;
display: grid;
grid-template-areas:
"search-result-name search-result-desc"
"search-result-type-signature search-result-type-signature";
grid-template-columns: .6fr .4fr;
/* A little margin ensures the browser's outlining of focused links has room to display. */
margin-left: 2px;
margin-right: 2px;
border-bottom: 1px solid var(--search-result-border-color);
gap: 1em;
column-gap: 1em;
}
.search-results > a > div.desc {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
flex: 2;
grid-area: search-result-desc;
}
.search-results a:hover,
@@ -1232,7 +1239,7 @@ so that we can apply CSS-filters to change the arrow color in themes */
display: flex;
align-items: center;
justify-content: start;
flex: 3;
grid-area: search-result-name;
}
.search-results .result-name .alias {
color: var(--search-results-alias-color);
@@ -1253,6 +1260,10 @@ so that we can apply CSS-filters to change the arrow color in themes */
.search-results .result-name .path > * {
display: inline;
}
.search-results .type-signature {
grid-area: search-result-type-signature;
white-space: pre-wrap;
}
.popover {
position: absolute;
+3
View File
@@ -92,6 +92,9 @@ let Results;
* parent: (Object|undefined),
* path: string,
* ty: number,
* type: FunctionSearchType?,
* displayType: Promise<Array<Array<string>>>|null,
* displayTypeMappedNames: Promise<Array<[string, Array<string>]>>|null,
* }}
*/
let ResultObject;
+608 -80
View File
@@ -15,7 +15,16 @@ if (!Array.prototype.toSpliced) {
};
}
(function() {
function onEachBtwn(arr, func, funcBtwn) {
let skipped = true;
for (const value of arr) {
if (!skipped) {
funcBtwn(value);
}
skipped = func(value);
}
}
// ==================== Core search logic begin ====================
// This mapping table should match the discriminants of
// `rustdoc::formats::item_type::ItemType` type in Rust.
@@ -50,8 +59,10 @@ const itemTypes = [
];
// used for special search precedence
const TY_PRIMITIVE = itemTypes.indexOf("primitive");
const TY_GENERIC = itemTypes.indexOf("generic");
const TY_IMPORT = itemTypes.indexOf("import");
const TY_TRAIT = itemTypes.indexOf("trait");
const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
// Hard limit on how deep to recurse into generics when doing type-driven search.
@@ -1117,6 +1128,13 @@ class DocSearch {
* @type {Map<string, {id: integer, assocOnly: boolean}>}
*/
this.typeNameIdMap = new Map();
/**
* Map from type ID to associated type name. Used for display,
* not for search.
*
* @type {Map<integer, string>}
*/
this.assocTypeIdNameMap = new Map();
this.ALIASES = new Map();
this.rootPath = rootPath;
this.searchState = searchState;
@@ -1161,6 +1179,14 @@ class DocSearch {
* Special type name IDs for searching higher order functions (`->` syntax).
*/
this.typeNameIdOfHof = this.buildTypeMapIndex("->");
/**
* Special type name IDs the output assoc type.
*/
this.typeNameIdOfOutput = this.buildTypeMapIndex("output", true);
/**
* Special type name IDs for searching by reference.
*/
this.typeNameIdOfReference = this.buildTypeMapIndex("reference");
/**
* Empty, immutable map used in item search types with no bindings.
@@ -1237,9 +1263,9 @@ class DocSearch {
*
* @return {Array<FunctionSearchType>}
*/
buildItemSearchTypeAll(types, lowercasePaths) {
buildItemSearchTypeAll(types, paths, lowercasePaths) {
return types.length > 0 ?
types.map(type => this.buildItemSearchType(type, lowercasePaths)) :
types.map(type => this.buildItemSearchType(type, paths, lowercasePaths)) :
this.EMPTY_GENERICS_ARRAY;
}
@@ -1248,7 +1274,7 @@ class DocSearch {
*
* @param {RawFunctionType} type
*/
buildItemSearchType(type, lowercasePaths, isAssocType) {
buildItemSearchType(type, paths, lowercasePaths, isAssocType) {
const PATH_INDEX_DATA = 0;
const GENERICS_DATA = 1;
const BINDINGS_DATA = 2;
@@ -1261,6 +1287,7 @@ class DocSearch {
pathIndex = type[PATH_INDEX_DATA];
generics = this.buildItemSearchTypeAll(
type[GENERICS_DATA],
paths,
lowercasePaths,
);
if (type.length > BINDINGS_DATA && type[BINDINGS_DATA].length > 0) {
@@ -1277,8 +1304,8 @@ class DocSearch {
//
// As a result, the key should never have generics on it.
return [
this.buildItemSearchType(assocType, lowercasePaths, true).id,
this.buildItemSearchTypeAll(constraints, lowercasePaths),
this.buildItemSearchType(assocType, paths, lowercasePaths, true).id,
this.buildItemSearchTypeAll(constraints, paths, lowercasePaths),
];
}));
} else {
@@ -1294,6 +1321,7 @@ class DocSearch {
// the actual names of generic parameters aren't stored, since they aren't API
result = {
id: pathIndex,
name: "",
ty: TY_GENERIC,
path: null,
exactPath: null,
@@ -1304,6 +1332,7 @@ class DocSearch {
// `0` is used as a sentinel because it's fewer bytes than `null`
result = {
id: null,
name: "",
ty: null,
path: null,
exactPath: null,
@@ -1312,8 +1341,13 @@ class DocSearch {
};
} else {
const item = lowercasePaths[pathIndex - 1];
const id = this.buildTypeMapIndex(item.name, isAssocType);
if (isAssocType) {
this.assocTypeIdNameMap.set(id, paths[pathIndex - 1].name);
}
result = {
id: this.buildTypeMapIndex(item.name, isAssocType),
id,
name: paths[pathIndex - 1].name,
ty: item.ty,
path: item.path,
exactPath: item.exactPath,
@@ -1355,7 +1389,7 @@ class DocSearch {
}
if (cr.ty === result.ty && cr.path === result.path
&& cr.bindings === result.bindings && cr.generics === result.generics
&& cr.ty === result.ty
&& cr.ty === result.ty && cr.name === result.name
) {
return cr;
}
@@ -1466,11 +1500,12 @@ class DocSearch {
* The raw function search type format is generated using serde in
* librustdoc/html/render/mod.rs: IndexItemFunctionType::write_to_string
*
* @param {Array<{name: string, ty: number}>} paths
* @param {Array<{name: string, ty: number}>} lowercasePaths
*
* @return {null|FunctionSearchType}
*/
const buildFunctionSearchTypeCallback = lowercasePaths => {
const buildFunctionSearchTypeCallback = (paths, lowercasePaths) => {
return functionSearchType => {
if (functionSearchType === 0) {
return null;
@@ -1480,11 +1515,16 @@ class DocSearch {
let inputs, output;
if (typeof functionSearchType[INPUTS_DATA] === "number") {
inputs = [
this.buildItemSearchType(functionSearchType[INPUTS_DATA], lowercasePaths),
this.buildItemSearchType(
functionSearchType[INPUTS_DATA],
paths,
lowercasePaths,
),
];
} else {
inputs = this.buildItemSearchTypeAll(
functionSearchType[INPUTS_DATA],
paths,
lowercasePaths,
);
}
@@ -1493,12 +1533,14 @@ class DocSearch {
output = [
this.buildItemSearchType(
functionSearchType[OUTPUT_DATA],
paths,
lowercasePaths,
),
];
} else {
output = this.buildItemSearchTypeAll(
functionSearchType[OUTPUT_DATA],
paths,
lowercasePaths,
);
}
@@ -1509,8 +1551,12 @@ class DocSearch {
const l = functionSearchType.length;
for (let i = 2; i < l; ++i) {
where_clause.push(typeof functionSearchType[i] === "number"
? [this.buildItemSearchType(functionSearchType[i], lowercasePaths)]
: this.buildItemSearchTypeAll(functionSearchType[i], lowercasePaths));
? [this.buildItemSearchType(functionSearchType[i], paths, lowercasePaths)]
: this.buildItemSearchTypeAll(
functionSearchType[i],
paths,
lowercasePaths,
));
}
return {
inputs, output, where_clause,
@@ -1554,6 +1600,13 @@ class DocSearch {
this.searchIndexEmptyDesc.set(crate, new RoaringBitmap(crateCorpus.e));
let descIndex = 0;
/**
* List of generic function type parameter names.
* Used for display, not for searching.
* @type {[string]}
*/
let lastParamNames = [];
// This object should have exactly the same set of fields as the "row"
// object defined below. Your JavaScript runtime will thank you.
// https://mathiasbynens.be/notes/shapes-ics
@@ -1568,6 +1621,7 @@ class DocSearch {
desc: crateCorpus.doc,
parent: undefined,
type: null,
paramNames: lastParamNames,
id,
word: crate,
normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""),
@@ -1604,6 +1658,10 @@ class DocSearch {
// an array of [(String) alias name
// [Number] index to items]
const aliases = crateCorpus.a;
// an array of [(Number) item index,
// (String) comma-separated list of function generic param names]
// an item whose index is not present will fall back to the previous present path
const itemParamNames = new Map(crateCorpus.P);
// an array of [{name: String, ty: Number}]
const lowercasePaths = [];
@@ -1611,7 +1669,7 @@ class DocSearch {
// a string representing the list of function types
const itemFunctionDecoder = new VlqHexDecoder(
crateCorpus.f,
buildFunctionSearchTypeCallback(lowercasePaths),
buildFunctionSearchTypeCallback(paths, lowercasePaths),
);
// convert `rawPaths` entries into object form
@@ -1662,6 +1720,9 @@ class DocSearch {
const name = itemNames[i] === "" ? lastName : itemNames[i];
const word = itemNames[i] === "" ? lastWord : itemNames[i].toLowerCase();
const path = itemPaths.has(i) ? itemPaths.get(i) : lastPath;
const paramNames = itemParamNames.has(i) ?
itemParamNames.get(i).split(",") :
lastParamNames;
const type = itemFunctionDecoder.next();
if (type !== null) {
if (type) {
@@ -1694,6 +1755,7 @@ class DocSearch {
itemPaths.get(itemReexports.get(i)) : path,
parent: itemParentIdx > 0 ? paths[itemParentIdx - 1] : undefined,
type,
paramNames,
id,
word,
normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
@@ -1704,6 +1766,7 @@ class DocSearch {
id += 1;
searchIndex.push(row);
lastPath = row.path;
lastParamNames = row.paramNames;
if (!this.searchIndexEmptyDesc.get(crate).contains(bitIndex)) {
descIndex += 1;
}
@@ -2048,18 +2111,21 @@ class DocSearch {
* marked for removal.
*
* @param {[ResultObject]} results
* @param {"sig"|"elems"|"returned"|null} typeInfo
* @param {ParsedQuery} query
* @returns {[ResultObject]}
*/
const transformResults = results => {
const transformResults = (results, typeInfo) => {
const duplicates = new Set();
const out = [];
for (const result of results) {
if (result.id !== -1) {
const obj = this.searchIndex[result.id];
obj.dist = result.dist;
const res = buildHrefAndPath(obj);
obj.displayPath = pathSplitter(res[0]);
const res = buildHrefAndPath(this.searchIndex[result.id]);
const obj = Object.assign({
dist: result.dist,
displayPath: pathSplitter(res[0]),
}, this.searchIndex[result.id]);
// To be sure than it some items aren't considered as duplicate.
obj.fullPath = res[2] + "|" + obj.ty;
@@ -2078,6 +2144,11 @@ class DocSearch {
duplicates.add(obj.fullPath);
duplicates.add(res[2]);
if (typeInfo !== null) {
obj.displayTypeSignature =
this.formatDisplayTypeSignature(obj, typeInfo);
}
obj.href = res[1];
out.push(obj);
if (out.length >= MAX_RESULTS) {
@@ -2088,6 +2159,327 @@ class DocSearch {
return out;
};
/**
* Add extra data to result objects, and filter items that have been
* marked for removal.
*
* The output is formatted as an array of hunks, where odd numbered
* hunks are highlighted and even numbered ones are not.
*
* @param {ResultObject} obj
* @param {"sig"|"elems"|"returned"|null} typeInfo
* @param {ParsedQuery} query
* @returns Promise<
* "type": Array<string>,
* "mappedNames": Map<string, string>,
* "whereClause": Map<string, Array<string>>,
* >
*/
this.formatDisplayTypeSignature = async(obj, typeInfo) => {
let fnInputs = null;
let fnOutput = null;
let mgens = null;
if (typeInfo !== "elems" && typeInfo !== "returned") {
fnInputs = unifyFunctionTypes(
obj.type.inputs,
parsedQuery.elems,
obj.type.where_clause,
null,
mgensScratch => {
fnOutput = unifyFunctionTypes(
obj.type.output,
parsedQuery.returned,
obj.type.where_clause,
mgensScratch,
mgensOut => {
mgens = mgensOut;
return true;
},
0,
);
return !!fnOutput;
},
0,
);
} else {
const arr = typeInfo === "elems" ? obj.type.inputs : obj.type.output;
const highlighted = unifyFunctionTypes(
arr,
parsedQuery.elems,
obj.type.where_clause,
null,
mgensOut => {
mgens = mgensOut;
return true;
},
0,
);
if (typeInfo === "elems") {
fnInputs = highlighted;
} else {
fnOutput = highlighted;
}
}
if (!fnInputs) {
fnInputs = obj.type.inputs;
}
if (!fnOutput) {
fnOutput = obj.type.output;
}
const mappedNames = new Map();
const whereClause = new Map();
const fnParamNames = obj.paramNames;
const queryParamNames = [];
/**
* Recursively writes a map of IDs to query generic names,
* which are later used to map query generic names to function generic names.
* For example, when the user writes `X -> Option<X>` and the function
* is actually written as `T -> Option<T>`, this function stores the
* mapping `(-1, "X")`, and the writeFn function looks up the entry
* for -1 to form the final, user-visible mapping of "X is T".
*
* @param {QueryElement} queryElem
*/
const remapQuery = queryElem => {
if (queryElem.id < 0) {
queryParamNames[-1 - queryElem.id] = queryElem.name;
}
if (queryElem.generics.length > 0) {
queryElem.generics.forEach(remapQuery);
}
if (queryElem.bindings.size > 0) {
[...queryElem.bindings.values()].flat().forEach(remapQuery);
}
};
parsedQuery.elems.forEach(remapQuery);
parsedQuery.returned.forEach(remapQuery);
/**
* Write text to a highlighting array.
* Index 0 is not highlighted, index 1 is highlighted,
* index 2 is not highlighted, etc.
*
* @param {{name: string, highlighted: bool|undefined}} fnType - input
* @param {[string]} result
*/
const pushText = (fnType, result) => {
// If !!(result.length % 2) == false, then pushing a new slot starts an even
// numbered slot. Even numbered slots are not highlighted.
//
// `highlighted` will not be defined if an entire subtree is not highlighted,
// so `!!` is used to coerce it to boolean. `result.length % 2` is used to
// check if the number is even, but it evaluates to a number, so it also
// needs coerced to a boolean.
if (!!(result.length % 2) === !!fnType.highlighted) {
result.push("");
} else if (result.length === 0 && !!fnType.highlighted) {
result.push("");
result.push("");
}
result[result.length - 1] += fnType.name;
};
/**
* Write a higher order function type: either a function pointer
* or a trait bound on Fn, FnMut, or FnOnce.
*
* @param {FunctionType} fnType - input
* @param {[string]} result
*/
const writeHof = (fnType, result) => {
const hofOutput = fnType.bindings.get(this.typeNameIdOfOutput) || [];
const hofInputs = fnType.generics;
pushText(fnType, result);
pushText({name: " (", highlighted: false}, result);
let needsComma = false;
for (const fnType of hofInputs) {
if (needsComma) {
pushText({ name: ", ", highlighted: false }, result);
}
needsComma = true;
writeFn(fnType, result);
}
pushText({
name: hofOutput.length === 0 ? ")" : ") -> ",
highlighted: false,
}, result);
if (hofOutput.length > 1) {
pushText({name: "(", highlighted: false}, result);
}
needsComma = false;
for (const fnType of hofOutput) {
if (needsComma) {
pushText({ name: ", ", highlighted: false }, result);
}
needsComma = true;
writeFn(fnType, result);
}
if (hofOutput.length > 1) {
pushText({name: ")", highlighted: false}, result);
}
};
/**
* Write a primitive type with special syntax, like `!` or `[T]`.
* Returns `false` if the supplied type isn't special.
*
* @param {FunctionType} fnType
* @param {[string]} result
*/
const writeSpecialPrimitive = (fnType, result) => {
if (fnType.id === this.typeNameIdOfArray || fnType.id === this.typeNameIdOfSlice ||
fnType.id === this.typeNameIdOfTuple || fnType.id === this.typeNameIdOfUnit) {
const [ob, sb] =
fnType.id === this.typeNameIdOfArray ||
fnType.id === this.typeNameIdOfSlice ?
["[", "]"] :
["(", ")"];
pushText({ name: ob, highlighted: fnType.highlighted }, result);
onEachBtwn(
fnType.generics,
nested => writeFn(nested, result),
() => pushText({ name: ", ", highlighted: false }, result),
);
pushText({ name: sb, highlighted: fnType.highlighted }, result);
return true;
} else if (fnType.id === this.typeNameIdOfReference) {
pushText({ name: "&", highlighted: fnType.highlighted }, result);
let prevHighlighted = false;
onEachBtwn(
fnType.generics,
value => {
prevHighlighted = value.highlighted;
writeFn(value, result);
},
value => pushText({
name: " ",
highlighted: prevHighlighted && value.highlighted,
}, result),
);
return true;
} else if (fnType.id === this.typeNameIdOfFn) {
writeHof(fnType, result);
return true;
}
return false;
};
/**
* Write a type. This function checks for special types,
* like slices, with their own formatting. It also handles
* updating the where clause and generic type param map.
*
* @param {FunctionType} fnType
* @param {[string]} result
*/
const writeFn = (fnType, result) => {
if (fnType.id < 0) {
const queryId = mgens && mgens.has(fnType.id) ? mgens.get(fnType.id) : null;
if (fnParamNames[-1 - fnType.id] === "") {
for (const nested of fnType.generics) {
writeFn(nested, result);
}
return;
} else if (queryId < 0) {
mappedNames.set(
fnParamNames[-1 - fnType.id],
queryParamNames[-1 - queryId],
);
}
pushText({
name: fnParamNames[-1 - fnType.id],
highlighted: !!fnType.highlighted,
}, result);
const where = [];
onEachBtwn(
fnType.generics,
nested => writeFn(nested, where),
() => pushText({ name: " + ", highlighted: false }, where),
);
if (where.length > 0) {
whereClause.set(fnParamNames[-1 - fnType.id], where);
}
} else {
if (fnType.ty === TY_PRIMITIVE) {
if (writeSpecialPrimitive(fnType, result)) {
return;
}
} else if (fnType.ty === TY_TRAIT && (
fnType.id === this.typeNameIdOfFn ||
fnType.id === this.typeNameIdOfFnMut ||
fnType.id === this.typeNameIdOfFnOnce)) {
writeHof(fnType, result);
return;
}
pushText(fnType, result);
let hasBindings = false;
if (fnType.bindings.size > 0) {
onEachBtwn(
fnType.bindings,
([key, values]) => {
const name = this.assocTypeIdNameMap.get(key);
if (values.length === 1 && values[0].id < 0 &&
`${fnType.name}::${name}` === fnParamNames[-1 - values[0].id]) {
// the internal `Item=Iterator::Item` type variable should be
// shown in the where clause and name mapping output, but is
// redundant in this spot
for (const value of values) {
writeFn(value, []);
}
return true;
}
if (!hasBindings) {
hasBindings = true;
pushText({ name: "<", highlighted: false }, result);
}
pushText({ name, highlighted: false }, result);
pushText({
name: values.length > 1 ? "=(" : "=",
highlighted: false,
}, result);
onEachBtwn(
values,
value => writeFn(value, result),
() => pushText({ name: " + ", highlighted: false }, result),
);
if (values.length > 1) {
pushText({ name: ")", highlighted: false }, result);
}
},
() => pushText({ name: ", ", highlighted: false }, result),
);
}
if (fnType.generics.length > 0) {
pushText({ name: hasBindings ? ", " : "<", highlighted: false }, result);
}
onEachBtwn(
fnType.generics,
value => writeFn(value, result),
() => pushText({ name: ", ", highlighted: false }, result),
);
if (hasBindings || fnType.generics.length > 0) {
pushText({ name: ">", highlighted: false }, result);
}
}
};
const type = [];
onEachBtwn(
fnInputs,
fnType => writeFn(fnType, type),
() => pushText({ name: ", ", highlighted: false }, type),
);
pushText({ name: " -> ", highlighted: false }, type);
onEachBtwn(
fnOutput,
fnType => writeFn(fnType, type),
() => pushText({ name: ", ", highlighted: false }, type),
);
return {type, mappedNames, whereClause};
};
/**
* This function takes a result map, and sorts it by various criteria, including edit
* distance, substring match, and the crate it comes from.
@@ -2097,7 +2489,7 @@ class DocSearch {
* @param {string} preferredCrate
* @returns {Promise<[ResultObject]>}
*/
const sortResults = async(results, isType, preferredCrate) => {
const sortResults = async(results, typeInfo, preferredCrate) => {
const userQuery = parsedQuery.userQuery;
const normalizedUserQuery = parsedQuery.userQuery.toLowerCase();
const result_list = [];
@@ -2207,17 +2599,17 @@ class DocSearch {
return 0;
});
return transformResults(result_list);
return transformResults(result_list, typeInfo);
};
/**
* This function checks if a list of search query `queryElems` can all be found in the
* search index (`fnTypes`).
*
* This function returns `true` on a match, or `false` if none. If `solutionCb` is
* This function returns highlighted results on a match, or `null`. If `solutionCb` is
* supplied, it will call that function with mgens, and that callback can accept or
* reject the result bu returning `true` or `false`. If the callback returns false,
* then this function will try with a different solution, or bail with false if it
* reject the result by returning `true` or `false`. If the callback returns false,
* then this function will try with a different solution, or bail with null if it
* runs out of candidates.
*
* @param {Array<FunctionType>} fnTypesIn - The objects to check.
@@ -2230,7 +2622,7 @@ class DocSearch {
* - Limit checks that Ty matches Vec<Ty>,
* but not Vec<ParamEnvAnd<WithInfcx<ConstTy<Interner<Ty=Ty>>>>>
*
* @return {boolean} - Returns true if a match, false otherwise.
* @return {[FunctionType]|null} - Returns highlighed results if a match, null otherwise.
*/
function unifyFunctionTypes(
fnTypesIn,
@@ -2241,17 +2633,17 @@ class DocSearch {
unboxingDepth,
) {
if (unboxingDepth >= UNBOXING_LIMIT) {
return false;
return null;
}
/**
* @type Map<integer, integer>|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}<span class="${type}">${name}</span>\
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 :(<br/>" +
@@ -3507,7 +4036,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
"href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
" <a href=\"https://crates.io/\">crates.io</a>.</li></ul>";
}
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 += `<h3 class="error">Query parser error: "${error.join("")}".</h3>`;
output += "<div id=\"search-tabs\">" +
makeTabHeader(0, "In Names", ret_others[1]) +
makeTabHeader(0, "In Names", results.others.length) +
"</div>";
currentTab = 0;
} else if (results.query.foundElems <= 1 && results.query.returned.length === 0) {
output += "<div id=\"search-tabs\">" +
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) +
"</div>";
} 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 += "<div id=\"search-tabs\">" +
makeTabHeader(0, signatureTabTitle, ret_others[1]) +
makeTabHeader(0, signatureTabTitle, results.others.length) +
"</div>";
currentTab = 0;
}
@@ -3647,11 +4170,17 @@ async function showResults(results, go_to_first, filterCrates) {
`Consider searching for "${targ}" instead.</h3>`;
}
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());
}
})();
+43 -11
View File
@@ -2,6 +2,14 @@
const fs = require("fs");
const path = require("path");
function arrayToCode(array) {
return array.map((value, index) => {
value = value.split("&nbsp;").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("&nbsp;").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);
@@ -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>,(T->bool)->(Extend<T>,Extend<T>)"
// 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<Iterator::Item>")
// Try a simple result that *won't* give an info box.
go-to: "file://" + |DOC_PATH| + "/lib2/index.html?search=F->lib2::WhereWhitespace<T>"
// 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<T>")
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']"
+156 -18
View File
@@ -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`<T>, F -> `Option`<U>',
'displayWhereClause': "F: `FnOnce` (T) -> U",
},
],
},
{
'query': 'option<t>, fnonce -> option',
'others': [
{
'path': 'std::option::Option',
'name': 'map',
'displayType': '`Option`<`T`>, F -> `Option`<U>',
'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> -> `T`',
'displayWhereClause': "T: `Default`",
},
{
'path': 'std::option::Option',
'name': 'get_or_insert_default',
'displayType': '&mut `Option`<T> -> &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> -> &`[`T`]`',
},
{
'path': 'std::option::Option',
'name': 'as_mut_slice',
'displayType': '&mut `Option`<T> -> &mut `[`T`]`',
},
],
},
{
'query': 'option<t>, option<t> -> option<t>',
'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<t>, option<u> -> option<u>',
'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<t>, option<u> -> option<t>',
'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<t>, option<u> -> option<t, u>',
'others': [
{ 'path': 'std::option::Option', 'name': 'zip' },
{
'path': 'std::option::Option',
'name': 'zip',
'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<(`T`, `U`)>',
},
],
},
{
'query': 'option<t>, e -> result<t, e>',
'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<option<t>, e> -> option<result<t, e>>',
'others': [
{ 'path': 'std::result::Result', 'name': 'transpose' },
{
'path': 'std::result::Result',
'name': 'transpose',
'displayType': '`Result`<`Option`<`T`>, `E`> -> `Option`<`Result`<`T`, `E`>>',
},
],
},
{
'query': 'option<t>, option<t> -> bool',
'others': [
{ 'path': 'std::option::Option', 'name': 'eq' },
{
'path': 'std::option::Option',
'name': 'eq',
'displayType': '&`Option`<`T`>, &`Option`<`T`> -> `bool`',
},
],
},
{
'query': 'option<option<t>> -> option<t>',
'others': [
{ 'path': 'std::option::Option', 'name': 'flatten' },
{
'path': 'std::option::Option',
'name': 'flatten',
'displayType': '`Option`<`Option`<`T`>> -> `Option`<`T`>',
},
],
},
{
'query': 'option<t>',
'returned': [
{ 'path': 'std::result::Result', 'name': 'ok' },
{
'path': 'std::result::Result',
'name': 'ok',
'displayType': 'Result<T, E> -> `Option`<`T`>',
},
],
},
{
'query': 'option<t>, (fnonce () -> u) -> option',
'others': [
{
'path': 'std::option::Option',
'name': 'map',
'displayType': '`Option`<`T`>, F -> `Option`<U>',
'displayMappedNames': `T = t, U = u`,
'displayWhereClause': "F: `FnOnce` (T) -> `U`",
},
{
'path': 'std::option::Option',
'name': 'and_then',
'displayType': '`Option`<`T`>, F -> `Option`<U>',
'displayMappedNames': `T = t, U = u`,
'displayWhereClause': "F: `FnOnce` (T) -> Option<`U`>",
},
{
'path': 'std::option::Option',
'name': 'zip_with',
'displayType': 'Option<T>, `Option`<`U`>, F -> `Option`<R>',
'displayMappedNames': `U = t, R = u`,
'displayWhereClause': "F: `FnOnce` (T, U) -> `R`",
},
{
'path': 'std::task::Poll',
'name': 'map_ok',
'displayType': 'Poll<`Option`<Result<`T`, E>>>, F -> Poll<`Option`<Result<U, E>>>',
'displayMappedNames': `T = t, U = u`,
'displayWhereClause': "F: `FnOnce` (T) -> `U`",
},
{
'path': 'std::task::Poll',
'name': 'map_err',
'displayType': 'Poll<`Option`<Result<`T`, E>>>, F -> Poll<`Option`<Result<T, U>>>',
'displayMappedNames': `T = t, U = u`,
'displayWhereClause': "F: `FnOnce` (E) -> `U`",
},
],
},
{
'query': 'option<t>, (fnonce () -> option<u>) -> option',
'others': [
{
'path': 'std::option::Option',
'name': 'and_then',
'displayType': '`Option`<`T`>, F -> `Option`<U>',
'displayMappedNames': `T = t, U = u`,
'displayWhereClause': "F: `FnOnce` (T) -> `Option`<`U`>",
},
],
},
];
+39
View File
@@ -0,0 +1,39 @@
// exact-check
const EXPECTED = [
// Trait-associated types (that is, associated types with no constraints)
// are treated like type parameters, so that you can "pattern match"
// them. We should avoid redundant output (no `Item=MyIter::Item` stuff)
// and should give reasonable results
{
'query': 'MyIter<T> -> Option<T>',
'correction': null,
'others': [
{
'path': 'assoc_type_unbound::MyIter',
'name': 'next',
'displayType': '&mut `MyIter` -> `Option`<`MyIter::Item`>',
'displayMappedNames': 'MyIter::Item = T',
'displayWhereClause': '',
},
],
},
{
'query': 'MyIter<Item=T> -> Option<T>',
'correction': null,
'others': [
{
'path': 'assoc_type_unbound::MyIter',
'name': 'next',
'displayType': '&mut `MyIter` -> `Option`<`MyIter::Item`>',
'displayMappedNames': 'MyIter::Item = T',
'displayWhereClause': '',
},
],
},
{
'query': 'MyIter<T> -> Option<Item=T>',
'correction': null,
'others': [],
},
];
+4
View File
@@ -0,0 +1,4 @@
pub trait MyIter {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
+42 -6
View File
@@ -7,16 +7,40 @@ const EXPECTED = [
'query': 'iterator<something> -> u32',
'correction': null,
'others': [
{ 'path': 'assoc_type::my', 'name': 'other_fn' },
{ 'path': 'assoc_type', 'name': 'my_fn' },
{
'path': 'assoc_type::my',
'name': 'other_fn',
'displayType': 'X -> `u32`',
'displayMappedNames': '',
'displayWhereClause': 'X: `Iterator`<`Something`>',
},
{
'path': 'assoc_type',
'name': 'my_fn',
'displayType': 'X -> `u32`',
'displayMappedNames': '',
'displayWhereClause': 'X: `Iterator`<Item=`Something`>',
},
],
},
{
'query': 'iterator<something>',
'correction': null,
'in_args': [
{ 'path': 'assoc_type::my', 'name': 'other_fn' },
{ 'path': 'assoc_type', 'name': 'my_fn' },
{
'path': 'assoc_type::my',
'name': 'other_fn',
'displayType': 'X -> u32',
'displayMappedNames': '',
'displayWhereClause': 'X: `Iterator`<`Something`>',
},
{
'path': 'assoc_type',
'name': 'my_fn',
'displayType': 'X -> u32',
'displayMappedNames': '',
'displayWhereClause': 'X: `Iterator`<Item=`Something`>',
},
],
},
{
@@ -26,8 +50,20 @@ const EXPECTED = [
{ 'path': 'assoc_type', 'name': 'Something' },
],
'in_args': [
{ 'path': 'assoc_type::my', 'name': 'other_fn' },
{ 'path': 'assoc_type', 'name': 'my_fn' },
{
'path': 'assoc_type::my',
'name': 'other_fn',
'displayType': '`X` -> u32',
'displayMappedNames': '',
'displayWhereClause': 'X: Iterator<`Something`>',
},
{
'path': 'assoc_type',
'name': 'my_fn',
'displayType': '`X` -> u32',
'displayMappedNames': '',
'displayWhereClause': 'X: Iterator<Item=`Something`>',
},
],
},
// if I write an explicit binding, only it shows up
+42 -6
View File
@@ -5,10 +5,22 @@ const EXPECTED = [
'query': 'Result<SomeTrait>',
'correction': null,
'in_args': [
{ 'path': 'generics_trait', 'name': 'beta' },
{
'path': 'generics_trait',
'name': 'beta',
'displayType': '`Result`<`T`, ()> -> ()',
'displayMappedNames': '',
'displayWhereClause': 'T: `SomeTrait`',
},
],
'returned': [
{ 'path': 'generics_trait', 'name': 'bet' },
{
'path': 'generics_trait',
'name': 'bet',
'displayType': ' -> `Result`<`T`, ()>',
'displayMappedNames': '',
'displayWhereClause': 'T: `SomeTrait`',
},
],
},
{
@@ -25,20 +37,44 @@ const EXPECTED = [
'query': 'OtherThingxxxxxxxx',
'correction': null,
'in_args': [
{ 'path': 'generics_trait', 'name': 'alpha' },
{
'path': 'generics_trait',
'name': 'alpha',
'displayType': 'Result<`T`, ()> -> ()',
'displayMappedNames': '',
'displayWhereClause': 'T: `OtherThingxxxxxxxx`',
},
],
'returned': [
{ 'path': 'generics_trait', 'name': 'alef' },
{
'path': 'generics_trait',
'name': 'alef',
'displayType': ' -> Result<`T`, ()>',
'displayMappedNames': '',
'displayWhereClause': 'T: `OtherThingxxxxxxxx`',
},
],
},
{
'query': 'OtherThingxxxxxxxy',
'correction': 'OtherThingxxxxxxxx',
'in_args': [
{ 'path': 'generics_trait', 'name': 'alpha' },
{
'path': 'generics_trait',
'name': 'alpha',
'displayType': 'Result<`T`, ()> -> ()',
'displayMappedNames': '',
'displayWhereClause': 'T: `OtherThingxxxxxxxx`',
},
],
'returned': [
{ 'path': 'generics_trait', 'name': 'alef' },
{
'path': 'generics_trait',
'name': 'alef',
'displayType': ' -> Result<`T`, ()>',
'displayMappedNames': '',
'displayWhereClause': 'T: `OtherThingxxxxxxxx`',
},
],
},
];