From 6ab240d3299b4b6953dc8140f1f40d62beee7fab Mon Sep 17 00:00:00 2001 From: Theemathas Chirananthavat Date: Mon, 27 Oct 2025 21:49:39 +0700 Subject: [PATCH 1/9] [beta-1.91] Add more context to the temporary lifetime extension FCW Mention in the FCW that the lifetime extension behavior was wrong since 1.88/1.89. --- compiler/rustc_borrowck/src/diagnostics/mod.rs | 1 + .../extended-super-let-bindings.stderr | 1 + .../macro-extended-temporary-scopes.e2021.stderr | 7 +++++++ .../macro-extended-temporary-scopes.e2024.stderr | 15 +++++++++++++++ .../user-defined-macros.stderr | 4 ++++ 5 files changed, 28 insertions(+) diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index e85945d633b66..3827c7268c228 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -1607,6 +1607,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { tcx.node_span_lint(MACRO_EXTENDED_TEMPORARY_SCOPES, lint_root, labels, |diag| { diag.primary_message("temporary lifetime will be shortened in Rust 1.92"); diag.note("consider using a `let` binding to create a longer lived value"); + diag.note("some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()`"); }); } } diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/extended-super-let-bindings.stderr b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/extended-super-let-bindings.stderr index 4dee6f4fc3212..038839eb92409 100644 --- a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/extended-super-let-bindings.stderr +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/extended-super-let-bindings.stderr @@ -9,6 +9,7 @@ LL | println!("{:?}{}", (), { format_args!("{:?}", ()) }); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` = note: `#[warn(macro_extended_temporary_scopes)]` (part of `#[warn(future_incompatible)]`) on by default warning: 1 warning emitted diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2021.stderr b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2021.stderr index c7a7e1b3238e9..1c890daeaa52b 100644 --- a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2021.stderr +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2021.stderr @@ -9,6 +9,7 @@ LL | println!("{:?}{:?}", (), if cond() { &format!("") } else { "" }); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` = note: `#[warn(macro_extended_temporary_scopes)]` (part of `#[warn(future_incompatible)]`) on by default = note: this warning originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -23,6 +24,7 @@ LL | println!("{:?}{:?}", (), if cond() { &"".to_string() } else { "" }); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:39:43 @@ -35,6 +37,7 @@ LL | println!("{:?}{:?}", (), if cond() { &("string".to_owned() + "string") = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:58:17 @@ -48,6 +51,7 @@ LL | } = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:54:14 @@ -61,6 +65,7 @@ LL | } else { = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:50:14 @@ -74,6 +79,7 @@ LL | } else if cond() { = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:46:14 @@ -87,6 +93,7 @@ LL | } else if cond() { = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: 7 warnings emitted diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2024.stderr b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2024.stderr index 0c61fd8d1b0e1..bd086e08956fd 100644 --- a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2024.stderr +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2024.stderr @@ -9,6 +9,7 @@ LL | println!("{:?}{:?}", { &temp() }, ()); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` = note: `#[warn(macro_extended_temporary_scopes)]` (part of `#[warn(future_incompatible)]`) on by default warning: temporary lifetime will be shortened in Rust 1.92 @@ -22,6 +23,7 @@ LL | println!("{:?}{:?}", (), if cond() { &format!("") } else { "" }); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` = note: this warning originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) warning: temporary lifetime will be shortened in Rust 1.92 @@ -35,6 +37,7 @@ LL | println!("{:?}{:?}", (), if cond() { &"".to_string() } else { "" }); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:39:43 @@ -47,6 +50,7 @@ LL | println!("{:?}{:?}", (), if cond() { &("string".to_owned() + "string") = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:58:17 @@ -60,6 +64,7 @@ LL | } = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:54:14 @@ -73,6 +78,7 @@ LL | } else { = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:50:14 @@ -86,6 +92,7 @@ LL | } else if cond() { = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:46:14 @@ -99,6 +106,7 @@ LL | } else if cond() { = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:65:18 @@ -111,6 +119,7 @@ LL | pin!(pin!({ &temp() })); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:96:13 @@ -123,6 +132,7 @@ LL | pin!({ &(1 / 0) }); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:99:17 @@ -135,6 +145,7 @@ LL | pin!({ &mut [()] }); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:102:13 @@ -147,6 +158,7 @@ LL | pin!({ &Some(String::new()) }); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:105:13 @@ -159,6 +171,7 @@ LL | pin!({ &(|| ())() }); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:108:13 @@ -171,6 +184,7 @@ LL | pin!({ &|| &local }); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:111:13 @@ -183,6 +197,7 @@ LL | pin!({ &CONST_STRING }); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: 15 warnings emitted diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/user-defined-macros.stderr b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/user-defined-macros.stderr index b4996952f4f11..313f7a6638150 100644 --- a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/user-defined-macros.stderr +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/user-defined-macros.stderr @@ -10,6 +10,7 @@ LL | wrap!(String::new()) = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` = note: `#[warn(macro_extended_temporary_scopes)]` (part of `#[warn(future_incompatible)]`) on by default warning: temporary lifetime will be shortened in Rust 1.92 @@ -27,6 +28,7 @@ LL | print_with_internal_wrap!(); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` = note: this warning originates in the macro `print_with_internal_wrap` (in Nightly builds, run with -Z macro-backtrace for more info) warning: temporary lifetime will be shortened in Rust 1.92 @@ -41,6 +43,7 @@ LL | external_macros::wrap!(String::new()) = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` warning: temporary lifetime will be shortened in Rust 1.92 --> $DIR/user-defined-macros.rs:52:5 @@ -54,6 +57,7 @@ LL | external_macros::print_with_internal_wrap!(); = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see = note: consider using a `let` binding to create a longer lived value + = note: some temporaries were previously incorrectly lifetime-extended since Rust 1.89 in formatting macros, and since Rust 1.88 in `pin!()` = note: this warning originates in the macro `external_macros::print_with_internal_wrap` (in Nightly builds, run with -Z macro-backtrace for more info) warning: 4 warnings emitted From 063ccaad42b209140afa1658c7d60f7604a5ca63 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 12 Sep 2025 11:32:51 -0700 Subject: [PATCH 2/9] rustdoc-search: reduce async machinery in value lookups This commit is a mirrored change from stringdex that makes `at()` not always return a promise, which is fine because we can still `await` it. (cherry picked from commit 43d45ef2da29aa438c32b5c872bc7a367ef2a2ab) --- src/librustdoc/html/static/js/search.js | 8 +- src/librustdoc/html/static/js/stringdex.d.ts | 2 +- src/librustdoc/html/static/js/stringdex.js | 97 +++++++++++--------- 3 files changed, 57 insertions(+), 50 deletions(-) diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 3b84ae2bed060..339b101d47111 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1758,12 +1758,8 @@ class DocSearch { const l = crateNames.length; const names = []; for (let i = 0; i < l; ++i) { - names.push(crateNames.at(i).then(name => { - if (name === undefined) { - return ""; - } - return this.utf8decoder.decode(name); - })); + const name = await crateNames.at(i); + names.push(name === undefined ? "" : this.utf8decoder.decode(name)); } return Promise.all(names); } diff --git a/src/librustdoc/html/static/js/stringdex.d.ts b/src/librustdoc/html/static/js/stringdex.d.ts index 2eb1fdf95d845..71c6bfdf48145 100644 --- a/src/librustdoc/html/static/js/stringdex.d.ts +++ b/src/librustdoc/html/static/js/stringdex.d.ts @@ -29,7 +29,7 @@ declare namespace stringdex { */ interface DataColumn { isEmpty(id: number): boolean; - at(id: number): Promise; + at(id: number): Promise|Uint8Array|undefined; search(name: Uint8Array|string): Promise; searchLev(name: Uint8Array|string): AsyncGenerator; length: number, diff --git a/src/librustdoc/html/static/js/stringdex.js b/src/librustdoc/html/static/js/stringdex.js index b7f605a10351d..4c4659628778d 100644 --- a/src/librustdoc/html/static/js/stringdex.js +++ b/src/librustdoc/html/static/js/stringdex.js @@ -2261,7 +2261,7 @@ function loadDatabase(hooks) { this.hashes = hashes; this.emptyset = emptyset; this.name = name; - /** @type {{"hash": Uint8Array, "data": Promise?, "end": number}[]} */ + /** @type {{"hash": Uint8Array, "data": Uint8Array[]?, "end": number}[]} */ this.buckets = []; this.bucket_keys = []; const l = counts.length; @@ -2295,64 +2295,75 @@ function loadDatabase(hooks) { /** * Look up a cell by row ID. * @param {number} id - * @returns {Promise} + * @returns {Promise|Uint8Array|undefined} */ - async at(id) { + at(id) { if (this.emptyset.contains(id)) { - return Promise.resolve(EMPTY_UINT8); + return EMPTY_UINT8; } else { let idx = -1; while (this.bucket_keys[idx + 1] <= id) { idx += 1; } if (idx === -1 || idx >= this.bucket_keys.length) { - return Promise.resolve(undefined); + return undefined; } else { const start = this.bucket_keys[idx]; - const {hash, end} = this.buckets[idx]; + const bucket = this.buckets[idx]; let data = this.buckets[idx].data; if (data === null) { - const dataSansEmptysetOrig = await registry.dataLoadByNameAndHash( - this.name, - hash, - ); - // After the `await` resolves, another task might fill - // in the data. If so, we should use that. - data = this.buckets[idx].data; - if (data !== null) { - return (await data)[id - start]; - } - const dataSansEmptyset = [...dataSansEmptysetOrig]; - /** @type {(Uint8Array[])|null} */ - let dataWithEmptyset = null; - let pos = start; - let insertCount = 0; - while (pos < end) { - if (this.emptyset.contains(pos)) { - if (dataWithEmptyset === null) { - dataWithEmptyset = dataSansEmptyset.splice(0, insertCount); - } else if (insertCount !== 0) { - dataWithEmptyset.push( - ...dataSansEmptyset.splice(0, insertCount), - ); - } - insertCount = 0; - dataWithEmptyset.push(EMPTY_UINT8); - } else { - insertCount += 1; - } - pos += 1; - } - data = Promise.resolve( - dataWithEmptyset === null ? - dataSansEmptyset : - dataWithEmptyset.concat(dataSansEmptyset), + return this.atAsyncFetch(id, start, bucket); + } else { + return data[id - start]; + } + } + } + } + /** + * Look up a cell by row ID. + * @param {number} id + * @param {number} start + * @param {{hash: Uint8Array, data: Uint8Array[] | null, end: number}} bucket + * @returns {Promise} + */ + async atAsyncFetch(id, start, bucket) { + const {hash, end} = bucket; + const dataSansEmptysetOrig = await registry.dataLoadByNameAndHash( + this.name, + hash, + ); + // After the `await` resolves, another task might fill + // in the data. If so, we should use that. + let data = bucket.data; + if (data !== null) { + return data[id - start]; + } + const dataSansEmptyset = [...dataSansEmptysetOrig]; + /** @type {(Uint8Array[])|null} */ + let dataWithEmptyset = null; + let pos = start; + let insertCount = 0; + while (pos < end) { + if (this.emptyset.contains(pos)) { + if (dataWithEmptyset === null) { + dataWithEmptyset = dataSansEmptyset.splice(0, insertCount); + } else if (insertCount !== 0) { + dataWithEmptyset.push( + ...dataSansEmptyset.splice(0, insertCount), ); - this.buckets[idx].data = data; } - return (await data)[id - start]; + insertCount = 0; + dataWithEmptyset.push(EMPTY_UINT8); + } else { + insertCount += 1; } + pos += 1; } + data = dataWithEmptyset === null ? + dataSansEmptyset : + dataWithEmptyset.concat(dataSansEmptyset); + bucket.data = data; + return data[id - start]; } /** * Search by exact substring From b77c3b193a86f6e27dc020505ca00494cc97117f Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 12 Sep 2025 11:36:38 -0700 Subject: [PATCH 3/9] rustdoc-search: delay loading type name IDs until type search This avoids blocking on these lookups, so name-based searches return results more quickly. (cherry picked from commit 277e845162299d533ce64a2a6dfa540d4f36fb30) --- src/librustdoc/html/static/js/rustdoc.d.ts | 25 +- src/librustdoc/html/static/js/search.js | 296 +++++++++++++++------ src/librustdoc/html/static/js/stringdex.js | 2 +- 3 files changed, 233 insertions(+), 90 deletions(-) diff --git a/src/librustdoc/html/static/js/rustdoc.d.ts b/src/librustdoc/html/static/js/rustdoc.d.ts index 938ccc7d2c32e..951eb2291b89e 100644 --- a/src/librustdoc/html/static/js/rustdoc.d.ts +++ b/src/librustdoc/html/static/js/rustdoc.d.ts @@ -348,7 +348,7 @@ declare namespace rustdoc { returned: rustdoc.QueryElement[], is_alias: boolean, alias?: string, - original?: rustdoc.Rlow, + item: rustdoc.Row, } /** @@ -533,4 +533,27 @@ declare namespace rustdoc { * Generated by `render_call_locations` in `render/mod.rs`. */ type ScrapedLoc = [[number, number], string, string] + + /** + * Each of these identifiers are used specially by + * type-driven search. Most of them are lang items + * in the compiler. + */ + type TypeNameIds = { + "typeNameIdOfOutput": number, + "typeNameIdOfFnPtr": number, + "typeNameIdOfFn": number, + "typeNameIdOfFnMut": number, + "typeNameIdOfFnOnce": number, + "typeNameIdOfArray": number, + "typeNameIdOfSlice": number, + "typeNameIdOfArrayOrSlice": number, + "typeNameIdOfTuple": number, + "typeNameIdOfUnit": number, + "typeNameIdOfTupleOrUnit": number, + "typeNameIdOfReference": number, + "typeNameIdOfPointer": number, + "typeNameIdOfHof": number, + "typeNameIdOfNever": number, + }; } diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 339b101d47111..482134933a61e 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1190,17 +1190,6 @@ class DocSearch { this.rootPath = rootPath; this.database = database; - this.typeNameIdOfOutput = -1; - this.typeNameIdOfArray = -1; - this.typeNameIdOfSlice = -1; - this.typeNameIdOfArrayOrSlice = -1; - this.typeNameIdOfTuple = -1; - this.typeNameIdOfUnit = -1; - this.typeNameIdOfTupleOrUnit = -1; - this.typeNameIdOfReference = -1; - this.typeNameIdOfPointer = -1; - this.typeNameIdOfHof = -1; - this.utf8decoder = new TextDecoder(); /** @type {Map} */ @@ -1208,14 +1197,49 @@ class DocSearch { } /** - * Load search index. If you do not call this function, `execQuery` - * will never fulfill. + * Load type name ID set. + * + * Each of these identifiers are used specially by + * type-driven search. Most of them are lang items + * in the compiler. + * + * Use this function, which caches the result, and not + * getTypeNameIdsAsync, which is an internal implementation + * detail for this. + * + * @return {Promise|rustdoc.TypeNameIds} */ - async buildIndex() { + getTypeNameIds() { + if (this.typeNameIds) { + return this.typeNameIds; + } const nn = this.database.getData("normalizedName"); if (!nn) { - return; + return { + typeNameIdOfOutput: -1, + typeNameIdOfFnPtr: -1, + typeNameIdOfFn: -1, + typeNameIdOfFnMut: -1, + typeNameIdOfFnOnce: -1, + typeNameIdOfArray: -1, + typeNameIdOfSlice: -1, + typeNameIdOfArrayOrSlice: -1, + typeNameIdOfTuple: -1, + typeNameIdOfUnit: -1, + typeNameIdOfTupleOrUnit: -1, + typeNameIdOfReference: -1, + typeNameIdOfPointer: -1, + typeNameIdOfHof: -1, + typeNameIdOfNever: -1, + }; } + return this.getTypeNameIdsAsync(nn); + } + /** + * @param {stringdex.DataColumn} nn + * @returns {Promise} + */ + async getTypeNameIdsAsync(nn) { // Each of these identifiers are used specially by // type-driven search. const [ @@ -1274,21 +1298,39 @@ class DocSearch { } return -1; }; - this.typeNameIdOfOutput = await first(output, TY_ASSOCTYPE, ""); - this.typeNameIdOfFnPtr = await first(fn, TY_PRIMITIVE, ""); - this.typeNameIdOfFn = await first(fn, TY_TRAIT, "core::ops"); - this.typeNameIdOfFnMut = await first(fnMut, TY_TRAIT, "core::ops"); - this.typeNameIdOfFnOnce = await first(fnOnce, TY_TRAIT, "core::ops"); - this.typeNameIdOfArray = await first(array, TY_PRIMITIVE, ""); - this.typeNameIdOfSlice = await first(slice, TY_PRIMITIVE, ""); - this.typeNameIdOfArrayOrSlice = await first(arrayOrSlice, TY_PRIMITIVE, ""); - this.typeNameIdOfTuple = await first(tuple, TY_PRIMITIVE, ""); - this.typeNameIdOfUnit = await first(unit, TY_PRIMITIVE, ""); - this.typeNameIdOfTupleOrUnit = await first(tupleOrUnit, TY_PRIMITIVE, ""); - this.typeNameIdOfReference = await first(reference, TY_PRIMITIVE, ""); - this.typeNameIdOfPointer = await first(pointer, TY_PRIMITIVE, ""); - this.typeNameIdOfHof = await first(hof, TY_PRIMITIVE, ""); - this.typeNameIdOfNever = await first(never, TY_PRIMITIVE, ""); + const typeNameIdOfOutput = await first(output, TY_ASSOCTYPE, ""); + const typeNameIdOfFnPtr = await first(fn, TY_PRIMITIVE, ""); + const typeNameIdOfFn = await first(fn, TY_TRAIT, "core::ops"); + const typeNameIdOfFnMut = await first(fnMut, TY_TRAIT, "core::ops"); + const typeNameIdOfFnOnce = await first(fnOnce, TY_TRAIT, "core::ops"); + const typeNameIdOfArray = await first(array, TY_PRIMITIVE, ""); + const typeNameIdOfSlice = await first(slice, TY_PRIMITIVE, ""); + const typeNameIdOfArrayOrSlice = await first(arrayOrSlice, TY_PRIMITIVE, ""); + const typeNameIdOfTuple = await first(tuple, TY_PRIMITIVE, ""); + const typeNameIdOfUnit = await first(unit, TY_PRIMITIVE, ""); + const typeNameIdOfTupleOrUnit = await first(tupleOrUnit, TY_PRIMITIVE, ""); + const typeNameIdOfReference = await first(reference, TY_PRIMITIVE, ""); + const typeNameIdOfPointer = await first(pointer, TY_PRIMITIVE, ""); + const typeNameIdOfHof = await first(hof, TY_PRIMITIVE, ""); + const typeNameIdOfNever = await first(never, TY_PRIMITIVE, ""); + this.typeNameIds = { + typeNameIdOfOutput, + typeNameIdOfFnPtr, + typeNameIdOfFn, + typeNameIdOfFnMut, + typeNameIdOfFnOnce, + typeNameIdOfArray, + typeNameIdOfSlice, + typeNameIdOfArrayOrSlice, + typeNameIdOfTuple, + typeNameIdOfUnit, + typeNameIdOfTupleOrUnit, + typeNameIdOfReference, + typeNameIdOfPointer, + typeNameIdOfHof, + typeNameIdOfNever, + }; + return this.typeNameIds; } /** @@ -1816,6 +1858,9 @@ class DocSearch { modulePathData, exactModuleName, exactModulePathData, + parentName, + parentPath, + crate, ] = await Promise.all([ entry && entry.modulePath !== null ? this.getName(entry.modulePath) : null, entry && entry.modulePath !== null ? this.getPathData(entry.modulePath) : null, @@ -1825,6 +1870,13 @@ class DocSearch { entry && entry.exactModulePath !== null ? this.getPathData(entry.exactModulePath) : null, + entry && entry.parent !== null ? + this.getName(entry.parent) : + null, + entry && entry.parent !== null ? + this.getPathData(entry.parent) : + null, + entry ? nonnull(await this.getName(entry.krate)) : "", ]); const name = name_ === null ? "" : name_; const normalizedName = (name.indexOf("_") === -1 ? @@ -1834,12 +1886,9 @@ class DocSearch { (modulePathData.modulePath === "" ? moduleName : `${modulePathData.modulePath}::${moduleName}`); - const [parentName, parentPath] = entry !== null && entry.parent !== null ? - await Promise.all([this.getName(entry.parent), this.getPathData(entry.parent)]) : - [null, null]; return { id, - crate: entry ? nonnull(await this.getName(entry.krate)) : "", + crate, ty: entry ? entry.ty : nonnull(path).ty, name, normalizedName, @@ -2144,6 +2193,7 @@ class DocSearch { * @returns {Promise} */ const formatDisplayTypeSignature = async(obj, typeInfo, elems, returned) => { + const typeNameIds = await this.getTypeNameIds(); const objType = obj.type; if (!objType) { return {type: [], mappedNames: new Map(), whereClause: new Map()}; @@ -2169,10 +2219,12 @@ class DocSearch { return true; }, 0, + typeNameIds, ); return !!fnOutput; }, 0, + typeNameIds, ); } else { const highlighted = unifyFunctionTypes( @@ -2185,6 +2237,7 @@ class DocSearch { return true; }, 0, + typeNameIds, ); if (typeInfo === "elems") { fnInputs = highlighted; @@ -2264,7 +2317,7 @@ class DocSearch { * @returns {Promise} */ const writeHof = async(fnType, result) => { - const hofOutput = fnType.bindings.get(this.typeNameIdOfOutput) || []; + const hofOutput = fnType.bindings.get(typeNameIds.typeNameIdOfOutput) || []; const hofInputs = fnType.generics; pushText(fnType, result); pushText({name: " (", highlighted: false}, result); @@ -2305,11 +2358,14 @@ class DocSearch { * @returns {Promise} */ const writeSpecialPrimitive = async(fnType, result) => { - if (fnType.id === this.typeNameIdOfArray || fnType.id === this.typeNameIdOfSlice || - fnType.id === this.typeNameIdOfTuple || fnType.id === this.typeNameIdOfUnit) { + if (fnType.id === typeNameIds.typeNameIdOfArray || + fnType.id === typeNameIds.typeNameIdOfSlice || + fnType.id === typeNameIds.typeNameIdOfTuple || + fnType.id === typeNameIds.typeNameIdOfUnit + ) { const [ob, sb] = - fnType.id === this.typeNameIdOfArray || - fnType.id === this.typeNameIdOfSlice ? + fnType.id === typeNameIds.typeNameIdOfArray || + fnType.id === typeNameIds.typeNameIdOfSlice ? ["[", "]"] : ["(", ")"]; pushText({ name: ob, highlighted: fnType.highlighted }, result); @@ -2321,7 +2377,7 @@ class DocSearch { ); pushText({ name: sb, highlighted: fnType.highlighted }, result); return true; - } else if (fnType.id === this.typeNameIdOfReference) { + } else if (fnType.id === typeNameIds.typeNameIdOfReference) { pushText({ name: "&", highlighted: fnType.highlighted }, result); let prevHighlighted = false; await onEachBtwnAsync( @@ -2337,7 +2393,7 @@ class DocSearch { }, result), ); return true; - } else if (fnType.id === this.typeNameIdOfPointer) { + } else if (fnType.id === typeNameIds.typeNameIdOfPointer) { pushText({ name: "*", highlighted: fnType.highlighted }, result); if (fnType.generics.length < 2) { pushText({ name: "const ", highlighted: fnType.highlighted }, result); @@ -2357,14 +2413,14 @@ class DocSearch { ); return true; } else if ( - fnType.id === this.typeNameIdOfFn || - fnType.id === this.typeNameIdOfFnMut || - fnType.id === this.typeNameIdOfFnOnce || - fnType.id === this.typeNameIdOfFnPtr + fnType.id === typeNameIds.typeNameIdOfFn || + fnType.id === typeNameIds.typeNameIdOfFnMut || + fnType.id === typeNameIds.typeNameIdOfFnOnce || + fnType.id === typeNameIds.typeNameIdOfFnPtr ) { await writeHof(fnType, result); return true; - } else if (fnType.id === this.typeNameIdOfNever) { + } else if (fnType.id === typeNameIds.typeNameIdOfNever) { pushText({ name: "!", highlighted: fnType.highlighted }, result); return true; } @@ -2422,10 +2478,10 @@ class DocSearch { return; } } else if (fnType.ty === TY_TRAIT && ( - fnType.id === this.typeNameIdOfFn || - fnType.id === this.typeNameIdOfFnMut || - fnType.id === this.typeNameIdOfFnOnce || - fnType.id === this.typeNameIdOfFnPtr + fnType.id === typeNameIds.typeNameIdOfFn || + fnType.id === typeNameIds.typeNameIdOfFnMut || + fnType.id === typeNameIds.typeNameIdOfFnOnce || + fnType.id === typeNameIds.typeNameIdOfFnPtr )) { await writeHof(fnType, result); return; @@ -2536,7 +2592,7 @@ class DocSearch { * Add extra data to result objects, and filter items that have been * marked for removal. * - * @param {[rustdoc.PlainResultObject, rustdoc.Row][]} results + * @param {rustdoc.PlainResultObject[]} results * @param {"sig"|"elems"|"returned"|null} typeInfo * @param {Set} duplicates * @returns {rustdoc.ResultObject[]} @@ -2544,7 +2600,8 @@ class DocSearch { const transformResults = (results, typeInfo, duplicates) => { const out = []; - for (const [result, item] of results) { + for (const result of results) { + const item = result.item; if (item.id !== -1) { const res = buildHrefAndPath(item); // many of these properties don't strictly need to be @@ -2629,7 +2686,7 @@ class DocSearch { const normalizedUserQuery = parsedQuery.userQuery.toLowerCase(); const isMixedCase = normalizedUserQuery !== userQuery; /** - * @type {[rustdoc.PlainResultObject, rustdoc.Row][]} + * @type {rustdoc.PlainResultObject[]} */ const result_list = []; for (const result of results.values()) { @@ -2637,23 +2694,22 @@ class DocSearch { continue; } /** - * @type {rustdoc.Row?} + * @type {rustdoc.Row} */ - const item = await this.getRow(result.id, typeInfo !== null); - if (!item) { - continue; - } + const item = result.item; if (filterCrates !== null && item.crate !== filterCrates) { continue; } if (item) { - result_list.push([result, item]); + result_list.push(result); } else { continue; } } - result_list.sort(([aaa, aai], [bbb, bbi]) => { + result_list.sort((aaa, bbb) => { + const aai = aaa.item; + const bbi = bbb.item; /** @type {number} */ let a; /** @type {number} */ @@ -2800,6 +2856,7 @@ class DocSearch { * @param {number} unboxingDepth * - Limit checks that Ty matches Vec, * but not Vec>>>> + * @param {rustdoc.TypeNameIds} typeNameIds * * @return {rustdoc.HighlightedFunctionType[]|null} * - Returns highlighted results if a match, null otherwise. @@ -2811,6 +2868,7 @@ class DocSearch { mgensIn, solutionCb, unboxingDepth, + typeNameIds, ) { if (unboxingDepth >= UNBOXING_LIMIT) { return null; @@ -2833,7 +2891,12 @@ class DocSearch { && queryElems[0].bindings.size === 0) { const queryElem = queryElems[0]; for (const [i, fnType] of fnTypesIn.entries()) { - if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, mgens)) { + if (!unifyFunctionTypeIsMatchCandidate( + fnType, + queryElem, + mgens, + typeNameIds, + )) { continue; } if (fnType.id !== null && @@ -2869,6 +2932,7 @@ class DocSearch { mgens ? new Map(mgens) : null, solutionCb, unboxingDepth, + typeNameIds, ) || fnType.generics, }); return highlighted; @@ -2881,6 +2945,7 @@ class DocSearch { whereClause, mgens, unboxingDepth + 1, + typeNameIds, )) { continue; } @@ -2894,6 +2959,7 @@ class DocSearch { mgens, solutionCb, unboxingDepth + 1, + typeNameIds, ); if (highlightedGenerics) { const highlighted = [...fnTypesIn]; @@ -2912,6 +2978,7 @@ class DocSearch { mgens ? new Map(mgens) : null, solutionCb, unboxingDepth + 1, + typeNameIds, ); if (highlightedGenerics) { const highlighted = [...fnTypesIn]; @@ -2956,7 +3023,12 @@ class DocSearch { let queryElemsTmp = null; for (let i = flast; i >= 0; i -= 1) { const fnType = fnTypes[i]; - if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, mgens)) { + if (!unifyFunctionTypeIsMatchCandidate( + fnType, + queryElem, + mgens, + typeNameIds, + )) { continue; } let mgensScratch; @@ -3000,6 +3072,7 @@ class DocSearch { whereClause, mgensScratch, unboxingDepth, + typeNameIds, ); if (!solution) { return false; @@ -3013,6 +3086,7 @@ class DocSearch { simplifiedMgens, solutionCb, unboxingDepth, + typeNameIds, ); if (unifiedGenerics !== null) { unifiedGenericsMgens = simplifiedMgens; @@ -3022,6 +3096,7 @@ class DocSearch { return false; }, unboxingDepth, + typeNameIds, ); if (passesUnification) { passesUnification.length = fl; @@ -3038,6 +3113,7 @@ class DocSearch { unifiedGenericsMgens, solutionCb, unboxingDepth, + typeNameIds, // @ts-expect-error ) : unifiedGenerics.splice(0, v.length)]; })), @@ -3057,6 +3133,7 @@ class DocSearch { whereClause, mgens, unboxingDepth + 1, + typeNameIds, )) { continue; } @@ -3073,6 +3150,7 @@ class DocSearch { mgens, solutionCb, unboxingDepth + 1, + typeNameIds, ); if (passesUnification) { const highlightedGenerics = passesUnification.slice( @@ -3113,6 +3191,7 @@ class DocSearch { * @param {number} unboxingDepth * - Limit checks that Ty matches Vec, * but not Vec>>>> + * @param {rustdoc.TypeNameIds} typeNameIds * * @return {rustdoc.HighlightedFunctionType[]|null} * - Returns highlighted results if a match, null otherwise. @@ -3124,6 +3203,7 @@ class DocSearch { mgensIn, solutionCb, unboxingDepth, + typeNameIds, ) { if (unboxingDepth >= UNBOXING_LIMIT) { return null; @@ -3141,7 +3221,12 @@ class DocSearch { } const fnType = fnTypesIn[0]; const queryElem = queryElems[0]; - if (unifyFunctionTypeIsMatchCandidate(fnType, queryElem, mgens)) { + if (unifyFunctionTypeIsMatchCandidate( + fnType, + queryElem, + mgens, + typeNameIds, + )) { if (fnType.id !== null && fnType.id < 0 && queryElem.id !== null && @@ -3159,6 +3244,7 @@ class DocSearch { mgensScratch, solutionCb, unboxingDepth, + typeNameIds, ); if (fnTypesRemaining) { const highlighted = [fnType, ...fnTypesRemaining]; @@ -3185,6 +3271,7 @@ class DocSearch { whereClause, mgensScratch, unboxingDepth, + typeNameIds, ); if (!solution) { return false; @@ -3198,6 +3285,7 @@ class DocSearch { simplifiedMgens, solutionCb, unboxingDepth, + typeNameIds, ); if (unifiedGenerics !== null) { return true; @@ -3205,6 +3293,7 @@ class DocSearch { } }, unboxingDepth, + typeNameIds, ); if (fnTypesRemaining) { const highlighted = [fnType, ...fnTypesRemaining]; @@ -3223,6 +3312,7 @@ class DocSearch { whereClause, mgens, unboxingDepth + 1, + typeNameIds, )) { let highlightedRemaining; if (fnType.id !== null && fnType.id < 0) { @@ -3244,6 +3334,7 @@ class DocSearch { mgensScratch, solutionCb, unboxingDepth, + typeNameIds, ); if (hl) { highlightedRemaining = hl; @@ -3251,6 +3342,7 @@ class DocSearch { return hl; }, unboxingDepth + 1, + typeNameIds, ); if (highlightedGenerics) { return [Object.assign({ @@ -3278,6 +3370,7 @@ class DocSearch { mgensScratch, solutionCb, unboxingDepth, + typeNameIds, ); if (hl) { highlightedRemaining = hl; @@ -3285,6 +3378,7 @@ class DocSearch { return hl; }, unboxingDepth + 1, + typeNameIds, ); if (highlightedGenerics) { return [Object.assign({}, fnType, { @@ -3310,10 +3404,11 @@ class DocSearch { * * @param {rustdoc.FunctionType} fnType * @param {rustdoc.QueryElement} queryElem + * @param {rustdoc.TypeNameIds} typeNameIds * @param {Map|null} mgensIn - Map query generics to function generics. * @returns {boolean} */ - const unifyFunctionTypeIsMatchCandidate = (fnType, queryElem, mgensIn) => { + const unifyFunctionTypeIsMatchCandidate = (fnType, queryElem, mgensIn, typeNameIds) => { // type filters look like `trait:Read` or `enum:Result` if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) { return false; @@ -3332,21 +3427,23 @@ class DocSearch { } else { // For these special cases, matching code need added to the inverted index. // search_index.rs -> convert_render_type does this - if (queryElem.id === this.typeNameIdOfArrayOrSlice && - (fnType.id === this.typeNameIdOfSlice || fnType.id === this.typeNameIdOfArray) + if (queryElem.id === typeNameIds.typeNameIdOfArrayOrSlice && + (fnType.id === typeNameIds.typeNameIdOfSlice || + fnType.id === typeNameIds.typeNameIdOfArray) ) { // [] matches primitive:array or primitive:slice // if it matches, then we're fine, and this is an appropriate match candidate - } else if (queryElem.id === this.typeNameIdOfTupleOrUnit && - (fnType.id === this.typeNameIdOfTuple || fnType.id === this.typeNameIdOfUnit) + } else if (queryElem.id === typeNameIds.typeNameIdOfTupleOrUnit && + (fnType.id === typeNameIds.typeNameIdOfTuple || + fnType.id === typeNameIds.typeNameIdOfUnit) ) { // () matches primitive:tuple or primitive:unit // if it matches, then we're fine, and this is an appropriate match candidate - } else if (queryElem.id === this.typeNameIdOfHof && ( - fnType.id === this.typeNameIdOfFn || - fnType.id === this.typeNameIdOfFnMut || - fnType.id === this.typeNameIdOfFnOnce || - fnType.id === this.typeNameIdOfFnPtr + } else if (queryElem.id === typeNameIds.typeNameIdOfHof && ( + fnType.id === typeNameIds.typeNameIdOfFn || + fnType.id === typeNameIds.typeNameIdOfFnMut || + fnType.id === typeNameIds.typeNameIdOfFnOnce || + fnType.id === typeNameIds.typeNameIdOfFnPtr )) { // -> matches fn, fnonce, and fnmut // if it matches, then we're fine, and this is an appropriate match candidate @@ -3410,6 +3507,7 @@ class DocSearch { * @param {Map|null} mgensIn - Map query generics to function generics. * Never modified. * @param {number} unboxingDepth + * @param {rustdoc.TypeNameIds} typeNameIds * @returns {false|{ * mgens: [Map|null], simplifiedGenerics: rustdoc.FunctionType[] * }} @@ -3420,6 +3518,7 @@ class DocSearch { whereClause, mgensIn, unboxingDepth, + typeNameIds, ) { if (fnType.bindings.size < queryElem.bindings.size) { return false; @@ -3451,6 +3550,7 @@ class DocSearch { return false; }, unboxingDepth, + typeNameIds, ); return newSolutions; }); @@ -3482,6 +3582,7 @@ class DocSearch { * @param {rustdoc.FunctionType[][]} whereClause - Trait bounds for generic items. * @param {Map|null} mgens - Map query generics to function generics. * @param {number} unboxingDepth + * @param {rustdoc.TypeNameIds} typeNameIds * @returns {boolean} */ function unifyFunctionTypeIsUnboxCandidate( @@ -3490,6 +3591,7 @@ class DocSearch { whereClause, mgens, unboxingDepth, + typeNameIds, ) { if (unboxingDepth >= UNBOXING_LIMIT) { return false; @@ -3508,6 +3610,7 @@ class DocSearch { whereClause, mgens, unboxingDepth, + typeNameIds, ); } else if (fnType.unboxFlag && (fnType.generics.length > 0 || fnType.bindings.size > 0)) { @@ -3521,6 +3624,7 @@ class DocSearch { whereClause, mgens, unboxingDepth, + typeNameIds, ); } return false; @@ -3533,14 +3637,15 @@ class DocSearch { * @param {rustdoc.QueryElement[]} elems * @param {rustdoc.FunctionType[]} list - A list of function types. * @param {rustdoc.FunctionType[][]} where_clause - Trait bounds for generic items. + * @param {rustdoc.TypeNameIds} typeNameIds */ - function containsTypeFromQuery(elems, list, where_clause) { + function containsTypeFromQuery(elems, list, where_clause, typeNameIds) { if (!list) return false; for (const ty of elems) { if (ty.id !== null && ty.id < 0) { continue; } - if (checkIfInList(list, ty, where_clause, null, 0)) { + if (checkIfInList(list, ty, where_clause, null, 0, typeNameIds)) { return true; } } @@ -3556,12 +3661,13 @@ class DocSearch { * @param {rustdoc.FunctionType[][]} whereClause - Trait bounds for generic items. * @param {Map|null} mgens - Map functions generics to query generics. * @param {number} unboxingDepth + * @param {rustdoc.TypeNameIds} typeNameIds * * @return {boolean} - Returns true if found, false otherwise. */ - function checkIfInList(list, elem, whereClause, mgens, unboxingDepth) { + function checkIfInList(list, elem, whereClause, mgens, unboxingDepth, typeNameIds) { for (const entry of list) { - if (checkType(entry, elem, whereClause, mgens, unboxingDepth)) { + if (checkType(entry, elem, whereClause, mgens, unboxingDepth, typeNameIds)) { return true; } } @@ -3576,11 +3682,12 @@ class DocSearch { * @param {rustdoc.QueryElement} elem - The element from the parsed query. * @param {rustdoc.FunctionType[][]} whereClause - Trait bounds for generic items. * @param {Map|null} mgens - Map query generics to function generics. + * @param {number} unboxingDepth + * @param {rustdoc.TypeNameIds} typeNameIds * * @return {boolean} - Returns true if the type matches, false otherwise. */ - // @ts-expect-error - const checkType = (row, elem, whereClause, mgens, unboxingDepth) => { + const checkType = (row, elem, whereClause, mgens, unboxingDepth, typeNameIds) => { if (unboxingDepth >= UNBOXING_LIMIT) { return false; } @@ -3589,9 +3696,9 @@ class DocSearch { row.generics.length === 0 && elem.generics.length === 0 && row.bindings.size === 0 && elem.bindings.size === 0 && // special case - elem.id !== this.typeNameIdOfArrayOrSlice && - elem.id !== this.typeNameIdOfHof && - elem.id !== this.typeNameIdOfTupleOrUnit + elem.id !== typeNameIds.typeNameIdOfArrayOrSlice && + elem.id !== typeNameIds.typeNameIdOfHof && + elem.id !== typeNameIds.typeNameIdOfTupleOrUnit ) { return row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty); } else { @@ -3603,6 +3710,7 @@ class DocSearch { mgens, () => true, unboxingDepth, + typeNameIds, ); } }; @@ -3669,7 +3777,10 @@ class DocSearch { /** * Compute an "edit distance" that ignores missing path elements. * @param {string[]} contains search query path - * @param {rustdoc.Row} row indexed item + * @param {{ + * modulePath: string, + * parent: { path: rustdoc.PathData, name: string}?, + * }} row indexed item * @returns {null|number} edit distance */ function checkRowPath(contains, row) { @@ -3748,7 +3859,7 @@ class DocSearch { is_alias: true, elems: [], // only used in type-based queries returned: [], // only used in type-based queries - original: await this.getRow(alias, false), + item: nonnull(await this.getRow(alias, false)), }; }; /** @@ -3830,6 +3941,7 @@ class DocSearch { elems: [], // only used in type-based queries returned: [], // only used in type-based queries is_alias: false, + item: row, } : null; } else { return { @@ -3844,6 +3956,7 @@ class DocSearch { elems: [], // only used in type-based queries returned: [], // only used in type-based queries is_alias: false, + item: row, }; } }; @@ -4355,9 +4468,10 @@ class DocSearch { }; // finally, we can do the actual unification loop - const [allInputs, allOutput] = await Promise.all([ + const [allInputs, allOutput, typeNameIds] = await Promise.all([ unpackPostingsListAll(inputs, "invertedFunctionInputsIndex"), unpackPostingsListAll(output, "invertedFunctionOutputIndex"), + this.getTypeNameIds(), ]); let checkCounter = 0; /** @@ -4426,12 +4540,18 @@ class DocSearch { mgens, checkTypeMgensForConflict, 0, // unboxing depth + typeNameIds, ); }, 0, // unboxing depth + typeNameIds, )) { return null; } + const item = await this.getRow(id, true); + if (!item) { + return null; + } const result = { id, dist: fnData.elemCount, @@ -4440,8 +4560,9 @@ class DocSearch { elems: inputs, returned: output, is_alias: false, + item, }; - const entry = await this.getEntryData(id); + const entry = item.entry; if ((entry && !isFnLikeTy(entry.ty)) || (isReturnTypeQuery && functionSignature && @@ -4449,6 +4570,7 @@ class DocSearch { output, functionSignature.inputs, functionSignature.where_clause, + typeNameIds, ) ) ) { @@ -5269,7 +5391,6 @@ if (ROOT_PATH === null) { const database = await Stringdex.loadDatabase(hooks); if (typeof window !== "undefined") { docSearch = new DocSearch(ROOT_PATH, database); - await docSearch.buildIndex(); onEachLazy(document.querySelectorAll( ".search-form.loading", ), form => { @@ -5282,7 +5403,6 @@ if (typeof window !== "undefined") { } } else if (typeof exports !== "undefined") { docSearch = new DocSearch(ROOT_PATH, database); - await docSearch.buildIndex(); return { docSearch, DocSearch }; } }; diff --git a/src/librustdoc/html/static/js/stringdex.js b/src/librustdoc/html/static/js/stringdex.js index 4c4659628778d..5bdc81e330efa 100644 --- a/src/librustdoc/html/static/js/stringdex.js +++ b/src/librustdoc/html/static/js/stringdex.js @@ -2310,7 +2310,7 @@ function loadDatabase(hooks) { } else { const start = this.bucket_keys[idx]; const bucket = this.buckets[idx]; - let data = this.buckets[idx].data; + const data = this.buckets[idx].data; if (data === null) { return this.atAsyncFetch(id, start, bucket); } else { From 812776042bcdac48f9479cbd6e945852e57063fd Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 25 Sep 2025 13:04:34 -0700 Subject: [PATCH 4/9] rustdoc-search: use the same ID for entry and path to same item This decreases the size of the compiler-doc from 57MiB to 56MiB. (cherry picked from commit b9037f97e38acc698af1260cad5d31ca07ac616a) --- src/librustdoc/html/render/search_index.rs | 32 ++++++++++++++++------ 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 2984f3ab50eea..02e1e0da99bad 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -239,6 +239,26 @@ impl SerializedSearchIndex { self.alias_pointers.push(alias_pointer); index } + fn add_entry(&mut self, name: Symbol, entry_data: EntryData, desc: String) -> usize { + let fqp = if let Some(module_path_index) = entry_data.module_path { + let mut fqp = self.path_data[module_path_index].as_ref().unwrap().module_path.clone(); + fqp.push(Symbol::intern(&self.names[module_path_index])); + fqp.push(name); + fqp + } else { + vec![name] + }; + if let Some(&other_path) = self.crate_paths_index.get(&(entry_data.ty, fqp)) + && self.entry_data[other_path].is_none() + && self.descs[other_path].is_empty() + { + self.entry_data[other_path] = Some(entry_data); + self.descs[other_path] = desc; + other_path + } else { + self.push(name.as_str().to_string(), None, Some(entry_data), desc, None, None, None) + } + } fn push_path(&mut self, name: String, path_data: PathData) -> usize { self.push(name, Some(path_data), None, String::new(), None, None, None) } @@ -1513,10 +1533,9 @@ pub(crate) fn build_index( .as_ref() .map(|path| serialized_index.get_id_by_module_path(path)); - let new_entry_id = serialized_index.push( - item.name.as_str().to_string(), - None, - Some(EntryData { + let new_entry_id = serialized_index.add_entry( + item.name, + EntryData { ty: item.ty, parent: item.parent_idx, module_path, @@ -1535,11 +1554,8 @@ pub(crate) fn build_index( None }, krate: crate_idx, - }), + }, item.desc.to_string(), - None, // filled in after all the types have been indexed - None, - None, ); // Aliases From b0dd64fec677c42be1549aa97706e1b09e363204 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 25 Sep 2025 15:16:16 -0700 Subject: [PATCH 5/9] rustdoc-search: add descriptive comment to space-saving hack (cherry picked from commit 9dce3e8556022599c1323cdad6a0d87d6991c8b5) --- src/librustdoc/html/render/search_index.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 02e1e0da99bad..497a529773831 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -239,6 +239,9 @@ impl SerializedSearchIndex { self.alias_pointers.push(alias_pointer); index } + /// Add potential search result to the database and return the row ID. + /// + /// The returned ID can be used to attach more data to the search result. fn add_entry(&mut self, name: Symbol, entry_data: EntryData, desc: String) -> usize { let fqp = if let Some(module_path_index) = entry_data.module_path { let mut fqp = self.path_data[module_path_index].as_ref().unwrap().module_path.clone(); @@ -248,6 +251,11 @@ impl SerializedSearchIndex { } else { vec![name] }; + // If a path with the same name already exists, but no entry does, + // we can fill in the entry without having to allocate a new row ID. + // + // Because paths and entries both share the same index, using the same + // ID saves space by making the tree smaller. if let Some(&other_path) = self.crate_paths_index.get(&(entry_data.ty, fqp)) && self.entry_data[other_path].is_none() && self.descs[other_path].is_empty() From 716b84ed5b39ada19963705d4fe7404173575e4f Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 24 Sep 2025 10:34:52 -0700 Subject: [PATCH 6/9] rustdoc-search: stringdex update with more packing Before: 18M build/x86_64-unknown-linux-gnu/doc/search.index/ 57M build/x86_64-unknown-linux-gnu/compiler-doc/search.index/ After: 16M build/x86_64-unknown-linux-gnu/doc/search.index/ 49M build/x86_64-unknown-linux-gnu/compiler-doc/search.index/ (cherry picked from commit 5bec161ce12501695356492129ea8fc952a30f85) --- Cargo.lock | 4 +- src/librustdoc/Cargo.toml | 2 +- src/librustdoc/html/static/js/stringdex.js | 78 ++++++++++++++-------- 3 files changed, 54 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c492e3af1623..3410f74ce4d86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5216,9 +5216,9 @@ dependencies = [ [[package]] name = "stringdex" -version = "0.0.1-alpha9" +version = "0.0.1-alpha10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7081029913fd7d591c0112182aba8c98ae886b4f12edb208130496cd17dc3c15" +checksum = "0fa846a7d509d1828a4f90962dc09810e161abcada7fc6a921e92c168d0811d7" dependencies = [ "stacker", ] diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index f37a8d85361f2..0d77fe64e30f8 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -21,7 +21,7 @@ rustdoc-json-types = { path = "../rustdoc-json-types" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" smallvec = "1.8.1" -stringdex = { version = "0.0.1-alpha9" } +stringdex = { version = "0.0.1-alpha10" } tempfile = "3" threadpool = "1.8.1" tracing = "0.1" diff --git a/src/librustdoc/html/static/js/stringdex.js b/src/librustdoc/html/static/js/stringdex.js index 5bdc81e330efa..6299576d56439 100644 --- a/src/librustdoc/html/static/js/stringdex.js +++ b/src/librustdoc/html/static/js/stringdex.js @@ -1108,22 +1108,39 @@ function loadDatabase(hooks) { const id2 = id1 + ((nodeid[4] << 8) | nodeid[5]); leaves = RoaringBitmap.makeSingleton(id1) .union(RoaringBitmap.makeSingleton(id2)); + } else if (!isWhole && (nodeid[0] & 0xf0) === 0x80) { + const id1 = ((nodeid[0] & 0x0f) << 16) | (nodeid[1] << 8) | nodeid[2]; + const id2 = id1 + ((nodeid[3] << 4) | ((nodeid[4] >> 4) & 0x0f)); + const id3 = id2 + (((nodeid[4] & 0x0f) << 8) | nodeid[5]); + leaves = RoaringBitmap.makeSingleton(id1) + .union(RoaringBitmap.makeSingleton(id2)) + .union(RoaringBitmap.makeSingleton(id3)); } else { leaves = RoaringBitmap.makeSingleton( (nodeid[2] << 24) | (nodeid[3] << 16) | (nodeid[4] << 8) | nodeid[5], ); } - const data = (nodeid[0] & 0x20) !== 0 ? - Uint8Array.of(((nodeid[0] & 0x0f) << 4) | (nodeid[1] >> 4)) : - EMPTY_UINT8; - newPromise = Promise.resolve(new PrefixSearchTree( - EMPTY_SEARCH_TREE_BRANCHES, - EMPTY_SEARCH_TREE_BRANCHES, - data, - isWhole ? leaves : EMPTY_BITMAP, - isWhole ? EMPTY_BITMAP : leaves, - )); + if (isWhole) { + const data = (nodeid[0] & 0x20) !== 0 ? + Uint8Array.of(((nodeid[0] & 0x0f) << 4) | (nodeid[1] >> 4)) : + EMPTY_UINT8; + newPromise = Promise.resolve(new PrefixSearchTree( + EMPTY_SEARCH_TREE_BRANCHES, + EMPTY_SEARCH_TREE_BRANCHES, + data, + leaves, + EMPTY_BITMAP, + )); + } else { + const data = (nodeid[0] & 0xf0) === 0x80 ? 0 : ( + ((nodeid[0] & 0x0f) << 4) | (nodeid[1] >> 4)); + newPromise = Promise.resolve(new SuffixSearchTree( + EMPTY_SEARCH_TREE_BRANCHES, + data, + leaves, + )); + } } else { const hashHex = makeHexFromUint8Array(nodeid); newPromise = new Promise((resolve, reject) => { @@ -2748,6 +2765,7 @@ function loadDatabase(hooks) { // because that's the canonical, hashed version of the data let compression_tag = input[i]; const is_pure_suffixes_only_node = (compression_tag & 0x01) !== 0; + let no_leaves_flag; if (compression_tag > 1) { // compressed node const is_long_compressed = (compression_tag & 0x04) !== 0; @@ -2759,7 +2777,8 @@ function loadDatabase(hooks) { compression_tag |= input[i] << 16; i += 1; } - let dlen = input[i]; + let dlen = input[i] & 0x7F; + no_leaves_flag = input[i] & 0x80; i += 1; if (is_data_compressed) { data = data_history[data_history.length - dlen - 1]; @@ -2786,10 +2805,15 @@ function loadDatabase(hooks) { let whole; let suffix; if (is_pure_suffixes_only_node) { - suffix = input[i] === 0 ? - EMPTY_BITMAP1 : - new RoaringBitmap(input, i); - i += suffix.consumed_len_bytes; + if (no_leaves_flag) { + whole = EMPTY_BITMAP; + suffix = EMPTY_BITMAP; + } else { + suffix = input[i] === 0 ? + EMPTY_BITMAP1 : + new RoaringBitmap(input, i); + i += suffix.consumed_len_bytes; + } tree = new SuffixSearchTree( branches, dlen, @@ -2807,7 +2831,7 @@ function loadDatabase(hooks) { let ci = 0; canonical[ci] = 1; ci += 1; - canonical[ci] = dlen; + canonical[ci] = dlen | no_leaves_flag; ci += 1; canonical[ci] = input[coffset]; // suffix child count ci += 1; @@ -2821,10 +2845,9 @@ function loadDatabase(hooks) { } siphashOfBytes(canonical.subarray(0, clen), 0, 0, 0, 0, hash); } else { - if (input[i] === 0xff) { + if (no_leaves_flag) { whole = EMPTY_BITMAP; - suffix = EMPTY_BITMAP1; - i += 1; + suffix = EMPTY_BITMAP; } else { whole = input[i] === 0 ? EMPTY_BITMAP1 : @@ -2856,7 +2879,7 @@ function loadDatabase(hooks) { let ci = 0; canonical[ci] = 0; ci += 1; - canonical[ci] = dlen; + canonical[ci] = dlen | no_leaves_flag; ci += 1; canonical.set(data, ci); ci += data.length; @@ -2880,9 +2903,11 @@ function loadDatabase(hooks) { } hash[2] &= 0x7f; } else { + i += 1; // uncompressed node - const dlen = input [i + 1]; - i += 2; + const dlen = input[i] & 0x7F; + no_leaves_flag = input[i] & 0x80; + i += 1; if (dlen === 0 || is_pure_suffixes_only_node) { data = EMPTY_UINT8; } else { @@ -2897,16 +2922,15 @@ function loadDatabase(hooks) { i += branches_consumed_len_bytes; let whole; let suffix; - if (is_pure_suffixes_only_node) { + if (no_leaves_flag) { + whole = EMPTY_BITMAP; + suffix = EMPTY_BITMAP; + } else if (is_pure_suffixes_only_node) { whole = EMPTY_BITMAP; suffix = input[i] === 0 ? EMPTY_BITMAP1 : new RoaringBitmap(input, i); i += suffix.consumed_len_bytes; - } else if (input[i] === 0xff) { - whole = EMPTY_BITMAP; - suffix = EMPTY_BITMAP; - i += 1; } else { whole = input[i] === 0 ? EMPTY_BITMAP1 : From 7ce30224e347370dd2b19dc6bda406488f973c67 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 13 Oct 2025 21:12:21 -0700 Subject: [PATCH 7/9] rustdoc-search: stringdex 0.0.2 Two index format tweaks that reduce the size of the standard library, compiler, and wordnet dictionary when I test it. (cherry picked from commit fdeb3633d9e2700e418efb032e3c2e472a7f0844) --- Cargo.lock | 4 +- src/librustdoc/Cargo.toml | 2 +- src/librustdoc/html/static/js/stringdex.js | 355 ++++++++++++++++++--- 3 files changed, 314 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3410f74ce4d86..022688b698f5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5216,9 +5216,9 @@ dependencies = [ [[package]] name = "stringdex" -version = "0.0.1-alpha10" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa846a7d509d1828a4f90962dc09810e161abcada7fc6a921e92c168d0811d7" +checksum = "18b3bd4f10d15ef859c40291769f0d85209de6b0f1c30713ff9cdf45ac43ea36" dependencies = [ "stacker", ] diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 0d77fe64e30f8..daa6595a65507 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -21,7 +21,7 @@ rustdoc-json-types = { path = "../rustdoc-json-types" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" smallvec = "1.8.1" -stringdex = { version = "0.0.1-alpha10" } +stringdex = "=0.0.2" tempfile = "3" threadpool = "1.8.1" tracing = "0.1" diff --git a/src/librustdoc/html/static/js/stringdex.js b/src/librustdoc/html/static/js/stringdex.js index 6299576d56439..d8b8c5bd70c13 100644 --- a/src/librustdoc/html/static/js/stringdex.js +++ b/src/librustdoc/html/static/js/stringdex.js @@ -1447,7 +1447,7 @@ function loadDatabase(hooks) { makeSearchTreeBranchesAlphaBitmapClass(LONG_ALPHABITMAP_CHARS, 4); /** - * @typedef {PrefixSearchTree|SuffixSearchTree} SearchTree + * @typedef {PrefixSearchTree|SuffixSearchTree|InlineNeighborsTree} SearchTree * @typedef {PrefixTrie|SuffixTrie} Trie */ @@ -1675,9 +1675,12 @@ function loadDatabase(hooks) { yield leaves; } } - /** @type {HashTable<[number, SearchTree][]>} */ + /** @type {HashTable<[number, PrefixSearchTree|SuffixSearchTree][]>} */ const subnodes = new HashTable(); - for await (const node of current_layer) { + for await (const nodeEncoded of current_layer) { + const node = nodeEncoded instanceof InlineNeighborsTree ? + nodeEncoded.decode() : + nodeEncoded; const branches = node.branches; const l = branches.subtrees.length; for (let i = 0; i < l; ++i) { @@ -1741,7 +1744,10 @@ function loadDatabase(hooks) { // we then yield the smallest ones (can't yield bigger ones // if we want to do them in order) for (const {node, len} of current_layer) { - const tree = await node; + const treeEncoded = await node; + const tree = treeEncoded instanceof InlineNeighborsTree ? + treeEncoded.decode() : + treeEncoded; if (!(tree instanceof PrefixSearchTree)) { continue; } @@ -1804,7 +1810,10 @@ function loadDatabase(hooks) { /** @type {HashTable<{byte: number, tree: PrefixSearchTree, len: number}[]>} */ const subnodes = new HashTable(); for await (const {node, len} of current_layer) { - const tree = await node; + const treeEncoded = await node; + const tree = treeEncoded instanceof InlineNeighborsTree ? + treeEncoded.decode() : + treeEncoded; if (!(tree instanceof PrefixSearchTree)) { continue; } @@ -2166,9 +2175,12 @@ function loadDatabase(hooks) { yield leaves; } } - /** @type {HashTable<[number, SearchTree][]>} */ + /** @type {HashTable<[number, PrefixSearchTree|SuffixSearchTree][]>} */ const subnodes = new HashTable(); - for await (const node of current_layer) { + for await (const nodeEncoded of current_layer) { + const node = nodeEncoded instanceof InlineNeighborsTree ? + nodeEncoded.decode() : + nodeEncoded; const branches = node.branches; const l = branches.subtrees.length; for (let i = 0; i < l; ++i) { @@ -2264,6 +2276,174 @@ function loadDatabase(hooks) { } } + /** + * Represents a subtree where all transitive leaves + * have a shared 16bit prefix and there are no sub-branches. + */ + class InlineNeighborsTree { + /** + * @param {Uint8Array} encoded + * @param {number} start + */ + constructor( + encoded, + start, + ) { + this.encoded = encoded; + this.start = start; + } + /** + * @return {PrefixSearchTree|SuffixSearchTree} + */ + decode() { + let i = this.start; + const encoded = this.encoded; + const has_branches = (encoded[i] & 0x04) !== 0; + /** @type {boolean} */ + const is_suffixes_only = (encoded[i] & 0x01) !== 0; + let leaves_count = ((encoded[i] >> 4) & 0x0f) + 1; + i += 1; + let branch_count = 0; + if (has_branches) { + branch_count = encoded[i] + 1; + i += 1; + } + const dlen = encoded[i] & 0x3f; + if ((encoded[i] & 0x80) !== 0) { + leaves_count = 0; + } + i += 1; + let data = EMPTY_UINT8; + if (!is_suffixes_only && dlen !== 0) { + data = encoded.subarray(i, i + dlen); + i += dlen; + } + const leaf_value_upper = encoded[i] | (encoded[i + 1] << 8); + i += 2; + /** @type {Promise[]} */ + const branch_nodes = []; + for (let j = 0; j < branch_count; j += 1) { + const branch_dlen = encoded[i] & 0x0f; + const branch_leaves_count = ((encoded[i] >> 4) & 0x0f) + 1; + i += 1; + let branch_data = EMPTY_UINT8; + if (!is_suffixes_only && branch_dlen !== 0) { + branch_data = encoded.subarray(i, i + branch_dlen); + i += branch_dlen; + } + const branch_leaves = new RoaringBitmap(null); + branch_leaves.keysAndCardinalities = Uint8Array.of( + leaf_value_upper & 0xff, + (leaf_value_upper >> 8) & 0xff, + (branch_leaves_count - 1) & 0xff, + ((branch_leaves_count - 1) >> 8) & 0xff, + ); + branch_leaves.containers = [ + new RoaringBitmapArray( + branch_leaves_count, + encoded.subarray(i, i + (branch_leaves_count * 2)), + ), + ]; + i += branch_leaves_count * 2; + branch_nodes.push(Promise.resolve( + is_suffixes_only ? + new SuffixSearchTree( + EMPTY_SEARCH_TREE_BRANCHES, + branch_dlen, + branch_leaves, + ) : + new PrefixSearchTree( + EMPTY_SEARCH_TREE_BRANCHES, + EMPTY_SEARCH_TREE_BRANCHES, + branch_data, + branch_leaves, + EMPTY_BITMAP, + ), + )); + } + /** @type {SearchTreeBranchesArray} */ + const branches = branch_count === 0 ? + EMPTY_SEARCH_TREE_BRANCHES : + new SearchTreeBranchesArray( + encoded.subarray(i, i + branch_count), + EMPTY_UINT8, + ); + i += branch_count; + branches.subtrees = branch_nodes; + let leaves = EMPTY_BITMAP; + if (leaves_count !== 0) { + leaves = new RoaringBitmap(null); + leaves.keysAndCardinalities = Uint8Array.of( + leaf_value_upper & 0xff, + (leaf_value_upper >> 8) & 0xff, + (leaves_count - 1) & 0xff, + ((leaves_count - 1) >> 8) & 0xff, + ); + leaves.containers = [ + new RoaringBitmapArray( + leaves_count, + encoded.subarray(i, i + (leaves_count * 2)), + ), + ]; + i += leaves_count * 2; + } + return is_suffixes_only ? + new SuffixSearchTree( + branches, + dlen, + leaves, + ) : + new PrefixSearchTree( + branches, + branches, + data, + leaves, + EMPTY_BITMAP, + ); + } + + /** + * Returns the Trie for the root node. + * + * A Trie pointer refers to a single node in a logical decompressed search tree + * (the real search tree is compressed). + * + * @param {DataColumn} dataColumn + * @param {Uint8ArraySearchPattern} searchPattern + * @return {Trie} + */ + trie(dataColumn, searchPattern) { + const tree = this.decode(); + return tree instanceof SuffixSearchTree ? + new SuffixTrie(tree, 0, dataColumn, searchPattern) : + new PrefixTrie(tree, 0, dataColumn, searchPattern); + } + + /** + * Return the trie representing `name` + * @param {Uint8Array|string} name + * @param {DataColumn} dataColumn + * @returns {Promise} + */ + search(name, dataColumn) { + return this.decode().search(name, dataColumn); + } + + /** + * @param {Uint8Array|string} name + * @param {DataColumn} dataColumn + * @returns {AsyncGenerator} + */ + searchLev(name, dataColumn) { + return this.decode().searchLev(name, dataColumn); + } + + /** @returns {RoaringBitmap} */ + getCurrentLeaves() { + return this.decode().getCurrentLeaves(); + } + } + class DataColumn { /** * Construct the wrapper object for a data column. @@ -2765,21 +2945,37 @@ function loadDatabase(hooks) { // because that's the canonical, hashed version of the data let compression_tag = input[i]; const is_pure_suffixes_only_node = (compression_tag & 0x01) !== 0; - let no_leaves_flag; - if (compression_tag > 1) { - // compressed node - const is_long_compressed = (compression_tag & 0x04) !== 0; - const is_data_compressed = (compression_tag & 0x08) !== 0; + const is_long_compressed = (compression_tag & 0x04) !== 0; + const is_data_compressed = (compression_tag & 0x08) !== 0; + i += 1; + if (is_long_compressed) { + compression_tag |= input[i] << 8; i += 1; - if (is_long_compressed) { - compression_tag |= input[i] << 8; - i += 1; - compression_tag |= input[i] << 16; - i += 1; - } - let dlen = input[i] & 0x7F; + } + /** @type {number} */ + let dlen; + /** @type {number} */ + let no_leaves_flag; + /** @type {number} */ + let inline_neighbors_flag; + if (is_data_compressed && is_pure_suffixes_only_node) { + dlen = 0; + no_leaves_flag = 0x80; + inline_neighbors_flag = 0; + } else { + dlen = input[i] & 0x3F; no_leaves_flag = input[i] & 0x80; + inline_neighbors_flag = input[i] & 0x40; i += 1; + } + if (inline_neighbors_flag !== 0) { + // node with packed leaves and common 16bit prefix + const leaves_count = no_leaves_flag !== 0 ? + 0 : + ((compression_tag >> 4) & 0x0f) + 1; + const branch_count = is_long_compressed ? + ((compression_tag >> 8) & 0xff) + 1 : + 0; if (is_data_compressed) { data = data_history[data_history.length - dlen - 1]; dlen = data.length; @@ -2791,6 +2987,72 @@ function loadDatabase(hooks) { new Uint8Array(input.buffer, i + input.byteOffset, dlen); i += dlen; } + const branches_start = i; + // leaf_value_upper + i += 2; + // branch_nodes + for (let j = 0; j < branch_count; j += 1) { + const branch_dlen = input[i] & 0x0f; + const branch_leaves_count = ((input[i] >> 4) & 0x0f) + 1; + i += 1; + if (!is_pure_suffixes_only_node) { + i += branch_dlen; + } + i += branch_leaves_count * 2; + } + // branch keys + i += branch_count; + // leaves + i += leaves_count * 2; + if (is_data_compressed) { + const clen = ( + 1 + // first compression header byte + (is_long_compressed ? 1 : 0) + // branch count + 1 + // data length and other flags + dlen + // data + (i - branches_start) // branches and leaves + ); + const canonical = new Uint8Array(clen); + let ci = 0; + canonical[ci] = input[start] ^ 0x08; + ci += 1; + if (is_long_compressed) { + canonical[ci] = input[start + ci]; + ci += 1; + } + canonical[ci] = dlen | no_leaves_flag | 0x40; + ci += 1; + for (let j = 0; j < dlen; j += 1) { + canonical[ci] = data[j]; + ci += 1; + } + for (let j = branches_start; j < i; j += 1) { + canonical[ci] = input[j]; + ci += 1; + } + tree = new InlineNeighborsTree(canonical, 0); + siphashOfBytes(canonical, 0, 0, 0, 0, hash); + } else { + tree = new InlineNeighborsTree(input, start); + siphashOfBytes(new Uint8Array( + input.buffer, + start + input.byteOffset, + i - start, + ), 0, 0, 0, 0, hash); + } + } else if (compression_tag > 1) { + // compressed node + if (is_pure_suffixes_only_node) { + data = EMPTY_UINT8; + } else if (is_data_compressed) { + data = data_history[data_history.length - dlen - 1]; + dlen = data.length; + } else { + data = dlen === 0 ? + EMPTY_UINT8 : + new Uint8Array(input.buffer, i + input.byteOffset, dlen); + i += dlen; + } const coffset = i; const { cpbranches, @@ -2820,19 +3082,27 @@ function loadDatabase(hooks) { suffix, ); const clen = ( - 3 + // lengths of children and data + // lengths of children and data + (is_data_compressed ? 2 : 3) + + // branches csnodes.length + csbranches.length + + // leaves suffix.consumed_len_bytes ); if (canonical.length < clen) { canonical = new Uint8Array(clen); } let ci = 0; - canonical[ci] = 1; - ci += 1; - canonical[ci] = dlen | no_leaves_flag; - ci += 1; + if (is_data_compressed) { + canonical[ci] = 0x09; + ci += 1; + } else { + canonical[ci] = 1; + ci += 1; + canonical[ci] = dlen | no_leaves_flag; + ci += 1; + } canonical[ci] = input[coffset]; // suffix child count ci += 1; canonical.set(csnodes, ci); @@ -2901,13 +3171,8 @@ function loadDatabase(hooks) { } siphashOfBytes(canonical.subarray(0, clen), 0, 0, 0, 0, hash); } - hash[2] &= 0x7f; } else { - i += 1; // uncompressed node - const dlen = input[i] & 0x7F; - no_leaves_flag = input[i] & 0x80; - i += 1; if (dlen === 0 || is_pure_suffixes_only_node) { data = EMPTY_UINT8; } else { @@ -2946,7 +3211,6 @@ function loadDatabase(hooks) { start + input.byteOffset, i - start, ), 0, 0, 0, 0, hash); - hash[2] &= 0x7f; tree = is_pure_suffixes_only_node ? new SuffixSearchTree( branches, @@ -2961,30 +3225,33 @@ function loadDatabase(hooks) { suffix, ); } + hash[2] &= 0x7f; hash_history.push({hash: truncatedHash.slice(), used: false}); if (data.length !== 0) { data_history.push(data); } - const tree_branch_nodeids = tree.branches.nodeids; - const tree_branch_subtrees = tree.branches.subtrees; - let j = 0; - let lb = tree.branches.subtrees.length; - while (j < lb) { - // node id with a 1 in its most significant bit is inlined, and, so - // it won't be in the stash - if ((tree_branch_nodeids[j * 6] & 0x80) === 0) { - const subtree = stash.getWithOffsetKey(tree_branch_nodeids, j * 6); - if (subtree !== undefined) { - tree_branch_subtrees[j] = Promise.resolve(subtree); + if (!(tree instanceof InlineNeighborsTree)) { + const tree_branch_nodeids = tree.branches.nodeids; + const tree_branch_subtrees = tree.branches.subtrees; + let j = 0; + const lb = tree.branches.subtrees.length; + while (j < lb) { + // node id with a 1 in its most significant bit is inlined, and, so + // it won't be in the stash + if ((tree_branch_nodeids[j * 6] & 0x80) === 0) { + const subtree = stash.getWithOffsetKey(tree_branch_nodeids, j * 6); + if (subtree !== undefined) { + tree_branch_subtrees[j] = Promise.resolve(subtree); + } } + j += 1; } - j += 1; } if (tree instanceof PrefixSearchTree) { const tree_mhp_branch_nodeids = tree.might_have_prefix_branches.nodeids; const tree_mhp_branch_subtrees = tree.might_have_prefix_branches.subtrees; - j = 0; - lb = tree.might_have_prefix_branches.subtrees.length; + let j = 0; + const lb = tree.might_have_prefix_branches.subtrees.length; while (j < lb) { // node id with a 1 in its most significant bit is inlined, and, so // it won't be in the stash From e4f50310ceffe983f95624e9611714c7fafabf1e Mon Sep 17 00:00:00 2001 From: Timo <30553356+y21@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:08:08 +0000 Subject: [PATCH 8/9] fix(len_zero): don't eagerly call `GenericArgs::type_at` (#15660) Fixes https://github.com/rust-lang/rust-clippy/issues/15657 changelog: [`len_zero`]: fix ICE when `fn len` has a return type without generic type params --- src/tools/clippy/clippy_lints/src/len_zero.rs | 6 +++--- src/tools/clippy/tests/ui/crashes/ice-15657.rs | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 src/tools/clippy/tests/ui/crashes/ice-15657.rs diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs index f44a5fdf715e5..28a0fbc051158 100644 --- a/src/tools/clippy/clippy_lints/src/len_zero.rs +++ b/src/tools/clippy/clippy_lints/src/len_zero.rs @@ -371,9 +371,9 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option Some(LenOutput::Integral), - ty::Adt(adt, subs) if subs.type_at(0).is_integral() => match cx.tcx.get_diagnostic_name(adt.did()) { - Some(sym::Option) => Some(LenOutput::Option(adt.did())), - Some(sym::Result) => Some(LenOutput::Result(adt.did())), + ty::Adt(adt, subs) => match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Option) => subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())), + Some(sym::Result) => subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())), _ => None, }, _ => None, diff --git a/src/tools/clippy/tests/ui/crashes/ice-15657.rs b/src/tools/clippy/tests/ui/crashes/ice-15657.rs new file mode 100644 index 0000000000000..c6f6506cd8d03 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-15657.rs @@ -0,0 +1,11 @@ +//@check-pass +#![warn(clippy::len_zero)] + +pub struct S1; +pub struct S2; + +impl S1 { + pub fn len(&self) -> S2 { + S2 + } +} From be999adba7cadc171c1f79fde2e527518775c7ec Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 27 Oct 2025 09:48:10 -0700 Subject: [PATCH 9/9] 1.91.0 stable release --- RELEASES.md | 337 +++++++++++++++++++++++++++++++++++++++++++++++++ src/ci/channel | 2 +- 2 files changed, 338 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 33abe45ce4629..74b0d4424c163 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,340 @@ +Version 1.91.0 (2025-10-30) +========================== + + + +Language +-------- + +- [Lower pattern bindings in the order they're written and base drop order on primary bindings' order](https://github.com/rust-lang/rust/pull/143764) +- [Stabilize declaration of C-style variadic functions for `sysv64`, `win64`, `efiapi`, and `aapcs` ABIs](https://github.com/rust-lang/rust/pull/144066). + This brings these ABIs in line with the C ABI: variadic functions can be declared in extern blocks but not defined. +- [Add `dangling_pointers_from_locals` lint to warn against dangling pointers from local variables](https://github.com/rust-lang/rust/pull/144322) +- [Upgrade `semicolon_in_expressions_from_macros` from warn to deny](https://github.com/rust-lang/rust/pull/144369) +- [Stabilize LoongArch32 inline assembly](https://github.com/rust-lang/rust/pull/144402) +- [Add warn-by-default `integer_to_ptr_transmutes` lint against integer-to-pointer transmutes](https://github.com/rust-lang/rust/pull/144531) +- [Stabilize `sse4a` and `tbm` target features](https://github.com/rust-lang/rust/pull/144542) +- [Add `target_env = "macabi"` and `target_env = "sim"` cfgs](https://github.com/rust-lang/rust/pull/139451) as replacements for the `target_abi` cfgs with the same values. + + + +Compiler +-------- + +- [Don't warn on never-to-any `as` casts as unreachable](https://github.com/rust-lang/rust/pull/144804) + + + +Platform Support +---------------- + +- [Promote `aarch64-pc-windows-gnullvm` and `x86_64-pc-windows-gnullvm` to Tier 2 with host tools.](https://github.com/rust-lang/rust/pull/143031) + Note: llvm-tools and MSI installers are missing but will be added in future releases. +- [Promote `aarch64-pc-windows-msvc` to Tier 1](https://github.com/rust-lang/rust/pull/145682) + +Refer to Rust's [platform support page][platform-support-doc] +for more information on Rust's tiered platform support. + +[platform-support-doc]: https://doc.rust-lang.org/rustc/platform-support.html + + + +Libraries +--------- + +- [Print thread ID in panic message](https://github.com/rust-lang/rust/pull/115746) +- [Fix overly restrictive lifetime in `core::panic::Location::file` return type](https://github.com/rust-lang/rust/pull/132087) +- [Guarantee parameter order for `_by()` variants of `min` / `max`/ `minmax` in `std::cmp`](https://github.com/rust-lang/rust/pull/139357) +- [Document assumptions about `Clone` and `Eq` traits](https://github.com/rust-lang/rust/pull/144330/) +- [`std::thread`: Return error if setting thread stack size fails](https://github.com/rust-lang/rust/pull/144210) + This used to panic within the standard library. + + + +Stabilized APIs +--------------- + +- [`Path::file_prefix`](https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.file_prefix) +- [`AtomicPtr::fetch_ptr_add`](https://doc.rust-lang.org/stable/std/sync/atomic/struct.AtomicPtr.html#method.fetch_ptr_add) +- [`AtomicPtr::fetch_ptr_sub`](https://doc.rust-lang.org/stable/std/sync/atomic/struct.AtomicPtr.html#method.fetch_ptr_sub) +- [`AtomicPtr::fetch_byte_add`](https://doc.rust-lang.org/stable/std/sync/atomic/struct.AtomicPtr.html#method.fetch_byte_add) +- [`AtomicPtr::fetch_byte_sub`](https://doc.rust-lang.org/stable/std/sync/atomic/struct.AtomicPtr.html#method.fetch_byte_sub) +- [`AtomicPtr::fetch_or`](https://doc.rust-lang.org/stable/std/sync/atomic/struct.AtomicPtr.html#method.fetch_or) +- [`AtomicPtr::fetch_and`](https://doc.rust-lang.org/stable/std/sync/atomic/struct.AtomicPtr.html#method.fetch_and) +- [`AtomicPtr::fetch_xor`](https://doc.rust-lang.org/stable/std/sync/atomic/struct.AtomicPtr.html#method.fetch_xor) +- [`{integer}::strict_add`](https://doc.rust-lang.org/stable/std/primitive.u32.html#method.strict_add) +- [`{integer}::strict_sub`](https://doc.rust-lang.org/stable/std/primitive.u32.html#method.strict_sub) +- [`{integer}::strict_mul`](https://doc.rust-lang.org/stable/std/primitive.u32.html#method.strict_mul) +- [`{integer}::strict_div`](https://doc.rust-lang.org/stable/std/primitive.i32.html#method.strict_div) +- [`{integer}::strict_div_euclid`](https://doc.rust-lang.org/stable/std/primitive.i32.html#method.strict_div_euclid) +- [`{integer}::strict_rem`](https://doc.rust-lang.org/stable/std/primitive.i32.html#method.strict_rem) +- [`{integer}::strict_rem_euclid`](https://doc.rust-lang.org/stable/std/primitive.i32.html#method.strict_rem_euclid) +- [`{integer}::strict_neg`](https://doc.rust-lang.org/stable/std/primitive.u32.html#method.strict_neg) +- [`{integer}::strict_shl`](https://doc.rust-lang.org/stable/std/primitive.u32.html#method.strict_shl) +- [`{integer}::strict_shr`](https://doc.rust-lang.org/stable/std/primitive.u32.html#method.strict_shr) +- [`{integer}::strict_pow`](https://doc.rust-lang.org/stable/std/primitive.u32.html#method.strict_pow) +- [`i{N}::strict_add_unsigned`](https://doc.rust-lang.org/stable/std/primitive.i32.html#method.strict_add_unsigned) +- [`i{N}::strict_sub_unsigned`](https://doc.rust-lang.org/stable/std/primitive.i32.html#method.strict_sub_unsigned) +- [`i{N}::strict_abs`](https://doc.rust-lang.org/stable/std/primitive.i32.html#method.strict_abs) +- [`u{N}::strict_add_signed`](https://doc.rust-lang.org/stable/std/primitive.u32.html#method.strict_add_signed) +- [`u{N}::strict_sub_signed`](https://doc.rust-lang.org/stable/std/primitive.u32.html#method.strict_sub_signed) +- [`PanicHookInfo::payload_as_str`](https://doc.rust-lang.org/stable/std/panic/struct.PanicHookInfo.html#method.payload_as_str) +- [`core::iter::chain`](https://doc.rust-lang.org/stable/core/iter/fn.chain.html) +- [`u{N}::checked_signed_diff`](https://doc.rust-lang.org/stable/std/primitive.u16.html#method.checked_signed_diff) +- [`core::array::repeat`](https://doc.rust-lang.org/stable/core/array/fn.repeat.html) +- [`PathBuf::add_extension`](https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.add_extension) +- [`PathBuf::with_added_extension`](https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.with_added_extension) +- [`Duration::from_mins`](https://doc.rust-lang.org/stable/std/time/struct.Duration.html#method.from_mins) +- [`Duration::from_hours`](https://doc.rust-lang.org/stable/std/time/struct.Duration.html#method.from_hours) +- [`impl PartialEq for PathBuf`](https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#impl-PartialEq%3Cstr%3E-for-PathBuf) +- [`impl PartialEq for PathBuf`](https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#impl-PartialEq%3CString%3E-for-PathBuf) +- [`impl PartialEq for Path`](https://doc.rust-lang.org/stable/std/path/struct.Path.html#impl-PartialEq%3Cstr%3E-for-Path) +- [`impl PartialEq for Path`](https://doc.rust-lang.org/stable/std/path/struct.Path.html#impl-PartialEq%3CString%3E-for-Path) +- [`impl PartialEq for String`](https://doc.rust-lang.org/stable/std/string/struct.String.html#impl-PartialEq%3CPathBuf%3E-for-String) +- [`impl PartialEq for String`](https://doc.rust-lang.org/stable/std/string/struct.String.html#impl-PartialEq%3CPath%3E-for-String) +- [`impl PartialEq for str`](https://doc.rust-lang.org/stable/std/primitive.str.html#impl-PartialEq%3CPathBuf%3E-for-str) +- [`impl PartialEq for str`](https://doc.rust-lang.org/stable/std/primitive.str.html#impl-PartialEq%3CPath%3E-for-str) +- [`Ipv4Addr::from_octets`](https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.from_octets) +- [`Ipv6Addr::from_octets`](https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.from_octets) +- [`Ipv6Addr::from_segments`](https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.from_segments) +- [`impl Default for Pin> where Box: Default, T: ?Sized`](https://doc.rust-lang.org/stable/std/default/trait.Default.html#impl-Default-for-Pin%3CBox%3CT%3E%3E) +- [`impl Default for Pin> where Rc: Default, T: ?Sized`](https://doc.rust-lang.org/stable/std/default/trait.Default.html#impl-Default-for-Pin%3CRc%3CT%3E%3E) +- [`impl Default for Pin> where Arc: Default, T: ?Sized`](https://doc.rust-lang.org/stable/std/default/trait.Default.html#impl-Default-for-Pin%3CArc%3CT%3E%3E) +- [`Cell::as_array_of_cells`](https://doc.rust-lang.org/stable/std/cell/struct.Cell.html#method.as_array_of_cells) +- [`u{N}::carrying_add`](https://doc.rust-lang.org/stable/std/primitive.u64.html#method.carrying_add) +- [`u{N}::borrowing_sub`](https://doc.rust-lang.org/stable/std/primitive.u64.html#method.borrowing_sub) +- [`u{N}::carrying_mul`](https://doc.rust-lang.org/stable/std/primitive.u64.html#method.carrying_mul) +- [`u{N}::carrying_mul_add`](https://doc.rust-lang.org/stable/std/primitive.u64.html#method.carrying_mul_add) +- [`BTreeMap::extract_if`](https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html#method.extract_if) +- [`BTreeSet::extract_if`](https://doc.rust-lang.org/stable/std/collections/struct.BTreeSet.html#method.extract_if) +- [`impl Debug for windows::ffi::EncodeWide<'_>`](https://doc.rust-lang.org/stable/std/os/windows/ffi/struct.EncodeWide.html#impl-Debug-for-EncodeWide%3C'_%3E) +- [`str::ceil_char_boundary`](https://doc.rust-lang.org/stable/std/primitive.str.html#method.ceil_char_boundary) +- [`str::floor_char_boundary`](https://doc.rust-lang.org/stable/std/primitive.str.html#method.floor_char_boundary) +- [`impl Sum for Saturating`](https://doc.rust-lang.org/stable/std/num/struct.Saturating.html#impl-Sum-for-Saturating%3Cu32%3E) +- [`impl Sum<&Self> for Saturating`](https://doc.rust-lang.org/stable/std/num/struct.Saturating.html#impl-Sum%3C%26Saturating%3Cu32%3E%3E-for-Saturating%3Cu32%3E) +- [`impl Product for Saturating`](https://doc.rust-lang.org/stable/std/num/struct.Saturating.html#impl-Product-for-Saturating%3Cu32%3E) +- [`impl Product<&Self> for Saturating`](https://doc.rust-lang.org/stable/std/num/struct.Saturating.html#impl-Product%3C%26Saturating%3Cu32%3E%3E-for-Saturating%3Cu32%3E) + +These previously stable APIs are now stable in const contexts: + +- [`<[T; N]>::each_ref`](https://doc.rust-lang.org/stable/std/primitive.array.html#method.each_ref) +- [`<[T; N]>::each_mut`](https://doc.rust-lang.org/stable/std/primitive.array.html#method.each_mut) +- [`OsString::new`](https://doc.rust-lang.org/stable/std/ffi/struct.OsString.html#method.new) +- [`PathBuf::new`](https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.new) +- [`TypeId::of`](https://doc.rust-lang.org/stable/std/any/struct.TypeId.html#method.of) +- [`ptr::with_exposed_provenance`](https://doc.rust-lang.org/stable/std/ptr/fn.with_exposed_provenance.html) +- [`ptr::with_exposed_provenance_mut`](https://doc.rust-lang.org/stable/std/ptr/fn.with_exposed_provenance_mut.html) + + + +Cargo +----- + +- 🎉 Stabilize `build.build-dir`. + This config sets the directory where intermediate build artifacts are stored. + These artifacts are produced by Cargo and rustc during the build process. + End users usually won't need to interact with them, and the layout inside + `build-dir` is an implementation detail that may change without notice. + ([config doc](https://doc.rust-lang.org/stable/cargo/reference/config.html#buildbuild-dir)) + ([build cache doc](https://doc.rust-lang.org/stable/cargo/reference/build-cache.html)) + [#15833](https://github.com/rust-lang/cargo/pull/15833) + [#15840](https://github.com/rust-lang/cargo/pull/15840) +- The `--target` flag and the `build.target` configuration can now take literal + `"host-tuple"` string, which will internally be substituted by the host + machine's target triple. + [#15838](https://github.com/rust-lang/cargo/pull/15838) + [#16003](https://github.com/rust-lang/cargo/pull/16003) + [#16032](https://github.com/rust-lang/cargo/pull/16032) + + + +Rustdoc +----- +- [In search results, rank doc aliases lower than non-alias items with the same name](https://github.com/rust-lang/rust/pull/145100) +- [Raw pointers now work in type-based search like references](https://github.com/rust-lang/rust/pull/145731). This means you can now search for things like `*const u8 ->`, and additionally functions that take or return raw pointers will now display their signature properly in search results. + + + +Compatibility Notes +------------------- + +- [Always require coroutine captures to be drop-live](https://github.com/rust-lang/rust/pull/144156) +- [Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var](https://github.com/rust-lang/rust/pull/131477). This should fix linking issues with `rustc` running inside Xcode. Libraries in `/usr/local/lib` may no longer be linked automatically, if you develop or use a crate that relies on this, you should explicitly set `cargo::rustc-link-search=/usr/local/lib` in a `build.rs` script. +- [Relaxed bounds in associated type bound position like in `TraitRef` are now correctly forbidden](https://github.com/rust-lang/rust/pull/135331) +- [Add unstable `#[sanitize(xyz = "on|off")]` built-in attribute that shadows procedural macros with the same name](https://github.com/rust-lang/rust/pull/142681) +- [Fix the drop checker being more permissive for bindings declared with let-else](https://github.com/rust-lang/rust/pull/143028) +- [Be more strict when parsing attributes, erroring on many invalid attributes](https://github.com/rust-lang/rust/pull/144689) + - [Error on invalid `#[should_panic]` attributes](https://github.com/rust-lang/rust/pull/143808) + - [Error on invalid `#[link]` attributes](https://github.com/rust-lang/rust/pull/143193) +- [Mark all deprecation lints in name resolution as deny-by-default and also report in dependencies](https://github.com/rust-lang/rust/pull/143929) +- The lint `semicolon_in_expressions_from_macros`, for `macro_rules!` macros in expression position that expand to end in a semicolon (`;`), is now deny-by-default. It was already warn-by-default, and a future compatibility warning (FCW) that warned even in dependencies. This lint will become a hard error in the future. +- [Trait impl modifiers (e.g., `unsafe`, `!`, `default`) in inherent impls are no longer syntactically valid](https://github.com/rust-lang/rust/pull/144386) +- [Start reporting future breakage for `ill_formed_attribute_input` in dependencies](https://github.com/rust-lang/rust/pull/144544) +- [Restrict the scope of temporaries created by the macros `pin!`, `format_args!`, `write!`, and `writeln!` in `if let` scrutinees in Rust Edition 2024.](https://github.com/rust-lang/rust/pull/145342) This applies [Rust Edition 2024's `if let` temporary scope rules](https://doc.rust-lang.org/edition-guide/rust-2024/temporary-if-let-scope.html) to these temporaries, which previously could live past the `if` expression regardless of Edition. +- [Invalid numeric literal suffixes in tuple indexing, tuple struct indexing, and struct field name positions are now correctly rejected](https://github.com/rust-lang/rust/pull/145463) +- [Closures marked with the keyword `static` are now syntactically invalid](https://github.com/rust-lang/rust/pull/145604) +- [Shebangs inside `--cfg` and `--check-cfg` arguments are no longer allowed](https://github.com/rust-lang/rust/pull/146211) +- [Add future incompatibility lint for temporary lifetime shortening in Rust 1.92](https://github.com/rust-lang/rust/pull/147056) + +Cargo compatibility notes: + +- `cargo publish` no longer keeps `.crate` tarballs as final build artifacts + when `build.build-dir` is set. These tarballs were previously included due to + an oversight and are now treated as intermediate artifacts. + To get `.crate` tarballs as final artifacts, use `cargo package`. + In a future version, this change will apply regardless of `build.build-dir`. + [#15910](https://github.com/rust-lang/cargo/pull/15910) +- Adjust Cargo messages to match rustc diagnostic style. + This changes some of the terminal colors used by Cargo messages. + [#15928](https://github.com/rust-lang/cargo/pull/15928) +- Tools and projects relying on the + [internal details of Cargo's `build-dir`](https://doc.rust-lang.org/cargo/reference/build-cache.html) + may not work for users changing their `build-dir` layout. + For those doing so, we'd recommend proactively testing these cases + particularly as we are considering changing the default location of the `build-dir` in the future + ([cargo#16147](https://github.com/rust-lang/cargo/issues/16147)). + If you can't migrate off of Cargo's internal details, + we'd like to learn more about your use case as we prepare to change the layout of the `build-dir` + ([cargo#15010](https://github.com/rust-lang/cargo/issues/15010)). + + + +Internal Changes +---------------- + +These changes do not affect any public interfaces of Rust, but they represent +significant improvements to the performance or internals of rustc and related +tools. + +- [Update to LLVM 21](https://github.com/rust-lang/rust/pull/143684) + + +Version 1.90.0 (2025-09-18) +=========================== + + + +Language +-------- +- [Split up the `unknown_or_malformed_diagnostic_attributes` lint](https://github.com/rust-lang/rust/pull/140717). This lint has been split up into four finer-grained lints, with `unknown_or_malformed_diagnostic_attributes` now being the lint group that contains these lints: + 1. `unknown_diagnostic_attributes`: unknown to the current compiler + 2. `misplaced_diagnostic_attributes`: placed on the wrong item + 3. `malformed_diagnostic_attributes`: malformed attribute syntax or options + 4. `malformed_diagnostic_format_literals`: malformed format string literal +- [Allow constants whose final value has references to mutable/external memory, but reject such constants as patterns](https://github.com/rust-lang/rust/pull/140942) +- [Allow volatile access to non-Rust memory, including address 0](https://github.com/rust-lang/rust/pull/141260) + + + + +Compiler +-------- +- [Use `lld` by default on `x86_64-unknown-linux-gnu`](https://github.com/rust-lang/rust/pull/140525). +- [Tier 3 `musl` targets now link dynamically by default](https://github.com/rust-lang/rust/pull/144410). Affected targets: + - `mips64-unknown-linux-muslabi64` + - `powerpc64-unknown-linux-musl` + - `powerpc-unknown-linux-musl` + - `powerpc-unknown-linux-muslspe` + - `riscv32gc-unknown-linux-musl` + - `s390x-unknown-linux-musl` + - `thumbv7neon-unknown-linux-musleabihf` + + + + +Platform Support +---------------- +- [Demote `x86_64-apple-darwin` to Tier 2 with host tools](https://github.com/rust-lang/rust/pull/145252) + + +Refer to Rust's [platform support page][platform-support-doc] +for more information on Rust's tiered platform support. + +[platform-support-doc]: https://doc.rust-lang.org/rustc/platform-support.html + + + +Libraries +--------- +- [Stabilize `u*::{checked,overflowing,saturating,wrapping}_sub_signed`](https://github.com/rust-lang/rust/issues/126043) +- [Allow comparisons between `CStr`, `CString`, and `Cow`](https://github.com/rust-lang/rust/pull/137268) +- [Remove some unsized tuple impls since unsized tuples can't be constructed](https://github.com/rust-lang/rust/pull/138340) +- [Set `MSG_NOSIGNAL` for `UnixStream`](https://github.com/rust-lang/rust/pull/140005) +- [`proc_macro::Ident::new` now supports `$crate`.](https://github.com/rust-lang/rust/pull/141996) +- [Guarantee the pointer returned from `Thread::into_raw` has at least 8 bytes of alignment](https://github.com/rust-lang/rust/pull/143859) + + + + +Stabilized APIs +--------------- + +- [`u{n}::checked_sub_signed`](https://doc.rust-lang.org/stable/std/primitive.usize.html#method.checked_sub_signed) +- [`u{n}::overflowing_sub_signed`](https://doc.rust-lang.org/stable/std/primitive.usize.html#method.overflowing_sub_signed) +- [`u{n}::saturating_sub_signed`](https://doc.rust-lang.org/stable/std/primitive.usize.html#method.saturating_sub_signed) +- [`u{n}::wrapping_sub_signed`](https://doc.rust-lang.org/stable/std/primitive.usize.html#method.wrapping_sub_signed) +- [`impl Copy for IntErrorKind`](https://doc.rust-lang.org/stable/std/num/enum.IntErrorKind.html#impl-Copy-for-IntErrorKind) +- [`impl Hash for IntErrorKind`](https://doc.rust-lang.org/stable/std/num/enum.IntErrorKind.html#impl-Hash-for-IntErrorKind) +- [`impl PartialEq<&CStr> for CStr`](https://doc.rust-lang.org/stable/std/ffi/struct.CStr.html#impl-PartialEq%3C%26CStr%3E-for-CStr) +- [`impl PartialEq for CStr`](https://doc.rust-lang.org/stable/std/ffi/struct.CStr.html#impl-PartialEq%3CCString%3E-for-CStr) +- [`impl PartialEq> for CStr`](https://doc.rust-lang.org/stable/std/ffi/struct.CStr.html#impl-PartialEq%3CCow%3C'_,+CStr%3E%3E-for-CStr) +- [`impl PartialEq<&CStr> for CString`](https://doc.rust-lang.org/stable/std/ffi/struct.CString.html#impl-PartialEq%3C%26CStr%3E-for-CString) +- [`impl PartialEq for CString`](https://doc.rust-lang.org/stable/std/ffi/struct.CString.html#impl-PartialEq%3CCStr%3E-for-CString) +- [`impl PartialEq> for CString`](https://doc.rust-lang.org/stable/std/ffi/struct.CString.html#impl-PartialEq%3CCow%3C'_,+CStr%3E%3E-for-CString) +- [`impl PartialEq<&CStr> for Cow`](https://doc.rust-lang.org/stable/std/borrow/enum.Cow.html#impl-PartialEq%3C%26CStr%3E-for-Cow%3C'_,+CStr%3E) +- [`impl PartialEq for Cow`](https://doc.rust-lang.org/stable/std/borrow/enum.Cow.html#impl-PartialEq%3CCStr%3E-for-Cow%3C'_,+CStr%3E) +- [`impl PartialEq for Cow`](https://doc.rust-lang.org/stable/std/borrow/enum.Cow.html#impl-PartialEq%3CCString%3E-for-Cow%3C'_,+CStr%3E) + + +These previously stable APIs are now stable in const contexts: + +- [`<[T]>::reverse`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.reverse) +- [`f32::floor`](https://doc.rust-lang.org/stable/std/primitive.f32.html#method.floor) +- [`f32::ceil`](https://doc.rust-lang.org/stable/std/primitive.f32.html#method.ceil) +- [`f32::trunc`](https://doc.rust-lang.org/stable/std/primitive.f32.html#method.trunc) +- [`f32::fract`](https://doc.rust-lang.org/stable/std/primitive.f32.html#method.fract) +- [`f32::round`](https://doc.rust-lang.org/stable/std/primitive.f32.html#method.round) +- [`f32::round_ties_even`](https://doc.rust-lang.org/stable/std/primitive.f32.html#method.round_ties_even) +- [`f64::floor`](https://doc.rust-lang.org/stable/std/primitive.f64.html#method.floor) +- [`f64::ceil`](https://doc.rust-lang.org/stable/std/primitive.f64.html#method.ceil) +- [`f64::trunc`](https://doc.rust-lang.org/stable/std/primitive.f64.html#method.trunc) +- [`f64::fract`](https://doc.rust-lang.org/stable/std/primitive.f64.html#method.fract) +- [`f64::round`](https://doc.rust-lang.org/stable/std/primitive.f64.html#method.round) +- [`f64::round_ties_even`](https://doc.rust-lang.org/stable/std/primitive.f64.html#method.round_ties_even) + + + + +Cargo +----- +- [Add `http.proxy-cainfo` config for proxy certs](https://github.com/rust-lang/cargo/pull/15374/) +- [Use `gix` for `cargo package`](https://github.com/rust-lang/cargo/pull/15534/) +- [feat(publish): Stabilize multi-package publishing](https://github.com/rust-lang/cargo/pull/15636/) + + + +Rustdoc +----- +- [Add ways to collapse all impl blocks](https://github.com/rust-lang/rust/pull/141663). Previously the "Summary" button and "-" keyboard shortcut would never collapse `impl` blocks, now they do when shift is held +- [Display unsafe attributes with `unsafe()` wrappers](https://github.com/rust-lang/rust/pull/143662) + + + + +Compatibility Notes +------------------- +- [Use `lld` by default on `x86_64-unknown-linux-gnu`](https://github.com/rust-lang/rust/pull/140525). + See also . +- [Make `core::iter::Fuse`'s `Default` impl construct `I::default()` internally as promised in the docs instead of always being empty](https://github.com/rust-lang/rust/pull/140985) +- [Set `MSG_NOSIGNAL` for `UnixStream`](https://github.com/rust-lang/rust/pull/140005) + This may change program behavior but results in the same behavior as other primitives (e.g., stdout, network sockets). + Programs relying on signals to terminate them should update handling of sockets to handle errors on write by exiting. +- [On Unix `std::env::home_dir` will use the fallback if the `HOME` environment variable is empty](https://github.com/rust-lang/rust/pull/141840) +- We now [reject unsupported `extern "{abi}"`s consistently in all positions](https://github.com/rust-lang/rust/pull/142134). This primarily affects the use of implementing traits on an `extern "{abi}"` function pointer, like `extern "stdcall" fn()`, on a platform that doesn't support that, like aarch64-unknown-linux-gnu. Direct usage of these unsupported ABI strings by declaring or defining functions was already rejected, so this is only a change for consistency. +- [const-eval: error when initializing a static writes to that static](https://github.com/rust-lang/rust/pull/143084) +- [Check that the `proc_macro_derive` macro has correct arguments when applied to the crate root](https://github.com/rust-lang/rust/pull/143607) + + Version 1.89.0 (2025-08-07) ========================== diff --git a/src/ci/channel b/src/ci/channel index 65b2df87f7df3..2bf5ad0447d33 100644 --- a/src/ci/channel +++ b/src/ci/channel @@ -1 +1 @@ -beta +stable