Skip to content

Commit 2e56927

Browse files
committed
rustdoc: search for slices and arrays by type with []
Part of rust-lang#60485
1 parent 3ed4c17 commit 2e56927

File tree

5 files changed

+487
-82
lines changed

5 files changed

+487
-82
lines changed

src/librustdoc/html/static/js/search.js

+151-82
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,46 @@ function initSearch(rawSearchIndex) {
208208
let typeNameIdMap;
209209
const ALIASES = new Map();
210210

211+
/**
212+
* Special type name IDs for searching by array.
213+
*/
214+
let typeNameIdOfArray;
215+
/**
216+
* Special type name IDs for searching by slice.
217+
*/
218+
let typeNameIdOfSlice;
219+
/**
220+
* Special type name IDs for searching by both array and slice (`[]` syntax).
221+
*/
222+
let typeNameIdOfArrayOrSlice;
223+
224+
/**
225+
* Add an item to the type Name->ID map, or, if one already exists, use it.
226+
* Returns the number. If name is "" or null, return -1 (pure generic).
227+
*
228+
* This is effectively string interning, so that function matching can be
229+
* done more quickly. Two types with the same name but different item kinds
230+
* get the same ID.
231+
*
232+
* @param {string} name
233+
*
234+
* @returns {integer}
235+
*/
236+
function buildTypeMapIndex(name) {
237+
238+
if (name === "" || name === null) {
239+
return -1;
240+
}
241+
242+
if (typeNameIdMap.has(name)) {
243+
return typeNameIdMap.get(name);
244+
} else {
245+
const id = typeNameIdMap.size;
246+
typeNameIdMap.set(name, id);
247+
return id;
248+
}
249+
}
250+
211251
function isWhitespace(c) {
212252
return " \t\n\r".indexOf(c) !== -1;
213253
}
@@ -217,7 +257,7 @@ function initSearch(rawSearchIndex) {
217257
}
218258

219259
function isEndCharacter(c) {
220-
return ",>-".indexOf(c) !== -1;
260+
return ",>-]".indexOf(c) !== -1;
221261
}
222262

223263
function isStopCharacter(c) {
@@ -466,35 +506,64 @@ function initSearch(rawSearchIndex) {
466506

467507
let start = parserState.pos;
468508
let end;
469-
// We handle the strings on their own mostly to make code easier to follow.
470-
if (parserState.userQuery[parserState.pos] === "\"") {
471-
start += 1;
472-
getStringElem(query, parserState, isInGenerics);
473-
end = parserState.pos - 1;
509+
if (parserState.userQuery[parserState.pos] === "[") {
510+
parserState.pos += 1;
511+
getItemsBefore(query, parserState, generics, "]");
512+
const typeFilter = parserState.typeFilter;
513+
if (typeFilter !== null && typeFilter !== "primitive") {
514+
throw [
515+
"Invalid search type: primitive ",
516+
"[]",
517+
" and ",
518+
typeFilter,
519+
" both specified",
520+
];
521+
}
522+
parserState.typeFilter = null;
523+
parserState.totalElems += 1;
524+
if (isInGenerics) {
525+
parserState.genericsElems += 1;
526+
}
527+
elems.push({
528+
name: "[]",
529+
id: -1,
530+
fullPath: ["[]"],
531+
pathWithoutLast: [],
532+
pathLast: "[]",
533+
generics,
534+
typeFilter: "primitive",
535+
});
474536
} else {
475-
end = getIdentEndPosition(parserState);
476-
}
477-
if (parserState.pos < parserState.length &&
478-
parserState.userQuery[parserState.pos] === "<"
479-
) {
480-
if (start >= end) {
481-
throw ["Found generics without a path"];
537+
// We handle the strings on their own mostly to make code easier to follow.
538+
if (parserState.userQuery[parserState.pos] === "\"") {
539+
start += 1;
540+
getStringElem(query, parserState, isInGenerics);
541+
end = parserState.pos - 1;
542+
} else {
543+
end = getIdentEndPosition(parserState);
482544
}
483-
parserState.pos += 1;
484-
getItemsBefore(query, parserState, generics, ">");
485-
}
486-
if (start >= end && generics.length === 0) {
487-
return;
545+
if (parserState.pos < parserState.length &&
546+
parserState.userQuery[parserState.pos] === "<"
547+
) {
548+
if (start >= end) {
549+
throw ["Found generics without a path"];
550+
}
551+
parserState.pos += 1;
552+
getItemsBefore(query, parserState, generics, ">");
553+
}
554+
if (start >= end && generics.length === 0) {
555+
return;
556+
}
557+
elems.push(
558+
createQueryElement(
559+
query,
560+
parserState,
561+
parserState.userQuery.slice(start, end),
562+
generics,
563+
isInGenerics
564+
)
565+
);
488566
}
489-
elems.push(
490-
createQueryElement(
491-
query,
492-
parserState,
493-
parserState.userQuery.slice(start, end),
494-
generics,
495-
isInGenerics
496-
)
497-
);
498567
}
499568

500569
/**
@@ -518,6 +587,17 @@ function initSearch(rawSearchIndex) {
518587
const oldTypeFilter = parserState.typeFilter;
519588
parserState.typeFilter = null;
520589

590+
let extra = "";
591+
if (endChar === ">") {
592+
extra = "<";
593+
} else if (endChar === "]") {
594+
extra = "[";
595+
} else if (endChar === "") {
596+
extra = "->";
597+
} else {
598+
extra = endChar;
599+
}
600+
521601
while (parserState.pos < parserState.length) {
522602
const c = parserState.userQuery[parserState.pos];
523603
if (c === endChar) {
@@ -547,14 +627,6 @@ function initSearch(rawSearchIndex) {
547627
foundStopChar = true;
548628
continue;
549629
} else if (isEndCharacter(c)) {
550-
let extra = "";
551-
if (endChar === ">") {
552-
extra = "<";
553-
} else if (endChar === "") {
554-
extra = "->";
555-
} else {
556-
extra = endChar;
557-
}
558630
throw ["Unexpected ", c, " after ", extra];
559631
}
560632
if (!foundStopChar) {
@@ -581,9 +653,9 @@ function initSearch(rawSearchIndex) {
581653
}
582654
const posBefore = parserState.pos;
583655
start = parserState.pos;
584-
getNextElem(query, parserState, elems, endChar === ">");
656+
getNextElem(query, parserState, elems, endChar !== "");
585657
if (endChar !== "" && parserState.pos >= parserState.length) {
586-
throw ["Unclosed ", "<"];
658+
throw ["Unclosed ", extra];
587659
}
588660
// This case can be encountered if `getNextElem` encountered a "stop character" right
589661
// from the start. For example if you have `,,` or `<>`. In this case, we simply move up
@@ -594,7 +666,7 @@ function initSearch(rawSearchIndex) {
594666
foundStopChar = false;
595667
}
596668
if (parserState.pos >= parserState.length && endChar !== "") {
597-
throw ["Unclosed ", "<"];
669+
throw ["Unclosed ", extra];
598670
}
599671
// We are either at the end of the string or on the `endChar` character, let's move forward
600672
// in any case.
@@ -779,7 +851,8 @@ function initSearch(rawSearchIndex) {
779851
*
780852
* ident = *(ALPHA / DIGIT / "_")
781853
* path = ident *(DOUBLE-COLON ident) [!]
782-
* arg = [type-filter *WS COLON *WS] path [generics]
854+
* slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
855+
* arg = [type-filter *WS COLON *WS] (path [generics] / slice)
783856
* type-sep = COMMA/WS *(COMMA/WS)
784857
* nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
785858
* generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
@@ -821,6 +894,8 @@ function initSearch(rawSearchIndex) {
821894
*
822895
* OPEN-ANGLE-BRACKET = "<"
823896
* CLOSE-ANGLE-BRACKET = ">"
897+
* OPEN-SQUARE-BRACKET = "["
898+
* CLOSE-SQUARE-BRACKET = "]"
824899
* COLON = ":"
825900
* DOUBLE-COLON = "::"
826901
* QUOTE = %x22
@@ -1170,7 +1245,22 @@ function initSearch(rawSearchIndex) {
11701245
// ones with no type filter, which can match any entry regardless of its
11711246
// own type.
11721247
for (const generic of elem.generics) {
1173-
if (generic.typeFilter !== -1 && !handleGeneric(generic)) {
1248+
if (generic.typeFilter === TY_PRIMITIVE &&
1249+
generic.id === typeNameIdOfArrayOrSlice) {
1250+
const genericArray = {
1251+
id: typeNameIdOfArray,
1252+
typeFilter: TY_PRIMITIVE,
1253+
generics: generic.generics,
1254+
};
1255+
const genericSlice = {
1256+
id: typeNameIdOfSlice,
1257+
typeFilter: TY_PRIMITIVE,
1258+
generics: generic.generics,
1259+
};
1260+
if (!handleGeneric(genericArray) && !handleGeneric(genericSlice)) {
1261+
return false;
1262+
}
1263+
} else if (generic.typeFilter !== -1 && !handleGeneric(generic)) {
11741264
return false;
11751265
}
11761266
}
@@ -1217,7 +1307,12 @@ function initSearch(rawSearchIndex) {
12171307
return row.generics.length > 0 ? checkIfInGenerics(row, elem) : false;
12181308
}
12191309

1220-
if (row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty)) {
1310+
const matchesExact = row.id === elem.id;
1311+
const matchesArrayOrSlice = elem.id === typeNameIdOfArrayOrSlice &&
1312+
(row.id === typeNameIdOfSlice || row.id === typeNameIdOfArray);
1313+
1314+
if ((matchesExact || matchesArrayOrSlice) &&
1315+
typePassesFilter(elem.typeFilter, row.ty)) {
12211316
if (elem.generics.length > 0) {
12221317
return checkGenerics(row, elem);
12231318
}
@@ -2082,34 +2177,6 @@ function initSearch(rawSearchIndex) {
20822177
filterCrates);
20832178
}
20842179

2085-
/**
2086-
* Add an item to the type Name->ID map, or, if one already exists, use it.
2087-
* Returns the number. If name is "" or null, return -1 (pure generic).
2088-
*
2089-
* This is effectively string interning, so that function matching can be
2090-
* done more quickly. Two types with the same name but different item kinds
2091-
* get the same ID.
2092-
*
2093-
* @param {Map<string, integer>} typeNameIdMap
2094-
* @param {string} name
2095-
*
2096-
* @returns {integer}
2097-
*/
2098-
function buildTypeMapIndex(typeNameIdMap, name) {
2099-
2100-
if (name === "" || name === null) {
2101-
return -1;
2102-
}
2103-
2104-
if (typeNameIdMap.has(name)) {
2105-
return typeNameIdMap.get(name);
2106-
} else {
2107-
const id = typeNameIdMap.size;
2108-
typeNameIdMap.set(name, id);
2109-
return id;
2110-
}
2111-
}
2112-
21132180
/**
21142181
* Convert a list of RawFunctionType / ID to object-based FunctionType.
21152182
*
@@ -2128,7 +2195,7 @@ function initSearch(rawSearchIndex) {
21282195
*
21292196
* @return {Array<FunctionSearchType>}
21302197
*/
2131-
function buildItemSearchTypeAll(types, lowercasePaths, typeNameIdMap) {
2198+
function buildItemSearchTypeAll(types, lowercasePaths) {
21322199
const PATH_INDEX_DATA = 0;
21332200
const GENERICS_DATA = 1;
21342201
return types.map(type => {
@@ -2140,15 +2207,14 @@ function initSearch(rawSearchIndex) {
21402207
pathIndex = type[PATH_INDEX_DATA];
21412208
generics = buildItemSearchTypeAll(
21422209
type[GENERICS_DATA],
2143-
lowercasePaths,
2144-
typeNameIdMap
2210+
lowercasePaths
21452211
);
21462212
}
21472213
return {
21482214
// `0` is used as a sentinel because it's fewer bytes than `null`
21492215
id: pathIndex === 0
21502216
? -1
2151-
: buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
2217+
: buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
21522218
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
21532219
generics: generics,
21542220
};
@@ -2171,7 +2237,7 @@ function initSearch(rawSearchIndex) {
21712237
*
21722238
* @return {null|FunctionSearchType}
21732239
*/
2174-
function buildFunctionSearchType(functionSearchType, lowercasePaths, typeNameIdMap) {
2240+
function buildFunctionSearchType(functionSearchType, lowercasePaths) {
21752241
const INPUTS_DATA = 0;
21762242
const OUTPUT_DATA = 1;
21772243
// `0` is used as a sentinel because it's fewer bytes than `null`
@@ -2184,15 +2250,14 @@ function initSearch(rawSearchIndex) {
21842250
inputs = [{
21852251
id: pathIndex === 0
21862252
? -1
2187-
: buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
2253+
: buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
21882254
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
21892255
generics: [],
21902256
}];
21912257
} else {
21922258
inputs = buildItemSearchTypeAll(
21932259
functionSearchType[INPUTS_DATA],
2194-
lowercasePaths,
2195-
typeNameIdMap
2260+
lowercasePaths
21962261
);
21972262
}
21982263
if (functionSearchType.length > 1) {
@@ -2201,15 +2266,14 @@ function initSearch(rawSearchIndex) {
22012266
output = [{
22022267
id: pathIndex === 0
22032268
? -1
2204-
: buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
2269+
: buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
22052270
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
22062271
generics: [],
22072272
}];
22082273
} else {
22092274
output = buildItemSearchTypeAll(
22102275
functionSearchType[OUTPUT_DATA],
2211-
lowercasePaths,
2212-
typeNameIdMap
2276+
lowercasePaths
22132277
);
22142278
}
22152279
} else {
@@ -2233,6 +2297,12 @@ function initSearch(rawSearchIndex) {
22332297
let currentIndex = 0;
22342298
let id = 0;
22352299

2300+
// Initialize type map indexes for primitive list types
2301+
// that can be searched using `[]` syntax.
2302+
typeNameIdOfArray = buildTypeMapIndex("array");
2303+
typeNameIdOfSlice = buildTypeMapIndex("slice");
2304+
typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
2305+
22362306
for (const crate in rawSearchIndex) {
22372307
if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) {
22382308
continue;
@@ -2363,8 +2433,7 @@ function initSearch(rawSearchIndex) {
23632433
parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
23642434
type: buildFunctionSearchType(
23652435
itemFunctionSearchTypes[i],
2366-
lowercasePaths,
2367-
typeNameIdMap
2436+
lowercasePaths
23682437
),
23692438
id: id,
23702439
normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),

tests/rustdoc-js-std/option-type-signatures.js

+7
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,11 @@ const EXPECTED = [
1212
{ 'path': 'std::option::Option', 'name': 'get_or_insert_default' },
1313
],
1414
},
15+
{
16+
'query': 'option -> []',
17+
'others': [
18+
{ 'path': 'std::option::Option', 'name': 'as_slice' },
19+
{ 'path': 'std::option::Option', 'name': 'as_mut_slice' },
20+
],
21+
},
1522
];

0 commit comments

Comments
 (0)