src/librustdoc/html/static/js/search.js JAVASCRIPT 5,596 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 5,596.
1// ignore-tidy-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});124const itemTypesName = Array.from(Object.keys(itemTypes));125126// When filtering, some types might be included as well. For example, when you filter on `constant`,127// we also include associated constant items.128//129// This map is built as follows: the first item of the array is the type to be included when the130// second type of the array is used as filter.131const itemParents = new Map([132    [itemTypes.associatedconstant, itemTypes.constant],133    [itemTypes.method, itemTypes.fn],134    [itemTypes.tymethod, itemTypes.fn],135    [itemTypes.primitive, itemTypes.type],136    [itemTypes.associatedtype, itemTypes.type],137    [itemTypes.traitalias, itemTypes.trait],138    [itemTypes.attr, itemTypes.macro],139    [itemTypes.derive, itemTypes.macro],140    [itemTypes.externcrate, itemTypes.import],141]);142143const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";144145// Hard limit on how deep to recurse into generics when doing type-driven search.146// This needs limited, partially because147// a search for `Ty` shouldn't match `WithInfcx<ParamEnvAnd<Vec<ConstTy<Interner<Ty=Ty>>>>>`,148// but mostly because this is the simplest and most principled way to limit the number149// of permutations we need to check.150const UNBOXING_LIMIT = 5;151152// used for search query verification153// because searches are often performed using substrings of identifiers,154// and not just full identiferes, we allow them to start with chars that otherwise155// can only appear in the middle of identifiers156const REGEX_IDENT = /\p{ID_Continue}+/uy;157const REGEX_INVALID_TYPE_FILTER = /[^a-z]/ui;158159const MAX_RESULTS = 200;160const NO_TYPE_FILTER = -1;161const DEPRECATED_COUNT_SELECTOR = "deprecated-count";162163/**164 * The [edit distance] is a metric for measuring the difference between two strings.165 *166 * [edit distance]: https://en.wikipedia.org/wiki/Edit_distance167 */168169/*170 * This function was translated, mostly line-for-line, from171 * https://github.com/rust-lang/rust/blob/ff4b772f805ec1e/compiler/rustc_span/src/edit_distance.rs172 *173 * The current implementation is the restricted Damerau-Levenshtein algorithm. It is restricted174 * because it does not permit modifying characters that have already been transposed. The specific175 * algorithm should not matter to the caller of the methods, which is why it is not noted in the176 * documentation.177 */178const editDistanceState = {179    /**180     * @type {number[]}181     */182    current: [],183    /**184     * @type {number[]}185     */186    prev: [],187    /**188     * @type {number[]}189     */190    prevPrev: [],191    /**192     * @param {string} a193     * @param {string} b194     * @param {number} limit195     * @returns196     */197    calculate: function calculate(a, b, limit) {198        // Ensure that `b` is the shorter string, minimizing memory use.199        if (a.length < b.length) {200            const aTmp = a;201            a = b;202            b = aTmp;203        }204205        const minDist = a.length - b.length;206        // If we know the limit will be exceeded, we can return early.207        if (minDist > limit) {208            return limit + 1;209        }210211        // Strip common prefix.212        // We know that `b` is the shorter string, so we don't need to check213        // `a.length`.214        while (b.length > 0 && b[0] === a[0]) {215            a = a.substring(1);216            b = b.substring(1);217        }218        // Strip common suffix.219        while (b.length > 0 && b[b.length - 1] === a[a.length - 1]) {220            a = a.substring(0, a.length - 1);221            b = b.substring(0, b.length - 1);222        }223224        // If either string is empty, the distance is the length of the other.225        // We know that `b` is the shorter string, so we don't need to check `a`.226        if (b.length === 0) {227            return minDist;228        }229230        const aLength = a.length;231        const bLength = b.length;232233        for (let i = 0; i <= bLength; ++i) {234            this.current[i] = 0;235            this.prev[i] = i;236            this.prevPrev[i] = Number.MAX_VALUE;237        }238239        // row by row240        for (let i = 1; i <= aLength; ++i) {241            this.current[0] = i;242            const aIdx = i - 1;243244            // column by column245            for (let j = 1; j <= bLength; ++j) {246                const bIdx = j - 1;247248                // There is no cost to substitute a character with itself.249                const substitutionCost = a[aIdx] === b[bIdx] ? 0 : 1;250251                this.current[j] = Math.min(252                    // deletion253                    this.prev[j] + 1,254                    // insertion255                    this.current[j - 1] + 1,256                    // substitution257                    this.prev[j - 1] + substitutionCost,258                );259260                if ((i > 1) && (j > 1) && (a[aIdx] === b[bIdx - 1]) && (a[aIdx - 1] === b[bIdx])) {261                    // transposition262                    this.current[j] = Math.min(263                        this.current[j],264                        this.prevPrev[j - 2] + 1,265                    );266                }267            }268269            // Rotate the buffers, reusing the memory270            const prevPrevTmp = this.prevPrev;271            this.prevPrev = this.prev;272            this.prev = this.current;273            this.current = prevPrevTmp;274        }275276        // `prev` because we already rotated the buffers.277        const distance = this.prev[bLength];278        return distance <= limit ? distance : (limit + 1);279    },280};281282/**283 * @param {string} a284 * @param {string} b285 * @param {number} limit286 * @returns287 */288function editDistance(a, b, limit) {289    return editDistanceState.calculate(a, b, limit);290}291292/**293 * @param {string} c294 * @returns {boolean}295 */296function isEndCharacter(c) {297    return "=,>-])".indexOf(c) !== -1;298}299300/**301 * Same thing as ItemType::is_fn_like in item_type.rs302 *303 * @param {rustdoc.ItemType} ty304 * @returns305 */306function isFnLikeTy(ty) {307    return ty === itemTypes.fn || ty === itemTypes.method || ty === itemTypes.tymethod;308}309310/**311 * Returns `true` if the given `c` character is a separator.312 *313 * @param {string} c314 *315 * @return {boolean}316 */317function isSeparatorCharacter(c) {318    return c === "," || c === "=";319}320321/**322 * Returns `true` if the current parser position is starting with "->".323 *324 * @param {rustdoc.ParserState} parserState325 *326 * @return {boolean}327 */328function isReturnArrow(parserState) {329    return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "->";330}331332/**333 * Increase current parser position until it doesn't find a whitespace anymore.334 *335 * @param {rustdoc.ParserState} parserState336 */337function skipWhitespace(parserState) {338    while (parserState.pos < parserState.userQuery.length) {339        const c = parserState.userQuery[parserState.pos];340        if (c !== " ") {341            break;342        }343        parserState.pos += 1;344    }345}346347/**348 * Returns `true` if the previous character is `lookingFor`.349 *350 * @param {rustdoc.ParserState} parserState351 * @param {String} lookingFor352 *353 * @return {boolean}354 */355function prevIs(parserState, lookingFor) {356    let pos = parserState.pos;357    while (pos > 0) {358        const c = parserState.userQuery[pos - 1];359        if (c === lookingFor) {360            return true;361        } else if (c !== " ") {362            break;363        }364        pos -= 1;365    }366    return false;367}368369/**370 * Returns `true` if the last element in the `elems` argument has generics.371 *372 * @param {Array<rustdoc.ParserQueryElement>} elems373 * @param {rustdoc.ParserState} parserState374 *375 * @return {boolean}376 */377function isLastElemGeneric(elems, parserState) {378    return (elems.length > 0 && elems[elems.length - 1].generics.length > 0) ||379        prevIs(parserState, ">");380}381382/**383 *384 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query385 * @param {rustdoc.ParserState} parserState386 * @param {rustdoc.ParserQueryElement[]} elems387 * @param {boolean} isInGenerics388 */389function getFilteredNextElem(query, parserState, elems, isInGenerics) {390    const start = parserState.pos;391    if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {392        throw ["Expected type filter before ", ":"];393    }394    getNextElem(query, parserState, elems, isInGenerics);395    if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {396        if (parserState.typeFilter !== null) {397            throw [398                "Unexpected ",399                ":",400                " (expected path after type filter ",401                parserState.typeFilter + ":",402                ")",403            ];404        }405        if (elems.length === 0) {406            throw ["Expected type filter before ", ":"];407        } else if (query.literalSearch) {408            throw ["Cannot use quotes on type filter"];409        }410        // The type filter doesn't count as an element since it's a modifier.411        const typeFilterElem = elems.pop();412        checkExtraTypeFilterCharacters(start, parserState);413        // typeFilterElem is not undefined. If it was, the elems.length check would have fired.414        // @ts-expect-error415        parserState.typeFilter = typeFilterElem.normalizedPathLast;416        parserState.pos += 1;417        parserState.totalElems -= 1;418        query.literalSearch = false;419        getNextElem(query, parserState, elems, isInGenerics);420    }421}422423/**424 * This function parses the next query element until it finds `endChar`,425 * calling `getNextElem` to collect each element.426 *427 * If there is no `endChar`, this function will implicitly stop at the end428 * without raising an error.429 *430 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query431 * @param {rustdoc.ParserState} parserState432 * @param {Array<rustdoc.ParserQueryElement>} elems433 *     - This is where the new {QueryElement} will be added.434 * @param {string} endChar - This function will stop when it'll encounter this435 *                           character.436 * @returns {{foundSeparator: boolean}}437 */438function getItemsBefore(query, parserState, elems, endChar) {439    let foundStopChar = true;440    let foundSeparator = false;441442    // If this is a generic, keep the outer item's type filter around.443    const oldTypeFilter = parserState.typeFilter;444    parserState.typeFilter = null;445    const oldIsInBinding = parserState.isInBinding;446    parserState.isInBinding = null;447448    // ML-style Higher Order Function notation449    //450    // a way to search for any closure or fn pointer regardless of451    // which closure trait is used452    //453    // Looks like this:454    //455    //     `option<t>, (t -> u) -> option<u>`456    //                  ^^^^^^457    //458    // The Rust-style closure notation is implemented in getNextElem459    let hofParameters = null;460461    let extra = "";462    if (endChar === ">") {463        extra = "<";464    } else if (endChar === "]") {465        extra = "[";466    } else if (endChar === ")") {467        extra = "(";468    } else if (endChar === "") {469        extra = "->";470    } else {471        extra = endChar;472    }473474    while (parserState.pos < parserState.length) {475        const c = parserState.userQuery[parserState.pos];476        if (c === endChar) {477            if (parserState.isInBinding) {478                throw ["Unexpected ", endChar, " after ", "="];479            }480            break;481        } else if (endChar !== "" && isReturnArrow(parserState)) {482            // ML-style HOF notation only works when delimited in something,483            // otherwise a function arrow starts the return type of the top484            if (parserState.isInBinding) {485                throw ["Unexpected ", "->", " after ", "="];486            }487            hofParameters = [...elems];488            elems.length = 0;489            parserState.pos += 2;490            foundStopChar = true;491            foundSeparator = false;492            continue;493        } else if (c === " ") {494            parserState.pos += 1;495            continue;496        } else if (isSeparatorCharacter(c)) {497            parserState.pos += 1;498            foundStopChar = true;499            foundSeparator = true;500            continue;501        } else if (c === ":" && isPathStart(parserState)) {502            throw ["Unexpected ", "::", ": paths cannot start with ", "::"];503        } else if (isEndCharacter(c)) {504            throw ["Unexpected ", c, " after ", extra];505        }506        if (!foundStopChar) {507            /** @type {string[]} */508            let extra = [];509            if (isLastElemGeneric(query.elems, parserState)) {510                extra = [" after ", ">"];511            } else if (prevIs(parserState, "\"")) {512                throw ["Cannot have more than one element if you use quotes"];513            }514            if (endChar !== "") {515                throw [516                    "Expected ",517                    ",",518                    ", ",519                    "=",520                    ", or ",521                    endChar,522                    ...extra,523                    ", found ",524                    c,525                ];526            }527            throw [528                "Expected ",529                ",",530                " or ",531                "=",532                ...extra,533                ", found ",534                c,535            ];536        }537        const posBefore = parserState.pos;538        getFilteredNextElem(query, parserState, elems, endChar !== "");539        if (endChar !== "" && parserState.pos >= parserState.length) {540            throw ["Unclosed ", extra];541        }542        // This case can be encountered if `getNextElem` encountered a "stop character"543        // right from the start. For example if you have `,,` or `<>`. In this case,544        // we simply move up the current position to continue the parsing.545        if (posBefore === parserState.pos) {546            parserState.pos += 1;547        }548        foundStopChar = false;549    }550    if (parserState.pos >= parserState.length && endChar !== "") {551        throw ["Unclosed ", extra];552    }553    // We are either at the end of the string or on the `endChar` character, let's move554    // forward in any case.555    parserState.pos += 1;556557    if (hofParameters) {558        // Commas in a HOF don't cause wrapping parens to become a tuple.559        // If you want a one-tuple with a HOF in it, write `((a -> b),)`.560        foundSeparator = false;561        // HOFs can't have directly nested bindings.562        if ([...elems, ...hofParameters].some(x => x.bindingName)563            || parserState.isInBinding) {564            throw ["Unexpected ", "=", " within ", "->"];565        }566        // HOFs are represented the same way closures are.567        // The arguments are wrapped in a tuple, and the output568        // is a binding, even though the compiler doesn't technically569        // represent fn pointers that way.570        const hofElem = makePrimitiveElement("->", {571            generics: hofParameters,572            bindings: new Map([["output", [...elems]]]),573            typeFilter: null,574        });575        elems.length = 0;576        elems[0] = hofElem;577    }578579    parserState.typeFilter = oldTypeFilter;580    parserState.isInBinding = oldIsInBinding;581582    return { foundSeparator };583}584585/**586 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query587 * @param {rustdoc.ParserState} parserState588 * @param {Array<rustdoc.ParserQueryElement>} elems589 *     - This is where the new {QueryElement} will be added.590 * @param {boolean} isInGenerics591 */592function getNextElem(query, parserState, elems, isInGenerics) {593    /** @type {rustdoc.ParserQueryElement[]} */594    const generics = [];595596    /** @type {function(string, string): void} */597    const handleRefOrPtr = (chr, name) => {598            if (parserState.typeFilter !== null && parserState.typeFilter !== "primitive") {599            throw [600                "Invalid search type: primitive ",601                chr,602                " and ",603                parserState.typeFilter,604                " both specified",605            ];606        }607        parserState.typeFilter = null;608        parserState.pos += 1;609        let c = parserState.userQuery[parserState.pos];610        while (c === " " && parserState.pos < parserState.length) {611            parserState.pos += 1;612            c = parserState.userQuery[parserState.pos];613        }614        const generics = [];615        const pos = parserState.pos;616        if (parserState.userQuery.slice(pos, pos + 3) === "mut") {617            generics.push(makePrimitiveElement("mut", { typeFilter: "keyword" }));618            parserState.pos += 3;619            c = parserState.userQuery[parserState.pos];620        } else if (chr === "*" && parserState.userQuery.slice(pos, pos + 5) === "const") {621            // make *const T parse the same as *T622            parserState.pos += 5;623            c = parserState.userQuery[parserState.pos];624        }625        while (c === " " && parserState.pos < parserState.length) {626            parserState.pos += 1;627            c = parserState.userQuery[parserState.pos];628        }629        if (!isEndCharacter(c) && parserState.pos < parserState.length) {630            getFilteredNextElem(query, parserState, generics, isInGenerics);631        }632        elems.push(makePrimitiveElement(name, { generics }));633    };634635    skipWhitespace(parserState);636    let start = parserState.pos;637    let end;638    if ("[(".indexOf(parserState.userQuery[parserState.pos]) !== -1) {639        let endChar = ")";640        let name = "()";641        let friendlyName = "tuple";642643        if (parserState.userQuery[parserState.pos] === "[") {644            endChar = "]";645            name = "[]";646            friendlyName = "slice";647        }648        parserState.pos += 1;649        const { foundSeparator } = getItemsBefore(query, parserState, generics, endChar);650        const typeFilter = parserState.typeFilter;651        const bindingName = parserState.isInBinding;652        parserState.typeFilter = null;653        parserState.isInBinding = null;654        for (const gen of generics) {655            if (gen.bindingName !== null) {656                throw ["Type parameter ", "=", ` cannot be within ${friendlyName} `, name];657            }658        }659        if (name === "()" && !foundSeparator && generics.length === 1660            && typeFilter === null) {661            elems.push(generics[0]);662        } else if (name === "()" && generics.length === 1 && generics[0].name === "->") {663            // `primitive:(a -> b)` parser to `primitive:"->"<output=b, (a,)>`664            // not `primitive:"()"<"->"<output=b, (a,)>>`665            generics[0].typeFilter = typeFilter;666            elems.push(generics[0]);667        } else {668            if (typeFilter !== null && typeFilter !== "primitive") {669                throw [670                    "Invalid search type: primitive ",671                    name,672                    " and ",673                    typeFilter,674                    " both specified",675                ];676            }677            parserState.totalElems += 1;678            if (isInGenerics) {679                parserState.genericsElems += 1;680            }681            elems.push(makePrimitiveElement(name, { bindingName, generics }));682        }683    } else if (parserState.userQuery[parserState.pos] === "&") {684        handleRefOrPtr("&", "reference");685    } else if (parserState.userQuery[parserState.pos] === "*") {686        handleRefOrPtr("*", "pointer");687    } else {688        const isStringElem = parserState.userQuery[start] === "\"";689        // We handle the strings on their own mostly to make code easier to follow.690        if (isStringElem) {691            start += 1;692            getStringElem(query, parserState, isInGenerics);693            end = parserState.pos - 1;694        } else {695            end = getIdentEndPosition(parserState);696        }697        if (parserState.pos < parserState.length &&698            parserState.userQuery[parserState.pos] === "<"699        ) {700            if (start >= end) {701                throw ["Found generics without a path"];702            }703            parserState.pos += 1;704            getItemsBefore(query, parserState, generics, ">");705        } else if (parserState.pos < parserState.length &&706            parserState.userQuery[parserState.pos] === "("707        ) {708            if (start >= end) {709                throw ["Found generics without a path"];710            }711            if (parserState.isInBinding) {712                throw ["Unexpected ", "(", " after ", "="];713            }714            parserState.pos += 1;715            const typeFilter = parserState.typeFilter;716            parserState.typeFilter = null;717            getItemsBefore(query, parserState, generics, ")");718            skipWhitespace(parserState);719            if (isReturnArrow(parserState)) {720                parserState.pos += 2;721                skipWhitespace(parserState);722                getFilteredNextElem(query, parserState, generics, isInGenerics);723                generics[generics.length - 1].bindingName = makePrimitiveElement("output");724            } else {725                generics.push(makePrimitiveElement(null, {726                    bindingName: makePrimitiveElement("output"),727                    typeFilter: null,728                }));729            }730            parserState.typeFilter = typeFilter;731        }732        if (isStringElem) {733            skipWhitespace(parserState);734        }735        if (start >= end && generics.length === 0) {736            return;737        }738        if (parserState.userQuery[parserState.pos] === "=") {739            if (parserState.isInBinding) {740                throw ["Cannot write ", "=", " twice in a binding"];741            }742            if (!isInGenerics) {743                throw ["Type parameter ", "=", " must be within generics list"];744            }745            const name = parserState.userQuery.slice(start, end).trim();746            if (name === "!") {747                throw ["Type parameter ", "=", " key cannot be ", "!", " never type"];748            }749            if (name.includes("!")) {750                throw ["Type parameter ", "=", " key cannot be ", "!", " macro"];751            }752            if (name.includes("::")) {753                throw ["Type parameter ", "=", " key cannot contain ", "::", " path"];754            }755            if (name.includes(":")) {756                throw ["Type parameter ", "=", " key cannot contain ", ":", " type"];757            }758            parserState.isInBinding = { name, generics };759        } else {760            elems.push(761                createQueryElement(762                    query,763                    parserState,764                    parserState.userQuery.slice(start, end),765                    generics,766                    isInGenerics,767                ),768            );769        }770    }771}772773/**774 * Checks that the type filter doesn't have unwanted characters like `<>` (which are ignored775 * if empty).776 *777 * @param {number} start778 * @param {rustdoc.ParserState} parserState779 */780function checkExtraTypeFilterCharacters(start, parserState) {781    const query = parserState.userQuery.slice(start, parserState.pos).trim();782783    const match = query.match(REGEX_INVALID_TYPE_FILTER);784    if (match) {785        throw [786            "Unexpected ",787            match[0],788            " in type filter (before ",789            ":",790            ")",791        ];792    }793}794795/**796 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query797 * @param {rustdoc.ParserState} parserState798 * @param {string} name - Name of the query element.799 * @param {Array<rustdoc.ParserQueryElement>} generics - List of generics of this query element.800 * @param {boolean} isInGenerics801 *802 * @return {rustdoc.ParserQueryElement} - The newly created `QueryElement`.803 */804function createQueryElement(query, parserState, name, generics, isInGenerics) {805    const path = name.trim();806    if (path.length === 0 && generics.length === 0) {807        throw ["Unexpected ", parserState.userQuery[parserState.pos]];808    }809    if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {810        throw ["Cannot have more than one element if you use quotes"];811    }812    const typeFilter = parserState.typeFilter;813    parserState.typeFilter = null;814    if (name.trim() === "!") {815        if (typeFilter !== null && typeFilter !== "primitive") {816            throw [817                "Invalid search type: primitive never type ",818                "!",819                " and ",820                typeFilter,821                " both specified",822            ];823        }824        if (generics.length !== 0) {825            throw [826                "Never type ",827                "!",828                " does not accept generic parameters",829            ];830        }831        const bindingName = parserState.isInBinding;832        parserState.isInBinding = null;833        return makePrimitiveElement("never", { bindingName });834    }835    const quadcolon = /::\s*::/.exec(path);836    if (path.startsWith("::")) {837        throw ["Paths cannot start with ", "::"];838    } else if (quadcolon !== null) {839        throw ["Unexpected ", quadcolon[0]];840    }841    const pathSegments = path.split(/(?:::\s*)|(?:\s+(?:::\s*)?)/).map(x => x.toLowerCase());842    // In case we only have something like `<p>`, there is no name.843    if (pathSegments.length === 0844        || (pathSegments.length === 1 && pathSegments[0] === "")) {845        if (generics.length > 0 || prevIs(parserState, ">")) {846            throw ["Found generics without a path"];847        } else {848            throw ["Unexpected ", parserState.userQuery[parserState.pos]];849        }850    }851    for (const [i, pathSegment] of pathSegments.entries()) {852        if (pathSegment === "!") {853            if (i !== 0) {854                throw ["Never type ", "!", " is not associated item"];855            }856            pathSegments[i] = "never";857        }858    }859    parserState.totalElems += 1;860    if (isInGenerics) {861        parserState.genericsElems += 1;862    }863    const bindingName = parserState.isInBinding;864    parserState.isInBinding = null;865    const bindings = new Map();866    const pathLast = pathSegments[pathSegments.length - 1];867    return {868        name: name.trim(),869        id: null,870        fullPath: pathSegments,871        pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),872        pathLast,873        normalizedPathLast: pathLast.replace(/_/g, ""),874        generics: generics.filter(gen => {875            // Syntactically, bindings are parsed as generics,876            // but the query engine treats them differently.877            if (gen.bindingName !== null && gen.bindingName.name !== null) {878                if (gen.name !== null) {879                    gen.bindingName.generics.unshift(gen);880                }881                bindings.set(882                    gen.bindingName.name.toLowerCase().replace(/_/g, ""),883                    gen.bindingName.generics,884                );885                return false;886            }887            return true;888        }),889        bindings,890        typeFilter,891        bindingName,892    };893}894895/**896 *897 * @param {string|null} name898 * @param {rustdoc.ParserQueryElementFields=} extra899 * @returns {rustdoc.ParserQueryElement}900 */901function makePrimitiveElement(name, extra) {902    return Object.assign({903        name,904        id: null,905        fullPath: [name],906        pathWithoutLast: [],907        pathLast: name,908        normalizedPathLast: name,909        generics: [],910        bindings: new Map(),911        typeFilter: "primitive",912        bindingName: null,913    }, extra);914}915916/**917 * If we encounter a `"`, then we try to extract the string918 * from it until we find another `"`.919 *920 * This function will throw an error in the following cases:921 * * There is already another string element.922 * * We are parsing a generic argument.923 * * There is more than one element.924 * * There is no closing `"`.925 *926 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query927 * @param {rustdoc.ParserState} parserState928 * @param {boolean} isInGenerics929 */930function getStringElem(query, parserState, isInGenerics) {931    if (isInGenerics) {932        throw ["Unexpected ", "\"", " in generics"];933    } else if (query.literalSearch) {934        throw ["Cannot have more than one literal search element"];935    } else if (parserState.totalElems - parserState.genericsElems > 0) {936        throw ["Cannot use literal search when there is more than one element"];937    }938    parserState.pos += 1;939    const start = parserState.pos;940    const end = getIdentEndPosition(parserState);941    if (parserState.pos >= parserState.length) {942        throw ["Unclosed ", "\""];943    } else if (parserState.userQuery[end] !== "\"") {944        throw ["Unexpected ", parserState.userQuery[end], " in a string element"];945    } else if (start === end) {946        throw ["Cannot have empty string element"];947    }948    // To skip the quote at the end.949    parserState.pos += 1;950    query.literalSearch = true;951}952953/**954 * This function goes through all characters until it reaches an invalid ident955 * character or the end of the query. It returns the position of the last956 * character of the ident.957 *958 * @param {rustdoc.ParserState} parserState959 *960 * @return {number}961 */962function getIdentEndPosition(parserState) {963    let afterIdent = consumeIdent(parserState);964    let end = parserState.pos;965    let macroExclamation = -1;966    while (parserState.pos < parserState.length) {967        const c = parserState.userQuery[parserState.pos];968        if (c === "!") {969            if (macroExclamation !== -1) {970                throw ["Cannot have more than one ", "!", " in an ident"];971            } else if (parserState.pos + 1 < parserState.length) {972                const pos = parserState.pos;973                parserState.pos++;974                const beforeIdent = consumeIdent(parserState);975                parserState.pos = pos;976                if (beforeIdent) {977                    throw ["Unexpected ", "!", ": it can only be at the end of an ident"];978                }979            }980            if (afterIdent) macroExclamation = parserState.pos;981        } else if (isPathSeparator(c)) {982            if (c === ":") {983                if (!isPathStart(parserState)) {984                    break;985                }986                // Skip current ":".987                parserState.pos += 1;988            } else {989                while (parserState.pos + 1 < parserState.length) {990                    const next_c = parserState.userQuery[parserState.pos + 1];991                    if (next_c !== " ") {992                        break;993                    }994                    parserState.pos += 1;995                }996            }997            if (macroExclamation !== -1) {998                throw ["Cannot have associated items in macros"];999            }1000        } else if (1001            c === "[" ||1002            c === "(" ||1003            isEndCharacter(c) ||1004            isSpecialStartCharacter(c) ||1005            isSeparatorCharacter(c)1006        ) {1007            break;1008        } else if (parserState.pos > 0) {1009            throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1],1010                " (not a valid identifier)"];1011        } else {1012            throw ["Unexpected ", c, " (not a valid identifier)"];1013        }1014        parserState.pos += 1;1015        afterIdent = consumeIdent(parserState);1016        end = parserState.pos;1017    }1018    if (macroExclamation !== -1) {1019        if (parserState.typeFilter === null) {1020            parserState.typeFilter = "macro";1021        } else if (parserState.typeFilter !== "macro") {1022            throw [1023                "Invalid search type: macro ",1024                "!",1025                " and ",1026                parserState.typeFilter,1027                " both specified",1028            ];1029        }1030        end = macroExclamation;1031    }1032    return end;1033}10341035/**1036 * @param {string} c1037 * @returns1038 */1039function isSpecialStartCharacter(c) {1040    return "<\"".indexOf(c) !== -1;1041}10421043/**1044 * Returns `true` if the current parser position is starting with "::".1045 *1046 * @param {rustdoc.ParserState} parserState1047 *1048 * @return {boolean}1049 */1050function isPathStart(parserState) {1051    return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "::";1052}10531054/**1055 * If the current parser position is at the beginning of an identifier,1056 * move the position to the end of it and return `true`. Otherwise, return `false`.1057 *1058 * @param {rustdoc.ParserState} parserState1059 *1060 * @return {boolean}1061 */1062function consumeIdent(parserState) {1063    REGEX_IDENT.lastIndex = parserState.pos;1064    const match = parserState.userQuery.match(REGEX_IDENT);1065    if (match) {1066        parserState.pos += match[0].length;1067        return true;1068    }1069    return false;1070}10711072/**1073 * Returns `true` if the given `c` character is a path separator. For example1074 * `:` in `a::b` or a whitespace in `a b`.1075 *1076 * @param {string} c1077 *1078 * @return {boolean}1079 */1080function isPathSeparator(c) {1081    return c === ":" || c === " ";1082}10831084/**1085 * Given an array and an ascending list of indices,1086 * efficiently removes each index in the array.1087 *1088 * @template T1089 * @param {Array<T>} a1090 * @param {Array<number>} idxList1091 */1092function removeIdxListAsc(a, idxList) {1093    if (idxList.length === 0) {1094        return;1095    }1096    let removed = 0;1097    let i = idxList[0];1098    let nextToRemove = idxList[0];1099    while (i < a.length - idxList.length) {1100        while (i === nextToRemove && removed < idxList.length) {1101            removed++;1102            i++;1103            nextToRemove = idxList[removed];1104        }1105        a[i] = a[i + removed];1106        i++;1107    }1108    // truncate array1109    a.length -= idxList.length;1110}11111112/**1113 * @template T1114 */1115class VlqHexDecoder {1116    /**1117     * @param {string} string1118     * @param {function(rustdoc.VlqData): T} cons1119     */1120    constructor(string, cons) {1121        this.string = string;1122        this.cons = cons;1123        this.offset = 0;1124        this.elemCount = 0;1125        /** @type {T[]} */1126        this.backrefQueue = [];1127    }1128    /**1129     * call after consuming `{`1130     * @returns {rustdoc.VlqData[]}1131     */1132    decodeList() {1133        let c = this.string.charCodeAt(this.offset);1134        const ret = [];1135        while (c !== 125) { // 125 = "}"1136            ret.push(this.decode());1137            c = this.string.charCodeAt(this.offset);1138        }1139        this.offset += 1; // eat cb1140        return ret;1141    }1142    /**1143     * consumes and returns a list or integer1144     * @returns {rustdoc.VlqData}1145     */1146    decode() {1147        let n = 0;1148        let c = this.string.charCodeAt(this.offset);1149        if (c === 123) { // 123 = "{"1150            this.offset += 1;1151            return this.decodeList();1152        }1153        while (c < 96) { // 96 = "`"1154            n = (n << 4) | (c & 0xF);1155            this.offset += 1;1156            c = this.string.charCodeAt(this.offset);1157        }1158        // last character >= la1159        n = (n << 4) | (c & 0xF);1160        const [sign, value] = [n & 1, n >> 1];1161        this.offset += 1;1162        this.elemCount += 1;1163        return sign ? -value : value;1164    }1165    /**1166     * @returns {T}1167     */1168    next() {1169        const c = this.string.charCodeAt(this.offset);1170        // sixteen characters after "0" are backref1171        if (c >= 48 && c < 64) { // 48 = "0", 64 = "@"1172            this.offset += 1;1173            return this.backrefQueue[c - 48];1174        }1175        // special exception: 0 doesn't use backref encoding1176        // it's already one character, and it's always nullish1177        if (c === 96) { // 96 = "`"1178            this.offset += 1;1179            return this.cons(0);1180        }1181        const result = this.cons(this.decode());1182        this.backrefQueue.unshift(result);1183        if (this.backrefQueue.length > 16) {1184            this.backrefQueue.pop();1185        }1186        return result;1187    }1188}11891190/** @type {Array<string>} */1191const EMPTY_STRING_ARRAY = [];11921193/** @type {Array<rustdoc.FunctionType>} */1194const EMPTY_GENERICS_ARRAY = [];11951196/** @type {Array<[number, rustdoc.FunctionType[]]>} */1197const EMPTY_BINDINGS_ARRAY = [];11981199/** @type {Map<number, Array<any>>} */1200const EMPTY_BINDINGS_MAP = new Map();12011202/**1203 * @param {string|null} typename1204 * @returns {number}1205 */1206function itemTypeFromName(typename) {1207    if (typename === null) {1208        return NO_TYPE_FILTER;1209    }1210    // @ts-expect-error1211    const index = itemTypes[typename];1212    if (index === undefined) {1213        throw ["Unknown type filter ", typename];1214    }1215    return index;1216}12171218class DocSearch {1219    /**1220     * @param {string} rootPath1221     * @param {stringdex.Database} database1222     */1223    constructor(rootPath, database) {1224        this.rootPath = rootPath;1225        this.database = database;12261227        this.utf8decoder = new TextDecoder();12281229        /** @type {Map<number|null, rustdoc.FunctionType>} */1230        this.TYPES_POOL = new Map();1231    }12321233    /**1234     * Load type name ID set.1235     *1236     * Each of these identifiers are used specially by1237     * type-driven search. Most of them are lang items1238     * in the compiler.1239     *1240     * Use this function, which caches the result, and not1241     * getTypeNameIdsAsync, which is an internal implementation1242     * detail for this.1243     *1244     * @return {Promise<rustdoc.TypeNameIds>|rustdoc.TypeNameIds}1245     */1246    getTypeNameIds() {1247        if (this.typeNameIds) {1248            return this.typeNameIds;1249        }1250        const nn = this.database.getData("normalizedName");1251        if (!nn) {1252            return {1253                typeNameIdOfOutput: -1,1254                typeNameIdOfFnPtr: -1,1255                typeNameIdOfFn: -1,1256                typeNameIdOfFnMut: -1,1257                typeNameIdOfFnOnce: -1,1258                typeNameIdOfArray: -1,1259                typeNameIdOfSlice: -1,1260                typeNameIdOfArrayOrSlice: -1,1261                typeNameIdOfTuple: -1,1262                typeNameIdOfUnit: -1,1263                typeNameIdOfTupleOrUnit: -1,1264                typeNameIdOfReference: -1,1265                typeNameIdOfPointer: -1,1266                typeNameIdOfHof: -1,1267                typeNameIdOfNever: -1,1268            };1269        }1270        return this.getTypeNameIdsAsync(nn);1271    }1272    /**1273     * @param {stringdex.DataColumn} nn1274     * @returns {Promise<rustdoc.TypeNameIds>}1275     */1276    async getTypeNameIdsAsync(nn) {1277        // Each of these identifiers are used specially by1278        // type-driven search.1279        const [1280            // output is the special associated type that goes1281            // after the arrow: the type checker desugars1282            // the path `Fn(a) -> b` into `Fn<Output=b, (a)>`1283            output,1284            // fn, fnmut, and fnonce all match `->`1285            fn,1286            fnMut,1287            fnOnce,1288            hof,1289            // array and slice both match `[]`1290            array,1291            slice,1292            arrayOrSlice,1293            // tuple and unit both match `()`1294            tuple,1295            unit,1296            tupleOrUnit,1297            // reference matches `&`1298            reference,1299            pointer,1300            // never matches `!`1301            never,1302        ] = await Promise.all([1303            nn.search("output"),1304            nn.search("fn"),1305            nn.search("fnmut"),1306            nn.search("fnonce"),1307            nn.search("->"),1308            nn.search("array"),1309            nn.search("slice"),1310            nn.search("[]"),1311            nn.search("tuple"),1312            nn.search("unit"),1313            nn.search("()"),1314            nn.search("reference"),1315            nn.search("pointer"),1316            nn.search("never"),1317        ]);1318        /**1319         * @param {stringdex.Trie|null|undefined} trie1320         * @param {rustdoc.ItemType} ty1321         * @param {string} modulePath1322         * @returns {Promise<number>}1323         * */1324        const first = async(trie, ty, modulePath) => {1325            if (trie) {1326                for (const id of trie.matches().entries()) {1327                    const pathData = await this.getPathData(id);1328                    if (pathData && pathData.ty === ty && pathData.modulePath === modulePath) {1329                        return id;1330                    }1331                }1332            }1333            return -1;1334        };1335        const typeNameIdOfOutput = await first(output, itemTypes.associatedtype, "");1336        const typeNameIdOfFnPtr = await first(fn, itemTypes.primitive, "");1337        const typeNameIdOfFn = await first(fn, itemTypes.trait, "core::ops");1338        const typeNameIdOfFnMut = await first(fnMut, itemTypes.trait, "core::ops");1339        const typeNameIdOfFnOnce = await first(fnOnce, itemTypes.trait, "core::ops");1340        const typeNameIdOfArray = await first(array, itemTypes.primitive, "");1341        const typeNameIdOfSlice = await first(slice, itemTypes.primitive, "");1342        const typeNameIdOfArrayOrSlice = await first(arrayOrSlice, itemTypes.primitive, "");1343        const typeNameIdOfTuple = await first(tuple, itemTypes.primitive, "");1344        const typeNameIdOfUnit = await first(unit, itemTypes.primitive, "");1345        const typeNameIdOfTupleOrUnit = await first(tupleOrUnit, itemTypes.primitive, "");1346        const typeNameIdOfReference = await first(reference, itemTypes.primitive, "");1347        const typeNameIdOfPointer = await first(pointer, itemTypes.primitive, "");1348        const typeNameIdOfHof = await first(hof, itemTypes.primitive, "");1349        const typeNameIdOfNever = await first(never, itemTypes.primitive, "");1350        this.typeNameIds = {1351            typeNameIdOfOutput,1352            typeNameIdOfFnPtr,1353            typeNameIdOfFn,1354            typeNameIdOfFnMut,1355            typeNameIdOfFnOnce,1356            typeNameIdOfArray,1357            typeNameIdOfSlice,1358            typeNameIdOfArrayOrSlice,1359            typeNameIdOfTuple,1360            typeNameIdOfUnit,1361            typeNameIdOfTupleOrUnit,1362            typeNameIdOfReference,1363            typeNameIdOfPointer,1364            typeNameIdOfHof,1365            typeNameIdOfNever,1366        };1367        return this.typeNameIds;1368    }13691370    /**1371     * Parses the query.1372     *1373     * The supported syntax by this parser is given in the rustdoc book chapter1374     * /src/doc/rustdoc/src/read-documentation/search.md1375     *1376     * When adding new things to the parser, add them there, too!1377     *1378     * @param  {string} userQuery - The user query1379     *1380     * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} - The parsed query1381     */1382    static parseQuery(userQuery) {1383        /**1384         * Takes the user search input and returns an empty `ParsedQuery`.1385         *1386         * @param {string} userQuery1387         *1388         * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>}1389         */1390        function newParsedQuery(userQuery) {1391            return {1392                userQuery,1393                elems: [],1394                returned: [],1395                // Total number of "top" elements (does not include generics).1396                foundElems: 0,1397                // Total number of elements (includes generics).1398                totalElems: 0,1399                literalSearch: false,1400                hasReturnArrow: false,1401                error: null,1402                correction: null,1403                proposeCorrectionFrom: null,1404                proposeCorrectionTo: null,1405                // bloom filter build from type ids1406                typeFingerprint: new Uint32Array(4),1407            };1408        }14091410        /**1411        * Parses the provided `query` input to fill `parserState`. If it encounters an error while1412        * parsing `query`, it'll throw an error.1413        *1414        * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query1415        * @param {rustdoc.ParserState} parserState1416        */1417        function parseInput(query, parserState) {1418            let foundStopChar = true;14191420            while (parserState.pos < parserState.length) {1421                const c = parserState.userQuery[parserState.pos];1422                if (isEndCharacter(c)) {1423                    foundStopChar = true;1424                    if (isSeparatorCharacter(c)) {1425                        parserState.pos += 1;1426                        continue;1427                    } else if (c === "-" || c === ">") {1428                        if (isReturnArrow(parserState)) {1429                            query.hasReturnArrow = true;1430                            break;1431                        }1432                        throw ["Unexpected ", c, " (did you mean ", "->", "?)"];1433                    } else if (parserState.pos > 0) {1434                        throw ["Unexpected ", c, " after ",1435                            parserState.userQuery[parserState.pos - 1]];1436                    }1437                    throw ["Unexpected ", c];1438                } else if (c === " ") {1439                    skipWhitespace(parserState);1440                    continue;1441                }1442                if (!foundStopChar) {1443                    let extra = EMPTY_STRING_ARRAY;1444                    if (isLastElemGeneric(query.elems, parserState)) {1445                        extra = [" after ", ">"];1446                    } else if (prevIs(parserState, "\"")) {1447                        throw ["Cannot have more than one element if you use quotes"];1448                    }1449                    if (parserState.typeFilter !== null) {1450                        throw [1451                            "Expected ",1452                            ",",1453                            " or ",1454                            "->",1455                            ...extra,1456                            ", found ",1457                            c,1458                        ];1459                    }1460                    throw [1461                        "Expected ",1462                        ",",1463                        ", ",1464                        ":",1465                        " or ",1466                        "->",1467                        ...extra,1468                        ", found ",1469                        c,1470                    ];1471                }1472                const before = query.elems.length;1473                getFilteredNextElem(query, parserState, query.elems, false);1474                if (query.elems.length === before) {1475                    // Nothing was added, weird... Let's increase the position to not remain stuck.1476                    parserState.pos += 1;1477                }1478                foundStopChar = false;1479            }1480            if (parserState.typeFilter !== null) {1481                throw [1482                    "Unexpected ",1483                    ":",1484                    " (expected path after type filter ",1485                    parserState.typeFilter + ":",1486                    ")",1487                ];1488            }1489            while (parserState.pos < parserState.length) {1490                if (isReturnArrow(parserState)) {1491                    parserState.pos += 2;1492                    skipWhitespace(parserState);1493                    // Get returned elements.1494                    getItemsBefore(query, parserState, query.returned, "");1495                    // Nothing can come afterward!1496                    query.hasReturnArrow = true;1497                    break;1498                } else {1499                    parserState.pos += 1;1500                }1501            }1502        }150315041505        userQuery = userQuery.trim().replace(/\r|\n|\t/g, " ");1506        const parserState = {1507            length: userQuery.length,1508            pos: 0,1509            // Total number of elements (includes generics).1510            totalElems: 0,1511            genericsElems: 0,1512            typeFilter: null,1513            isInBinding: null,1514            userQuery,1515        };1516        let query = newParsedQuery(userQuery);15171518        try {1519            parseInput(query, parserState);15201521            // Scan for invalid type filters, so that we can report the error1522            // outside the search loop.1523            /** @param {rustdoc.ParserQueryElement} elem */1524            const checkTypeFilter = elem => {1525                const ty = itemTypeFromName(elem.typeFilter);1526                if (ty === itemTypes.generic && elem.generics.length !== 0) {1527                    throw [1528                        "Generic type parameter ",1529                        elem.name,1530                        " does not accept generic parameters",1531                    ];1532                }1533                for (const generic of elem.generics) {1534                    checkTypeFilter(generic);1535                }1536                for (const constraints of elem.bindings.values()) {1537                    for (const constraint of constraints) {1538                        checkTypeFilter(constraint);1539                    }1540                }1541            };1542            for (const elem of query.elems) {1543                checkTypeFilter(elem);1544            }1545            for (const elem of query.returned) {1546                checkTypeFilter(elem);1547            }1548        } catch (err) {1549            query = newParsedQuery(userQuery);1550            if (Array.isArray(err) && err.every(elem => typeof elem === "string")) {1551                query.error = err;1552            } else {1553                // rethrow the error if it isn't a string array1554                throw err;1555            }15561557            return query;1558        }1559        if (!query.literalSearch) {1560            // If there is more than one element in the query, we switch to literalSearch in any1561            // case.1562            query.literalSearch = parserState.totalElems > 1;1563        }1564        query.foundElems = query.elems.length + query.returned.length;1565        query.totalElems = parserState.totalElems;1566        return query;1567    }15681569    /**1570     * @param {number} id1571     * @returns {Promise<string|null>}1572     */1573    async getName(id) {1574        const ni = this.database.getData("name");1575        if (!ni) {1576            return null;1577        }1578        const name = await ni.at(id);1579        return name === undefined || name === null ? null : this.utf8decoder.decode(name);1580    }15811582    /**1583     * @param {number} id1584     * @returns {Promise<string|null>}1585     */1586    async getDesc(id) {1587        const di = this.database.getData("desc");1588        if (!di) {1589            return null;1590        }1591        const desc = await di.at(id);1592        return desc === undefined || desc === null ? null : this.utf8decoder.decode(desc);1593    }15941595    /**1596     * @param {number} id1597     * @returns {Promise<number|null>}1598     */1599    async getAliasTarget(id) {1600        const ai = this.database.getData("alias");1601        if (!ai) {1602            return null;1603        }1604        const bytes = await ai.at(id);1605        if (bytes === undefined || bytes === null || bytes.length === 0) {1606            return null;1607        } else {1608            /** @type {string} */1609            const encoded = this.utf8decoder.decode(bytes);1610            /** @type {number|null} */1611            const decoded = JSON.parse(encoded);1612            return decoded;1613        }1614    }16151616    /**1617     * @param {number} id1618     * @returns {Promise<rustdoc.EntryData|null>}1619     */1620    async getEntryData(id) {1621        const ei = this.database.getData("entry");1622        if (!ei) {1623            return null;1624        }1625        const encoded = this.utf8decoder.decode(await ei.at(id));1626        if (encoded === "" || encoded === undefined || encoded === null) {1627            return null;1628        }1629        /**1630         * krate,1631         * ty,1632         * module_path,1633         * exact_module_path,1634         * parent,1635         * trait_parent,1636         * deprecated,1637         * unstable,1638         * associated_item_disambiguator1639         * @type {rustdoc.ArrayWithOptionals<[1640         *     number,1641         *     rustdoc.ItemType,1642         *     number,1643         *     number,1644         *     number,1645         *     number,1646         *     number,1647         *     number,1648         * ], [string]>}1649         */1650        const raw = JSON.parse(encoded);1651        return {1652            krate: raw[0],1653            ty: raw[1],1654            modulePath: raw[2] === 0 ? null : raw[2] - 1,1655            exactModulePath: raw[3] === 0 ? null : raw[3] - 1,1656            parent: raw[4] === 0 ? null : raw[4] - 1,1657            traitParent: raw[5] === 0 ? null : raw[5] - 1,1658            deprecated: raw[6] === 1 ? true : false,1659            unstable: raw[7] === 1 ? true : false,1660            associatedItemDisambiguatorOrExternCrateUrl: raw.length === 8 ? null : raw[8],1661        };1662    }16631664    /**1665     * @param {number} id1666     * @returns {Promise<rustdoc.PathData|null>}1667     */1668    async getPathData(id) {1669        const pi = this.database.getData("path");1670        if (!pi) {1671            return null;1672        }1673        const encoded = this.utf8decoder.decode(await pi.at(id));1674        if (encoded === "" || encoded === undefined || encoded === null) {1675            return null;1676        }1677        /**1678         * ty, module_path, exact_module_path, search_unbox, inverted_function_signature_index1679         * @type {rustdoc.ArrayWithOptionals<[rustdoc.ItemType, string], [string|0, 0|1, string]>}1680         */1681        const raw = JSON.parse(encoded);1682        return {1683            ty: raw[0],1684            modulePath: raw[1],1685            exactModulePath: raw[2] === 0 || raw[2] === undefined ? raw[1] : raw[2],1686        };1687    }16881689    /**1690     * @param {number} id1691     * @returns {Promise<rustdoc.FunctionData|null>}1692     */1693    async getFunctionData(id) {1694        const fi = this.database.getData("function");1695        if (!fi) {1696            return null;1697        }1698        const encoded = this.utf8decoder.decode(await fi.at(id));1699        if (encoded === "" || encoded === undefined || encoded === null) {1700            return null;1701        }1702        /**1703         * function_signature, param_names1704         * @type {[string, string[]]}1705         */1706        const raw = JSON.parse(encoded);17071708        const parser = new VlqHexDecoder(raw[0], async functionSearchType => {1709            if (typeof functionSearchType === "number") {1710                return null;1711            }1712            const INPUTS_DATA = 0;1713            const OUTPUT_DATA = 1;1714            /** @type {Promise<rustdoc.FunctionType[]>} */1715            let inputs_;1716            /** @type {Promise<rustdoc.FunctionType[]>} */1717            let output_;1718            if (typeof functionSearchType[INPUTS_DATA] === "number") {1719                inputs_ = Promise.all([1720                    this.buildItemSearchType(functionSearchType[INPUTS_DATA]),1721                ]);1722            } else {1723                // @ts-ignore1724                inputs_ = this.buildItemSearchTypeAll(functionSearchType[INPUTS_DATA]);1725            }1726            if (functionSearchType.length > 1) {1727                if (typeof functionSearchType[OUTPUT_DATA] === "number") {1728                    output_ = Promise.all([1729                        this.buildItemSearchType(functionSearchType[OUTPUT_DATA]),1730                    ]);1731                } else {1732                    // @ts-expect-error1733                    output_ = this.buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA]);1734                }1735            } else {1736                output_ = Promise.resolve(EMPTY_GENERICS_ARRAY);1737            }1738            /** @type {Promise<rustdoc.FunctionType[]>[]} */1739            const where_clause_ = [];1740            const l = functionSearchType.length;1741            for (let i = 2; i < l; ++i) {1742                where_clause_.push(typeof functionSearchType[i] === "number"1743                    // @ts-expect-error1744                    ? Promise.all([this.buildItemSearchType(functionSearchType[i])])1745                    // @ts-expect-error1746                    : this.buildItemSearchTypeAll(functionSearchType[i]),1747                );1748            }1749            const [inputs, output, where_clause] = await Promise.all([1750                inputs_,1751                output_,1752                Promise.all(where_clause_),1753            ]);1754            return {1755                inputs, output, where_clause,1756            };1757        });17581759        return {1760            functionSignature: await parser.next(),1761            paramNames: raw[1],1762            elemCount: parser.elemCount,1763        };1764    }17651766    /**1767     * @param {number} id1768     * @returns {Promise<rustdoc.TypeData|null>}1769     */1770    async getTypeData(id) {1771        const ti = this.database.getData("type");1772        if (!ti) {1773            return null;1774        }1775        const encoded = this.utf8decoder.decode(await ti.at(id));1776        if (encoded === "" || encoded === undefined || encoded === null) {1777            return null;1778        }1779        /**1780         * function_signature, param_names1781         * @type {[string, string, number] | [string, string] | [] | null}1782         */1783        const raw = JSON.parse(encoded);17841785        if (!raw || raw.length === 0) {1786            return null;1787        }17881789        let searchUnbox = false;1790        const invertedFunctionInputsIndex = [];1791        const invertedFunctionOutputIndex = [];17921793        if (typeof raw[0] === "string") {1794            if (raw[2]) {1795                searchUnbox = true;1796            }1797            // the inverted function signature index is a list of bitmaps,1798            // by number of types that appear in the function1799            let i = 0;1800            let pb = makeUint8ArrayFromBase64(raw[0]);1801            let l = pb.length;1802            while (i < l) {1803                if (pb[i] === 0) {1804                    invertedFunctionInputsIndex.push(RoaringBitmap.empty());1805                    i += 1;1806                } else {1807                    const bitmap = new RoaringBitmap(pb, i);1808                    i += bitmap.consumed_len_bytes;1809                    invertedFunctionInputsIndex.push(bitmap);1810                }1811            }1812            i = 0;1813            pb = makeUint8ArrayFromBase64(raw[1]);1814            l = pb.length;1815            while (i < l) {1816                if (pb[i] === 0) {1817                    invertedFunctionOutputIndex.push(RoaringBitmap.empty());1818                    i += 1;1819                } else {1820                    const bitmap = new RoaringBitmap(pb, i);1821                    i += bitmap.consumed_len_bytes;1822                    invertedFunctionOutputIndex.push(bitmap);1823                }1824            }1825        } else if (raw[0]) {1826            searchUnbox = true;1827        }18281829        return { searchUnbox, invertedFunctionInputsIndex, invertedFunctionOutputIndex };1830    }18311832    /**1833     * @returns {Promise<string[]>}1834     */1835    async getCrateNameList() {1836        const crateNames = this.database.getData("crateNames");1837        if (!crateNames) {1838            return [];1839        }1840        const l = crateNames.length;1841        const names = [];1842        for (let i = 0; i < l; ++i) {1843            const name = await crateNames.at(i);1844            names.push(name === undefined ? "" : this.utf8decoder.decode(name));1845        }1846        return Promise.all(names);1847    }18481849    /**1850     * @param {number} id non-negative generic index1851     * @returns {Promise<stringdex.RoaringBitmap[]>}1852     */1853    async getGenericInvertedIndex(id) {1854        const gii = this.database.getData("generic_inverted_index");1855        if (!gii) {1856            return [];1857        }1858        const pb = await gii.at(id);1859        if (pb === undefined || pb === null || pb.length === 0) {1860            return [];1861        }18621863        const invertedFunctionSignatureIndex = [];1864        // the inverted function signature index is a list of bitmaps,1865        // by number of types that appear in the function1866        let i = 0;1867        const l = pb.length;1868        while (i < l) {1869            if (pb[i] === 0) {1870                invertedFunctionSignatureIndex.push(RoaringBitmap.empty());1871                i += 1;1872            } else {1873                const bitmap = new RoaringBitmap(pb, i);1874                i += bitmap.consumed_len_bytes;1875                invertedFunctionSignatureIndex.push(bitmap);1876            }1877        }1878        return invertedFunctionSignatureIndex;1879    }18801881    /**1882     * @param {number} id1883     * @param {boolean} loadFunctionData1884     * @returns {Promise<rustdoc.Row?>}1885     */1886    async getRow(id, loadFunctionData) {1887        const [name_, entry, path, functionData] = await Promise.all([1888            this.getName(id),1889            this.getEntryData(id),1890            this.getPathData(id),1891            loadFunctionData ? this.getFunctionData(id) : null,1892        ]);1893        if (!entry && !path) {1894            return null;1895        }1896        /** @type {function("parent" | "traitParent"): Promise<rustdoc.RowParent>} */1897        const buildParentLike = async field => {1898            const [name, path] = entry !== null && entry[field] !== null ?1899                await Promise.all([this.getName(entry[field]), this.getPathData(entry[field])]) :1900                [null, null];1901            if (name !== null && path !== null) {1902                return { name, path };1903            }1904            return null;1905        };19061907        const [1908            moduleName,1909            modulePathData,1910            exactModuleName,1911            exactModulePathData,1912            parent,1913            traitParent,1914            crateOrNull,1915        ] = await Promise.all([1916            entry && entry.modulePath !== null ? this.getName(entry.modulePath) : null,1917            entry && entry.modulePath !== null ? this.getPathData(entry.modulePath) : null,1918            entry && entry.exactModulePath !== null ?1919                this.getName(entry.exactModulePath) :1920                null,1921            entry && entry.exactModulePath !== null ?1922                this.getPathData(entry.exactModulePath) :1923                null,1924            buildParentLike("parent"),1925            buildParentLike("traitParent"),1926            entry ? this.getName(entry.krate) : "",1927        ]);1928        const crate = crateOrNull === null ? "" : crateOrNull;1929        const name = name_ === null ? "" : name_;1930        const normalizedName = (name.indexOf("_") === -1 ?1931            name :1932            name.replace(/_/g, "")).toLowerCase();1933        const modulePath = modulePathData === null || moduleName === null ? "" :1934            (modulePathData.modulePath === "" ?1935                moduleName :1936                `${modulePathData.modulePath}::${moduleName}`);19371938        return {1939            id,1940            crate,1941            ty: entry ? entry.ty : nonnull(path).ty,1942            name,1943            normalizedName,1944            modulePath,1945            exactModulePath: exactModulePathData === null || exactModuleName === null ? modulePath :1946                (exactModulePathData.exactModulePath === "" ?1947                    exactModuleName :1948                    `${exactModulePathData.exactModulePath}::${exactModuleName}`),1949            entry,1950            path,1951            functionData,1952            deprecated: entry ? entry.deprecated : false,1953            unstable: entry ? entry.unstable : false,1954            parent,1955            traitParent,1956        };1957    }19581959    /**1960     * Convert a list of RawFunctionType / ID to object-based FunctionType.1961     *1962     * Crates often have lots of functions in them, and it's common to have a large number of1963     * functions that operate on a small set of data types, so the search index compresses them1964     * by encoding function parameter and return types as indexes into an array of names.1965     *1966     * Even when a general-purpose compression algorithm is used, this is still a win.1967     * I checked. https://github.com/rust-lang/rust/pull/98475#issue-12843959851968     *1969     * The format for individual function types is encoded in1970     * librustdoc/html/render/mod.rs: impl Serialize for RenderType1971     *1972     * @param {null|Array<rustdoc.RawFunctionType>} types1973     *1974     * @return {Promise<Array<rustdoc.FunctionType>>}1975     */1976    async buildItemSearchTypeAll(types) {1977        return types && types.length > 0 ?1978            await Promise.all(types.map(type => this.buildItemSearchType(type))) :1979            EMPTY_GENERICS_ARRAY;1980    }19811982    /**1983     * Converts a single type.1984     *1985     * @param {rustdoc.RawFunctionType} type1986     * @return {Promise<rustdoc.FunctionType>}1987     */1988    async buildItemSearchType(type) {1989        const PATH_INDEX_DATA = 0;1990        const GENERICS_DATA = 1;1991        const BINDINGS_DATA = 2;1992        let id, generics;1993        /**1994         * @type {Map<number, rustdoc.FunctionType[]>}1995         */1996        let bindings;1997        if (typeof type === "number") {1998            id = type;1999            generics = EMPTY_GENERICS_ARRAY;2000            bindings = EMPTY_BINDINGS_MAP;

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.