src/librustdoc/html/static/js/search.js JAVASCRIPT 5,608 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 5,608.
1// ignore-tidy-file-filelength2/* global addClass, getNakedUrl, getVar, getSettingValue, hasClass, nonnull */3/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi */45"use strict";67/**8 * @param {stringdex.Stringdex} Stringdex9 * @param {typeof stringdex.RoaringBitmap} RoaringBitmap10 * @param {stringdex.Hooks} hooks11 */12const initSearch = async function(Stringdex, RoaringBitmap, hooks) {1314// polyfill15// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSpliced16if (!Array.prototype.toSpliced) {17    // Can't use arrow functions, because we want `this`18    Array.prototype.toSpliced = function() {19        const me = this.slice();20        // @ts-expect-error21        Array.prototype.splice.apply(me, arguments);22        return me;23    };24}2526/**27 *28 * @template T29 * @param {Iterable<T>} arr30 * @param {function(T): Promise<any>} func31 * @param {function(T): void} funcBtwn32 */33async function onEachBtwnAsync(arr, func, funcBtwn) {34    let skipped = true;35    for (const value of arr) {36        if (!skipped) {37            funcBtwn(value);38        }39        skipped = await func(value);40    }41}4243/**44 * Allow the browser to redraw.45 * @returns {Promise<void>}46 */47const yieldToBrowser = typeof window !== "undefined" && window.requestIdleCallback ?48    function() {49        return new Promise((resolve, _reject) => {50            window.requestIdleCallback(resolve);51        });52    } :53    function() {54        return new Promise((resolve, _reject) => {55            setTimeout(resolve, 0);56        });57    };5859/**60 * Promise-based timer wrapper.61 * @param {number} ms62 * @returns {Promise<void>}63 */64const timeout = function(ms) {65    return new Promise((resolve, _reject) => {66        setTimeout(resolve, ms);67    });68};6970if (!Promise.withResolvers) {71    /**72     * Polyfill73     * @template T74     * @returns {{75            "promise": Promise<T>,76            "resolve": (function(T): void),77            "reject": (function(any): void)78        }}79     */80    Promise.withResolvers = () => {81        let resolve, reject;82        const promise = new Promise((res, rej) => {83          resolve = res;84          reject = rej;85        });86        // @ts-expect-error87        return {promise, resolve, reject};88    };89}9091// ==================== Core search logic begin ====================92// This mapping table should match the discriminants of93// `rustdoc::formats::item_type::ItemType` type in Rust.94const itemTypes = Object.freeze({95    keyword: 0,96    primitive: 1,97    mod: 2,98    externcrate: 3,99    import: 4,100    struct: 5,101    enum: 6,102    fn: 7,103    type: 8,104    static: 9,105    trait: 10,106    impl: 11,107    tymethod: 12,108    method: 13,109    structfield: 14,110    variant: 15,111    macro: 16,112    associatedtype: 17,113    constant: 18,114    associatedconstant: 19,115    union: 20,116    foreigntype: 21,117    existential: 22,118    attr: 23,119    derive: 24,120    traitalias: 25,121    generic: 26,122    attribute: 27,123    decl_macro_attribute: 28,124    decl_macro_derive: 29,125});126const itemTypesName = Array.from(Object.keys(itemTypes));127128// When filtering, some types might be included as well. For example, when you filter on `constant`,129// we also include associated constant items.130//131// This map is built as follows: the first item of the array is the type to be included when the132// second type of the array is used as filter.133const itemParents = new Map([134    [itemTypes.associatedconstant, itemTypes.constant],135    [itemTypes.method, itemTypes.fn],136    [itemTypes.tymethod, itemTypes.fn],137    [itemTypes.primitive, itemTypes.type],138    [itemTypes.associatedtype, itemTypes.type],139    [itemTypes.traitalias, itemTypes.trait],140    [itemTypes.attr, itemTypes.macro],141    [itemTypes.derive, itemTypes.macro],142    [itemTypes.externcrate, itemTypes.import],143]);144145const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";146147// Hard limit on how deep to recurse into generics when doing type-driven search.148// This needs limited, partially because149// a search for `Ty` shouldn't match `WithInfcx<ParamEnvAnd<Vec<ConstTy<Interner<Ty=Ty>>>>>`,150// but mostly because this is the simplest and most principled way to limit the number151// of permutations we need to check.152const UNBOXING_LIMIT = 5;153154// used for search query verification155// because searches are often performed using substrings of identifiers,156// and not just full identiferes, we allow them to start with chars that otherwise157// can only appear in the middle of identifiers158const REGEX_IDENT = /\p{ID_Continue}+/uy;159const REGEX_INVALID_TYPE_FILTER = /[^a-z]/ui;160161const MAX_RESULTS = 200;162const NO_TYPE_FILTER = -1;163const DEPRECATED_COUNT_SELECTOR = "deprecated-count";164165/**166 * The [edit distance] is a metric for measuring the difference between two strings.167 *168 * [edit distance]: https://en.wikipedia.org/wiki/Edit_distance169 */170171/*172 * This function was translated, mostly line-for-line, from173 * https://github.com/rust-lang/rust/blob/ff4b772f805ec1e/compiler/rustc_span/src/edit_distance.rs174 *175 * The current implementation is the restricted Damerau-Levenshtein algorithm. It is restricted176 * because it does not permit modifying characters that have already been transposed. The specific177 * algorithm should not matter to the caller of the methods, which is why it is not noted in the178 * documentation.179 */180const editDistanceState = {181    /**182     * @type {number[]}183     */184    current: [],185    /**186     * @type {number[]}187     */188    prev: [],189    /**190     * @type {number[]}191     */192    prevPrev: [],193    /**194     * @param {string} a195     * @param {string} b196     * @param {number} limit197     * @returns198     */199    calculate: function calculate(a, b, limit) {200        // Ensure that `b` is the shorter string, minimizing memory use.201        if (a.length < b.length) {202            const aTmp = a;203            a = b;204            b = aTmp;205        }206207        const minDist = a.length - b.length;208        // If we know the limit will be exceeded, we can return early.209        if (minDist > limit) {210            return limit + 1;211        }212213        // Strip common prefix.214        // We know that `b` is the shorter string, so we don't need to check215        // `a.length`.216        while (b.length > 0 && b[0] === a[0]) {217            a = a.substring(1);218            b = b.substring(1);219        }220        // Strip common suffix.221        while (b.length > 0 && b[b.length - 1] === a[a.length - 1]) {222            a = a.substring(0, a.length - 1);223            b = b.substring(0, b.length - 1);224        }225226        // If either string is empty, the distance is the length of the other.227        // We know that `b` is the shorter string, so we don't need to check `a`.228        if (b.length === 0) {229            return minDist;230        }231232        const aLength = a.length;233        const bLength = b.length;234235        for (let i = 0; i <= bLength; ++i) {236            this.current[i] = 0;237            this.prev[i] = i;238            this.prevPrev[i] = Number.MAX_VALUE;239        }240241        // row by row242        for (let i = 1; i <= aLength; ++i) {243            this.current[0] = i;244            const aIdx = i - 1;245246            // column by column247            for (let j = 1; j <= bLength; ++j) {248                const bIdx = j - 1;249250                // There is no cost to substitute a character with itself.251                const substitutionCost = a[aIdx] === b[bIdx] ? 0 : 1;252253                this.current[j] = Math.min(254                    // deletion255                    this.prev[j] + 1,256                    // insertion257                    this.current[j - 1] + 1,258                    // substitution259                    this.prev[j - 1] + substitutionCost,260                );261262                if ((i > 1) && (j > 1) && (a[aIdx] === b[bIdx - 1]) && (a[aIdx - 1] === b[bIdx])) {263                    // transposition264                    this.current[j] = Math.min(265                        this.current[j],266                        this.prevPrev[j - 2] + 1,267                    );268                }269            }270271            // Rotate the buffers, reusing the memory272            const prevPrevTmp = this.prevPrev;273            this.prevPrev = this.prev;274            this.prev = this.current;275            this.current = prevPrevTmp;276        }277278        // `prev` because we already rotated the buffers.279        const distance = this.prev[bLength];280        return distance <= limit ? distance : (limit + 1);281    },282};283284/**285 * @param {string} a286 * @param {string} b287 * @param {number} limit288 * @returns289 */290function editDistance(a, b, limit) {291    return editDistanceState.calculate(a, b, limit);292}293294/**295 * @param {string} c296 * @returns {boolean}297 */298function isEndCharacter(c) {299    return "=,>-])".indexOf(c) !== -1;300}301302/**303 * Same thing as ItemType::is_fn_like in item_type.rs304 *305 * @param {rustdoc.ItemType} ty306 * @returns307 */308function isFnLikeTy(ty) {309    return ty === itemTypes.fn || ty === itemTypes.method || ty === itemTypes.tymethod;310}311312/**313 * Returns `true` if the given `c` character is a separator.314 *315 * @param {string} c316 *317 * @return {boolean}318 */319function isSeparatorCharacter(c) {320    return c === "," || c === "=";321}322323/**324 * Returns `true` if the current parser position is starting with "->".325 *326 * @param {rustdoc.ParserState} parserState327 *328 * @return {boolean}329 */330function isReturnArrow(parserState) {331    return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "->";332}333334/**335 * Increase current parser position until it doesn't find a whitespace anymore.336 *337 * @param {rustdoc.ParserState} parserState338 */339function skipWhitespace(parserState) {340    while (parserState.pos < parserState.userQuery.length) {341        const c = parserState.userQuery[parserState.pos];342        if (c !== " ") {343            break;344        }345        parserState.pos += 1;346    }347}348349/**350 * Returns `true` if the previous character is `lookingFor`.351 *352 * @param {rustdoc.ParserState} parserState353 * @param {String} lookingFor354 *355 * @return {boolean}356 */357function prevIs(parserState, lookingFor) {358    let pos = parserState.pos;359    while (pos > 0) {360        const c = parserState.userQuery[pos - 1];361        if (c === lookingFor) {362            return true;363        } else if (c !== " ") {364            break;365        }366        pos -= 1;367    }368    return false;369}370371/**372 * Returns `true` if the last element in the `elems` argument has generics.373 *374 * @param {Array<rustdoc.ParserQueryElement>} elems375 * @param {rustdoc.ParserState} parserState376 *377 * @return {boolean}378 */379function isLastElemGeneric(elems, parserState) {380    return (elems.length > 0 && elems[elems.length - 1].generics.length > 0) ||381        prevIs(parserState, ">");382}383384/**385 *386 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query387 * @param {rustdoc.ParserState} parserState388 * @param {rustdoc.ParserQueryElement[]} elems389 * @param {boolean} isInGenerics390 */391function getFilteredNextElem(query, parserState, elems, isInGenerics) {392    const start = parserState.pos;393    if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {394        throw ["Expected type filter before ", ":"];395    }396    getNextElem(query, parserState, elems, isInGenerics);397    if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {398        if (parserState.typeFilter !== null) {399            throw [400                "Unexpected ",401                ":",402                " (expected path after type filter ",403                parserState.typeFilter + ":",404                ")",405            ];406        }407        if (elems.length === 0) {408            throw ["Expected type filter before ", ":"];409        } else if (query.literalSearch) {410            throw ["Cannot use quotes on type filter"];411        }412        // The type filter doesn't count as an element since it's a modifier.413        const typeFilterElem = elems.pop();414        checkExtraTypeFilterCharacters(start, parserState);415        // typeFilterElem is not undefined. If it was, the elems.length check would have fired.416        // @ts-expect-error417        parserState.typeFilter = typeFilterElem.normalizedPathLast;418        parserState.pos += 1;419        parserState.totalElems -= 1;420        query.literalSearch = false;421        getNextElem(query, parserState, elems, isInGenerics);422    }423}424425/**426 * This function parses the next query element until it finds `endChar`,427 * calling `getNextElem` to collect each element.428 *429 * If there is no `endChar`, this function will implicitly stop at the end430 * without raising an error.431 *432 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query433 * @param {rustdoc.ParserState} parserState434 * @param {Array<rustdoc.ParserQueryElement>} elems435 *     - This is where the new {QueryElement} will be added.436 * @param {string} endChar - This function will stop when it'll encounter this437 *                           character.438 * @returns {{foundSeparator: boolean}}439 */440function getItemsBefore(query, parserState, elems, endChar) {441    let foundStopChar = true;442    let foundSeparator = false;443444    // If this is a generic, keep the outer item's type filter around.445    const oldTypeFilter = parserState.typeFilter;446    parserState.typeFilter = null;447    const oldIsInBinding = parserState.isInBinding;448    parserState.isInBinding = null;449450    // ML-style Higher Order Function notation451    //452    // a way to search for any closure or fn pointer regardless of453    // which closure trait is used454    //455    // Looks like this:456    //457    //     `option<t>, (t -> u) -> option<u>`458    //                  ^^^^^^459    //460    // The Rust-style closure notation is implemented in getNextElem461    let hofParameters = null;462463    let extra = "";464    if (endChar === ">") {465        extra = "<";466    } else if (endChar === "]") {467        extra = "[";468    } else if (endChar === ")") {469        extra = "(";470    } else if (endChar === "") {471        extra = "->";472    } else {473        extra = endChar;474    }475476    while (parserState.pos < parserState.length) {477        const c = parserState.userQuery[parserState.pos];478        if (c === endChar) {479            if (parserState.isInBinding) {480                throw ["Unexpected ", endChar, " after ", "="];481            }482            break;483        } else if (endChar !== "" && isReturnArrow(parserState)) {484            // ML-style HOF notation only works when delimited in something,485            // otherwise a function arrow starts the return type of the top486            if (parserState.isInBinding) {487                throw ["Unexpected ", "->", " after ", "="];488            }489            hofParameters = [...elems];490            elems.length = 0;491            parserState.pos += 2;492            foundStopChar = true;493            foundSeparator = false;494            continue;495        } else if (c === " ") {496            parserState.pos += 1;497            continue;498        } else if (isSeparatorCharacter(c)) {499            parserState.pos += 1;500            foundStopChar = true;501            foundSeparator = true;502            continue;503        } else if (c === ":" && isPathStart(parserState)) {504            throw ["Unexpected ", "::", ": paths cannot start with ", "::"];505        } else if (isEndCharacter(c)) {506            throw ["Unexpected ", c, " after ", extra];507        }508        if (!foundStopChar) {509            /** @type {string[]} */510            let extra = [];511            if (isLastElemGeneric(query.elems, parserState)) {512                extra = [" after ", ">"];513            } else if (prevIs(parserState, "\"")) {514                throw ["Cannot have more than one element if you use quotes"];515            }516            if (endChar !== "") {517                throw [518                    "Expected ",519                    ",",520                    ", ",521                    "=",522                    ", or ",523                    endChar,524                    ...extra,525                    ", found ",526                    c,527                ];528            }529            throw [530                "Expected ",531                ",",532                " or ",533                "=",534                ...extra,535                ", found ",536                c,537            ];538        }539        const posBefore = parserState.pos;540        getFilteredNextElem(query, parserState, elems, endChar !== "");541        if (endChar !== "" && parserState.pos >= parserState.length) {542            throw ["Unclosed ", extra];543        }544        // This case can be encountered if `getNextElem` encountered a "stop character"545        // right from the start. For example if you have `,,` or `<>`. In this case,546        // we simply move up the current position to continue the parsing.547        if (posBefore === parserState.pos) {548            parserState.pos += 1;549        }550        foundStopChar = false;551    }552    if (parserState.pos >= parserState.length && endChar !== "") {553        throw ["Unclosed ", extra];554    }555    // We are either at the end of the string or on the `endChar` character, let's move556    // forward in any case.557    parserState.pos += 1;558559    if (hofParameters) {560        // Commas in a HOF don't cause wrapping parens to become a tuple.561        // If you want a one-tuple with a HOF in it, write `((a -> b),)`.562        foundSeparator = false;563        // HOFs can't have directly nested bindings.564        if ([...elems, ...hofParameters].some(x => x.bindingName)565            || parserState.isInBinding) {566            throw ["Unexpected ", "=", " within ", "->"];567        }568        // HOFs are represented the same way closures are.569        // The arguments are wrapped in a tuple, and the output570        // is a binding, even though the compiler doesn't technically571        // represent fn pointers that way.572        const hofElem = makePrimitiveElement("->", {573            generics: hofParameters,574            bindings: new Map([["output", [...elems]]]),575            typeFilter: null,576        });577        elems.length = 0;578        elems[0] = hofElem;579    }580581    parserState.typeFilter = oldTypeFilter;582    parserState.isInBinding = oldIsInBinding;583584    return { foundSeparator };585}586587/**588 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query589 * @param {rustdoc.ParserState} parserState590 * @param {Array<rustdoc.ParserQueryElement>} elems591 *     - This is where the new {QueryElement} will be added.592 * @param {boolean} isInGenerics593 */594function getNextElem(query, parserState, elems, isInGenerics) {595    /** @type {rustdoc.ParserQueryElement[]} */596    const generics = [];597598    /** @type {function(string, string): void} */599    const handleRefOrPtr = (chr, name) => {600            if (parserState.typeFilter !== null && parserState.typeFilter !== "primitive") {601            throw [602                "Invalid search type: primitive ",603                chr,604                " and ",605                parserState.typeFilter,606                " both specified",607            ];608        }609        parserState.typeFilter = null;610        parserState.pos += 1;611        let c = parserState.userQuery[parserState.pos];612        while (c === " " && parserState.pos < parserState.length) {613            parserState.pos += 1;614            c = parserState.userQuery[parserState.pos];615        }616        const generics = [];617        const pos = parserState.pos;618        if (parserState.userQuery.slice(pos, pos + 3) === "mut") {619            generics.push(makePrimitiveElement("mut", { typeFilter: "keyword" }));620            parserState.pos += 3;621            c = parserState.userQuery[parserState.pos];622        } else if (chr === "*" && parserState.userQuery.slice(pos, pos + 5) === "const") {623            // make *const T parse the same as *T624            parserState.pos += 5;625            c = parserState.userQuery[parserState.pos];626        }627        while (c === " " && parserState.pos < parserState.length) {628            parserState.pos += 1;629            c = parserState.userQuery[parserState.pos];630        }631        if (!isEndCharacter(c) && parserState.pos < parserState.length) {632            getFilteredNextElem(query, parserState, generics, isInGenerics);633        }634        elems.push(makePrimitiveElement(name, { generics }));635    };636637    skipWhitespace(parserState);638    let start = parserState.pos;639    let end;640    if ("[(".indexOf(parserState.userQuery[parserState.pos]) !== -1) {641        let endChar = ")";642        let name = "()";643        let friendlyName = "tuple";644645        if (parserState.userQuery[parserState.pos] === "[") {646            endChar = "]";647            name = "[]";648            friendlyName = "slice";649        }650        parserState.pos += 1;651        const { foundSeparator } = getItemsBefore(query, parserState, generics, endChar);652        const typeFilter = parserState.typeFilter;653        const bindingName = parserState.isInBinding;654        parserState.typeFilter = null;655        parserState.isInBinding = null;656        for (const gen of generics) {657            if (gen.bindingName !== null) {658                throw ["Type parameter ", "=", ` cannot be within ${friendlyName} `, name];659            }660        }661        if (name === "()" && !foundSeparator && generics.length === 1662            && typeFilter === null) {663            elems.push(generics[0]);664        } else if (name === "()" && generics.length === 1 && generics[0].name === "->") {665            // `primitive:(a -> b)` parser to `primitive:"->"<output=b, (a,)>`666            // not `primitive:"()"<"->"<output=b, (a,)>>`667            generics[0].typeFilter = typeFilter;668            elems.push(generics[0]);669        } else {670            if (typeFilter !== null && typeFilter !== "primitive") {671                throw [672                    "Invalid search type: primitive ",673                    name,674                    " and ",675                    typeFilter,676                    " both specified",677                ];678            }679            parserState.totalElems += 1;680            if (isInGenerics) {681                parserState.genericsElems += 1;682            }683            elems.push(makePrimitiveElement(name, { bindingName, generics }));684        }685    } else if (parserState.userQuery[parserState.pos] === "&") {686        handleRefOrPtr("&", "reference");687    } else if (parserState.userQuery[parserState.pos] === "*") {688        handleRefOrPtr("*", "pointer");689    } else {690        const isStringElem = parserState.userQuery[start] === "\"";691        // We handle the strings on their own mostly to make code easier to follow.692        if (isStringElem) {693            start += 1;694            getStringElem(query, parserState, isInGenerics);695            end = parserState.pos - 1;696        } else {697            end = getIdentEndPosition(parserState);698        }699        if (parserState.pos < parserState.length &&700            parserState.userQuery[parserState.pos] === "<"701        ) {702            if (start >= end) {703                throw ["Found generics without a path"];704            }705            parserState.pos += 1;706            getItemsBefore(query, parserState, generics, ">");707        } else if (parserState.pos < parserState.length &&708            parserState.userQuery[parserState.pos] === "("709        ) {710            if (start >= end) {711                throw ["Found generics without a path"];712            }713            if (parserState.isInBinding) {714                throw ["Unexpected ", "(", " after ", "="];715            }716            parserState.pos += 1;717            const typeFilter = parserState.typeFilter;718            parserState.typeFilter = null;719            getItemsBefore(query, parserState, generics, ")");720            skipWhitespace(parserState);721            if (isReturnArrow(parserState)) {722                parserState.pos += 2;723                skipWhitespace(parserState);724                getFilteredNextElem(query, parserState, generics, isInGenerics);725                generics[generics.length - 1].bindingName = makePrimitiveElement("output");726            } else {727                generics.push(makePrimitiveElement(null, {728                    bindingName: makePrimitiveElement("output"),729                    typeFilter: null,730                }));731            }732            parserState.typeFilter = typeFilter;733        }734        if (isStringElem) {735            skipWhitespace(parserState);736        }737        if (start >= end && generics.length === 0) {738            return;739        }740        if (parserState.userQuery[parserState.pos] === "=") {741            if (parserState.isInBinding) {742                throw ["Cannot write ", "=", " twice in a binding"];743            }744            if (!isInGenerics) {745                throw ["Type parameter ", "=", " must be within generics list"];746            }747            const name = parserState.userQuery.slice(start, end).trim();748            if (name === "!") {749                throw ["Type parameter ", "=", " key cannot be ", "!", " never type"];750            }751            if (name.includes("!")) {752                throw ["Type parameter ", "=", " key cannot be ", "!", " macro"];753            }754            if (name.includes("::")) {755                throw ["Type parameter ", "=", " key cannot contain ", "::", " path"];756            }757            if (name.includes(":")) {758                throw ["Type parameter ", "=", " key cannot contain ", ":", " type"];759            }760            parserState.isInBinding = { name, generics };761        } else {762            elems.push(763                createQueryElement(764                    query,765                    parserState,766                    parserState.userQuery.slice(start, end),767                    generics,768                    isInGenerics,769                ),770            );771        }772    }773}774775/**776 * Checks that the type filter doesn't have unwanted characters like `<>` (which are ignored777 * if empty).778 *779 * @param {number} start780 * @param {rustdoc.ParserState} parserState781 */782function checkExtraTypeFilterCharacters(start, parserState) {783    const query = parserState.userQuery.slice(start, parserState.pos).trim();784785    const match = query.match(REGEX_INVALID_TYPE_FILTER);786    if (match) {787        throw [788            "Unexpected ",789            match[0],790            " in type filter (before ",791            ":",792            ")",793        ];794    }795}796797/**798 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query799 * @param {rustdoc.ParserState} parserState800 * @param {string} name - Name of the query element.801 * @param {Array<rustdoc.ParserQueryElement>} generics - List of generics of this query element.802 * @param {boolean} isInGenerics803 *804 * @return {rustdoc.ParserQueryElement} - The newly created `QueryElement`.805 */806function createQueryElement(query, parserState, name, generics, isInGenerics) {807    const path = name.trim();808    if (path.length === 0 && generics.length === 0) {809        throw ["Unexpected ", parserState.userQuery[parserState.pos]];810    }811    if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {812        throw ["Cannot have more than one element if you use quotes"];813    }814    const typeFilter = parserState.typeFilter;815    parserState.typeFilter = null;816    if (name.trim() === "!") {817        if (typeFilter !== null && typeFilter !== "primitive") {818            throw [819                "Invalid search type: primitive never type ",820                "!",821                " and ",822                typeFilter,823                " both specified",824            ];825        }826        if (generics.length !== 0) {827            throw [828                "Never type ",829                "!",830                " does not accept generic parameters",831            ];832        }833        const bindingName = parserState.isInBinding;834        parserState.isInBinding = null;835        return makePrimitiveElement("never", { bindingName });836    }837    const quadcolon = /::\s*::/.exec(path);838    if (path.startsWith("::")) {839        throw ["Paths cannot start with ", "::"];840    } else if (quadcolon !== null) {841        throw ["Unexpected ", quadcolon[0]];842    }843    const pathSegments = path.split(/(?:::\s*)|(?:\s+(?:::\s*)?)/).map(x => x.toLowerCase());844    // In case we only have something like `<p>`, there is no name.845    if (pathSegments.length === 0846        || (pathSegments.length === 1 && pathSegments[0] === "")) {847        if (generics.length > 0 || prevIs(parserState, ">")) {848            throw ["Found generics without a path"];849        } else {850            throw ["Unexpected ", parserState.userQuery[parserState.pos]];851        }852    }853    for (const [i, pathSegment] of pathSegments.entries()) {854        if (pathSegment === "!") {855            if (i !== 0) {856                throw ["Never type ", "!", " is not associated item"];857            }858            pathSegments[i] = "never";859        }860    }861    parserState.totalElems += 1;862    if (isInGenerics) {863        parserState.genericsElems += 1;864    }865    const bindingName = parserState.isInBinding;866    parserState.isInBinding = null;867    const bindings = new Map();868    const pathLast = pathSegments[pathSegments.length - 1];869    return {870        name: name.trim(),871        id: null,872        fullPath: pathSegments,873        pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),874        pathLast,875        normalizedPathLast: pathLast.replace(/_/g, ""),876        generics: generics.filter(gen => {877            // Syntactically, bindings are parsed as generics,878            // but the query engine treats them differently.879            if (gen.bindingName !== null && gen.bindingName.name !== null) {880                if (gen.name !== null) {881                    gen.bindingName.generics.unshift(gen);882                }883                bindings.set(884                    gen.bindingName.name.toLowerCase().replace(/_/g, ""),885                    gen.bindingName.generics,886                );887                return false;888            }889            return true;890        }),891        bindings,892        typeFilter,893        bindingName,894    };895}896897/**898 *899 * @param {string|null} name900 * @param {rustdoc.ParserQueryElementFields=} extra901 * @returns {rustdoc.ParserQueryElement}902 */903function makePrimitiveElement(name, extra) {904    return Object.assign({905        name,906        id: null,907        fullPath: [name],908        pathWithoutLast: [],909        pathLast: name,910        normalizedPathLast: name,911        generics: [],912        bindings: new Map(),913        typeFilter: "primitive",914        bindingName: null,915    }, extra);916}917918/**919 * If we encounter a `"`, then we try to extract the string920 * from it until we find another `"`.921 *922 * This function will throw an error in the following cases:923 * * There is already another string element.924 * * We are parsing a generic argument.925 * * There is more than one element.926 * * There is no closing `"`.927 *928 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query929 * @param {rustdoc.ParserState} parserState930 * @param {boolean} isInGenerics931 */932function getStringElem(query, parserState, isInGenerics) {933    if (isInGenerics) {934        throw ["Unexpected ", "\"", " in generics"];935    } else if (query.literalSearch) {936        throw ["Cannot have more than one literal search element"];937    } else if (parserState.totalElems - parserState.genericsElems > 0) {938        throw ["Cannot use literal search when there is more than one element"];939    }940    parserState.pos += 1;941    const start = parserState.pos;942    const end = getIdentEndPosition(parserState);943    if (parserState.pos >= parserState.length) {944        throw ["Unclosed ", "\""];945    } else if (parserState.userQuery[end] !== "\"") {946        throw ["Unexpected ", parserState.userQuery[end], " in a string element"];947    } else if (start === end) {948        throw ["Cannot have empty string element"];949    }950    // To skip the quote at the end.951    parserState.pos += 1;952    query.literalSearch = true;953}954955/**956 * This function goes through all characters until it reaches an invalid ident957 * character or the end of the query. It returns the position of the last958 * character of the ident.959 *960 * @param {rustdoc.ParserState} parserState961 *962 * @return {number}963 */964function getIdentEndPosition(parserState) {965    let afterIdent = consumeIdent(parserState);966    let end = parserState.pos;967    let macroExclamation = -1;968    while (parserState.pos < parserState.length) {969        const c = parserState.userQuery[parserState.pos];970        if (c === "!") {971            if (macroExclamation !== -1) {972                throw ["Cannot have more than one ", "!", " in an ident"];973            } else if (parserState.pos + 1 < parserState.length) {974                const pos = parserState.pos;975                parserState.pos++;976                const beforeIdent = consumeIdent(parserState);977                parserState.pos = pos;978                if (beforeIdent) {979                    throw ["Unexpected ", "!", ": it can only be at the end of an ident"];980                }981            }982            if (afterIdent) macroExclamation = parserState.pos;983        } else if (isPathSeparator(c)) {984            if (c === ":") {985                if (!isPathStart(parserState)) {986                    break;987                }988                // Skip current ":".989                parserState.pos += 1;990            } else {991                while (parserState.pos + 1 < parserState.length) {992                    const next_c = parserState.userQuery[parserState.pos + 1];993                    if (next_c !== " ") {994                        break;995                    }996                    parserState.pos += 1;997                }998            }999            if (macroExclamation !== -1) {1000                throw ["Cannot have associated items in macros"];1001            }1002        } else if (1003            c === "[" ||1004            c === "(" ||1005            isEndCharacter(c) ||1006            isSpecialStartCharacter(c) ||1007            isSeparatorCharacter(c)1008        ) {1009            break;1010        } else if (parserState.pos > 0) {1011            throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1],1012                " (not a valid identifier)"];1013        } else {1014            throw ["Unexpected ", c, " (not a valid identifier)"];1015        }1016        parserState.pos += 1;1017        afterIdent = consumeIdent(parserState);1018        end = parserState.pos;1019    }1020    if (macroExclamation !== -1) {1021        if (parserState.typeFilter === null) {1022            parserState.typeFilter = "macro";1023        } else if (parserState.typeFilter !== "macro") {1024            throw [1025                "Invalid search type: macro ",1026                "!",1027                " and ",1028                parserState.typeFilter,1029                " both specified",1030            ];1031        }1032        end = macroExclamation;1033    }1034    return end;1035}10361037/**1038 * @param {string} c1039 * @returns1040 */1041function isSpecialStartCharacter(c) {1042    return "<\"".indexOf(c) !== -1;1043}10441045/**1046 * Returns `true` if the current parser position is starting with "::".1047 *1048 * @param {rustdoc.ParserState} parserState1049 *1050 * @return {boolean}1051 */1052function isPathStart(parserState) {1053    return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "::";1054}10551056/**1057 * If the current parser position is at the beginning of an identifier,1058 * move the position to the end of it and return `true`. Otherwise, return `false`.1059 *1060 * @param {rustdoc.ParserState} parserState1061 *1062 * @return {boolean}1063 */1064function consumeIdent(parserState) {1065    REGEX_IDENT.lastIndex = parserState.pos;1066    const match = parserState.userQuery.match(REGEX_IDENT);1067    if (match) {1068        parserState.pos += match[0].length;1069        return true;1070    }1071    return false;1072}10731074/**1075 * Returns `true` if the given `c` character is a path separator. For example1076 * `:` in `a::b` or a whitespace in `a b`.1077 *1078 * @param {string} c1079 *1080 * @return {boolean}1081 */1082function isPathSeparator(c) {1083    return c === ":" || c === " ";1084}10851086/**1087 * Given an array and an ascending list of indices,1088 * efficiently removes each index in the array.1089 *1090 * @template T1091 * @param {Array<T>} a1092 * @param {Array<number>} idxList1093 */1094function removeIdxListAsc(a, idxList) {1095    if (idxList.length === 0) {1096        return;1097    }1098    let removed = 0;1099    let i = idxList[0];1100    let nextToRemove = idxList[0];1101    while (i < a.length - idxList.length) {1102        while (i === nextToRemove && removed < idxList.length) {1103            removed++;1104            i++;1105            nextToRemove = idxList[removed];1106        }1107        a[i] = a[i + removed];1108        i++;1109    }1110    // truncate array1111    a.length -= idxList.length;1112}11131114/**1115 * @template T1116 */1117class VlqHexDecoder {1118    /**1119     * @param {string} string1120     * @param {function(rustdoc.VlqData): T} cons1121     */1122    constructor(string, cons) {1123        this.string = string;1124        this.cons = cons;1125        this.offset = 0;1126        this.elemCount = 0;1127        /** @type {T[]} */1128        this.backrefQueue = [];1129    }1130    /**1131     * call after consuming `{`1132     * @returns {rustdoc.VlqData[]}1133     */1134    decodeList() {1135        let c = this.string.charCodeAt(this.offset);1136        const ret = [];1137        while (c !== 125) { // 125 = "}"1138            ret.push(this.decode());1139            c = this.string.charCodeAt(this.offset);1140        }1141        this.offset += 1; // eat cb1142        return ret;1143    }1144    /**1145     * consumes and returns a list or integer1146     * @returns {rustdoc.VlqData}1147     */1148    decode() {1149        let n = 0;1150        let c = this.string.charCodeAt(this.offset);1151        if (c === 123) { // 123 = "{"1152            this.offset += 1;1153            return this.decodeList();1154        }1155        while (c < 96) { // 96 = "`"1156            n = (n << 4) | (c & 0xF);1157            this.offset += 1;1158            c = this.string.charCodeAt(this.offset);1159        }1160        // last character >= la1161        n = (n << 4) | (c & 0xF);1162        const [sign, value] = [n & 1, n >> 1];1163        this.offset += 1;1164        this.elemCount += 1;1165        return sign ? -value : value;1166    }1167    /**1168     * @returns {T}1169     */1170    next() {1171        const c = this.string.charCodeAt(this.offset);1172        // sixteen characters after "0" are backref1173        if (c >= 48 && c < 64) { // 48 = "0", 64 = "@"1174            this.offset += 1;1175            return this.backrefQueue[c - 48];1176        }1177        // special exception: 0 doesn't use backref encoding1178        // it's already one character, and it's always nullish1179        if (c === 96) { // 96 = "`"1180            this.offset += 1;1181            return this.cons(0);1182        }1183        const result = this.cons(this.decode());1184        this.backrefQueue.unshift(result);1185        if (this.backrefQueue.length > 16) {1186            this.backrefQueue.pop();1187        }1188        return result;1189    }1190}11911192/** @type {Array<string>} */1193const EMPTY_STRING_ARRAY = [];11941195/** @type {Array<rustdoc.FunctionType>} */1196const EMPTY_GENERICS_ARRAY = [];11971198/** @type {Array<[number, rustdoc.FunctionType[]]>} */1199const EMPTY_BINDINGS_ARRAY = [];12001201/** @type {Map<number, Array<any>>} */1202const EMPTY_BINDINGS_MAP = new Map();12031204/**1205 * @param {string|null} typename1206 * @returns {number}1207 */1208function itemTypeFromName(typename) {1209    if (typename === null) {1210        return NO_TYPE_FILTER;1211    }1212    // @ts-expect-error1213    const index = itemTypes[typename];1214    if (index === undefined) {1215        throw ["Unknown type filter ", typename];1216    }1217    return index;1218}12191220class DocSearch {1221    /**1222     * @param {string} rootPath1223     * @param {stringdex.Database} database1224     */1225    constructor(rootPath, database) {1226        this.rootPath = rootPath;1227        this.database = database;12281229        this.utf8decoder = new TextDecoder();12301231        /** @type {Map<number|null, rustdoc.FunctionType>} */1232        this.TYPES_POOL = new Map();1233    }12341235    /**1236     * Load type name ID set.1237     *1238     * Each of these identifiers are used specially by1239     * type-driven search. Most of them are lang items1240     * in the compiler.1241     *1242     * Use this function, which caches the result, and not1243     * getTypeNameIdsAsync, which is an internal implementation1244     * detail for this.1245     *1246     * @return {Promise<rustdoc.TypeNameIds>|rustdoc.TypeNameIds}1247     */1248    getTypeNameIds() {1249        if (this.typeNameIds) {1250            return this.typeNameIds;1251        }1252        const nn = this.database.getData("normalizedName");1253        if (!nn) {1254            return {1255                typeNameIdOfOutput: -1,1256                typeNameIdOfFnPtr: -1,1257                typeNameIdOfFn: -1,1258                typeNameIdOfFnMut: -1,1259                typeNameIdOfFnOnce: -1,1260                typeNameIdOfArray: -1,1261                typeNameIdOfSlice: -1,1262                typeNameIdOfArrayOrSlice: -1,1263                typeNameIdOfTuple: -1,1264                typeNameIdOfUnit: -1,1265                typeNameIdOfTupleOrUnit: -1,1266                typeNameIdOfReference: -1,1267                typeNameIdOfPointer: -1,1268                typeNameIdOfHof: -1,1269                typeNameIdOfNever: -1,1270            };1271        }1272        return this.getTypeNameIdsAsync(nn);1273    }1274    /**1275     * @param {stringdex.DataColumn} nn1276     * @returns {Promise<rustdoc.TypeNameIds>}1277     */1278    async getTypeNameIdsAsync(nn) {1279        // Each of these identifiers are used specially by1280        // type-driven search.1281        const [1282            // output is the special associated type that goes1283            // after the arrow: the type checker desugars1284            // the path `Fn(a) -> b` into `Fn<Output=b, (a)>`1285            output,1286            // fn, fnmut, and fnonce all match `->`1287            fn,1288            fnMut,1289            fnOnce,1290            hof,1291            // array and slice both match `[]`1292            array,1293            slice,1294            arrayOrSlice,1295            // tuple and unit both match `()`1296            tuple,1297            unit,1298            tupleOrUnit,1299            // reference matches `&`1300            reference,1301            pointer,1302            // never matches `!`1303            never,1304        ] = await Promise.all([1305            nn.search("output"),1306            nn.search("fn"),1307            nn.search("fnmut"),1308            nn.search("fnonce"),1309            nn.search("->"),1310            nn.search("array"),1311            nn.search("slice"),1312            nn.search("[]"),1313            nn.search("tuple"),1314            nn.search("unit"),1315            nn.search("()"),1316            nn.search("reference"),1317            nn.search("pointer"),1318            nn.search("never"),1319        ]);1320        /**1321         * @param {stringdex.Trie|null|undefined} trie1322         * @param {rustdoc.ItemType} ty1323         * @param {string} modulePath1324         * @returns {Promise<number>}1325         * */1326        const first = async(trie, ty, modulePath) => {1327            if (trie) {1328                for (const id of trie.matches().entries()) {1329                    const pathData = await this.getPathData(id);1330                    if (pathData && pathData.ty === ty && pathData.modulePath === modulePath) {1331                        return id;1332                    }1333                }1334            }1335            return -1;1336        };1337        const typeNameIdOfOutput = await first(output, itemTypes.associatedtype, "");1338        const typeNameIdOfFnPtr = await first(fn, itemTypes.primitive, "");1339        const typeNameIdOfFn = await first(fn, itemTypes.trait, "core::ops");1340        const typeNameIdOfFnMut = await first(fnMut, itemTypes.trait, "core::ops");1341        const typeNameIdOfFnOnce = await first(fnOnce, itemTypes.trait, "core::ops");1342        const typeNameIdOfArray = await first(array, itemTypes.primitive, "");1343        const typeNameIdOfSlice = await first(slice, itemTypes.primitive, "");1344        const typeNameIdOfArrayOrSlice = await first(arrayOrSlice, itemTypes.primitive, "");1345        const typeNameIdOfTuple = await first(tuple, itemTypes.primitive, "");1346        const typeNameIdOfUnit = await first(unit, itemTypes.primitive, "");1347        const typeNameIdOfTupleOrUnit = await first(tupleOrUnit, itemTypes.primitive, "");1348        const typeNameIdOfReference = await first(reference, itemTypes.primitive, "");1349        const typeNameIdOfPointer = await first(pointer, itemTypes.primitive, "");1350        const typeNameIdOfHof = await first(hof, itemTypes.primitive, "");1351        const typeNameIdOfNever = await first(never, itemTypes.primitive, "");1352        this.typeNameIds = {1353            typeNameIdOfOutput,1354            typeNameIdOfFnPtr,1355            typeNameIdOfFn,1356            typeNameIdOfFnMut,1357            typeNameIdOfFnOnce,1358            typeNameIdOfArray,1359            typeNameIdOfSlice,1360            typeNameIdOfArrayOrSlice,1361            typeNameIdOfTuple,1362            typeNameIdOfUnit,1363            typeNameIdOfTupleOrUnit,1364            typeNameIdOfReference,1365            typeNameIdOfPointer,1366            typeNameIdOfHof,1367            typeNameIdOfNever,1368        };1369        return this.typeNameIds;1370    }13711372    /**1373     * Parses the query.1374     *1375     * The supported syntax by this parser is given in the rustdoc book chapter1376     * /src/doc/rustdoc/src/read-documentation/search.md1377     *1378     * When adding new things to the parser, add them there, too!1379     *1380     * @param  {string} userQuery - The user query1381     *1382     * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} - The parsed query1383     */1384    static parseQuery(userQuery) {1385        /**1386         * Takes the user search input and returns an empty `ParsedQuery`.1387         *1388         * @param {string} userQuery1389         *1390         * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>}1391         */1392        function newParsedQuery(userQuery) {1393            return {1394                userQuery,1395                elems: [],1396                returned: [],1397                // Total number of "top" elements (does not include generics).1398                foundElems: 0,1399                // Total number of elements (includes generics).1400                totalElems: 0,1401                literalSearch: false,1402                hasReturnArrow: false,1403                error: null,1404                correction: null,1405                proposeCorrectionFrom: null,1406                proposeCorrectionTo: null,1407                // bloom filter build from type ids1408                typeFingerprint: new Uint32Array(4),1409            };1410        }14111412        /**1413        * Parses the provided `query` input to fill `parserState`. If it encounters an error while1414        * parsing `query`, it'll throw an error.1415        *1416        * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query1417        * @param {rustdoc.ParserState} parserState1418        */1419        function parseInput(query, parserState) {1420            let foundStopChar = true;14211422            while (parserState.pos < parserState.length) {1423                const c = parserState.userQuery[parserState.pos];1424                if (isEndCharacter(c)) {1425                    foundStopChar = true;1426                    if (isSeparatorCharacter(c)) {1427                        parserState.pos += 1;1428                        continue;1429                    } else if (c === "-" || c === ">") {1430                        if (isReturnArrow(parserState)) {1431                            query.hasReturnArrow = true;1432                            break;1433                        }1434                        throw ["Unexpected ", c, " (did you mean ", "->", "?)"];1435                    } else if (parserState.pos > 0) {1436                        throw ["Unexpected ", c, " after ",1437                            parserState.userQuery[parserState.pos - 1]];1438                    }1439                    throw ["Unexpected ", c];1440                } else if (c === " ") {1441                    skipWhitespace(parserState);1442                    continue;1443                }1444                if (!foundStopChar) {1445                    let extra = EMPTY_STRING_ARRAY;1446                    if (isLastElemGeneric(query.elems, parserState)) {1447                        extra = [" after ", ">"];1448                    } else if (prevIs(parserState, "\"")) {1449                        throw ["Cannot have more than one element if you use quotes"];1450                    }1451                    if (parserState.typeFilter !== null) {1452                        throw [1453                            "Expected ",1454                            ",",1455                            " or ",1456                            "->",1457                            ...extra,1458                            ", found ",1459                            c,1460                        ];1461                    }1462                    throw [1463                        "Expected ",1464                        ",",1465                        ", ",1466                        ":",1467                        " or ",1468                        "->",1469                        ...extra,1470                        ", found ",1471                        c,1472                    ];1473                }1474                const before = query.elems.length;1475                getFilteredNextElem(query, parserState, query.elems, false);1476                if (query.elems.length === before) {1477                    // Nothing was added, weird... Let's increase the position to not remain stuck.1478                    parserState.pos += 1;1479                }1480                foundStopChar = false;1481            }1482            if (parserState.typeFilter !== null) {1483                throw [1484                    "Unexpected ",1485                    ":",1486                    " (expected path after type filter ",1487                    parserState.typeFilter + ":",1488                    ")",1489                ];1490            }1491            while (parserState.pos < parserState.length) {1492                if (isReturnArrow(parserState)) {1493                    parserState.pos += 2;1494                    skipWhitespace(parserState);1495                    // Get returned elements.1496                    getItemsBefore(query, parserState, query.returned, "");1497                    // Nothing can come afterward!1498                    query.hasReturnArrow = true;1499                    break;1500                } else {1501                    parserState.pos += 1;1502                }1503            }1504        }150515061507        userQuery = userQuery.trim().replace(/\r|\n|\t/g, " ");1508        const parserState = {1509            length: userQuery.length,1510            pos: 0,1511            // Total number of elements (includes generics).1512            totalElems: 0,1513            genericsElems: 0,1514            typeFilter: null,1515            isInBinding: null,1516            userQuery,1517        };1518        let query = newParsedQuery(userQuery);15191520        try {1521            parseInput(query, parserState);15221523            // Scan for invalid type filters, so that we can report the error1524            // outside the search loop.1525            /** @param {rustdoc.ParserQueryElement} elem */1526            const checkTypeFilter = elem => {1527                const ty = itemTypeFromName(elem.typeFilter);1528                if (ty === itemTypes.generic && elem.generics.length !== 0) {1529                    throw [1530                        "Generic type parameter ",1531                        elem.name,1532                        " does not accept generic parameters",1533                    ];1534                }1535                for (const generic of elem.generics) {1536                    checkTypeFilter(generic);1537                }1538                for (const constraints of elem.bindings.values()) {1539                    for (const constraint of constraints) {1540                        checkTypeFilter(constraint);1541                    }1542                }1543            };1544            for (const elem of query.elems) {1545                checkTypeFilter(elem);1546            }1547            for (const elem of query.returned) {1548                checkTypeFilter(elem);1549            }1550        } catch (err) {1551            query = newParsedQuery(userQuery);1552            if (Array.isArray(err) && err.every(elem => typeof elem === "string")) {1553                query.error = err;1554            } else {1555                // rethrow the error if it isn't a string array1556                throw err;1557            }15581559            return query;1560        }1561        if (!query.literalSearch) {1562            // If there is more than one element in the query, we switch to literalSearch in any1563            // case.1564            query.literalSearch = parserState.totalElems > 1;1565        }1566        query.foundElems = query.elems.length + query.returned.length;1567        query.totalElems = parserState.totalElems;1568        return query;1569    }15701571    /**1572     * @param {number} id1573     * @returns {Promise<string|null>}1574     */1575    async getName(id) {1576        const ni = this.database.getData("name");1577        if (!ni) {1578            return null;1579        }1580        const name = await ni.at(id);1581        return name === undefined || name === null ? null : this.utf8decoder.decode(name);1582    }15831584    /**1585     * @param {number} id1586     * @returns {Promise<string|null>}1587     */1588    async getDesc(id) {1589        const di = this.database.getData("desc");1590        if (!di) {1591            return null;1592        }1593        const desc = await di.at(id);1594        return desc === undefined || desc === null ? null : this.utf8decoder.decode(desc);1595    }15961597    /**1598     * @param {number} id1599     * @returns {Promise<number|null>}1600     */1601    async getAliasTarget(id) {1602        const ai = this.database.getData("alias");1603        if (!ai) {1604            return null;1605        }1606        const bytes = await ai.at(id);1607        if (bytes === undefined || bytes === null || bytes.length === 0) {1608            return null;1609        } else {1610            /** @type {string} */1611            const encoded = this.utf8decoder.decode(bytes);1612            /** @type {number|null} */1613            const decoded = JSON.parse(encoded);1614            return decoded;1615        }1616    }16171618    /**1619     * @param {number} id1620     * @returns {Promise<rustdoc.EntryData|null>}1621     */1622    async getEntryData(id) {1623        const ei = this.database.getData("entry");1624        if (!ei) {1625            return null;1626        }1627        const encoded = this.utf8decoder.decode(await ei.at(id));1628        if (encoded === "" || encoded === undefined || encoded === null) {1629            return null;1630        }1631        /**1632         * krate,1633         * ty,1634         * module_path,1635         * exact_module_path,1636         * parent,1637         * trait_parent,1638         * deprecated,1639         * unstable,1640         * associated_item_disambiguator1641         * @type {rustdoc.ArrayWithOptionals<[1642         *     number,1643         *     rustdoc.ItemType,1644         *     number,1645         *     number,1646         *     number,1647         *     number,1648         *     number,1649         *     number,1650         * ], [string]>}1651         */1652        const raw = JSON.parse(encoded);1653        const item = {1654            krate: raw[0],1655            ty: raw[1],1656            modulePath: raw[2] === 0 ? null : raw[2] - 1,1657            exactModulePath: raw[3] === 0 ? null : raw[3] - 1,1658            parent: raw[4] === 0 ? null : raw[4] - 1,1659            traitParent: raw[5] === 0 ? null : raw[5] - 1,1660            deprecated: raw[6] === 1 ? true : false,1661            unstable: raw[7] === 1 ? true : false,1662            associatedItemDisambiguatorOrExternCrateUrl: raw.length === 8 ? null : raw[8],1663            forceMacroHref: false,1664        };1665        if (item.ty === itemTypes.decl_macro_attribute || item.ty === itemTypes.decl_macro_derive) {1666            // "proc attribute" is 23, "proc derive" is 24 whereas "decl macro attribute" is 28 and1667            // "decl macro derive" is 29, so 5 of difference to go from the latter to the former.1668            item.ty -= 5;1669            item.forceMacroHref = true;1670        }1671        return item;1672    }16731674    /**1675     * @param {number} id1676     * @returns {Promise<rustdoc.PathData|null>}1677     */1678    async getPathData(id) {1679        const pi = this.database.getData("path");1680        if (!pi) {1681            return null;1682        }1683        const encoded = this.utf8decoder.decode(await pi.at(id));1684        if (encoded === "" || encoded === undefined || encoded === null) {1685            return null;1686        }1687        /**1688         * ty, module_path, exact_module_path, search_unbox, inverted_function_signature_index1689         * @type {rustdoc.ArrayWithOptionals<[rustdoc.ItemType, string], [string|0, 0|1, string]>}1690         */1691        const raw = JSON.parse(encoded);1692        return {1693            ty: raw[0],1694            modulePath: raw[1],1695            exactModulePath: raw[2] === 0 || raw[2] === undefined ? raw[1] : raw[2],1696        };1697    }16981699    /**1700     * @param {number} id1701     * @returns {Promise<rustdoc.FunctionData|null>}1702     */1703    async getFunctionData(id) {1704        const fi = this.database.getData("function");1705        if (!fi) {1706            return null;1707        }1708        const encoded = this.utf8decoder.decode(await fi.at(id));1709        if (encoded === "" || encoded === undefined || encoded === null) {1710            return null;1711        }1712        /**1713         * function_signature, param_names1714         * @type {[string, string[]]}1715         */1716        const raw = JSON.parse(encoded);17171718        const parser = new VlqHexDecoder(raw[0], async functionSearchType => {1719            if (typeof functionSearchType === "number") {1720                return null;1721            }1722            const INPUTS_DATA = 0;1723            const OUTPUT_DATA = 1;1724            /** @type {Promise<rustdoc.FunctionType[]>} */1725            let inputs_;1726            /** @type {Promise<rustdoc.FunctionType[]>} */1727            let output_;1728            if (typeof functionSearchType[INPUTS_DATA] === "number") {1729                inputs_ = Promise.all([1730                    this.buildItemSearchType(functionSearchType[INPUTS_DATA]),1731                ]);1732            } else {1733                // @ts-ignore1734                inputs_ = this.buildItemSearchTypeAll(functionSearchType[INPUTS_DATA]);1735            }1736            if (functionSearchType.length > 1) {1737                if (typeof functionSearchType[OUTPUT_DATA] === "number") {1738                    output_ = Promise.all([1739                        this.buildItemSearchType(functionSearchType[OUTPUT_DATA]),1740                    ]);1741                } else {1742                    // @ts-expect-error1743                    output_ = this.buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA]);1744                }1745            } else {1746                output_ = Promise.resolve(EMPTY_GENERICS_ARRAY);1747            }1748            /** @type {Promise<rustdoc.FunctionType[]>[]} */1749            const where_clause_ = [];1750            const l = functionSearchType.length;1751            for (let i = 2; i < l; ++i) {1752                where_clause_.push(typeof functionSearchType[i] === "number"1753                    // @ts-expect-error1754                    ? Promise.all([this.buildItemSearchType(functionSearchType[i])])1755                    // @ts-expect-error1756                    : this.buildItemSearchTypeAll(functionSearchType[i]),1757                );1758            }1759            const [inputs, output, where_clause] = await Promise.all([1760                inputs_,1761                output_,1762                Promise.all(where_clause_),1763            ]);1764            return {1765                inputs, output, where_clause,1766            };1767        });17681769        return {1770            functionSignature: await parser.next(),1771            paramNames: raw[1],1772            elemCount: parser.elemCount,1773        };1774    }17751776    /**1777     * @param {number} id1778     * @returns {Promise<rustdoc.TypeData|null>}1779     */1780    async getTypeData(id) {1781        const ti = this.database.getData("type");1782        if (!ti) {1783            return null;1784        }1785        const encoded = this.utf8decoder.decode(await ti.at(id));1786        if (encoded === "" || encoded === undefined || encoded === null) {1787            return null;1788        }1789        /**1790         * function_signature, param_names1791         * @type {[string, string, number] | [string, string] | [] | null}1792         */1793        const raw = JSON.parse(encoded);17941795        if (!raw || raw.length === 0) {1796            return null;1797        }17981799        let searchUnbox = false;1800        const invertedFunctionInputsIndex = [];1801        const invertedFunctionOutputIndex = [];18021803        if (typeof raw[0] === "string") {1804            if (raw[2]) {1805                searchUnbox = true;1806            }1807            // the inverted function signature index is a list of bitmaps,1808            // by number of types that appear in the function1809            let i = 0;1810            let pb = makeUint8ArrayFromBase64(raw[0]);1811            let l = pb.length;1812            while (i < l) {1813                if (pb[i] === 0) {1814                    invertedFunctionInputsIndex.push(RoaringBitmap.empty());1815                    i += 1;1816                } else {1817                    const bitmap = new RoaringBitmap(pb, i);1818                    i += bitmap.consumed_len_bytes;1819                    invertedFunctionInputsIndex.push(bitmap);1820                }1821            }1822            i = 0;1823            pb = makeUint8ArrayFromBase64(raw[1]);1824            l = pb.length;1825            while (i < l) {1826                if (pb[i] === 0) {1827                    invertedFunctionOutputIndex.push(RoaringBitmap.empty());1828                    i += 1;1829                } else {1830                    const bitmap = new RoaringBitmap(pb, i);1831                    i += bitmap.consumed_len_bytes;1832                    invertedFunctionOutputIndex.push(bitmap);1833                }1834            }1835        } else if (raw[0]) {1836            searchUnbox = true;1837        }18381839        return { searchUnbox, invertedFunctionInputsIndex, invertedFunctionOutputIndex };1840    }18411842    /**1843     * @returns {Promise<string[]>}1844     */1845    async getCrateNameList() {1846        const crateNames = this.database.getData("crateNames");1847        if (!crateNames) {1848            return [];1849        }1850        const l = crateNames.length;1851        const names = [];1852        for (let i = 0; i < l; ++i) {1853            const name = await crateNames.at(i);1854            names.push(name === undefined ? "" : this.utf8decoder.decode(name));1855        }1856        return Promise.all(names);1857    }18581859    /**1860     * @param {number} id non-negative generic index1861     * @returns {Promise<stringdex.RoaringBitmap[]>}1862     */1863    async getGenericInvertedIndex(id) {1864        const gii = this.database.getData("generic_inverted_index");1865        if (!gii) {1866            return [];1867        }1868        const pb = await gii.at(id);1869        if (pb === undefined || pb === null || pb.length === 0) {1870            return [];1871        }18721873        const invertedFunctionSignatureIndex = [];1874        // the inverted function signature index is a list of bitmaps,1875        // by number of types that appear in the function1876        let i = 0;1877        const l = pb.length;1878        while (i < l) {1879            if (pb[i] === 0) {1880                invertedFunctionSignatureIndex.push(RoaringBitmap.empty());1881                i += 1;1882            } else {1883                const bitmap = new RoaringBitmap(pb, i);1884                i += bitmap.consumed_len_bytes;1885                invertedFunctionSignatureIndex.push(bitmap);1886            }1887        }1888        return invertedFunctionSignatureIndex;1889    }18901891    /**1892     * @param {number} id1893     * @param {boolean} loadFunctionData1894     * @returns {Promise<rustdoc.Row?>}1895     */1896    async getRow(id, loadFunctionData) {1897        const [name_, entry, path, functionData] = await Promise.all([1898            this.getName(id),1899            this.getEntryData(id),1900            this.getPathData(id),1901            loadFunctionData ? this.getFunctionData(id) : null,1902        ]);1903        if (!entry && !path) {1904            return null;1905        }1906        /** @type {function("parent" | "traitParent"): Promise<rustdoc.RowParent>} */1907        const buildParentLike = async field => {1908            const [name, path] = entry !== null && entry[field] !== null ?1909                await Promise.all([this.getName(entry[field]), this.getPathData(entry[field])]) :1910                [null, null];1911            if (name !== null && path !== null) {1912                return { name, path };1913            }1914            return null;1915        };19161917        const [1918            moduleName,1919            modulePathData,1920            exactModuleName,1921            exactModulePathData,1922            parent,1923            traitParent,1924            crateOrNull,1925        ] = await Promise.all([1926            entry && entry.modulePath !== null ? this.getName(entry.modulePath) : null,1927            entry && entry.modulePath !== null ? this.getPathData(entry.modulePath) : null,1928            entry && entry.exactModulePath !== null ?1929                this.getName(entry.exactModulePath) :1930                null,1931            entry && entry.exactModulePath !== null ?1932                this.getPathData(entry.exactModulePath) :1933                null,1934            buildParentLike("parent"),1935            buildParentLike("traitParent"),1936            entry ? this.getName(entry.krate) : "",1937        ]);1938        const crate = crateOrNull === null ? "" : crateOrNull;1939        const name = name_ === null ? "" : name_;1940        const normalizedName = (name.indexOf("_") === -1 ?1941            name :1942            name.replace(/_/g, "")).toLowerCase();1943        const modulePath = modulePathData === null || moduleName === null ? "" :1944            (modulePathData.modulePath === "" ?1945                moduleName :1946                `${modulePathData.modulePath}::${moduleName}`);19471948        return {1949            id,1950            crate,1951            ty: entry ? entry.ty : nonnull(path).ty,1952            name,1953            normalizedName,1954            modulePath,1955            exactModulePath: exactModulePathData === null || exactModuleName === null ? modulePath :1956                (exactModulePathData.exactModulePath === "" ?1957                    exactModuleName :1958                    `${exactModulePathData.exactModulePath}::${exactModuleName}`),1959            entry,1960            path,1961            functionData,1962            deprecated: entry ? entry.deprecated : false,1963            unstable: entry ? entry.unstable : false,1964            parent,1965            traitParent,1966        };1967    }19681969    /**1970     * Convert a list of RawFunctionType / ID to object-based FunctionType.1971     *1972     * Crates often have lots of functions in them, and it's common to have a large number of1973     * functions that operate on a small set of data types, so the search index compresses them1974     * by encoding function parameter and return types as indexes into an array of names.1975     *1976     * Even when a general-purpose compression algorithm is used, this is still a win.1977     * I checked. https://github.com/rust-lang/rust/pull/98475#issue-12843959851978     *1979     * The format for individual function types is encoded in1980     * librustdoc/html/render/mod.rs: impl Serialize for RenderType1981     *1982     * @param {null|Array<rustdoc.RawFunctionType>} types1983     *1984     * @return {Promise<Array<rustdoc.FunctionType>>}1985     */1986    async buildItemSearchTypeAll(types) {1987        return types && types.length > 0 ?1988            await Promise.all(types.map(type => this.buildItemSearchType(type))) :1989            EMPTY_GENERICS_ARRAY;1990    }19911992    /**1993     * Converts a single type.1994     *1995     * @param {rustdoc.RawFunctionType} type1996     * @return {Promise<rustdoc.FunctionType>}1997     */1998    async buildItemSearchType(type) {1999        const PATH_INDEX_DATA = 0;2000        const GENERICS_DATA = 1;

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.