• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* global addClass, getNakedUrl, getSettingValue */
2/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */
3
4"use strict";
5
6(function() {
7// This mapping table should match the discriminants of
8// `rustdoc::formats::item_type::ItemType` type in Rust.
9const itemTypes = [
10    "mod",
11    "externcrate",
12    "import",
13    "struct",
14    "enum",
15    "fn",
16    "type",
17    "static",
18    "trait",
19    "impl",
20    "tymethod",
21    "method",
22    "structfield",
23    "variant",
24    "macro",
25    "primitive",
26    "associatedtype",
27    "constant",
28    "associatedconstant",
29    "union",
30    "foreigntype",
31    "keyword",
32    "existential",
33    "attr",
34    "derive",
35    "traitalias",
36];
37
38const longItemTypes = [
39    "module",
40    "extern crate",
41    "re-export",
42    "struct",
43    "enum",
44    "function",
45    "type alias",
46    "static",
47    "trait",
48    "",
49    "trait method",
50    "method",
51    "struct field",
52    "enum variant",
53    "macro",
54    "primitive type",
55    "assoc type",
56    "constant",
57    "assoc const",
58    "union",
59    "foreign type",
60    "keyword",
61    "existential type",
62    "attribute macro",
63    "derive macro",
64    "trait alias",
65];
66
67// used for special search precedence
68const TY_PRIMITIVE = itemTypes.indexOf("primitive");
69const TY_KEYWORD = itemTypes.indexOf("keyword");
70const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
71
72function hasOwnPropertyRustdoc(obj, property) {
73    return Object.prototype.hasOwnProperty.call(obj, property);
74}
75
76// In the search display, allows to switch between tabs.
77function printTab(nb) {
78    let iter = 0;
79    let foundCurrentTab = false;
80    let foundCurrentResultSet = false;
81    onEachLazy(document.getElementById("search-tabs").childNodes, elem => {
82        if (nb === iter) {
83            addClass(elem, "selected");
84            foundCurrentTab = true;
85        } else {
86            removeClass(elem, "selected");
87        }
88        iter += 1;
89    });
90    const isTypeSearch = (nb > 0 || iter === 1);
91    iter = 0;
92    onEachLazy(document.getElementById("results").childNodes, elem => {
93        if (nb === iter) {
94            addClass(elem, "active");
95            foundCurrentResultSet = true;
96        } else {
97            removeClass(elem, "active");
98        }
99        iter += 1;
100    });
101    if (foundCurrentTab && foundCurrentResultSet) {
102        searchState.currentTab = nb;
103        // Corrections only kick in on type-based searches.
104        const correctionsElem = document.getElementsByClassName("search-corrections");
105        if (isTypeSearch) {
106            removeClass(correctionsElem[0], "hidden");
107        } else {
108            addClass(correctionsElem[0], "hidden");
109        }
110    } else if (nb !== 0) {
111        printTab(0);
112    }
113}
114
115/**
116 * The [edit distance] is a metric for measuring the difference between two strings.
117 *
118 * [edit distance]: https://en.wikipedia.org/wiki/Edit_distance
119 */
120
121/*
122 * This function was translated, mostly line-for-line, from
123 * https://github.com/rust-lang/rust/blob/ff4b772f805ec1e/compiler/rustc_span/src/edit_distance.rs
124 *
125 * The current implementation is the restricted Damerau-Levenshtein algorithm. It is restricted
126 * because it does not permit modifying characters that have already been transposed. The specific
127 * algorithm should not matter to the caller of the methods, which is why it is not noted in the
128 * documentation.
129 */
130const editDistanceState = {
131    current: [],
132    prev: [],
133    prevPrev: [],
134    calculate: function calculate(a, b, limit) {
135        // Ensure that `b` is the shorter string, minimizing memory use.
136        if (a.length < b.length) {
137            const aTmp = a;
138            a = b;
139            b = aTmp;
140        }
141
142        const minDist = a.length - b.length;
143        // If we know the limit will be exceeded, we can return early.
144        if (minDist > limit) {
145            return limit + 1;
146        }
147
148        // Strip common prefix.
149        // We know that `b` is the shorter string, so we don't need to check
150        // `a.length`.
151        while (b.length > 0 && b[0] === a[0]) {
152            a = a.substring(1);
153            b = b.substring(1);
154        }
155        // Strip common suffix.
156        while (b.length > 0 && b[b.length - 1] === a[a.length - 1]) {
157            a = a.substring(0, a.length - 1);
158            b = b.substring(0, b.length - 1);
159        }
160
161        // If either string is empty, the distance is the length of the other.
162        // We know that `b` is the shorter string, so we don't need to check `a`.
163        if (b.length === 0) {
164            return minDist;
165        }
166
167        const aLength = a.length;
168        const bLength = b.length;
169
170        for (let i = 0; i <= bLength; ++i) {
171            this.current[i] = 0;
172            this.prev[i] = i;
173            this.prevPrev[i] = Number.MAX_VALUE;
174        }
175
176        // row by row
177        for (let i = 1; i <= aLength; ++i) {
178            this.current[0] = i;
179            const aIdx = i - 1;
180
181            // column by column
182            for (let j = 1; j <= bLength; ++j) {
183                const bIdx = j - 1;
184
185                // There is no cost to substitute a character with itself.
186                const substitutionCost = a[aIdx] === b[bIdx] ? 0 : 1;
187
188                this.current[j] = Math.min(
189                    // deletion
190                    this.prev[j] + 1,
191                    // insertion
192                    this.current[j - 1] + 1,
193                    // substitution
194                    this.prev[j - 1] + substitutionCost
195                );
196
197                if ((i > 1) && (j > 1) && (a[aIdx] === b[bIdx - 1]) && (a[aIdx - 1] === b[bIdx])) {
198                    // transposition
199                    this.current[j] = Math.min(
200                        this.current[j],
201                        this.prevPrev[j - 2] + 1
202                    );
203                }
204            }
205
206            // Rotate the buffers, reusing the memory
207            const prevPrevTmp = this.prevPrev;
208            this.prevPrev = this.prev;
209            this.prev = this.current;
210            this.current = prevPrevTmp;
211        }
212
213        // `prev` because we already rotated the buffers.
214        const distance = this.prev[bLength];
215        return distance <= limit ? distance : (limit + 1);
216    },
217};
218
219function editDistance(a, b, limit) {
220    return editDistanceState.calculate(a, b, limit);
221}
222
223function initSearch(rawSearchIndex) {
224    const MAX_RESULTS = 200;
225    const NO_TYPE_FILTER = -1;
226    /**
227     *  @type {Array<Row>}
228     */
229    let searchIndex;
230    let currentResults;
231    /**
232     * Map from normalized type names to integers. Used to make type search
233     * more efficient.
234     *
235     * @type {Map<string, integer>}
236     */
237    let typeNameIdMap;
238    const ALIASES = new Map();
239
240    /**
241     * Special type name IDs for searching by array.
242     */
243    let typeNameIdOfArray;
244    /**
245     * Special type name IDs for searching by slice.
246     */
247    let typeNameIdOfSlice;
248    /**
249     * Special type name IDs for searching by both array and slice (`[]` syntax).
250     */
251    let typeNameIdOfArrayOrSlice;
252
253    /**
254     * Add an item to the type Name->ID map, or, if one already exists, use it.
255     * Returns the number. If name is "" or null, return -1 (pure generic).
256     *
257     * This is effectively string interning, so that function matching can be
258     * done more quickly. Two types with the same name but different item kinds
259     * get the same ID.
260     *
261     * @param {string} name
262     *
263     * @returns {integer}
264     */
265    function buildTypeMapIndex(name) {
266
267        if (name === "" || name === null) {
268            return -1;
269        }
270
271        if (typeNameIdMap.has(name)) {
272            return typeNameIdMap.get(name);
273        } else {
274            const id = typeNameIdMap.size;
275            typeNameIdMap.set(name, id);
276            return id;
277        }
278    }
279
280    function isWhitespace(c) {
281        return " \t\n\r".indexOf(c) !== -1;
282    }
283
284    function isSpecialStartCharacter(c) {
285        return "<\"".indexOf(c) !== -1;
286    }
287
288    function isEndCharacter(c) {
289        return ",>-]".indexOf(c) !== -1;
290    }
291
292    function isStopCharacter(c) {
293        return isEndCharacter(c);
294    }
295
296    function isErrorCharacter(c) {
297        return "()".indexOf(c) !== -1;
298    }
299
300    function itemTypeFromName(typename) {
301        const index = itemTypes.findIndex(i => i === typename);
302        if (index < 0) {
303            throw ["Unknown type filter ", typename];
304        }
305        return index;
306    }
307
308    /**
309     * If we encounter a `"`, then we try to extract the string from it until we find another `"`.
310     *
311     * This function will throw an error in the following cases:
312     * * There is already another string element.
313     * * We are parsing a generic argument.
314     * * There is more than one element.
315     * * There is no closing `"`.
316     *
317     * @param {ParsedQuery} query
318     * @param {ParserState} parserState
319     * @param {boolean} isInGenerics
320     */
321    function getStringElem(query, parserState, isInGenerics) {
322        if (isInGenerics) {
323            throw ["Unexpected ", "\"", " in generics"];
324        } else if (query.literalSearch) {
325            throw ["Cannot have more than one literal search element"];
326        } else if (parserState.totalElems - parserState.genericsElems > 0) {
327            throw ["Cannot use literal search when there is more than one element"];
328        }
329        parserState.pos += 1;
330        const start = parserState.pos;
331        const end = getIdentEndPosition(parserState);
332        if (parserState.pos >= parserState.length) {
333            throw ["Unclosed ", "\""];
334        } else if (parserState.userQuery[end] !== "\"") {
335            throw ["Unexpected ", parserState.userQuery[end], " in a string element"];
336        } else if (start === end) {
337            throw ["Cannot have empty string element"];
338        }
339        // To skip the quote at the end.
340        parserState.pos += 1;
341        query.literalSearch = true;
342    }
343
344    /**
345     * Returns `true` if the current parser position is starting with "::".
346     *
347     * @param {ParserState} parserState
348     *
349     * @return {boolean}
350     */
351    function isPathStart(parserState) {
352        return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "::";
353    }
354
355    /**
356     * Returns `true` if the current parser position is starting with "->".
357     *
358     * @param {ParserState} parserState
359     *
360     * @return {boolean}
361     */
362    function isReturnArrow(parserState) {
363        return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "->";
364    }
365
366    /**
367     * Returns `true` if the given `c` character is valid for an ident.
368     *
369     * @param {string} c
370     *
371     * @return {boolean}
372     */
373    function isIdentCharacter(c) {
374        return (
375            c === "_" ||
376            (c >= "0" && c <= "9") ||
377            (c >= "a" && c <= "z") ||
378            (c >= "A" && c <= "Z"));
379    }
380
381    /**
382     * Returns `true` if the given `c` character is a separator.
383     *
384     * @param {string} c
385     *
386     * @return {boolean}
387     */
388    function isSeparatorCharacter(c) {
389        return c === ",";
390    }
391
392/**
393     * Returns `true` if the given `c` character is a path separator. For example
394     * `:` in `a::b` or a whitespace in `a b`.
395     *
396     * @param {string} c
397     *
398     * @return {boolean}
399     */
400    function isPathSeparator(c) {
401        return c === ":" || isWhitespace(c);
402    }
403
404    /**
405     * Returns `true` if the previous character is `lookingFor`.
406     *
407     * @param {ParserState} parserState
408     * @param {String} lookingFor
409     *
410     * @return {boolean}
411     */
412    function prevIs(parserState, lookingFor) {
413        let pos = parserState.pos;
414        while (pos > 0) {
415            const c = parserState.userQuery[pos - 1];
416            if (c === lookingFor) {
417                return true;
418            } else if (!isWhitespace(c)) {
419                break;
420            }
421            pos -= 1;
422        }
423        return false;
424    }
425
426    /**
427     * Returns `true` if the last element in the `elems` argument has generics.
428     *
429     * @param {Array<QueryElement>} elems
430     * @param {ParserState} parserState
431     *
432     * @return {boolean}
433     */
434    function isLastElemGeneric(elems, parserState) {
435        return (elems.length > 0 && elems[elems.length - 1].generics.length > 0) ||
436            prevIs(parserState, ">");
437    }
438
439    /**
440     * Increase current parser position until it doesn't find a whitespace anymore.
441     *
442     * @param {ParserState} parserState
443     */
444    function skipWhitespace(parserState) {
445        while (parserState.pos < parserState.userQuery.length) {
446            const c = parserState.userQuery[parserState.pos];
447            if (!isWhitespace(c)) {
448                break;
449            }
450            parserState.pos += 1;
451        }
452    }
453
454    /**
455     * @param {ParsedQuery} query
456     * @param {ParserState} parserState
457     * @param {string} name                  - Name of the query element.
458     * @param {Array<QueryElement>} generics - List of generics of this query element.
459     *
460     * @return {QueryElement}                - The newly created `QueryElement`.
461     */
462    function createQueryElement(query, parserState, name, generics, isInGenerics) {
463        const path = name.trim();
464        if (path.length === 0 && generics.length === 0) {
465            throw ["Unexpected ", parserState.userQuery[parserState.pos]];
466        } else if (path === "*") {
467            throw ["Unexpected ", "*"];
468        }
469        if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {
470            throw ["Cannot have more than one element if you use quotes"];
471        }
472        const typeFilter = parserState.typeFilter;
473        parserState.typeFilter = null;
474        if (name === "!") {
475            if (typeFilter !== null && typeFilter !== "primitive") {
476                throw [
477                    "Invalid search type: primitive never type ",
478                    "!",
479                    " and ",
480                    typeFilter,
481                    " both specified",
482                ];
483            }
484            if (generics.length !== 0) {
485                throw [
486                    "Never type ",
487                    "!",
488                    " does not accept generic parameters",
489                ];
490            }
491            return {
492                name: "never",
493                id: -1,
494                fullPath: ["never"],
495                pathWithoutLast: [],
496                pathLast: "never",
497                generics: [],
498                typeFilter: "primitive",
499            };
500        }
501        if (path.startsWith("::")) {
502            throw ["Paths cannot start with ", "::"];
503        } else if (path.endsWith("::")) {
504            throw ["Paths cannot end with ", "::"];
505        } else if (path.includes("::::")) {
506            throw ["Unexpected ", "::::"];
507        } else if (path.includes(" ::")) {
508            throw ["Unexpected ", " ::"];
509        } else if (path.includes(":: ")) {
510            throw ["Unexpected ", ":: "];
511        }
512        const pathSegments = path.split(/::|\s+/);
513        // In case we only have something like `<p>`, there is no name.
514        if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === "")) {
515            if (generics.length > 0 || prevIs(parserState, ">")) {
516                throw ["Found generics without a path"];
517            } else {
518                throw ["Unexpected ", parserState.userQuery[parserState.pos]];
519            }
520        }
521        for (const [i, pathSegment] of pathSegments.entries()) {
522            if (pathSegment === "!") {
523                if (i !== 0) {
524                    throw ["Never type ", "!", " is not associated item"];
525                }
526                pathSegments[i] = "never";
527            }
528        }
529        parserState.totalElems += 1;
530        if (isInGenerics) {
531            parserState.genericsElems += 1;
532        }
533        return {
534            name: name.trim(),
535            id: -1,
536            fullPath: pathSegments,
537            pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
538            pathLast: pathSegments[pathSegments.length - 1],
539            generics: generics,
540            typeFilter,
541        };
542    }
543
544    /**
545     * This function goes through all characters until it reaches an invalid ident character or the
546     * end of the query. It returns the position of the last character of the ident.
547     *
548     * @param {ParserState} parserState
549     *
550     * @return {integer}
551     */
552    function getIdentEndPosition(parserState) {
553        const start = parserState.pos;
554        let end = parserState.pos;
555        let foundExclamation = -1;
556        while (parserState.pos < parserState.length) {
557            const c = parserState.userQuery[parserState.pos];
558            if (!isIdentCharacter(c)) {
559                if (c === "!") {
560                    if (foundExclamation !== -1) {
561                        throw ["Cannot have more than one ", "!", " in an ident"];
562                    } else if (parserState.pos + 1 < parserState.length &&
563                        isIdentCharacter(parserState.userQuery[parserState.pos + 1])
564                    ) {
565                        throw ["Unexpected ", "!", ": it can only be at the end of an ident"];
566                    }
567                    foundExclamation = parserState.pos;
568                } else if (isErrorCharacter(c)) {
569                    throw ["Unexpected ", c];
570                } else if (isPathSeparator(c)) {
571                    if (c === ":") {
572                        if (!isPathStart(parserState)) {
573                            break;
574                        }
575                        // Skip current ":".
576                        parserState.pos += 1;
577                    } else {
578                        while (parserState.pos + 1 < parserState.length) {
579                            const next_c = parserState.userQuery[parserState.pos + 1];
580                            if (!isWhitespace(next_c)) {
581                                break;
582                            }
583                            parserState.pos += 1;
584                        }
585                    }
586                    if (foundExclamation !== -1) {
587                        if (foundExclamation !== start &&
588                            isIdentCharacter(parserState.userQuery[foundExclamation - 1])
589                        ) {
590                            throw ["Cannot have associated items in macros"];
591                        } else {
592                            // while the never type has no associated macros, we still
593                            // can parse a path like that
594                            foundExclamation = -1;
595                        }
596                    }
597                } else if (
598                    c === "[" ||
599                    isStopCharacter(c) ||
600                    isSpecialStartCharacter(c) ||
601                    isSeparatorCharacter(c)
602                ) {
603                    break;
604                } else {
605                    throw ["Unexpected ", c];
606                }
607            }
608            parserState.pos += 1;
609            end = parserState.pos;
610        }
611        // if start == end - 1, we got the never type
612        if (foundExclamation !== -1 &&
613            foundExclamation !== start &&
614            isIdentCharacter(parserState.userQuery[foundExclamation - 1])
615        ) {
616            if (parserState.typeFilter === null) {
617                parserState.typeFilter = "macro";
618            } else if (parserState.typeFilter !== "macro") {
619                throw [
620                    "Invalid search type: macro ",
621                    "!",
622                    " and ",
623                    parserState.typeFilter,
624                    " both specified",
625                ];
626            }
627            end = foundExclamation;
628        }
629        return end;
630    }
631
632    /**
633     * @param {ParsedQuery} query
634     * @param {ParserState} parserState
635     * @param {Array<QueryElement>} elems - This is where the new {QueryElement} will be added.
636     * @param {boolean} isInGenerics
637     */
638    function getNextElem(query, parserState, elems, isInGenerics) {
639        const generics = [];
640
641        skipWhitespace(parserState);
642        let start = parserState.pos;
643        let end;
644        if (parserState.userQuery[parserState.pos] === "[") {
645            parserState.pos += 1;
646            getItemsBefore(query, parserState, generics, "]");
647            const typeFilter = parserState.typeFilter;
648            if (typeFilter !== null && typeFilter !== "primitive") {
649                throw [
650                    "Invalid search type: primitive ",
651                    "[]",
652                    " and ",
653                    typeFilter,
654                    " both specified",
655                ];
656            }
657            parserState.typeFilter = null;
658            parserState.totalElems += 1;
659            if (isInGenerics) {
660                parserState.genericsElems += 1;
661            }
662            elems.push({
663                name: "[]",
664                id: -1,
665                fullPath: ["[]"],
666                pathWithoutLast: [],
667                pathLast: "[]",
668                generics,
669                typeFilter: "primitive",
670            });
671        } else {
672            const isStringElem = parserState.userQuery[start] === "\"";
673            // We handle the strings on their own mostly to make code easier to follow.
674            if (isStringElem) {
675                start += 1;
676                getStringElem(query, parserState, isInGenerics);
677                end = parserState.pos - 1;
678            } else {
679                end = getIdentEndPosition(parserState);
680            }
681            if (parserState.pos < parserState.length &&
682                parserState.userQuery[parserState.pos] === "<"
683            ) {
684                if (start >= end) {
685                    throw ["Found generics without a path"];
686                }
687                parserState.pos += 1;
688                getItemsBefore(query, parserState, generics, ">");
689            }
690            if (isStringElem) {
691                skipWhitespace(parserState);
692            }
693            if (start >= end && generics.length === 0) {
694                return;
695            }
696            elems.push(
697                createQueryElement(
698                    query,
699                    parserState,
700                    parserState.userQuery.slice(start, end),
701                    generics,
702                    isInGenerics
703                )
704            );
705        }
706    }
707
708    /**
709     * This function parses the next query element until it finds `endChar`, calling `getNextElem`
710     * to collect each element.
711     *
712     * If there is no `endChar`, this function will implicitly stop at the end without raising an
713     * error.
714     *
715     * @param {ParsedQuery} query
716     * @param {ParserState} parserState
717     * @param {Array<QueryElement>} elems - This is where the new {QueryElement} will be added.
718     * @param {string} endChar            - This function will stop when it'll encounter this
719     *                                      character.
720     */
721    function getItemsBefore(query, parserState, elems, endChar) {
722        let foundStopChar = true;
723        let start = parserState.pos;
724
725        // If this is a generic, keep the outer item's type filter around.
726        const oldTypeFilter = parserState.typeFilter;
727        parserState.typeFilter = null;
728
729        let extra = "";
730        if (endChar === ">") {
731            extra = "<";
732        } else if (endChar === "]") {
733            extra = "[";
734        } else if (endChar === "") {
735            extra = "->";
736        } else {
737            extra = endChar;
738        }
739
740        while (parserState.pos < parserState.length) {
741            const c = parserState.userQuery[parserState.pos];
742            if (c === endChar) {
743                break;
744            } else if (isSeparatorCharacter(c)) {
745                parserState.pos += 1;
746                foundStopChar = true;
747                continue;
748            } else if (c === ":" && isPathStart(parserState)) {
749                throw ["Unexpected ", "::", ": paths cannot start with ", "::"];
750            }  else if (c === ":") {
751                if (parserState.typeFilter !== null) {
752                    throw ["Unexpected ", ":"];
753                }
754                if (elems.length === 0) {
755                    throw ["Expected type filter before ", ":"];
756                } else if (query.literalSearch) {
757                    throw ["Cannot use quotes on type filter"];
758                }
759                // The type filter doesn't count as an element since it's a modifier.
760                const typeFilterElem = elems.pop();
761                checkExtraTypeFilterCharacters(start, parserState);
762                parserState.typeFilter = typeFilterElem.name;
763                parserState.pos += 1;
764                parserState.totalElems -= 1;
765                query.literalSearch = false;
766                foundStopChar = true;
767                continue;
768            } else if (isEndCharacter(c)) {
769                throw ["Unexpected ", c, " after ", extra];
770            }
771            if (!foundStopChar) {
772                let extra = [];
773                if (isLastElemGeneric(query.elems, parserState)) {
774                    extra = [" after ", ">"];
775                } else if (prevIs(parserState, "\"")) {
776                    throw ["Cannot have more than one element if you use quotes"];
777                }
778                if (endChar !== "") {
779                    throw [
780                        "Expected ",
781                        ",",
782                        " or ",
783                        endChar,
784                        ...extra,
785                        ", found ",
786                        c,
787                    ];
788                }
789                throw [
790                    "Expected ",
791                    ",",
792                    ...extra,
793                    ", found ",
794                    c,
795                ];
796            }
797            const posBefore = parserState.pos;
798            start = parserState.pos;
799            getNextElem(query, parserState, elems, endChar !== "");
800            if (endChar !== "" && parserState.pos >= parserState.length) {
801                throw ["Unclosed ", extra];
802            }
803            // This case can be encountered if `getNextElem` encountered a "stop character" right
804            // from the start. For example if you have `,,` or `<>`. In this case, we simply move up
805            // the current position to continue the parsing.
806            if (posBefore === parserState.pos) {
807                parserState.pos += 1;
808            }
809            foundStopChar = false;
810        }
811        if (parserState.pos >= parserState.length && endChar !== "") {
812            throw ["Unclosed ", extra];
813        }
814        // We are either at the end of the string or on the `endChar` character, let's move forward
815        // in any case.
816        parserState.pos += 1;
817
818        parserState.typeFilter = oldTypeFilter;
819    }
820
821    /**
822     * Checks that the type filter doesn't have unwanted characters like `<>` (which are ignored
823     * if empty).
824     *
825     * @param {ParserState} parserState
826     */
827    function checkExtraTypeFilterCharacters(start, parserState) {
828        const query = parserState.userQuery.slice(start, parserState.pos).trim();
829
830        for (const c in query) {
831            if (!isIdentCharacter(query[c])) {
832                throw [
833                    "Unexpected ",
834                    query[c],
835                    " in type filter (before ",
836                    ":",
837                    ")",
838                ];
839            }
840        }
841    }
842
843    /**
844     * Parses the provided `query` input to fill `parserState`. If it encounters an error while
845     * parsing `query`, it'll throw an error.
846     *
847     * @param {ParsedQuery} query
848     * @param {ParserState} parserState
849     */
850    function parseInput(query, parserState) {
851        let foundStopChar = true;
852        let start = parserState.pos;
853
854        while (parserState.pos < parserState.length) {
855            const c = parserState.userQuery[parserState.pos];
856            if (isStopCharacter(c)) {
857                foundStopChar = true;
858                if (isSeparatorCharacter(c)) {
859                    parserState.pos += 1;
860                    continue;
861                } else if (c === "-" || c === ">") {
862                    if (isReturnArrow(parserState)) {
863                        break;
864                    }
865                    throw ["Unexpected ", c, " (did you mean ", "->", "?)"];
866                }
867                throw ["Unexpected ", c];
868            } else if (c === ":" && !isPathStart(parserState)) {
869                if (parserState.typeFilter !== null) {
870                    throw [
871                        "Unexpected ",
872                        ":",
873                        " (expected path after type filter ",
874                        parserState.typeFilter + ":",
875                        ")",
876                    ];
877                } else if (query.elems.length === 0) {
878                    throw ["Expected type filter before ", ":"];
879                } else if (query.literalSearch) {
880                    throw ["Cannot use quotes on type filter"];
881                }
882                // The type filter doesn't count as an element since it's a modifier.
883                const typeFilterElem = query.elems.pop();
884                checkExtraTypeFilterCharacters(start, parserState);
885                parserState.typeFilter = typeFilterElem.name;
886                parserState.pos += 1;
887                parserState.totalElems -= 1;
888                query.literalSearch = false;
889                foundStopChar = true;
890                continue;
891            } else if (isWhitespace(c)) {
892                skipWhitespace(parserState);
893                continue;
894            }
895            if (!foundStopChar) {
896                let extra = "";
897                if (isLastElemGeneric(query.elems, parserState)) {
898                    extra = [" after ", ">"];
899                } else if (prevIs(parserState, "\"")) {
900                    throw ["Cannot have more than one element if you use quotes"];
901                }
902                if (parserState.typeFilter !== null) {
903                    throw [
904                        "Expected ",
905                        ",",
906                        " or ",
907                        "->",
908                        ...extra,
909                        ", found ",
910                        c,
911                    ];
912                }
913                throw [
914                    "Expected ",
915                    ",",
916                    ", ",
917                    ":",
918                    " or ",
919                    "->",
920                    ...extra,
921                    ", found ",
922                    c,
923                ];
924            }
925            const before = query.elems.length;
926            start = parserState.pos;
927            getNextElem(query, parserState, query.elems, false);
928            if (query.elems.length === before) {
929                // Nothing was added, weird... Let's increase the position to not remain stuck.
930                parserState.pos += 1;
931            }
932            foundStopChar = false;
933        }
934        if (parserState.typeFilter !== null) {
935            throw [
936                "Unexpected ",
937                ":",
938                " (expected path after type filter ",
939                parserState.typeFilter + ":",
940                ")",
941            ];
942        }
943        while (parserState.pos < parserState.length) {
944            if (isReturnArrow(parserState)) {
945                parserState.pos += 2;
946                skipWhitespace(parserState);
947                // Get returned elements.
948                getItemsBefore(query, parserState, query.returned, "");
949                // Nothing can come afterward!
950                if (query.returned.length === 0) {
951                    throw ["Expected at least one item after ", "->"];
952                }
953                break;
954            } else {
955                parserState.pos += 1;
956            }
957        }
958    }
959
960    /**
961     * Takes the user search input and returns an empty `ParsedQuery`.
962     *
963     * @param {string} userQuery
964     *
965     * @return {ParsedQuery}
966     */
967    function newParsedQuery(userQuery) {
968        return {
969            original: userQuery,
970            userQuery: userQuery.toLowerCase(),
971            elems: [],
972            returned: [],
973            // Total number of "top" elements (does not include generics).
974            foundElems: 0,
975            literalSearch: false,
976            error: null,
977            correction: null,
978        };
979    }
980
981    /**
982     * Build an URL with search parameters.
983     *
984     * @param {string} search            - The current search being performed.
985     * @param {string|null} filterCrates - The current filtering crate (if any).
986     *
987     * @return {string}
988     */
989    function buildUrl(search, filterCrates) {
990        let extra = "?search=" + encodeURIComponent(search);
991
992        if (filterCrates !== null) {
993            extra += "&filter-crate=" + encodeURIComponent(filterCrates);
994        }
995        return getNakedUrl() + extra + window.location.hash;
996    }
997
998    /**
999     * Return the filtering crate or `null` if there is none.
1000     *
1001     * @return {string|null}
1002     */
1003    function getFilterCrates() {
1004        const elem = document.getElementById("crate-search");
1005
1006        if (elem &&
1007            elem.value !== "all crates" &&
1008            hasOwnPropertyRustdoc(rawSearchIndex, elem.value)
1009        ) {
1010            return elem.value;
1011        }
1012        return null;
1013    }
1014
1015    /**
1016     * Parses the query.
1017     *
1018     * The supported syntax by this parser is as follow:
1019     *
1020     * ident = *(ALPHA / DIGIT / "_")
1021     * path = ident *(DOUBLE-COLON/{WS} ident) [!]
1022     * slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
1023     * arg = [type-filter *WS COLON *WS] (path [generics] / slice)
1024     * type-sep = *WS COMMA *(COMMA)
1025     * nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
1026     * generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
1027     *            CLOSE-ANGLE-BRACKET
1028     * return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
1029     *
1030     * exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
1031     * type-search = [ nonempty-arg-list ] [ return-args ]
1032     *
1033     * query = *WS (exact-search / type-search) *WS
1034     *
1035     * type-filter = (
1036     *     "mod" /
1037     *     "externcrate" /
1038     *     "import" /
1039     *     "struct" /
1040     *     "enum" /
1041     *     "fn" /
1042     *     "type" /
1043     *     "static" /
1044     *     "trait" /
1045     *     "impl" /
1046     *     "tymethod" /
1047     *     "method" /
1048     *     "structfield" /
1049     *     "variant" /
1050     *     "macro" /
1051     *     "primitive" /
1052     *     "associatedtype" /
1053     *     "constant" /
1054     *     "associatedconstant" /
1055     *     "union" /
1056     *     "foreigntype" /
1057     *     "keyword" /
1058     *     "existential" /
1059     *     "attr" /
1060     *     "derive" /
1061     *     "traitalias")
1062     *
1063     * OPEN-ANGLE-BRACKET = "<"
1064     * CLOSE-ANGLE-BRACKET = ">"
1065     * OPEN-SQUARE-BRACKET = "["
1066     * CLOSE-SQUARE-BRACKET = "]"
1067     * COLON = ":"
1068     * DOUBLE-COLON = "::"
1069     * QUOTE = %x22
1070     * COMMA = ","
1071     * RETURN-ARROW = "->"
1072     *
1073     * ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
1074     * DIGIT = %x30-39
1075     * WS = %x09 / " "
1076     *
1077     * @param  {string} val     - The user query
1078     *
1079     * @return {ParsedQuery}    - The parsed query
1080     */
1081    function parseQuery(userQuery) {
1082        function convertTypeFilterOnElem(elem) {
1083            if (elem.typeFilter !== null) {
1084                let typeFilter = elem.typeFilter;
1085                if (typeFilter === "const") {
1086                    typeFilter = "constant";
1087                }
1088                elem.typeFilter = itemTypeFromName(typeFilter);
1089            } else {
1090                elem.typeFilter = NO_TYPE_FILTER;
1091            }
1092            for (const elem2 of elem.generics) {
1093                convertTypeFilterOnElem(elem2);
1094            }
1095        }
1096        userQuery = userQuery.trim();
1097        const parserState = {
1098            length: userQuery.length,
1099            pos: 0,
1100            // Total number of elements (includes generics).
1101            totalElems: 0,
1102            genericsElems: 0,
1103            typeFilter: null,
1104            userQuery: userQuery.toLowerCase(),
1105        };
1106        let query = newParsedQuery(userQuery);
1107
1108        try {
1109            parseInput(query, parserState);
1110            for (const elem of query.elems) {
1111                convertTypeFilterOnElem(elem);
1112            }
1113            for (const elem of query.returned) {
1114                convertTypeFilterOnElem(elem);
1115            }
1116        } catch (err) {
1117            query = newParsedQuery(userQuery);
1118            query.error = err;
1119            return query;
1120        }
1121
1122        if (!query.literalSearch) {
1123            // If there is more than one element in the query, we switch to literalSearch in any
1124            // case.
1125            query.literalSearch = parserState.totalElems > 1;
1126        }
1127        query.foundElems = query.elems.length + query.returned.length;
1128        return query;
1129    }
1130
1131    /**
1132     * Creates the query results.
1133     *
1134     * @param {Array<Result>} results_in_args
1135     * @param {Array<Result>} results_returned
1136     * @param {Array<Result>} results_others
1137     * @param {ParsedQuery} parsedQuery
1138     *
1139     * @return {ResultsTable}
1140     */
1141    function createQueryResults(results_in_args, results_returned, results_others, parsedQuery) {
1142        return {
1143            "in_args": results_in_args,
1144            "returned": results_returned,
1145            "others": results_others,
1146            "query": parsedQuery,
1147        };
1148    }
1149
1150    /**
1151     * Executes the parsed query and builds a {ResultsTable}.
1152     *
1153     * @param  {ParsedQuery} parsedQuery - The parsed user query
1154     * @param  {Object} searchWords      - The list of search words to query against
1155     * @param  {Object} [filterCrates]   - Crate to search in if defined
1156     * @param  {Object} [currentCrate]   - Current crate, to rank results from this crate higher
1157     *
1158     * @return {ResultsTable}
1159     */
1160    function execQuery(parsedQuery, searchWords, filterCrates, currentCrate) {
1161        const results_others = new Map(), results_in_args = new Map(),
1162            results_returned = new Map();
1163
1164        /**
1165         * Add extra data to result objects, and filter items that have been
1166         * marked for removal.
1167         *
1168         * @param {[ResultObject]} results
1169         * @returns {[ResultObject]}
1170         */
1171        function transformResults(results) {
1172            const duplicates = new Set();
1173            const out = [];
1174
1175            for (const result of results) {
1176                if (result.id > -1) {
1177                    const obj = searchIndex[result.id];
1178                    obj.dist = result.dist;
1179                    const res = buildHrefAndPath(obj);
1180                    obj.displayPath = pathSplitter(res[0]);
1181                    obj.fullPath = obj.displayPath + obj.name;
1182                    // To be sure than it some items aren't considered as duplicate.
1183                    obj.fullPath += "|" + obj.ty;
1184
1185                    if (duplicates.has(obj.fullPath)) {
1186                        continue;
1187                    }
1188                    duplicates.add(obj.fullPath);
1189
1190                    obj.href = res[1];
1191                    out.push(obj);
1192                    if (out.length >= MAX_RESULTS) {
1193                        break;
1194                    }
1195                }
1196            }
1197            return out;
1198        }
1199
1200        /**
1201         * This function takes a result map, and sorts it by various criteria, including edit
1202         * distance, substring match, and the crate it comes from.
1203         *
1204         * @param {Results} results
1205         * @param {boolean} isType
1206         * @param {string} preferredCrate
1207         * @returns {[ResultObject]}
1208         */
1209        function sortResults(results, isType, preferredCrate) {
1210            // if there are no results then return to default and fail
1211            if (results.size === 0) {
1212                return [];
1213            }
1214
1215            const userQuery = parsedQuery.userQuery;
1216            const result_list = [];
1217            for (const result of results.values()) {
1218                result.word = searchWords[result.id];
1219                result.item = searchIndex[result.id] || {};
1220                result_list.push(result);
1221            }
1222
1223            result_list.sort((aaa, bbb) => {
1224                let a, b;
1225
1226                // sort by exact match with regard to the last word (mismatch goes later)
1227                a = (aaa.word !== userQuery);
1228                b = (bbb.word !== userQuery);
1229                if (a !== b) {
1230                    return a - b;
1231                }
1232
1233                // sort by index of keyword in item name (no literal occurrence goes later)
1234                a = (aaa.index < 0);
1235                b = (bbb.index < 0);
1236                if (a !== b) {
1237                    return a - b;
1238                }
1239
1240                // Sort by distance in the path part, if specified
1241                // (less changes required to match means higher rankings)
1242                a = aaa.path_dist;
1243                b = bbb.path_dist;
1244                if (a !== b) {
1245                    return a - b;
1246                }
1247
1248                // (later literal occurrence, if any, goes later)
1249                a = aaa.index;
1250                b = bbb.index;
1251                if (a !== b) {
1252                    return a - b;
1253                }
1254
1255                // Sort by distance in the name part, the last part of the path
1256                // (less changes required to match means higher rankings)
1257                a = (aaa.dist);
1258                b = (bbb.dist);
1259                if (a !== b) {
1260                    return a - b;
1261                }
1262
1263                // sort deprecated items later
1264                a = aaa.item.deprecated;
1265                b = bbb.item.deprecated;
1266                if (a !== b) {
1267                    return a - b;
1268                }
1269
1270                // sort by crate (current crate comes first)
1271                a = (aaa.item.crate !== preferredCrate);
1272                b = (bbb.item.crate !== preferredCrate);
1273                if (a !== b) {
1274                    return a - b;
1275                }
1276
1277                // sort by item name length (longer goes later)
1278                a = aaa.word.length;
1279                b = bbb.word.length;
1280                if (a !== b) {
1281                    return a - b;
1282                }
1283
1284                // sort by item name (lexicographically larger goes later)
1285                a = aaa.word;
1286                b = bbb.word;
1287                if (a !== b) {
1288                    return (a > b ? +1 : -1);
1289                }
1290
1291                // special precedence for primitive and keyword pages
1292                if ((aaa.item.ty === TY_PRIMITIVE && bbb.item.ty !== TY_KEYWORD) ||
1293                    (aaa.item.ty === TY_KEYWORD && bbb.item.ty !== TY_PRIMITIVE)) {
1294                    return -1;
1295                }
1296                if ((bbb.item.ty === TY_PRIMITIVE && aaa.item.ty !== TY_PRIMITIVE) ||
1297                    (bbb.item.ty === TY_KEYWORD && aaa.item.ty !== TY_KEYWORD)) {
1298                    return 1;
1299                }
1300
1301                // sort by description (no description goes later)
1302                a = (aaa.item.desc === "");
1303                b = (bbb.item.desc === "");
1304                if (a !== b) {
1305                    return a - b;
1306                }
1307
1308                // sort by type (later occurrence in `itemTypes` goes later)
1309                a = aaa.item.ty;
1310                b = bbb.item.ty;
1311                if (a !== b) {
1312                    return a - b;
1313                }
1314
1315                // sort by path (lexicographically larger goes later)
1316                a = aaa.item.path;
1317                b = bbb.item.path;
1318                if (a !== b) {
1319                    return (a > b ? +1 : -1);
1320                }
1321
1322                // que sera, sera
1323                return 0;
1324            });
1325
1326            let nameSplit = null;
1327            if (parsedQuery.elems.length === 1) {
1328                const hasPath = typeof parsedQuery.elems[0].path === "undefined";
1329                nameSplit = hasPath ? null : parsedQuery.elems[0].path;
1330            }
1331
1332            for (const result of result_list) {
1333                // this validation does not make sense when searching by types
1334                if (result.dontValidate) {
1335                    continue;
1336                }
1337                const name = result.item.name.toLowerCase(),
1338                    path = result.item.path.toLowerCase(),
1339                    parent = result.item.parent;
1340
1341                if (!isType && !validateResult(name, path, nameSplit, parent)) {
1342                    result.id = -1;
1343                }
1344            }
1345            return transformResults(result_list);
1346        }
1347
1348        /**
1349         * This function checks generics in search query `queryElem` can all be found in the
1350         * search index (`fnType`),
1351         *
1352         * @param {FunctionType} fnType     - The object to check.
1353         * @param {QueryElement} queryElem  - The element from the parsed query.
1354         *
1355         * @return {boolean} - Returns true if a match, false otherwise.
1356         */
1357        function checkGenerics(fnType, queryElem) {
1358            return unifyFunctionTypes(fnType.generics, queryElem.generics);
1359        }
1360        /**
1361         * This function checks if a list of search query `queryElems` can all be found in the
1362         * search index (`fnTypes`).
1363         *
1364         * @param {Array<FunctionType>} fnTypes    - The objects to check.
1365         * @param {Array<QueryElement>} queryElems - The elements from the parsed query.
1366         *
1367         * @return {boolean} - Returns true if a match, false otherwise.
1368         */
1369        function unifyFunctionTypes(fnTypes, queryElems) {
1370            // This search engine implements order-agnostic unification. There
1371            // should be no missing duplicates (generics have "bag semantics"),
1372            // and the row is allowed to have extras.
1373            if (queryElems.length === 0) {
1374                return true;
1375            }
1376            if (!fnTypes || fnTypes.length === 0) {
1377                return false;
1378            }
1379            /**
1380             * @type Map<integer, QueryElement[]>
1381             */
1382            const queryElemSet = new Map();
1383            const addQueryElemToQueryElemSet = function addQueryElemToQueryElemSet(queryElem) {
1384                let currentQueryElemList;
1385                if (queryElemSet.has(queryElem.id)) {
1386                    currentQueryElemList = queryElemSet.get(queryElem.id);
1387                } else {
1388                    currentQueryElemList = [];
1389                    queryElemSet.set(queryElem.id, currentQueryElemList);
1390                }
1391                currentQueryElemList.push(queryElem);
1392            };
1393            for (const queryElem of queryElems) {
1394                addQueryElemToQueryElemSet(queryElem);
1395            }
1396            /**
1397             * @type Map<integer, FunctionType[]>
1398             */
1399            const fnTypeSet = new Map();
1400            const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) {
1401                // Pure generic, or an item that's not matched by any query elems.
1402                // Try [unboxing] it.
1403                //
1404                // [unboxing]:
1405                // http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
1406                const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice);
1407                if (fnType.id === -1 || !(
1408                    queryElemSet.has(fnType.id) ||
1409                    (fnType.id === typeNameIdOfSlice && queryContainsArrayOrSliceElem) ||
1410                    (fnType.id === typeNameIdOfArray && queryContainsArrayOrSliceElem)
1411                )) {
1412                    for (const innerFnType of fnType.generics) {
1413                        addFnTypeToFnTypeSet(innerFnType);
1414                    }
1415                    return;
1416                }
1417                let currentQueryElemList = queryElemSet.get(fnType.id) || [];
1418                let matchIdx = currentQueryElemList.findIndex(queryElem => {
1419                    return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
1420                        checkGenerics(fnType, queryElem);
1421                });
1422                if (matchIdx === -1 &&
1423                    (fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) &&
1424                    queryContainsArrayOrSliceElem
1425                ) {
1426                    currentQueryElemList = queryElemSet.get(typeNameIdOfArrayOrSlice) || [];
1427                    matchIdx = currentQueryElemList.findIndex(queryElem => {
1428                        return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
1429                            checkGenerics(fnType, queryElem);
1430                    });
1431                }
1432                // None of the query elems match the function type.
1433                // Try [unboxing] it.
1434                if (matchIdx === -1) {
1435                    for (const innerFnType of fnType.generics) {
1436                        addFnTypeToFnTypeSet(innerFnType);
1437                    }
1438                    return;
1439                }
1440                let currentFnTypeList;
1441                if (fnTypeSet.has(fnType.id)) {
1442                    currentFnTypeList = fnTypeSet.get(fnType.id);
1443                } else {
1444                    currentFnTypeList = [];
1445                    fnTypeSet.set(fnType.id, currentFnTypeList);
1446                }
1447                currentFnTypeList.push(fnType);
1448            };
1449            for (const fnType of fnTypes) {
1450                addFnTypeToFnTypeSet(fnType);
1451            }
1452            const doHandleQueryElemList = (currentFnTypeList, queryElemList) => {
1453                if (queryElemList.length === 0) {
1454                    return true;
1455                }
1456                // Multiple items in one list might match multiple items in another.
1457                // Since an item with fewer generics can match an item with more, we
1458                // need to check all combinations for a potential match.
1459                const queryElem = queryElemList.pop();
1460                const l = currentFnTypeList.length;
1461                for (let i = 0; i < l; i += 1) {
1462                    const fnType = currentFnTypeList[i];
1463                    if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
1464                        continue;
1465                    }
1466                    if (queryElem.generics.length === 0 || checkGenerics(fnType, queryElem)) {
1467                        currentFnTypeList.splice(i, 1);
1468                        const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
1469                        if (result) {
1470                            return true;
1471                        }
1472                        currentFnTypeList.splice(i, 0, fnType);
1473                    }
1474                }
1475                return false;
1476            };
1477            const handleQueryElemList = (id, queryElemList) => {
1478                if (!fnTypeSet.has(id)) {
1479                    if (id === typeNameIdOfArrayOrSlice) {
1480                        return handleQueryElemList(typeNameIdOfSlice, queryElemList) ||
1481                            handleQueryElemList(typeNameIdOfArray, queryElemList);
1482                    }
1483                    return false;
1484                }
1485                const currentFnTypeList = fnTypeSet.get(id);
1486                if (currentFnTypeList.length < queryElemList.length) {
1487                    // It's not possible for all the query elems to find a match.
1488                    return false;
1489                }
1490                const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
1491                if (result) {
1492                    // Found a solution.
1493                    // Any items that weren't used for it can be unboxed, and might form
1494                    // part of the solution for another item.
1495                    for (const innerFnType of currentFnTypeList) {
1496                        addFnTypeToFnTypeSet(innerFnType);
1497                    }
1498                    fnTypeSet.delete(id);
1499                }
1500                return result;
1501            };
1502            let queryElemSetSize = -1;
1503            while (queryElemSetSize !== queryElemSet.size) {
1504                queryElemSetSize = queryElemSet.size;
1505                for (const [id, queryElemList] of queryElemSet) {
1506                    if (handleQueryElemList(id, queryElemList)) {
1507                        queryElemSet.delete(id);
1508                    }
1509                }
1510            }
1511            return queryElemSetSize === 0;
1512        }
1513
1514        /**
1515          * This function checks if the object (`row`) matches the given type (`elem`) and its
1516          * generics (if any).
1517          *
1518          * @param {Array<FunctionType>} list
1519          * @param {QueryElement} elem    - The element from the parsed query.
1520          *
1521          * @return {boolean} - Returns true if found, false otherwise.
1522          */
1523        function checkIfInList(list, elem) {
1524            for (const entry of list) {
1525                if (checkType(entry, elem)) {
1526                    return true;
1527                }
1528            }
1529            return false;
1530        }
1531
1532        /**
1533          * This function checks if the object (`row`) matches the given type (`elem`) and its
1534          * generics (if any).
1535          *
1536          * @param {Row} row
1537          * @param {QueryElement} elem      - The element from the parsed query.
1538          *
1539          * @return {boolean} - Returns true if the type matches, false otherwise.
1540          */
1541        function checkType(row, elem) {
1542            if (row.id === -1) {
1543                // This is a pure "generic" search, no need to run other checks.
1544                return row.generics.length > 0 ? checkIfInList(row.generics, elem) : false;
1545            }
1546
1547            const matchesExact = row.id === elem.id;
1548            const matchesArrayOrSlice = elem.id === typeNameIdOfArrayOrSlice &&
1549                (row.id === typeNameIdOfSlice || row.id === typeNameIdOfArray);
1550
1551            if ((matchesExact || matchesArrayOrSlice) &&
1552                typePassesFilter(elem.typeFilter, row.ty)) {
1553                if (elem.generics.length > 0) {
1554                    return checkGenerics(row, elem);
1555                }
1556                return true;
1557            }
1558
1559            // If the current item does not match, try [unboxing] the generic.
1560            // [unboxing]:
1561            //   https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
1562            return checkIfInList(row.generics, elem);
1563        }
1564
1565        function checkPath(contains, ty, maxEditDistance) {
1566            if (contains.length === 0) {
1567                return 0;
1568            }
1569            let ret_dist = maxEditDistance + 1;
1570            const path = ty.path.split("::");
1571
1572            if (ty.parent && ty.parent.name) {
1573                path.push(ty.parent.name.toLowerCase());
1574            }
1575
1576            const length = path.length;
1577            const clength = contains.length;
1578            if (clength > length) {
1579                return maxEditDistance + 1;
1580            }
1581            for (let i = 0; i < length; ++i) {
1582                if (i + clength > length) {
1583                    break;
1584                }
1585                let dist_total = 0;
1586                let aborted = false;
1587                for (let x = 0; x < clength; ++x) {
1588                    const dist = editDistance(path[i + x], contains[x], maxEditDistance);
1589                    if (dist > maxEditDistance) {
1590                        aborted = true;
1591                        break;
1592                    }
1593                    dist_total += dist;
1594                }
1595                if (!aborted) {
1596                    ret_dist = Math.min(ret_dist, Math.round(dist_total / clength));
1597                }
1598            }
1599            return ret_dist;
1600        }
1601
1602        function typePassesFilter(filter, type) {
1603            // No filter or Exact mach
1604            if (filter <= NO_TYPE_FILTER || filter === type) return true;
1605
1606            // Match related items
1607            const name = itemTypes[type];
1608            switch (itemTypes[filter]) {
1609                case "constant":
1610                    return name === "associatedconstant";
1611                case "fn":
1612                    return name === "method" || name === "tymethod";
1613                case "type":
1614                    return name === "primitive" || name === "associatedtype";
1615                case "trait":
1616                    return name === "traitalias";
1617            }
1618
1619            // No match
1620            return false;
1621        }
1622
1623        function createAliasFromItem(item) {
1624            return {
1625                crate: item.crate,
1626                name: item.name,
1627                path: item.path,
1628                desc: item.desc,
1629                ty: item.ty,
1630                parent: item.parent,
1631                type: item.type,
1632                is_alias: true,
1633                deprecated: item.deprecated,
1634            };
1635        }
1636
1637        function handleAliases(ret, query, filterCrates, currentCrate) {
1638            const lowerQuery = query.toLowerCase();
1639            // We separate aliases and crate aliases because we want to have current crate
1640            // aliases to be before the others in the displayed results.
1641            const aliases = [];
1642            const crateAliases = [];
1643            if (filterCrates !== null) {
1644                if (ALIASES.has(filterCrates) && ALIASES.get(filterCrates).has(lowerQuery)) {
1645                    const query_aliases = ALIASES.get(filterCrates).get(lowerQuery);
1646                    for (const alias of query_aliases) {
1647                        aliases.push(createAliasFromItem(searchIndex[alias]));
1648                    }
1649                }
1650            } else {
1651                for (const [crate, crateAliasesIndex] of ALIASES) {
1652                    if (crateAliasesIndex.has(lowerQuery)) {
1653                        const pushTo = crate === currentCrate ? crateAliases : aliases;
1654                        const query_aliases = crateAliasesIndex.get(lowerQuery);
1655                        for (const alias of query_aliases) {
1656                            pushTo.push(createAliasFromItem(searchIndex[alias]));
1657                        }
1658                    }
1659                }
1660            }
1661
1662            const sortFunc = (aaa, bbb) => {
1663                if (aaa.path < bbb.path) {
1664                    return 1;
1665                } else if (aaa.path === bbb.path) {
1666                    return 0;
1667                }
1668                return -1;
1669            };
1670            crateAliases.sort(sortFunc);
1671            aliases.sort(sortFunc);
1672
1673            const pushFunc = alias => {
1674                alias.alias = query;
1675                const res = buildHrefAndPath(alias);
1676                alias.displayPath = pathSplitter(res[0]);
1677                alias.fullPath = alias.displayPath + alias.name;
1678                alias.href = res[1];
1679
1680                ret.others.unshift(alias);
1681                if (ret.others.length > MAX_RESULTS) {
1682                    ret.others.pop();
1683                }
1684            };
1685
1686            aliases.forEach(pushFunc);
1687            crateAliases.forEach(pushFunc);
1688        }
1689
1690        /**
1691         * This function adds the given result into the provided `results` map if it matches the
1692         * following condition:
1693         *
1694         * * If it is a "literal search" (`parsedQuery.literalSearch`), then `dist` must be 0.
1695         * * If it is not a "literal search", `dist` must be <= `maxEditDistance`.
1696         *
1697         * The `results` map contains information which will be used to sort the search results:
1698         *
1699         * * `fullId` is a `string`` used as the key of the object we use for the `results` map.
1700         * * `id` is the index in both `searchWords` and `searchIndex` arrays for this element.
1701         * * `index` is an `integer`` used to sort by the position of the word in the item's name.
1702         * * `dist` is the main metric used to sort the search results.
1703         * * `path_dist` is zero if a single-component search query is used, otherwise it's the
1704         *   distance computed for everything other than the last path component.
1705         *
1706         * @param {Results} results
1707         * @param {string} fullId
1708         * @param {integer} id
1709         * @param {integer} index
1710         * @param {integer} dist
1711         * @param {integer} path_dist
1712         */
1713        function addIntoResults(results, fullId, id, index, dist, path_dist, maxEditDistance) {
1714            const inBounds = dist <= maxEditDistance || index !== -1;
1715            if (dist === 0 || (!parsedQuery.literalSearch && inBounds)) {
1716                if (results.has(fullId)) {
1717                    const result = results.get(fullId);
1718                    if (result.dontValidate || result.dist <= dist) {
1719                        return;
1720                    }
1721                }
1722                results.set(fullId, {
1723                    id: id,
1724                    index: index,
1725                    dontValidate: parsedQuery.literalSearch,
1726                    dist: dist,
1727                    path_dist: path_dist,
1728                });
1729            }
1730        }
1731
1732        /**
1733         * This function is called in case the query is only one element (with or without generics).
1734         * This element will be compared to arguments' and returned values' items and also to items.
1735         *
1736         * Other important thing to note: since there is only one element, we use edit
1737         * distance for name comparisons.
1738         *
1739         * @param {Row} row
1740         * @param {integer} pos              - Position in the `searchIndex`.
1741         * @param {QueryElement} elem        - The element from the parsed query.
1742         * @param {Results} results_others   - Unqualified results (not in arguments nor in
1743         *                                     returned values).
1744         * @param {Results} results_in_args  - Matching arguments results.
1745         * @param {Results} results_returned - Matching returned arguments results.
1746         */
1747        function handleSingleArg(
1748            row,
1749            pos,
1750            elem,
1751            results_others,
1752            results_in_args,
1753            results_returned,
1754            maxEditDistance
1755        ) {
1756            if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
1757                return;
1758            }
1759            let index = -1, path_dist = 0;
1760            const fullId = row.id;
1761            const searchWord = searchWords[pos];
1762
1763            const in_args = row.type && row.type.inputs && checkIfInList(row.type.inputs, elem);
1764            if (in_args) {
1765                // path_dist is 0 because no parent path information is currently stored
1766                // in the search index
1767                addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance);
1768            }
1769            const returned = row.type && row.type.output && checkIfInList(row.type.output, elem);
1770            if (returned) {
1771                addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance);
1772            }
1773
1774            if (!typePassesFilter(elem.typeFilter, row.ty)) {
1775                return;
1776            }
1777
1778            const row_index = row.normalizedName.indexOf(elem.pathLast);
1779            const word_index = searchWord.indexOf(elem.pathLast);
1780
1781            // lower indexes are "better" matches
1782            // rank based on the "best" match
1783            if (row_index === -1) {
1784                index = word_index;
1785            } else if (word_index === -1) {
1786                index = row_index;
1787            } else if (word_index < row_index) {
1788                index = word_index;
1789            } else {
1790                index = row_index;
1791            }
1792
1793            if (elem.fullPath.length > 1) {
1794                path_dist = checkPath(elem.pathWithoutLast, row, maxEditDistance);
1795                if (path_dist > maxEditDistance) {
1796                    return;
1797                }
1798            }
1799
1800            if (parsedQuery.literalSearch) {
1801                if (searchWord === elem.name) {
1802                    addIntoResults(results_others, fullId, pos, index, 0, path_dist);
1803                }
1804                return;
1805            }
1806
1807            const dist = editDistance(searchWord, elem.pathLast, maxEditDistance);
1808
1809            if (index === -1 && dist + path_dist > maxEditDistance) {
1810                return;
1811            }
1812
1813            addIntoResults(results_others, fullId, pos, index, dist, path_dist, maxEditDistance);
1814        }
1815
1816        /**
1817         * This function is called in case the query has more than one element. In this case, it'll
1818         * try to match the items which validates all the elements. For `aa -> bb` will look for
1819         * functions which have a parameter `aa` and has `bb` in its returned values.
1820         *
1821         * @param {Row} row
1822         * @param {integer} pos      - Position in the `searchIndex`.
1823         * @param {Object} results
1824         */
1825        function handleArgs(row, pos, results) {
1826            if (!row || (filterCrates !== null && row.crate !== filterCrates) || !row.type) {
1827                return;
1828            }
1829
1830            // If the result is too "bad", we return false and it ends this search.
1831            if (!unifyFunctionTypes(row.type.inputs, parsedQuery.elems)) {
1832                return;
1833            }
1834            if (!unifyFunctionTypes(row.type.output, parsedQuery.returned)) {
1835                return;
1836            }
1837
1838            addIntoResults(results, row.id, pos, 0, 0, 0, Number.MAX_VALUE);
1839        }
1840
1841        function innerRunQuery() {
1842            let elem, i, nSearchWords, in_returned, row;
1843
1844            let queryLen = 0;
1845            for (const elem of parsedQuery.elems) {
1846                queryLen += elem.name.length;
1847            }
1848            for (const elem of parsedQuery.returned) {
1849                queryLen += elem.name.length;
1850            }
1851            const maxEditDistance = Math.floor(queryLen / 3);
1852
1853            /**
1854             * Convert names to ids in parsed query elements.
1855             * This is not used for the "In Names" tab, but is used for the
1856             * "In Params", "In Returns", and "In Function Signature" tabs.
1857             *
1858             * If there is no matching item, but a close-enough match, this
1859             * function also that correction.
1860             *
1861             * See `buildTypeMapIndex` for more information.
1862             *
1863             * @param {QueryElement} elem
1864             */
1865            function convertNameToId(elem) {
1866                if (typeNameIdMap.has(elem.name)) {
1867                    elem.id = typeNameIdMap.get(elem.name);
1868                } else if (!parsedQuery.literalSearch) {
1869                    let match = -1;
1870                    let matchDist = maxEditDistance + 1;
1871                    let matchName = "";
1872                    for (const [name, id] of typeNameIdMap) {
1873                        const dist = editDistance(name, elem.name, maxEditDistance);
1874                        if (dist <= matchDist && dist <= maxEditDistance) {
1875                            if (dist === matchDist && matchName > name) {
1876                                continue;
1877                            }
1878                            match = id;
1879                            matchDist = dist;
1880                            matchName = name;
1881                        }
1882                    }
1883                    if (match !== -1) {
1884                        parsedQuery.correction = matchName;
1885                    }
1886                    elem.id = match;
1887                }
1888                for (const elem2 of elem.generics) {
1889                    convertNameToId(elem2);
1890                }
1891            }
1892
1893            for (const elem of parsedQuery.elems) {
1894                convertNameToId(elem);
1895            }
1896            for (const elem of parsedQuery.returned) {
1897                convertNameToId(elem);
1898            }
1899
1900            if (parsedQuery.foundElems === 1) {
1901                if (parsedQuery.elems.length === 1) {
1902                    elem = parsedQuery.elems[0];
1903                    for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
1904                        // It means we want to check for this element everywhere (in names, args and
1905                        // returned).
1906                        handleSingleArg(
1907                            searchIndex[i],
1908                            i,
1909                            elem,
1910                            results_others,
1911                            results_in_args,
1912                            results_returned,
1913                            maxEditDistance
1914                        );
1915                    }
1916                } else if (parsedQuery.returned.length === 1) {
1917                    // We received one returned argument to check, so looking into returned values.
1918                    elem = parsedQuery.returned[0];
1919                    for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
1920                        row = searchIndex[i];
1921                        in_returned = row.type &&
1922                            unifyFunctionTypes(row.type.output, parsedQuery.returned);
1923                        if (in_returned) {
1924                            addIntoResults(
1925                                results_others,
1926                                row.id,
1927                                i,
1928                                -1,
1929                                0,
1930                                Number.MAX_VALUE
1931                            );
1932                        }
1933                    }
1934                }
1935            } else if (parsedQuery.foundElems > 0) {
1936                for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
1937                    handleArgs(searchIndex[i], i, results_others);
1938                }
1939            }
1940        }
1941
1942        if (parsedQuery.error === null) {
1943            innerRunQuery();
1944        }
1945
1946        const ret = createQueryResults(
1947            sortResults(results_in_args, true, currentCrate),
1948            sortResults(results_returned, true, currentCrate),
1949            sortResults(results_others, false, currentCrate),
1950            parsedQuery);
1951        handleAliases(ret, parsedQuery.original.replace(/"/g, ""), filterCrates, currentCrate);
1952        if (parsedQuery.error !== null && ret.others.length !== 0) {
1953            // It means some doc aliases were found so let's "remove" the error!
1954            ret.query.error = null;
1955        }
1956        return ret;
1957    }
1958
1959    /**
1960     * Validate performs the following boolean logic. For example:
1961     * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
1962     * exists in (name || path || parent) OR => ("file" && "open") exists in
1963     * (name || path )
1964     *
1965     * This could be written functionally, but I wanted to minimise
1966     * functions on stack.
1967     *
1968     * @param  {string} name   - The name of the result
1969     * @param  {string} path   - The path of the result
1970     * @param  {string} keys   - The keys to be used (["file", "open"])
1971     * @param  {Object} parent - The parent of the result
1972     *
1973     * @return {boolean}       - Whether the result is valid or not
1974     */
1975    function validateResult(name, path, keys, parent, maxEditDistance) {
1976        if (!keys || !keys.length) {
1977            return true;
1978        }
1979        for (const key of keys) {
1980            // each check is for validation so we negate the conditions and invalidate
1981            if (!(
1982                // check for an exact name match
1983                name.indexOf(key) > -1 ||
1984                // then an exact path match
1985                path.indexOf(key) > -1 ||
1986                // next if there is a parent, check for exact parent match
1987                (parent !== undefined && parent.name !== undefined &&
1988                    parent.name.toLowerCase().indexOf(key) > -1) ||
1989                // lastly check to see if the name was an editDistance match
1990                editDistance(name, key, maxEditDistance) <= maxEditDistance)) {
1991                return false;
1992            }
1993        }
1994        return true;
1995    }
1996
1997    function nextTab(direction) {
1998        const next = (searchState.currentTab + direction + 3) % searchState.focusedByTab.length;
1999        searchState.focusedByTab[searchState.currentTab] = document.activeElement;
2000        printTab(next);
2001        focusSearchResult();
2002    }
2003
2004    // Focus the first search result on the active tab, or the result that
2005    // was focused last time this tab was active.
2006    function focusSearchResult() {
2007        const target = searchState.focusedByTab[searchState.currentTab] ||
2008            document.querySelectorAll(".search-results.active a").item(0) ||
2009            document.querySelectorAll("#search-tabs button").item(searchState.currentTab);
2010        searchState.focusedByTab[searchState.currentTab] = null;
2011        if (target) {
2012            target.focus();
2013        }
2014    }
2015
2016    function buildHrefAndPath(item) {
2017        let displayPath;
2018        let href;
2019        const type = itemTypes[item.ty];
2020        const name = item.name;
2021        let path = item.path;
2022
2023        if (type === "mod") {
2024            displayPath = path + "::";
2025            href = ROOT_PATH + path.replace(/::/g, "/") + "/" +
2026                name + "/index.html";
2027        } else if (type === "import") {
2028            displayPath = item.path + "::";
2029            href = ROOT_PATH + item.path.replace(/::/g, "/") + "/index.html#reexport." + name;
2030        } else if (type === "primitive" || type === "keyword") {
2031            displayPath = "";
2032            href = ROOT_PATH + path.replace(/::/g, "/") +
2033                "/" + type + "." + name + ".html";
2034        } else if (type === "externcrate") {
2035            displayPath = "";
2036            href = ROOT_PATH + name + "/index.html";
2037        } else if (item.parent !== undefined) {
2038            const myparent = item.parent;
2039            let anchor = "#" + type + "." + name;
2040            const parentType = itemTypes[myparent.ty];
2041            let pageType = parentType;
2042            let pageName = myparent.name;
2043
2044            if (parentType === "primitive") {
2045                displayPath = myparent.name + "::";
2046            } else if (type === "structfield" && parentType === "variant") {
2047                // Structfields belonging to variants are special: the
2048                // final path element is the enum name.
2049                const enumNameIdx = item.path.lastIndexOf("::");
2050                const enumName = item.path.substr(enumNameIdx + 2);
2051                path = item.path.substr(0, enumNameIdx);
2052                displayPath = path + "::" + enumName + "::" + myparent.name + "::";
2053                anchor = "#variant." + myparent.name + ".field." + name;
2054                pageType = "enum";
2055                pageName = enumName;
2056            } else {
2057                displayPath = path + "::" + myparent.name + "::";
2058            }
2059            href = ROOT_PATH + path.replace(/::/g, "/") +
2060                "/" + pageType +
2061                "." + pageName +
2062                ".html" + anchor;
2063        } else {
2064            displayPath = item.path + "::";
2065            href = ROOT_PATH + item.path.replace(/::/g, "/") +
2066                "/" + type + "." + name + ".html";
2067        }
2068        return [displayPath, href];
2069    }
2070
2071    function pathSplitter(path) {
2072        const tmp = "<span>" + path.replace(/::/g, "::</span><span>");
2073        if (tmp.endsWith("<span>")) {
2074            return tmp.slice(0, tmp.length - 6);
2075        }
2076        return tmp;
2077    }
2078
2079    /**
2080     * Render a set of search results for a single tab.
2081     * @param {Array<?>}    array   - The search results for this tab
2082     * @param {ParsedQuery} query
2083     * @param {boolean}     display - True if this is the active tab
2084     */
2085    function addTab(array, query, display) {
2086        let extraClass = "";
2087        if (display === true) {
2088            extraClass = " active";
2089        }
2090
2091        const output = document.createElement("div");
2092        let length = 0;
2093        if (array.length > 0) {
2094            output.className = "search-results " + extraClass;
2095
2096            array.forEach(item => {
2097                const name = item.name;
2098                const type = itemTypes[item.ty];
2099                const longType = longItemTypes[item.ty];
2100                const typeName = longType.length !== 0 ? `${longType}` : "?";
2101
2102                length += 1;
2103
2104                const link = document.createElement("a");
2105                link.className = "result-" + type;
2106                link.href = item.href;
2107
2108                const resultName = document.createElement("div");
2109                resultName.className = "result-name";
2110
2111                if (item.is_alias) {
2112                    const alias = document.createElement("span");
2113                    alias.className = "alias";
2114
2115                    const bold = document.createElement("b");
2116                    bold.innerText = item.alias;
2117                    alias.appendChild(bold);
2118
2119                    alias.insertAdjacentHTML(
2120                        "beforeend",
2121                        "<i class=\"grey\">&nbsp;- see&nbsp;</i>");
2122
2123                    resultName.appendChild(alias);
2124                }
2125
2126                resultName.insertAdjacentHTML(
2127                    "beforeend",
2128                    `\
2129<span class="typename">${typeName}</span>\
2130<div class="path">\
2131 ${item.displayPath}<span class="${type}">${name}</span>\
2132</div>`);
2133                link.appendChild(resultName);
2134
2135                const description = document.createElement("div");
2136                description.className = "desc";
2137                description.insertAdjacentHTML("beforeend", item.desc);
2138
2139                link.appendChild(description);
2140                output.appendChild(link);
2141            });
2142        } else if (query.error === null) {
2143            output.className = "search-failed" + extraClass;
2144            output.innerHTML = "No results :(<br/>" +
2145                "Try on <a href=\"https://duckduckgo.com/?q=" +
2146                encodeURIComponent("rust " + query.userQuery) +
2147                "\">DuckDuckGo</a>?<br/><br/>" +
2148                "Or try looking in one of these:<ul><li>The <a " +
2149                "href=\"https://doc.rust-lang.org/reference/index.html\">Rust Reference</a> " +
2150                " for technical details about the language.</li><li><a " +
2151                "href=\"https://doc.rust-lang.org/rust-by-example/index.html\">Rust By " +
2152                "Example</a> for expository code examples.</a></li><li>The <a " +
2153                "href=\"https://doc.rust-lang.org/book/index.html\">Rust Book</a> for " +
2154                "introductions to language features and the language itself.</li><li><a " +
2155                "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
2156                " <a href=\"https://crates.io/\">crates.io</a>.</li></ul>";
2157        }
2158        return [output, length];
2159    }
2160
2161    function makeTabHeader(tabNb, text, nbElems) {
2162        if (searchState.currentTab === tabNb) {
2163            return "<button class=\"selected\">" + text +
2164                   " <span class=\"count\">(" + nbElems + ")</span></button>";
2165        }
2166        return "<button>" + text + " <span class=\"count\">(" + nbElems + ")</span></button>";
2167    }
2168
2169    /**
2170     * @param {ResultsTable} results
2171     * @param {boolean} go_to_first
2172     * @param {string} filterCrates
2173     */
2174    function showResults(results, go_to_first, filterCrates) {
2175        const search = searchState.outputElement();
2176        if (go_to_first || (results.others.length === 1
2177            && getSettingValue("go-to-only-result") === "true")
2178        ) {
2179            // Needed to force re-execution of JS when coming back to a page. Let's take this
2180            // scenario as example:
2181            //
2182            // 1. You have the "Directly go to item in search if there is only one result" option
2183            //    enabled.
2184            // 2. You make a search which results only one result, leading you automatically to
2185            //    this result.
2186            // 3. You go back to previous page.
2187            //
2188            // Now, without the call below, the JS will not be re-executed and the previous state
2189            // will be used, starting search again since the search input is not empty, leading you
2190            // back to the previous page again.
2191            window.onunload = () => {};
2192            searchState.removeQueryParameters();
2193            const elem = document.createElement("a");
2194            elem.href = results.others[0].href;
2195            removeClass(elem, "active");
2196            // For firefox, we need the element to be in the DOM so it can be clicked.
2197            document.body.appendChild(elem);
2198            elem.click();
2199            return;
2200        }
2201        if (results.query === undefined) {
2202            results.query = parseQuery(searchState.input.value);
2203        }
2204
2205        currentResults = results.query.userQuery;
2206
2207        const ret_others = addTab(results.others, results.query, true);
2208        const ret_in_args = addTab(results.in_args, results.query, false);
2209        const ret_returned = addTab(results.returned, results.query, false);
2210
2211        // Navigate to the relevant tab if the current tab is empty, like in case users search
2212        // for "-> String". If they had selected another tab previously, they have to click on
2213        // it again.
2214        let currentTab = searchState.currentTab;
2215        if ((currentTab === 0 && ret_others[1] === 0) ||
2216                (currentTab === 1 && ret_in_args[1] === 0) ||
2217                (currentTab === 2 && ret_returned[1] === 0)) {
2218            if (ret_others[1] !== 0) {
2219                currentTab = 0;
2220            } else if (ret_in_args[1] !== 0) {
2221                currentTab = 1;
2222            } else if (ret_returned[1] !== 0) {
2223                currentTab = 2;
2224            }
2225        }
2226
2227        let crates = "";
2228        const crates_list = Object.keys(rawSearchIndex);
2229        if (crates_list.length > 1) {
2230            crates = " in&nbsp;<div id=\"crate-search-div\"><select id=\"crate-search\">" +
2231                "<option value=\"all crates\">all crates</option>";
2232            for (const c of crates_list) {
2233                crates += `<option value="${c}" ${c === filterCrates && "selected"}>${c}</option>`;
2234            }
2235            crates += "</select></div>";
2236        }
2237
2238        let output = `<h1 class="search-results-title">Results${crates}</h1>`;
2239        if (results.query.error !== null) {
2240            const error = results.query.error;
2241            error.forEach((value, index) => {
2242                value = value.split("<").join("&lt;").split(">").join("&gt;");
2243                if (index % 2 !== 0) {
2244                    error[index] = `<code>${value.replaceAll(" ", "&nbsp;")}</code>`;
2245                } else {
2246                    error[index] = value;
2247                }
2248            });
2249            output += `<h3 class="error">Query parser error: "${error.join("")}".</h3>`;
2250            output += "<div id=\"search-tabs\">" +
2251                makeTabHeader(0, "In Names", ret_others[1]) +
2252                "</div>";
2253            currentTab = 0;
2254        } else if (results.query.foundElems <= 1 && results.query.returned.length === 0) {
2255            output += "<div id=\"search-tabs\">" +
2256                makeTabHeader(0, "In Names", ret_others[1]) +
2257                makeTabHeader(1, "In Parameters", ret_in_args[1]) +
2258                makeTabHeader(2, "In Return Types", ret_returned[1]) +
2259                "</div>";
2260        } else {
2261            const signatureTabTitle =
2262                results.query.elems.length === 0 ? "In Function Return Types" :
2263                results.query.returned.length === 0 ? "In Function Parameters" :
2264                "In Function Signatures";
2265            output += "<div id=\"search-tabs\">" +
2266                makeTabHeader(0, signatureTabTitle, ret_others[1]) +
2267                "</div>";
2268            currentTab = 0;
2269        }
2270
2271        if (results.query.correction !== null) {
2272            const orig = results.query.returned.length > 0
2273                ? results.query.returned[0].name
2274                : results.query.elems[0].name;
2275            output += "<h3 class=\"search-corrections\">" +
2276                `Type "${orig}" not found. ` +
2277                "Showing results for closest type name " +
2278                `"${results.query.correction}" instead.</h3>`;
2279        }
2280
2281        const resultsElem = document.createElement("div");
2282        resultsElem.id = "results";
2283        resultsElem.appendChild(ret_others[0]);
2284        resultsElem.appendChild(ret_in_args[0]);
2285        resultsElem.appendChild(ret_returned[0]);
2286
2287        search.innerHTML = output;
2288        const crateSearch = document.getElementById("crate-search");
2289        if (crateSearch) {
2290            crateSearch.addEventListener("input", updateCrate);
2291        }
2292        search.appendChild(resultsElem);
2293        // Reset focused elements.
2294        searchState.showResults(search);
2295        const elems = document.getElementById("search-tabs").childNodes;
2296        searchState.focusedByTab = [];
2297        let i = 0;
2298        for (const elem of elems) {
2299            const j = i;
2300            elem.onclick = () => printTab(j);
2301            searchState.focusedByTab.push(null);
2302            i += 1;
2303        }
2304        printTab(currentTab);
2305    }
2306
2307    function updateSearchHistory(url) {
2308        if (!browserSupportsHistoryApi()) {
2309            return;
2310        }
2311        const params = searchState.getQueryStringParams();
2312        if (!history.state && !params.search) {
2313            history.pushState(null, "", url);
2314        } else {
2315            history.replaceState(null, "", url);
2316        }
2317    }
2318
2319    /**
2320     * Perform a search based on the current state of the search input element
2321     * and display the results.
2322     * @param {Event}   [e]       - The event that triggered this search, if any
2323     * @param {boolean} [forced]
2324     */
2325    function search(e, forced) {
2326        if (e) {
2327            e.preventDefault();
2328        }
2329        const query = parseQuery(searchState.input.value.trim());
2330        let filterCrates = getFilterCrates();
2331
2332        if (!forced && query.userQuery === currentResults) {
2333            if (query.userQuery.length > 0) {
2334                putBackSearch();
2335            }
2336            return;
2337        }
2338
2339        searchState.setLoadingSearch();
2340
2341        const params = searchState.getQueryStringParams();
2342
2343        // In case we have no information about the saved crate and there is a URL query parameter,
2344        // we override it with the URL query parameter.
2345        if (filterCrates === null && params["filter-crate"] !== undefined) {
2346            filterCrates = params["filter-crate"];
2347        }
2348
2349        // Update document title to maintain a meaningful browser history
2350        searchState.title = "Results for " + query.original + " - Rust";
2351
2352        // Because searching is incremental by character, only the most
2353        // recent search query is added to the browser history.
2354        updateSearchHistory(buildUrl(query.original, filterCrates));
2355
2356        showResults(
2357            execQuery(query, searchWords, filterCrates, window.currentCrate),
2358            params.go_to_first,
2359            filterCrates);
2360    }
2361
2362    /**
2363     * Convert a list of RawFunctionType / ID to object-based FunctionType.
2364     *
2365     * Crates often have lots of functions in them, and it's common to have a large number of
2366     * functions that operate on a small set of data types, so the search index compresses them
2367     * by encoding function parameter and return types as indexes into an array of names.
2368     *
2369     * Even when a general-purpose compression algorithm is used, this is still a win. I checked.
2370     * https://github.com/rust-lang/rust/pull/98475#issue-1284395985
2371     *
2372     * The format for individual function types is encoded in
2373     * librustdoc/html/render/mod.rs: impl Serialize for RenderType
2374     *
2375     * @param {null|Array<RawFunctionType>} types
2376     * @param {Array<{name: string, ty: number}>} lowercasePaths
2377     *
2378     * @return {Array<FunctionSearchType>}
2379     */
2380    function buildItemSearchTypeAll(types, lowercasePaths) {
2381        const PATH_INDEX_DATA = 0;
2382        const GENERICS_DATA = 1;
2383        return types.map(type => {
2384            let pathIndex, generics;
2385            if (typeof type === "number") {
2386                pathIndex = type;
2387                generics = [];
2388            } else {
2389                pathIndex = type[PATH_INDEX_DATA];
2390                generics = buildItemSearchTypeAll(
2391                    type[GENERICS_DATA],
2392                    lowercasePaths
2393                );
2394            }
2395            return {
2396                // `0` is used as a sentinel because it's fewer bytes than `null`
2397                id: pathIndex === 0
2398                    ? -1
2399                    : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
2400                ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
2401                generics: generics,
2402            };
2403        });
2404    }
2405
2406    /**
2407     * Convert from RawFunctionSearchType to FunctionSearchType.
2408     *
2409     * Crates often have lots of functions in them, and function signatures are sometimes complex,
2410     * so rustdoc uses a pretty tight encoding for them. This function converts it to a simpler,
2411     * object-based encoding so that the actual search code is more readable and easier to debug.
2412     *
2413     * The raw function search type format is generated using serde in
2414     * librustdoc/html/render/mod.rs: impl Serialize for IndexItemFunctionType
2415     *
2416     * @param {RawFunctionSearchType} functionSearchType
2417     * @param {Array<{name: string, ty: number}>} lowercasePaths
2418     * @param {Map<string, integer>}
2419     *
2420     * @return {null|FunctionSearchType}
2421     */
2422    function buildFunctionSearchType(functionSearchType, lowercasePaths) {
2423        const INPUTS_DATA = 0;
2424        const OUTPUT_DATA = 1;
2425        // `0` is used as a sentinel because it's fewer bytes than `null`
2426        if (functionSearchType === 0) {
2427            return null;
2428        }
2429        let inputs, output;
2430        if (typeof functionSearchType[INPUTS_DATA] === "number") {
2431            const pathIndex = functionSearchType[INPUTS_DATA];
2432            inputs = [{
2433                id: pathIndex === 0
2434                    ? -1
2435                    : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
2436                ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
2437                generics: [],
2438            }];
2439        } else {
2440            inputs = buildItemSearchTypeAll(
2441                functionSearchType[INPUTS_DATA],
2442                lowercasePaths
2443            );
2444        }
2445        if (functionSearchType.length > 1) {
2446            if (typeof functionSearchType[OUTPUT_DATA] === "number") {
2447                const pathIndex = functionSearchType[OUTPUT_DATA];
2448                output = [{
2449                    id: pathIndex === 0
2450                        ? -1
2451                        : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
2452                    ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
2453                    generics: [],
2454                }];
2455            } else {
2456                output = buildItemSearchTypeAll(
2457                    functionSearchType[OUTPUT_DATA],
2458                    lowercasePaths
2459                );
2460            }
2461        } else {
2462            output = [];
2463        }
2464        return {
2465            inputs, output,
2466        };
2467    }
2468
2469    function buildIndex(rawSearchIndex) {
2470        searchIndex = [];
2471        /**
2472         * List of normalized search words (ASCII lowercased, and undescores removed).
2473         *
2474         * @type {Array<string>}
2475         */
2476        const searchWords = [];
2477        typeNameIdMap = new Map();
2478        const charA = "A".charCodeAt(0);
2479        let currentIndex = 0;
2480        let id = 0;
2481
2482        // Initialize type map indexes for primitive list types
2483        // that can be searched using `[]` syntax.
2484        typeNameIdOfArray = buildTypeMapIndex("array");
2485        typeNameIdOfSlice = buildTypeMapIndex("slice");
2486        typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
2487
2488        for (const crate in rawSearchIndex) {
2489            if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) {
2490                continue;
2491            }
2492
2493            let crateSize = 0;
2494
2495            /**
2496             * The raw search data for a given crate. `n`, `t`, `d`, and `q`, `i`, and `f`
2497             * are arrays with the same length. n[i] contains the name of an item.
2498             * t[i] contains the type of that item (as a string of characters that represent an
2499             * offset in `itemTypes`). d[i] contains the description of that item.
2500             *
2501             * q[i] contains the full path of the item, or an empty string indicating
2502             * "same as q[i-1]".
2503             *
2504             * i[i] contains an item's parent, usually a module. For compactness,
2505             * it is a set of indexes into the `p` array.
2506             *
2507             * f[i] contains function signatures, or `0` if the item isn't a function.
2508             * Functions are themselves encoded as arrays. The first item is a list of
2509             * types representing the function's inputs, and the second list item is a list
2510             * of types representing the function's output. Tuples are flattened.
2511             * Types are also represented as arrays; the first item is an index into the `p`
2512             * array, while the second is a list of types representing any generic parameters.
2513             *
2514             * `a` defines aliases with an Array of pairs: [name, offset], where `offset`
2515             * points into the n/t/d/q/i/f arrays.
2516             *
2517             * `doc` contains the description of the crate.
2518             *
2519             * `p` is a list of path/type pairs. It is used for parents and function parameters.
2520             *
2521             * @type {{
2522             *   doc: string,
2523             *   a: Object,
2524             *   n: Array<string>,
2525             *   t: String,
2526             *   d: Array<string>,
2527             *   q: Array<[Number, string]>,
2528             *   i: Array<Number>,
2529             *   f: Array<RawFunctionSearchType>,
2530             *   p: Array<Object>,
2531             *   c: Array<Number>
2532             * }}
2533             */
2534            const crateCorpus = rawSearchIndex[crate];
2535
2536            searchWords.push(crate);
2537            // This object should have exactly the same set of fields as the "row"
2538            // object defined below. Your JavaScript runtime will thank you.
2539            // https://mathiasbynens.be/notes/shapes-ics
2540            const crateRow = {
2541                crate: crate,
2542                ty: 1, // == ExternCrate
2543                name: crate,
2544                path: "",
2545                desc: crateCorpus.doc,
2546                parent: undefined,
2547                type: null,
2548                id: id,
2549                normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""),
2550                deprecated: null,
2551            };
2552            id += 1;
2553            searchIndex.push(crateRow);
2554            currentIndex += 1;
2555
2556            // a String of one character item type codes
2557            const itemTypes = crateCorpus.t;
2558            // an array of (String) item names
2559            const itemNames = crateCorpus.n;
2560            // an array of [(Number) item index,
2561            //              (String) full path]
2562            // an item whose index is not present will fall back to the previous present path
2563            // i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present,
2564            // 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11
2565            const itemPaths = new Map(crateCorpus.q);
2566            // an array of (String) descriptions
2567            const itemDescs = crateCorpus.d;
2568            // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
2569            const itemParentIdxs = crateCorpus.i;
2570            // an array of (Object | null) the type of the function, if any
2571            const itemFunctionSearchTypes = crateCorpus.f;
2572            // an array of (Number) indices for the deprecated items
2573            const deprecatedItems = new Set(crateCorpus.c);
2574            // an array of [(Number) item type,
2575            //              (String) name]
2576            const paths = crateCorpus.p;
2577            // an array of [(String) alias name
2578            //             [Number] index to items]
2579            const aliases = crateCorpus.a;
2580
2581            // an array of [{name: String, ty: Number}]
2582            const lowercasePaths = [];
2583
2584            // convert `rawPaths` entries into object form
2585            // generate normalizedPaths for function search mode
2586            let len = paths.length;
2587            for (let i = 0; i < len; ++i) {
2588                lowercasePaths.push({ty: paths[i][0], name: paths[i][1].toLowerCase()});
2589                paths[i] = {ty: paths[i][0], name: paths[i][1]};
2590            }
2591
2592            // convert `item*` into an object form, and construct word indices.
2593            //
2594            // before any analysis is performed lets gather the search terms to
2595            // search against apart from the rest of the data.  This is a quick
2596            // operation that is cached for the life of the page state so that
2597            // all other search operations have access to this cached data for
2598            // faster analysis operations
2599            len = itemTypes.length;
2600            let lastPath = "";
2601            for (let i = 0; i < len; ++i) {
2602                let word = "";
2603                // This object should have exactly the same set of fields as the "crateRow"
2604                // object defined above.
2605                if (typeof itemNames[i] === "string") {
2606                    word = itemNames[i].toLowerCase();
2607                }
2608                searchWords.push(word);
2609                const row = {
2610                    crate: crate,
2611                    ty: itemTypes.charCodeAt(i) - charA,
2612                    name: itemNames[i],
2613                    path: itemPaths.has(i) ? itemPaths.get(i) : lastPath,
2614                    desc: itemDescs[i],
2615                    parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
2616                    type: buildFunctionSearchType(
2617                        itemFunctionSearchTypes[i],
2618                        lowercasePaths
2619                    ),
2620                    id: id,
2621                    normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
2622                    deprecated: deprecatedItems.has(i),
2623                };
2624                id += 1;
2625                searchIndex.push(row);
2626                lastPath = row.path;
2627                crateSize += 1;
2628            }
2629
2630            if (aliases) {
2631                const currentCrateAliases = new Map();
2632                ALIASES.set(crate, currentCrateAliases);
2633                for (const alias_name in aliases) {
2634                    if (!hasOwnPropertyRustdoc(aliases, alias_name)) {
2635                        continue;
2636                    }
2637
2638                    let currentNameAliases;
2639                    if (currentCrateAliases.has(alias_name)) {
2640                        currentNameAliases = currentCrateAliases.get(alias_name);
2641                    } else {
2642                        currentNameAliases = [];
2643                        currentCrateAliases.set(alias_name, currentNameAliases);
2644                    }
2645                    for (const local_alias of aliases[alias_name]) {
2646                        currentNameAliases.push(local_alias + currentIndex);
2647                    }
2648                }
2649            }
2650            currentIndex += crateSize;
2651        }
2652        return searchWords;
2653    }
2654
2655    /**
2656     * Callback for when the search form is submitted.
2657     * @param {Event} [e] - The event that triggered this call, if any
2658     */
2659    function onSearchSubmit(e) {
2660        e.preventDefault();
2661        searchState.clearInputTimeout();
2662        search();
2663    }
2664
2665    function putBackSearch() {
2666        const search_input = searchState.input;
2667        if (!searchState.input) {
2668            return;
2669        }
2670        if (search_input.value !== "" && !searchState.isDisplayed()) {
2671            searchState.showResults();
2672            if (browserSupportsHistoryApi()) {
2673                history.replaceState(null, "",
2674                    buildUrl(search_input.value, getFilterCrates()));
2675            }
2676            document.title = searchState.title;
2677        }
2678    }
2679
2680    function registerSearchEvents() {
2681        const params = searchState.getQueryStringParams();
2682
2683        // Populate search bar with query string search term when provided,
2684        // but only if the input bar is empty. This avoid the obnoxious issue
2685        // where you start trying to do a search, and the index loads, and
2686        // suddenly your search is gone!
2687        if (searchState.input.value === "") {
2688            searchState.input.value = params.search || "";
2689        }
2690
2691        const searchAfter500ms = () => {
2692            searchState.clearInputTimeout();
2693            if (searchState.input.value.length === 0) {
2694                searchState.hideResults();
2695            } else {
2696                searchState.timeout = setTimeout(search, 500);
2697            }
2698        };
2699        searchState.input.onkeyup = searchAfter500ms;
2700        searchState.input.oninput = searchAfter500ms;
2701        document.getElementsByClassName("search-form")[0].onsubmit = onSearchSubmit;
2702        searchState.input.onchange = e => {
2703            if (e.target !== document.activeElement) {
2704                // To prevent doing anything when it's from a blur event.
2705                return;
2706            }
2707            // Do NOT e.preventDefault() here. It will prevent pasting.
2708            searchState.clearInputTimeout();
2709            // zero-timeout necessary here because at the time of event handler execution the
2710            // pasted content is not in the input field yet. Shouldn’t make any difference for
2711            // change, though.
2712            setTimeout(search, 0);
2713        };
2714        searchState.input.onpaste = searchState.input.onchange;
2715
2716        searchState.outputElement().addEventListener("keydown", e => {
2717            // We only handle unmodified keystrokes here. We don't want to interfere with,
2718            // for instance, alt-left and alt-right for history navigation.
2719            if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
2720                return;
2721            }
2722            // up and down arrow select next/previous search result, or the
2723            // search box if we're already at the top.
2724            if (e.which === 38) { // up
2725                const previous = document.activeElement.previousElementSibling;
2726                if (previous) {
2727                    previous.focus();
2728                } else {
2729                    searchState.focus();
2730                }
2731                e.preventDefault();
2732            } else if (e.which === 40) { // down
2733                const next = document.activeElement.nextElementSibling;
2734                if (next) {
2735                    next.focus();
2736                }
2737                const rect = document.activeElement.getBoundingClientRect();
2738                if (window.innerHeight - rect.bottom < rect.height) {
2739                    window.scrollBy(0, rect.height);
2740                }
2741                e.preventDefault();
2742            } else if (e.which === 37) { // left
2743                nextTab(-1);
2744                e.preventDefault();
2745            } else if (e.which === 39) { // right
2746                nextTab(1);
2747                e.preventDefault();
2748            }
2749        });
2750
2751        searchState.input.addEventListener("keydown", e => {
2752            if (e.which === 40) { // down
2753                focusSearchResult();
2754                e.preventDefault();
2755            }
2756        });
2757
2758        searchState.input.addEventListener("focus", () => {
2759            putBackSearch();
2760        });
2761
2762        searchState.input.addEventListener("blur", () => {
2763            searchState.input.placeholder = searchState.input.origPlaceholder;
2764        });
2765
2766        // Push and pop states are used to add search results to the browser
2767        // history.
2768        if (browserSupportsHistoryApi()) {
2769            // Store the previous <title> so we can revert back to it later.
2770            const previousTitle = document.title;
2771
2772            window.addEventListener("popstate", e => {
2773                const params = searchState.getQueryStringParams();
2774                // Revert to the previous title manually since the History
2775                // API ignores the title parameter.
2776                document.title = previousTitle;
2777                // When browsing forward to search results the previous
2778                // search will be repeated, so the currentResults are
2779                // cleared to ensure the search is successful.
2780                currentResults = null;
2781                // Synchronize search bar with query string state and
2782                // perform the search. This will empty the bar if there's
2783                // nothing there, which lets you really go back to a
2784                // previous state with nothing in the bar.
2785                if (params.search && params.search.length > 0) {
2786                    searchState.input.value = params.search;
2787                    // Some browsers fire "onpopstate" for every page load
2788                    // (Chrome), while others fire the event only when actually
2789                    // popping a state (Firefox), which is why search() is
2790                    // called both here and at the end of the startSearch()
2791                    // function.
2792                    search(e);
2793                } else {
2794                    searchState.input.value = "";
2795                    // When browsing back from search results the main page
2796                    // visibility must be reset.
2797                    searchState.hideResults();
2798                }
2799            });
2800        }
2801
2802        // This is required in firefox to avoid this problem: Navigating to a search result
2803        // with the keyboard, hitting enter, and then hitting back would take you back to
2804        // the doc page, rather than the search that should overlay it.
2805        // This was an interaction between the back-forward cache and our handlers
2806        // that try to sync state between the URL and the search input. To work around it,
2807        // do a small amount of re-init on page show.
2808        window.onpageshow = () => {
2809            const qSearch = searchState.getQueryStringParams().search;
2810            if (searchState.input.value === "" && qSearch) {
2811                searchState.input.value = qSearch;
2812            }
2813            search();
2814        };
2815    }
2816
2817    function updateCrate(ev) {
2818        if (ev.target.value === "all crates") {
2819            // If we don't remove it from the URL, it'll be picked up again by the search.
2820            const query = searchState.input.value.trim();
2821            updateSearchHistory(buildUrl(query, null));
2822        }
2823        // In case you "cut" the entry from the search input, then change the crate filter
2824        // before paste back the previous search, you get the old search results without
2825        // the filter. To prevent this, we need to remove the previous results.
2826        currentResults = null;
2827        search(undefined, true);
2828    }
2829
2830    /**
2831     *  @type {Array<string>}
2832     */
2833    const searchWords = buildIndex(rawSearchIndex);
2834    if (typeof window !== "undefined") {
2835        registerSearchEvents();
2836        // If there's a search term in the URL, execute the search now.
2837        if (window.searchState.getQueryStringParams().search) {
2838            search();
2839        }
2840    }
2841
2842    if (typeof exports !== "undefined") {
2843        exports.initSearch = initSearch;
2844        exports.execQuery = execQuery;
2845        exports.parseQuery = parseQuery;
2846    }
2847    return searchWords;
2848}
2849
2850if (typeof window !== "undefined") {
2851    window.initSearch = initSearch;
2852    if (window.searchIndex !== undefined) {
2853        initSearch(window.searchIndex);
2854    }
2855} else {
2856    // Running in Node, not a browser. Run initSearch just to produce the
2857    // exports.
2858    initSearch({});
2859}
2860
2861
2862})();
2863