From 24be917c03eb8e6702415266a07cb35aa8946be7 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 27 Oct 2025 14:47:37 -0700 Subject: [PATCH 1/7] rustdoc: add mergeable CCI docs to the unstable feature book --- src/doc/rustdoc/src/unstable-features.md | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 04d3c0cd630f..73986199661f 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -197,6 +197,37 @@ themselves marked as unstable. To use any of these options, pass `-Z unstable-op the flag in question to Rustdoc on the command-line. To do this from Cargo, you can either use the `RUSTDOCFLAGS` environment variable or the `cargo rustdoc` command. +### `--merge`, `--parts-out-dir`, and `--include-parts-dir` + +These options control how rustdoc handles files that combine data from multiple crates. + +By default, they act like `--merge=shared` is set, and `--parts-out-dir` and `--include-parts-dir` +are turned off. The `--merge=shared` mode causes rustdoc to load the existing data in the out-dir, +combine the new crate data into it, and write the result. This is very easy to use in scripts that +manually invoke rustdoc, but it's also slow, because it performs O(crates) work on +every crate, meaning it performs O(crates2) work. + +```console +$ rustdoc crate1.rs --out-dir=doc +$ cat doc/search.index/crateNames/* +rd_("fcrate1") +$ rustdoc crate2.rs --out-dir=doc +$ cat doc/search.index/crateNames/* +rd_("fcrate1fcrate2") +``` + +To delay shared-data merging until the end of a build, so that you only have to perform O(crates) +work, use `--merge=none` on every crate except the last one, which will use `--merge=finalize`. + +```console +$ rustdoc +nightly crate1.rs --merge=none --parts-out-dir=crate1.d -Zunstable-options +$ cat doc/search.index/crateNames/* +cat: 'doc/search.index/crateNames/*': No such file or directory +$ rustdoc +nightly crate2.rs --merge=finalize --include-parts-dir=crate1.d -Zunstable-options +$ cat doc/search.index/crateNames/* +rd_("fcrate1fcrate2") +``` + ### `--document-hidden-items`: Show items that are `#[doc(hidden)]` From 0786642d61f660e7872a4e2ce8d19b74efa13105 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 28 Oct 2025 15:51:03 -0700 Subject: [PATCH 2/7] rustdoc: clean up and further test mergeable CCI This fixes a limitation that prevented me from adding it to Cargo. The rust compiler-docs bundle is built by running multiple `cargo` commands in succession, and I want to support that, so I'm stuck putting the doc parts all in one directory, so that subsequent `cargo` runs can pick up the previous runs' data. It's less clean, but it should still be usable in hermetic build environments if you give every crate its own directory (which you needed to do before, oddly enough). --- src/librustdoc/config.rs | 17 ++++--- src/librustdoc/html/render/write_shared.rs | 32 +++++++++---- .../run-make/rustdoc-merge-directory/dep1.rs | 4 ++ .../run-make/rustdoc-merge-directory/dep2.rs | 4 ++ .../rustdoc-merge-directory/dep_missing.rs | 4 ++ .../run-make/rustdoc-merge-directory/rmake.rs | 46 +++++++++++++++++++ .../rustdoc-merge-no-input-finalize/rmake.rs | 2 +- 7 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 tests/run-make/rustdoc-merge-directory/dep1.rs create mode 100644 tests/run-make/rustdoc-merge-directory/dep2.rs create mode 100644 tests/run-make/rustdoc-merge-directory/dep_missing.rs create mode 100644 tests/run-make/rustdoc-merge-directory/rmake.rs diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index cf0858810f55..521f3e638a3a 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -975,15 +975,16 @@ fn parse_extern_html_roots( Ok(externs) } -/// Path directly to crate-info file. +/// Path directly to crate-info directory. /// -/// For example, `/home/user/project/target/doc.parts//crate-info`. +/// For example, `/home/user/project/target/doc.parts`. +/// Each crate has its info stored in a file called `CRATENAME.json`. #[derive(Clone, Debug)] pub(crate) struct PathToParts(pub(crate) PathBuf); impl PathToParts { fn from_flag(path: String) -> Result { - let mut path = PathBuf::from(path); + let path = PathBuf::from(path); // check here is for diagnostics if path.exists() && !path.is_dir() { Err(format!( @@ -992,20 +993,22 @@ impl PathToParts { )) } else { // if it doesn't exist, we'll create it. worry about that in write_shared - path.push("crate-info"); Ok(PathToParts(path)) } } } -/// Reports error if --include-parts-dir / crate-info is not a file +/// Reports error if --include-parts-dir is not a directory fn parse_include_parts_dir(m: &getopts::Matches) -> Result, String> { let mut ret = Vec::new(); for p in m.opt_strs("include-parts-dir") { let p = PathToParts::from_flag(p)?; // this is just for diagnostic - if !p.0.is_file() { - return Err(format!("--include-parts-dir expected {} to be a file", p.0.display())); + if !p.0.is_dir() { + return Err(format!( + "--include-parts-dir expected {} to be a directory", + p.0.display() + )); } ret.push(p); } diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 3a1db805d01c..69282551f041 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -14,7 +14,7 @@ //! or contains "invocation-specific". use std::cell::RefCell; -use std::ffi::OsString; +use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io::{self, Write as _}; use std::iter::once; @@ -83,9 +83,11 @@ pub(crate) fn write_shared( }; if let Some(parts_out_dir) = &opt.parts_out_dir { - create_parents(&parts_out_dir.0)?; + let mut parts_out_file = parts_out_dir.0.clone(); + parts_out_file.push(&format!("{crate_name}.json")); + create_parents(&parts_out_file)?; try_err!( - fs::write(&parts_out_dir.0, serde_json::to_string(&info).unwrap()), + fs::write(&parts_out_file, serde_json::to_string(&info).unwrap()), &parts_out_dir.0 ); } @@ -237,13 +239,25 @@ impl CrateInfo { pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result, Error> { parts_paths .iter() - .map(|parts_path| { - let path = &parts_path.0; - let parts = try_err!(fs::read(path), &path); - let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &path); - Ok::<_, Error>(parts) + .fold(Ok(Vec::new()), |acc, parts_path| { + let mut acc = acc?; + let dir = &parts_path.0; + acc.append(&mut try_err!(std::fs::read_dir(dir), dir.as_path()) + .filter_map(|file| { + let to_crate_info = |file: Result| -> Result, Error> { + let file = try_err!(file, dir.as_path()); + if file.path().extension() != Some(OsStr::new("json")) { + return Ok(None); + } + let parts = try_err!(fs::read(file.path()), file.path()); + let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), file.path()); + Ok(Some(parts)) + }; + to_crate_info(file).transpose() + }) + .collect::, Error>>()?); + Ok(acc) }) - .collect::, Error>>() } } diff --git a/tests/run-make/rustdoc-merge-directory/dep1.rs b/tests/run-make/rustdoc-merge-directory/dep1.rs new file mode 100644 index 000000000000..5a1238adec0e --- /dev/null +++ b/tests/run-make/rustdoc-merge-directory/dep1.rs @@ -0,0 +1,4 @@ +//@ hasraw crates.js 'dep1' +//@ hasraw search.index/name/*.js 'Dep1' +//@ has dep1/index.html +pub struct Dep1; diff --git a/tests/run-make/rustdoc-merge-directory/dep2.rs b/tests/run-make/rustdoc-merge-directory/dep2.rs new file mode 100644 index 000000000000..238ff2e4f9b7 --- /dev/null +++ b/tests/run-make/rustdoc-merge-directory/dep2.rs @@ -0,0 +1,4 @@ +//@ hasraw crates.js 'dep1' +//@ hasraw search.index/name/*.js 'Dep1' +//@ has dep2/index.html +pub struct Dep2; diff --git a/tests/run-make/rustdoc-merge-directory/dep_missing.rs b/tests/run-make/rustdoc-merge-directory/dep_missing.rs new file mode 100644 index 000000000000..74236aef47ea --- /dev/null +++ b/tests/run-make/rustdoc-merge-directory/dep_missing.rs @@ -0,0 +1,4 @@ +//@ !hasraw crates.js 'dep_missing' +//@ !hasraw search.index/name/*.js 'DepMissing' +//@ has dep_missing/index.html +pub struct DepMissing; diff --git a/tests/run-make/rustdoc-merge-directory/rmake.rs b/tests/run-make/rustdoc-merge-directory/rmake.rs new file mode 100644 index 000000000000..e4695ddad0b4 --- /dev/null +++ b/tests/run-make/rustdoc-merge-directory/rmake.rs @@ -0,0 +1,46 @@ +// Running --merge=finalize without an input crate root should not trigger ICE. +// Issue: https://github.com/rust-lang/rust/issues/146646 + +//@ needs-target-std + +use run_make_support::{htmldocck, path, rustdoc}; + +fn main() { + let out_dir = path("out"); + let merged_dir = path("merged"); + let parts_out_dir = path("parts"); + + rustdoc() + .input("dep1.rs") + .out_dir(&out_dir) + .arg("-Zunstable-options") + .arg(format!("--parts-out-dir={}", parts_out_dir.display())) + .arg("--merge=none") + .run(); + assert!(parts_out_dir.join("dep1.json").exists()); + + rustdoc() + .input("dep2.rs") + .out_dir(&out_dir) + .arg("-Zunstable-options") + .arg(format!("--parts-out-dir={}", parts_out_dir.display())) + .arg("--merge=none") + .run(); + assert!(parts_out_dir.join("dep2.json").exists()); + + // dep_missing is different, because --parts-out-dir is not supplied + rustdoc().input("dep_missing.rs").out_dir(&out_dir).run(); + assert!(parts_out_dir.join("dep2.json").exists()); + + let output = rustdoc() + .arg("-Zunstable-options") + .out_dir(&out_dir) + .arg(format!("--include-parts-dir={}", parts_out_dir.display())) + .arg("--merge=finalize") + .run(); + output.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug."); + + htmldocck().arg(&out_dir).arg("dep1.rs").run(); + htmldocck().arg(&out_dir).arg("dep2.rs").run(); + htmldocck().arg(out_dir).arg("dep_missing.rs").run(); +} diff --git a/tests/run-make/rustdoc-merge-no-input-finalize/rmake.rs b/tests/run-make/rustdoc-merge-no-input-finalize/rmake.rs index 0b1e1948d5fc..dadfe61235ac 100644 --- a/tests/run-make/rustdoc-merge-no-input-finalize/rmake.rs +++ b/tests/run-make/rustdoc-merge-no-input-finalize/rmake.rs @@ -16,7 +16,7 @@ fn main() { .arg(format!("--parts-out-dir={}", parts_out_dir.display())) .arg("--merge=none") .run(); - assert!(parts_out_dir.join("crate-info").exists()); + assert!(parts_out_dir.join("sierra.json").exists()); let output = rustdoc() .arg("-Zunstable-options") From d3e4dd29a8c617e1c7bf74cf09452f76bd5f90eb Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Mon, 24 Nov 2025 11:20:03 +0900 Subject: [PATCH 3/7] Use `let...else` consistently in user-facing diagnostics --- compiler/rustc_hir_analysis/src/check/region.rs | 2 +- compiler/rustc_mir_build/messages.ftl | 2 +- compiler/rustc_parse/src/parser/stmt.rs | 2 +- tests/ui/empty/empty-never-array.stderr | 2 +- tests/ui/error-codes/E0005.stderr | 2 +- .../ui/feature-gates/feature-gate-exhaustive-patterns.stderr | 2 +- .../feature-gate-half-open-range-patterns-in-slices.stderr | 2 +- .../slice_pattern_syntax_problem1.stderr | 2 +- tests/ui/pattern/issue-106552.stderr | 2 +- .../pattern/usefulness/empty-types.exhaustive_patterns.stderr | 2 +- tests/ui/pattern/usefulness/empty-types.never_pats.stderr | 4 ++-- tests/ui/pattern/usefulness/empty-types.normal.stderr | 4 ++-- tests/ui/pattern/usefulness/issue-31561.stderr | 2 +- .../ui/pattern/usefulness/non-exhaustive-defined-here.stderr | 2 +- tests/ui/recursion/recursive-types-are-not-uninhabited.stderr | 2 +- tests/ui/rfcs/rfc-2008-non-exhaustive/omitted-patterns.stderr | 2 +- tests/ui/uninhabited/missing-if-let-or-let-else.rs | 4 ++-- tests/ui/uninhabited/missing-if-let-or-let-else.stderr | 4 ++-- .../uninhabited-irrefutable.exhaustive_patterns.stderr | 2 +- tests/ui/uninhabited/uninhabited-irrefutable.normal.stderr | 2 +- tests/ui/uninhabited/uninhabited-irrefutable.rs | 2 +- 21 files changed, 25 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index 4e605ef62519..0e8cdc266f89 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -99,7 +99,7 @@ fn resolve_block<'tcx>( for (i, statement) in blk.stmts.iter().enumerate() { match statement.kind { hir::StmtKind::Let(LetStmt { els: Some(els), .. }) => { - // Let-else has a special lexical structure for variables. + // let-else has a special lexical structure for variables. // First we take a checkpoint of the current scope context here. let mut prev_cx = visitor.cx; diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl index 4a741169443d..f65b99d5f99f 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -334,7 +334,7 @@ mir_build_suggest_if_let = you might want to use `if let` to ignore the {$count *[other] variants that aren't } matched -mir_build_suggest_let_else = you might want to use `let else` to handle the {$count -> +mir_build_suggest_let_else = you might want to use `let...else` to handle the {$count -> [one] variant that isn't *[other] variants that aren't } matched diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 3fe8971f3d6c..26393bf61a32 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -867,7 +867,7 @@ impl<'a> Parser<'a> { if let_else || !if_let { err.span_suggestion_verbose( block_span.shrink_to_lo(), - format!("{alternatively}you might have meant to use `let else`"), + format!("{alternatively}you might have meant to use `let...else`"), "else ".to_string(), if let_else { Applicability::MachineApplicable diff --git a/tests/ui/empty/empty-never-array.stderr b/tests/ui/empty/empty-never-array.stderr index ee04ff162a4c..cd8a80e3d49d 100644 --- a/tests/ui/empty/empty-never-array.stderr +++ b/tests/ui/empty/empty-never-array.stderr @@ -14,7 +14,7 @@ LL | enum Helper { LL | T(T, [!; 0]), | - not covered = note: the matched value is of type `Helper` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Helper::U(u) = Helper::T(t, []) else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/error-codes/E0005.stderr b/tests/ui/error-codes/E0005.stderr index c643ee07a373..004812cad9f1 100644 --- a/tests/ui/error-codes/E0005.stderr +++ b/tests/ui/error-codes/E0005.stderr @@ -7,7 +7,7 @@ LL | let Some(y) = x; = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `Option` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Some(y) = x else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/feature-gates/feature-gate-exhaustive-patterns.stderr b/tests/ui/feature-gates/feature-gate-exhaustive-patterns.stderr index b596da8463f2..614f382d6732 100644 --- a/tests/ui/feature-gates/feature-gate-exhaustive-patterns.stderr +++ b/tests/ui/feature-gates/feature-gate-exhaustive-patterns.stderr @@ -7,7 +7,7 @@ LL | let Ok(_x) = &foo(); = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `&Result` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Ok(_x) = &foo() else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/half-open-range-patterns/feature-gate-half-open-range-patterns-in-slices.stderr b/tests/ui/half-open-range-patterns/feature-gate-half-open-range-patterns-in-slices.stderr index 65903dbe12e5..9e13cf510e83 100644 --- a/tests/ui/half-open-range-patterns/feature-gate-half-open-range-patterns-in-slices.stderr +++ b/tests/ui/half-open-range-patterns/feature-gate-half-open-range-patterns-in-slices.stderr @@ -17,7 +17,7 @@ LL | let [a @ 3.., b @ ..3, c @ 4..6, ..] = xs; = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `[i32; 8]` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let [a @ 3.., b @ ..3, c @ 4..6, ..] = xs else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/half-open-range-patterns/slice_pattern_syntax_problem1.stderr b/tests/ui/half-open-range-patterns/slice_pattern_syntax_problem1.stderr index 17b65c1dae54..dec0fe1fe0d7 100644 --- a/tests/ui/half-open-range-patterns/slice_pattern_syntax_problem1.stderr +++ b/tests/ui/half-open-range-patterns/slice_pattern_syntax_problem1.stderr @@ -17,7 +17,7 @@ LL | let [a @ 3.., b @ ..3, c @ 4..6, ..] = xs; = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `[i32; 8]` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let [a @ 3.., b @ ..3, c @ 4..6, ..] = xs else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/pattern/issue-106552.stderr b/tests/ui/pattern/issue-106552.stderr index 6d9a989f182e..06f33ecf1066 100644 --- a/tests/ui/pattern/issue-106552.stderr +++ b/tests/ui/pattern/issue-106552.stderr @@ -25,7 +25,7 @@ LL | let x @ 5 = 6; = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `i32` -help: you might want to use `let else` to handle the variants that aren't matched +help: you might want to use `let...else` to handle the variants that aren't matched | LL | let x @ 5 = 6 else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/pattern/usefulness/empty-types.exhaustive_patterns.stderr b/tests/ui/pattern/usefulness/empty-types.exhaustive_patterns.stderr index d241f417553f..a234ad435c97 100644 --- a/tests/ui/pattern/usefulness/empty-types.exhaustive_patterns.stderr +++ b/tests/ui/pattern/usefulness/empty-types.exhaustive_patterns.stderr @@ -152,7 +152,7 @@ LL | let Ok(_x) = res_u32_never.as_ref(); = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `Result<&u32, &!>` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Ok(_x) = res_u32_never.as_ref() else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/pattern/usefulness/empty-types.never_pats.stderr b/tests/ui/pattern/usefulness/empty-types.never_pats.stderr index ea63d7ba1afd..3fff1a3805c8 100644 --- a/tests/ui/pattern/usefulness/empty-types.never_pats.stderr +++ b/tests/ui/pattern/usefulness/empty-types.never_pats.stderr @@ -106,7 +106,7 @@ LL | let Ok(_x) = res_u32_never.as_ref(); = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `Result<&u32, &!>` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Ok(_x) = res_u32_never.as_ref() else { todo!() }; | ++++++++++++++++ @@ -120,7 +120,7 @@ LL | let Ok(_x) = &res_u32_never; = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `&Result` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Ok(_x) = &res_u32_never else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/pattern/usefulness/empty-types.normal.stderr b/tests/ui/pattern/usefulness/empty-types.normal.stderr index a1a44e777442..28f9650557ef 100644 --- a/tests/ui/pattern/usefulness/empty-types.normal.stderr +++ b/tests/ui/pattern/usefulness/empty-types.normal.stderr @@ -97,7 +97,7 @@ LL | let Ok(_x) = res_u32_never.as_ref(); = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `Result<&u32, &!>` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Ok(_x) = res_u32_never.as_ref() else { todo!() }; | ++++++++++++++++ @@ -111,7 +111,7 @@ LL | let Ok(_x) = &res_u32_never; = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `&Result` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Ok(_x) = &res_u32_never else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/pattern/usefulness/issue-31561.stderr b/tests/ui/pattern/usefulness/issue-31561.stderr index 382b2337ffab..389c1126b984 100644 --- a/tests/ui/pattern/usefulness/issue-31561.stderr +++ b/tests/ui/pattern/usefulness/issue-31561.stderr @@ -17,7 +17,7 @@ LL | Bar, LL | Baz | --- not covered = note: the matched value is of type `Thing` -help: you might want to use `let else` to handle the variants that aren't matched +help: you might want to use `let...else` to handle the variants that aren't matched | LL | let Thing::Foo(y) = Thing::Foo(1) else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/pattern/usefulness/non-exhaustive-defined-here.stderr b/tests/ui/pattern/usefulness/non-exhaustive-defined-here.stderr index 48d7a636055d..d31510d66e0c 100644 --- a/tests/ui/pattern/usefulness/non-exhaustive-defined-here.stderr +++ b/tests/ui/pattern/usefulness/non-exhaustive-defined-here.stderr @@ -183,7 +183,7 @@ LL | enum Opt { LL | None, | ---- not covered = note: the matched value is of type `Opt` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Opt::Some(ref _x) = e else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/recursion/recursive-types-are-not-uninhabited.stderr b/tests/ui/recursion/recursive-types-are-not-uninhabited.stderr index 35d436a1413e..cd78c0f0bb45 100644 --- a/tests/ui/recursion/recursive-types-are-not-uninhabited.stderr +++ b/tests/ui/recursion/recursive-types-are-not-uninhabited.stderr @@ -7,7 +7,7 @@ LL | let Ok(x) = res; = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `Result>` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Ok(x) = res else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/rfcs/rfc-2008-non-exhaustive/omitted-patterns.stderr b/tests/ui/rfcs/rfc-2008-non-exhaustive/omitted-patterns.stderr index c2c9ac15ab22..f8bf6f8cb28b 100644 --- a/tests/ui/rfcs/rfc-2008-non-exhaustive/omitted-patterns.stderr +++ b/tests/ui/rfcs/rfc-2008-non-exhaustive/omitted-patterns.stderr @@ -138,7 +138,7 @@ LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `NonExhaustiveEnum` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/uninhabited/missing-if-let-or-let-else.rs b/tests/ui/uninhabited/missing-if-let-or-let-else.rs index 51fedb797562..c8cc672a82aa 100644 --- a/tests/ui/uninhabited/missing-if-let-or-let-else.rs +++ b/tests/ui/uninhabited/missing-if-let-or-let-else.rs @@ -6,14 +6,14 @@ fn a() { } fn b() { let Some(x) = foo() { //~ ERROR expected one of - //~^ HELP you might have meant to use `let else` + //~^ HELP you might have meant to use `let...else` return; } } fn c() { let Some(x) = foo() { //~ ERROR expected one of //~^ HELP you might have meant to use `if let` - //~| HELP alternatively, you might have meant to use `let else` + //~| HELP alternatively, you might have meant to use `let...else` // The parser check happens pre-macro-expansion, so we don't know for sure. println!("{x}"); } diff --git a/tests/ui/uninhabited/missing-if-let-or-let-else.stderr b/tests/ui/uninhabited/missing-if-let-or-let-else.stderr index 4b78a0fa16e8..f13147dd7fc5 100644 --- a/tests/ui/uninhabited/missing-if-let-or-let-else.stderr +++ b/tests/ui/uninhabited/missing-if-let-or-let-else.stderr @@ -15,7 +15,7 @@ error: expected one of `.`, `;`, `?`, `else`, or an operator, found `{` LL | let Some(x) = foo() { | ^ expected one of `.`, `;`, `?`, `else`, or an operator | -help: you might have meant to use `let else` +help: you might have meant to use `let...else` | LL | let Some(x) = foo() else { | ++++ @@ -30,7 +30,7 @@ help: you might have meant to use `if let` | LL | if let Some(x) = foo() { | ++ -help: alternatively, you might have meant to use `let else` +help: alternatively, you might have meant to use `let...else` | LL | let Some(x) = foo() else { | ++++ diff --git a/tests/ui/uninhabited/uninhabited-irrefutable.exhaustive_patterns.stderr b/tests/ui/uninhabited/uninhabited-irrefutable.exhaustive_patterns.stderr index 0e87f14aa14a..a4270c29ff35 100644 --- a/tests/ui/uninhabited/uninhabited-irrefutable.exhaustive_patterns.stderr +++ b/tests/ui/uninhabited/uninhabited-irrefutable.exhaustive_patterns.stderr @@ -16,7 +16,7 @@ LL | A(foo::SecretlyEmpty), | - not covered = note: pattern `Foo::A(_)` is currently uninhabited, but this variant contains private fields which may become inhabited in the future = note: the matched value is of type `Foo` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Foo::D(_y, _z) = x else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/uninhabited/uninhabited-irrefutable.normal.stderr b/tests/ui/uninhabited/uninhabited-irrefutable.normal.stderr index 0e87f14aa14a..a4270c29ff35 100644 --- a/tests/ui/uninhabited/uninhabited-irrefutable.normal.stderr +++ b/tests/ui/uninhabited/uninhabited-irrefutable.normal.stderr @@ -16,7 +16,7 @@ LL | A(foo::SecretlyEmpty), | - not covered = note: pattern `Foo::A(_)` is currently uninhabited, but this variant contains private fields which may become inhabited in the future = note: the matched value is of type `Foo` -help: you might want to use `let else` to handle the variant that isn't matched +help: you might want to use `let...else` to handle the variant that isn't matched | LL | let Foo::D(_y, _z) = x else { todo!() }; | ++++++++++++++++ diff --git a/tests/ui/uninhabited/uninhabited-irrefutable.rs b/tests/ui/uninhabited/uninhabited-irrefutable.rs index 3f7414e596bf..9f4731e6e71a 100644 --- a/tests/ui/uninhabited/uninhabited-irrefutable.rs +++ b/tests/ui/uninhabited/uninhabited-irrefutable.rs @@ -34,5 +34,5 @@ fn main() { //~| NOTE for more information //~| NOTE pattern `Foo::A(_)` is currently uninhabited //~| NOTE the matched value is of type `Foo` - //~| HELP you might want to use `let else` + //~| HELP you might want to use `let...else` } From 522e47fd602a1622570fe38d50fa889e8e5f0a86 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 24 Nov 2025 01:37:55 +0100 Subject: [PATCH 4/7] miri: use tikv-jemalloc-sys from sysroot This allows LTO to work when compiling jemalloc, which should yield a small performance boost, and makes miri's behaviour here match clippy and rustdoc. --- Cargo.lock | 1 - src/bootstrap/src/core/build_steps/tool.rs | 5 +++++ src/tools/miri/Cargo.toml | 8 +------- src/tools/miri/src/bin/miri.rs | 10 +++++++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04397408099e..4d8d5111583e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2486,7 +2486,6 @@ dependencies = [ "serde_json", "smallvec", "tempfile", - "tikv-jemalloc-sys", "ui_test", ] diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index 0d765018d77b..535e6a510ca6 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -1567,6 +1567,11 @@ tool_rustc_extended!(Miri { tool_name: "miri", stable: false, add_bins_to_sysroot: ["miri"], + add_features: |builder, target, features| { + if builder.config.jemalloc(target) { + features.push("jemalloc".to_string()); + } + }, // Always compile also tests when building miri. Otherwise feature unification can cause rebuilds between building and testing miri. cargo_args: &["--all-targets"], }); diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 611e549930a9..2235203e2d79 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -29,13 +29,6 @@ directories = "6" bitflags = "2.6" serde_json = { version = "1.0", optional = true } -# Copied from `compiler/rustc/Cargo.toml`. -# But only for some targets, it fails for others. Rustc configures this in its CI, but we can't -# easily use that since we support of-tree builds. -[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies.tikv-jemalloc-sys] -version = "0.6.1" -features = ['override_allocator_on_supported_platforms'] - [target.'cfg(unix)'.dependencies] libc = "0.2" # native-lib dependencies @@ -75,6 +68,7 @@ stack-cache = [] expensive-consistency-checks = ["stack-cache"] tracing = ["serde_json"] native-lib = ["dep:libffi", "dep:libloading", "dep:capstone", "dep:ipc-channel", "dep:nix", "dep:serde"] +jemalloc = [] [lints.rust.unexpected_cfgs] level = "warn" diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index d7c5cb68e4f0..9f1ff238359a 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -21,9 +21,13 @@ extern crate rustc_session; extern crate rustc_span; /// See docs in https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc/src/main.rs -/// and https://github.com/rust-lang/rust/pull/146627 for why we need this `use` statement. -#[cfg(any(target_os = "linux", target_os = "macos"))] -use tikv_jemalloc_sys as _; +/// and https://github.com/rust-lang/rust/pull/146627 for why we need this. +/// +/// FIXME(madsmtm): This is loaded from the sysroot that was built with the other `rustc` crates +/// above, instead of via Cargo as you'd normally do. This is currently needed for LTO due to +/// https://github.com/rust-lang/cc-rs/issues/1613. +#[cfg(feature = "jemalloc")] +extern crate tikv_jemalloc_sys as _; mod log; From c524ed710d1573f8f5c92281fc2efd1d29918c56 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 22 Nov 2025 12:45:31 +0100 Subject: [PATCH 5/7] Make more functions return `fmt::Result` and reduce number of `.unwrap()` calls --- src/librustdoc/html/format.rs | 6 +- src/librustdoc/html/length_limit.rs | 4 +- src/librustdoc/html/render/mod.rs | 85 ++++++++++++------------ src/librustdoc/html/render/print_item.rs | 62 +++++++++-------- 4 files changed, 78 insertions(+), 79 deletions(-) diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index dd91cec531f5..eee13ff2b0dc 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -1252,9 +1252,9 @@ struct Indent(usize); impl Display for Indent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (0..self.0).for_each(|_| { - f.write_char(' ').unwrap(); - }); + for _ in 0..self.0 { + f.write_char(' ')?; + } Ok(()) } } diff --git a/src/librustdoc/html/length_limit.rs b/src/librustdoc/html/length_limit.rs index fdb1ddc1f531..f786215a93be 100644 --- a/src/librustdoc/html/length_limit.rs +++ b/src/librustdoc/html/length_limit.rs @@ -87,7 +87,7 @@ impl HtmlWithLimit { pub(super) fn close_tag(&mut self) { if let Some(tag_name) = self.unclosed_tags.pop() { // Close the most recently opened tag. - write!(self.buf, "").unwrap() + write!(self.buf, "").expect("infallible string operation"); } // There are valid cases where `close_tag()` is called without // there being any tags to close. For example, this occurs when @@ -99,7 +99,7 @@ impl HtmlWithLimit { /// Write all queued tags and add them to the `unclosed_tags` list. fn flush_queue(&mut self) { for tag_name in self.queued_tags.drain(..) { - write!(self.buf, "<{tag_name}>").unwrap(); + write!(self.buf, "<{tag_name}>").expect("infallible string operation"); self.unclosed_tags.push(tag_name); } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 871ed53bd338..9c8e599104be 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -916,7 +916,7 @@ fn render_impls( impls: &[&Impl], containing_item: &clean::Item, toggle_open_by_default: bool, -) { +) -> fmt::Result { let mut rendered_impls = impls .iter() .map(|i| { @@ -942,7 +942,7 @@ fn render_impls( }) .collect::>(); rendered_impls.sort(); - w.write_str(&rendered_impls.join("")).unwrap(); + w.write_str(&rendered_impls.join("")) } /// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item. @@ -1037,7 +1037,7 @@ fn assoc_const( ) -> impl fmt::Display { let tcx = cx.tcx(); fmt::from_fn(move |w| { - render_attributes_in_code(w, it, &" ".repeat(indent), cx); + render_attributes_in_code(w, it, &" ".repeat(indent), cx)?; write!( w, "{indent}{vis}const {name}{generics}: {ty}", @@ -1145,10 +1145,10 @@ fn assoc_method( let (indent, indent_str, end_newline) = if parent == ItemType::Trait { header_len += 4; let indent_str = " "; - render_attributes_in_code(w, meth, indent_str, cx); + render_attributes_in_code(w, meth, indent_str, cx)?; (4, indent_str, Ending::NoNewline) } else { - render_attributes_in_code(w, meth, "", cx); + render_attributes_in_code(w, meth, "", cx)?; (0, "", Ending::Newline) }; write!( @@ -1365,10 +1365,10 @@ fn render_all_impls( concrete: &[&Impl], synthetic: &[&Impl], blanket_impl: &[&Impl], -) { +) -> fmt::Result { let impls = { let mut buf = String::new(); - render_impls(cx, &mut buf, concrete, containing_item, true); + render_impls(cx, &mut buf, concrete, containing_item, true)?; buf }; if !impls.is_empty() { @@ -1376,8 +1376,7 @@ fn render_all_impls( w, "{}
{impls}
", write_impl_section_heading("Trait Implementations", "trait-implementations") - ) - .unwrap(); + )?; } if !synthetic.is_empty() { @@ -1385,10 +1384,9 @@ fn render_all_impls( w, "{}
", write_impl_section_heading("Auto Trait Implementations", "synthetic-implementations",) - ) - .unwrap(); - render_impls(cx, &mut w, synthetic, containing_item, false); - w.write_str("
").unwrap(); + )?; + render_impls(cx, &mut w, synthetic, containing_item, false)?; + w.write_str("")?; } if !blanket_impl.is_empty() { @@ -1396,11 +1394,11 @@ fn render_all_impls( w, "{}
", write_impl_section_heading("Blanket Implementations", "blanket-implementations") - ) - .unwrap(); - render_impls(cx, &mut w, blanket_impl, containing_item, false); - w.write_str("
").unwrap(); + )?; + render_impls(cx, &mut w, blanket_impl, containing_item, false)?; + w.write_str("")?; } + Ok(()) } fn render_assoc_items( @@ -1412,8 +1410,7 @@ fn render_assoc_items( fmt::from_fn(move |f| { let mut derefs = DefIdSet::default(); derefs.insert(it); - render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs); - Ok(()) + render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs) }) } @@ -1424,10 +1421,10 @@ fn render_assoc_items_inner( it: DefId, what: AssocItemRender<'_>, derefs: &mut DefIdSet, -) { +) -> fmt::Result { info!("Documenting associated items of {:?}", containing_item.name); let cache = &cx.shared.cache; - let Some(v) = cache.impls.get(&it) else { return }; + let Some(v) = cache.impls.get(&it) else { return Ok(()) }; let (mut non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none()); if !non_trait.is_empty() { @@ -1511,8 +1508,7 @@ fn render_assoc_items_inner( matches!(what, AssocItemRender::DerefFor { .. }) .then_some("") .maybe_display(), - ) - .unwrap(); + )?; } } @@ -1522,13 +1518,13 @@ fn render_assoc_items_inner( if let Some(impl_) = deref_impl { let has_deref_mut = traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait()); - render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs); + render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs)?; } // If we were already one level into rendering deref methods, we don't want to render // anything after recursing into any further deref methods above. if let AssocItemRender::DerefFor { .. } = what { - return; + return Ok(()); } let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = @@ -1536,8 +1532,9 @@ fn render_assoc_items_inner( let (blanket_impl, concrete): (Vec<&Impl>, _) = concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket()); - render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl); + render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl)?; } + Ok(()) } /// `derefs` is the set of all deref targets that have already been handled. @@ -1548,7 +1545,7 @@ fn render_deref_methods( container_item: &clean::Item, deref_mut: bool, derefs: &mut DefIdSet, -) { +) -> fmt::Result { let cache = cx.cache(); let deref_type = impl_.inner_impl().trait_.as_ref().unwrap(); let (target, real_target) = impl_ @@ -1574,15 +1571,16 @@ fn render_deref_methods( // `impl Deref for S` if did == type_did || !derefs.insert(did) { // Avoid infinite cycles - return; + return Ok(()); } } - render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs); + render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs)?; } else if let Some(prim) = target.primitive_type() && let Some(&did) = cache.primitive_locations.get(&prim) { - render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs); + render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs)?; } + Ok(()) } fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> bool { @@ -1805,8 +1803,7 @@ fn render_impl( // because impls can't have a stability. if !item.doc_value().is_empty() { document_item_info(cx, it, Some(parent)) - .render_into(&mut info_buffer) - .unwrap(); + .render_into(&mut info_buffer)?; doc_buffer = document_full(item, cx, HeadingOffset::H5).to_string(); short_documented = false; } else { @@ -1823,9 +1820,7 @@ fn render_impl( } } } else { - document_item_info(cx, item, Some(parent)) - .render_into(&mut info_buffer) - .unwrap(); + document_item_info(cx, item, Some(parent)).render_into(&mut info_buffer)?; if rendering_params.show_def_docs { doc_buffer = document_full(item, cx, HeadingOffset::H5).to_string(); short_documented = false; @@ -2920,7 +2915,7 @@ fn render_attributes_in_code( item: &clean::Item, prefix: &str, cx: &Context<'_>, -) { +) -> fmt::Result { for attr in &item.attrs.other_attrs { let hir::Attribute::Parsed(kind) = attr else { continue }; let attr = match kind { @@ -2934,24 +2929,30 @@ fn render_attributes_in_code( AttributeKind::NonExhaustive(..) => Cow::Borrowed("#[non_exhaustive]"), _ => continue, }; - render_code_attribute(prefix, attr.as_ref(), w); + render_code_attribute(prefix, attr.as_ref(), w)?; } if let Some(def_id) = item.def_id() && let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id) { - render_code_attribute(prefix, &repr, w); + render_code_attribute(prefix, &repr, w)?; } + Ok(()) } -fn render_repr_attribute_in_code(w: &mut impl fmt::Write, cx: &Context<'_>, def_id: DefId) { +fn render_repr_attribute_in_code( + w: &mut impl fmt::Write, + cx: &Context<'_>, + def_id: DefId, +) -> fmt::Result { if let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id) { - render_code_attribute("", &repr, w); + render_code_attribute("", &repr, w)?; } + Ok(()) } -fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) { - write!(w, "
{prefix}{attr}
").unwrap(); +fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) -> fmt::Result { + write!(w, "
{prefix}{attr}
") } /// Compute the *public* `#[repr]` of the item given by `DefId`. diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index fa7c9e75fdac..0c511738d7c8 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -457,7 +457,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i "\ " )?; - render_attributes_in_code(w, myitem, "", cx); + render_attributes_in_code(w, myitem, "", cx)?; write!( w, "{vis}{imp}{stab_tags}\ @@ -625,7 +625,7 @@ fn item_function(cx: &Context<'_>, it: &clean::Item, f: &clean::Function) -> imp let notable_traits = notable_traits_button(&f.decl.output, cx).maybe_display(); wrap_item(w, |w| { - render_attributes_in_code(w, it, "", cx); + render_attributes_in_code(w, it, "", cx)?; write!( w, "{vis}{constness}{asyncness}{safety}{abi}fn \ @@ -666,7 +666,7 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: // Output the trait definition wrap_item(w, |mut w| { - render_attributes_in_code(&mut w, it, "", cx); + render_attributes_in_code(&mut w, it, "", cx)?; write!( w, "{vis}{safety}{is_auto}trait {name}{generics}{bounds}", @@ -1240,7 +1240,7 @@ fn item_trait_alias( ) -> impl fmt::Display { fmt::from_fn(|w| { wrap_item(w, |w| { - render_attributes_in_code(w, it, "", cx); + render_attributes_in_code(w, it, "", cx)?; write!( w, "trait {name}{generics} = {bounds}{where_clause};", @@ -1268,7 +1268,7 @@ fn item_trait_alias( fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) -> impl fmt::Display { fmt::from_fn(|w| { wrap_item(w, |w| { - render_attributes_in_code(w, it, "", cx); + render_attributes_in_code(w, it, "", cx)?; write!( w, "{vis}type {name}{generics}{where_clause} = {type_};", @@ -1464,7 +1464,7 @@ impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { fn print_field_attrs(&self, field: &'a clean::Item) -> impl Display { fmt::from_fn(move |w| { - render_attributes_in_code(w, field, "", self.cx); + render_attributes_in_code(w, field, "", self.cx)?; Ok(()) }) } @@ -1554,9 +1554,9 @@ impl<'clean> DisplayEnum<'clean> { wrap_item(w, |w| { if is_type_alias { // For now the only attributes we render for type aliases are `repr` attributes. - render_repr_attribute_in_code(w, cx, self.def_id); + render_repr_attribute_in_code(w, cx, self.def_id)?; } else { - render_attributes_in_code(w, it, "", cx); + render_attributes_in_code(w, it, "", cx)?; } write!( w, @@ -1695,7 +1695,7 @@ fn render_enum_fields( if v.is_stripped() { continue; } - render_attributes_in_code(w, v, TAB, cx); + render_attributes_in_code(w, v, TAB, cx)?; w.write_str(TAB)?; match v.kind { clean::VariantItem(ref var) => match var.kind { @@ -1779,7 +1779,7 @@ fn item_variants( ) .maybe_display() )?; - render_attributes_in_code(w, variant, "", cx); + render_attributes_in_code(w, variant, "", cx)?; if let clean::VariantItem(ref var) = variant.kind && let clean::VariantKind::CLike = var.kind { @@ -1855,7 +1855,7 @@ fn item_variants( ยง\ " )?; - render_attributes_in_code(w, field, "", cx); + render_attributes_in_code(w, field, "", cx)?; write!( w, "{f}: {t}\ @@ -1881,7 +1881,7 @@ fn item_macro(cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) -> impl fmt: fmt::from_fn(|w| { wrap_item(w, |w| { // FIXME: Also print `#[doc(hidden)]` for `macro_rules!` if it `is_doc_hidden`. - render_attributes_in_code(w, it, "", cx); + render_attributes_in_code(w, it, "", cx)?; if !t.macro_rules { write!(w, "{}", visibility_print_with_space(it, cx))?; } @@ -1927,16 +1927,15 @@ fn item_primitive(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display { let def_id = it.item_id.expect_def_id(); write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?; if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { - write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All))?; + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)) } else { // We handle the "reference" primitive type on its own because we only want to list // implementations on generic types. let (concrete, synthetic, blanket_impl) = get_filtered_impls_for_reference(&cx.shared, it); - render_all_impls(w, cx, it, &concrete, &synthetic, &blanket_impl); + render_all_impls(w, cx, it, &concrete, &synthetic, &blanket_impl) } - Ok(()) }) } @@ -1950,7 +1949,7 @@ fn item_constant( fmt::from_fn(|w| { wrap_item(w, |w| { let tcx = cx.tcx(); - render_attributes_in_code(w, it, "", cx); + render_attributes_in_code(w, it, "", cx)?; write!( w, @@ -2016,9 +2015,9 @@ impl<'a> DisplayStruct<'a> { wrap_item(w, |w| { if is_type_alias { // For now the only attributes we render for type aliases are `repr` attributes. - render_repr_attribute_in_code(w, cx, self.def_id); + render_repr_attribute_in_code(w, cx, self.def_id)?; } else { - render_attributes_in_code(w, it, "", cx); + render_attributes_in_code(w, it, "", cx)?; } write!( w, @@ -2097,7 +2096,7 @@ fn item_fields( ", item_type = ItemType::StructField, )?; - render_attributes_in_code(w, field, "", cx); + render_attributes_in_code(w, field, "", cx)?; write!( w, "{field_name}: {ty}\ @@ -2120,7 +2119,7 @@ fn item_static( ) -> impl fmt::Display { fmt::from_fn(move |w| { wrap_item(w, |w| { - render_attributes_in_code(w, it, "", cx); + render_attributes_in_code(w, it, "", cx)?; write!( w, "{vis}{safe}static {mutability}{name}: {typ}", @@ -2140,8 +2139,8 @@ fn item_foreign_type(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display { fmt::from_fn(|w| { wrap_item(w, |w| { w.write_str("extern {\n")?; - render_attributes_in_code(w, it, "", cx); - write!(w, " {}type {};\n}}", visibility_print_with_space(it, cx), it.name.unwrap(),) + render_attributes_in_code(w, it, "", cx)?; + write!(w, " {}type {};\n}}", visibility_print_with_space(it, cx), it.name.unwrap()) })?; write!( @@ -2292,15 +2291,14 @@ fn print_bounds( .maybe_display() } -fn wrap_item(w: &mut W, f: F) -> T +fn wrap_item(w: &mut W, f: F) -> fmt::Result where W: fmt::Write, - F: FnOnce(&mut W) -> T, + F: FnOnce(&mut W) -> fmt::Result, { - write!(w, r#"
"#).unwrap();
-    let res = f(w);
-    write!(w, "
").unwrap(); - res + w.write_str(r#"
"#)?;
+    f(w)?;
+    w.write_str("
") } #[derive(PartialEq, Eq)] @@ -2370,9 +2368,9 @@ fn render_union( fmt::from_fn(move |mut f| { if is_type_alias { // For now the only attributes we render for type aliases are `repr` attributes. - render_repr_attribute_in_code(f, cx, def_id); + render_repr_attribute_in_code(f, cx, def_id)?; } else { - render_attributes_in_code(f, it, "", cx); + render_attributes_in_code(f, it, "", cx)?; } write!(f, "{}union {}", visibility_print_with_space(it, cx), it.name.unwrap(),)?; @@ -2403,7 +2401,7 @@ fn render_union( for field in fields { if let clean::StructFieldItem(ref ty) = field.kind { - render_attributes_in_code(&mut f, field, " ", cx); + render_attributes_in_code(&mut f, field, " ", cx)?; writeln!( f, " {}{}: {},", @@ -2500,7 +2498,7 @@ fn render_struct_fields( } for field in fields { if let clean::StructFieldItem(ref ty) = field.kind { - render_attributes_in_code(w, field, &format!("{tab} "), cx); + render_attributes_in_code(w, field, &format!("{tab} "), cx)?; writeln!( w, "{tab} {vis}{name}: {ty},", From 4d4f3151fb02b85de504a40e54484ea4645c1326 Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 22 Nov 2025 11:52:22 +0800 Subject: [PATCH 6/7] Add suggest alternatives for Out-of-range \x escapes --- compiler/rustc_parse/messages.ftl | 2 - compiler/rustc_parse/src/errors.rs | 6 -- .../src/lexer/unescape_error_reporting.rs | 19 ++++- .../parser/ascii-only-character-escape.stderr | 9 +++ ...-of-range-hex-escape-suggestions-148917.rs | 21 +++++ ...range-hex-escape-suggestions-148917.stderr | 77 +++++++++++++++++++ 6 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 tests/ui/parser/out-of-range-hex-escape-suggestions-148917.rs create mode 100644 tests/ui/parser/out-of-range-hex-escape-suggestions-148917.stderr diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index c74333f5655a..7055e60956a9 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -732,8 +732,6 @@ parse_or_in_let_chain = `||` operators are not supported in let chain conditions parse_or_pattern_not_allowed_in_fn_parameters = function parameters require top-level or-patterns in parentheses parse_or_pattern_not_allowed_in_let_binding = `let` bindings require top-level or-patterns in parentheses -parse_out_of_range_hex_escape = out of range hex escape - .label = must be a character in the range [\x00-\x7f] parse_outer_attr_explanation = outer attributes, like `#[test]`, annotate the item following them diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index bde179c9438f..62a333fbf81d 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -2455,12 +2455,6 @@ pub(crate) enum UnescapeError { is_hex: bool, ch: String, }, - #[diag(parse_out_of_range_hex_escape)] - OutOfRangeHexEscape( - #[primary_span] - #[label] - Span, - ), #[diag(parse_leading_underscore_unicode_escape)] LeadingUnderscoreUnicodeEscape { #[primary_span] diff --git a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs index ec59a1a01314..895374ab2cc4 100644 --- a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs +++ b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs @@ -226,7 +226,24 @@ pub(crate) fn emit_unescape_error( err.emit() } EscapeError::OutOfRangeHexEscape => { - dcx.emit_err(UnescapeError::OutOfRangeHexEscape(err_span)) + let mut err = dcx.struct_span_err(err_span, "out of range hex escape"); + err.span_label(err_span, "must be a character in the range [\\x00-\\x7f]"); + + let escape_str = &lit[range]; + if lit.len() <= 4 + && escape_str.len() == 4 + && escape_str.starts_with("\\x") + && let Ok(value) = u8::from_str_radix(&escape_str[2..4], 16) + && matches!(mode, Mode::Char | Mode::Str) + { + err.help(format!("if you want to write a byte literal, use `b'{}'`", escape_str)); + err.help(format!( + "if you want to write a Unicode character, use `'\\u{{{:X}}}'`", + value + )); + } + + err.emit() } EscapeError::LeadingUnderscoreUnicodeEscape => { let (c, span) = last_char(); diff --git a/tests/ui/parser/ascii-only-character-escape.stderr b/tests/ui/parser/ascii-only-character-escape.stderr index b599b35f4b32..78844ae385e4 100644 --- a/tests/ui/parser/ascii-only-character-escape.stderr +++ b/tests/ui/parser/ascii-only-character-escape.stderr @@ -3,18 +3,27 @@ error: out of range hex escape | LL | let x = "\x80"; | ^^^^ must be a character in the range [\x00-\x7f] + | + = help: if you want to write a byte literal, use `b'\x80'` + = help: if you want to write a Unicode character, use `'\u{80}'` error: out of range hex escape --> $DIR/ascii-only-character-escape.rs:3:14 | LL | let y = "\xff"; | ^^^^ must be a character in the range [\x00-\x7f] + | + = help: if you want to write a byte literal, use `b'\xff'` + = help: if you want to write a Unicode character, use `'\u{FF}'` error: out of range hex escape --> $DIR/ascii-only-character-escape.rs:4:14 | LL | let z = "\xe2"; | ^^^^ must be a character in the range [\x00-\x7f] + | + = help: if you want to write a byte literal, use `b'\xe2'` + = help: if you want to write a Unicode character, use `'\u{E2}'` error: aborting due to 3 previous errors diff --git a/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.rs b/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.rs new file mode 100644 index 000000000000..ee8489fef800 --- /dev/null +++ b/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.rs @@ -0,0 +1,21 @@ +fn main() { + let _c = '\xFF'; //~ ERROR out of range hex escape + let _s = "\xFF"; //~ ERROR out of range hex escape + + let _c2 = '\xff'; //~ ERROR out of range hex escape + let _s2 = "\xff"; //~ ERROR out of range hex escape + + let _c3 = '\x80'; //~ ERROR out of range hex escape + let _s3 = "\x80"; //~ ERROR out of range hex escape + + // Byte literals should not get suggestions (they're already valid) + let _b = b'\xFF'; // OK + let _bs = b"\xFF"; // OK + + dbg!('\xFF'); //~ ERROR out of range hex escape + + // do not suggest for out of range escapes that are too long + dbg!("\xFFFFF"); //~ ERROR out of range hex escape + + dbg!("this is some kind of string \xa7"); //~ ERROR out of range hex escape +} diff --git a/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.stderr b/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.stderr new file mode 100644 index 000000000000..e0485bfe50ea --- /dev/null +++ b/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.stderr @@ -0,0 +1,77 @@ +error: out of range hex escape + --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:2:15 + | +LL | let _c = '\xFF'; + | ^^^^ must be a character in the range [\x00-\x7f] + | + = help: if you want to write a byte literal, use `b'\xFF'` + = help: if you want to write a Unicode character, use `'\u{FF}'` + +error: out of range hex escape + --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:3:15 + | +LL | let _s = "\xFF"; + | ^^^^ must be a character in the range [\x00-\x7f] + | + = help: if you want to write a byte literal, use `b'\xFF'` + = help: if you want to write a Unicode character, use `'\u{FF}'` + +error: out of range hex escape + --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:5:16 + | +LL | let _c2 = '\xff'; + | ^^^^ must be a character in the range [\x00-\x7f] + | + = help: if you want to write a byte literal, use `b'\xff'` + = help: if you want to write a Unicode character, use `'\u{FF}'` + +error: out of range hex escape + --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:6:16 + | +LL | let _s2 = "\xff"; + | ^^^^ must be a character in the range [\x00-\x7f] + | + = help: if you want to write a byte literal, use `b'\xff'` + = help: if you want to write a Unicode character, use `'\u{FF}'` + +error: out of range hex escape + --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:8:16 + | +LL | let _c3 = '\x80'; + | ^^^^ must be a character in the range [\x00-\x7f] + | + = help: if you want to write a byte literal, use `b'\x80'` + = help: if you want to write a Unicode character, use `'\u{80}'` + +error: out of range hex escape + --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:9:16 + | +LL | let _s3 = "\x80"; + | ^^^^ must be a character in the range [\x00-\x7f] + | + = help: if you want to write a byte literal, use `b'\x80'` + = help: if you want to write a Unicode character, use `'\u{80}'` + +error: out of range hex escape + --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:15:11 + | +LL | dbg!('\xFF'); + | ^^^^ must be a character in the range [\x00-\x7f] + | + = help: if you want to write a byte literal, use `b'\xFF'` + = help: if you want to write a Unicode character, use `'\u{FF}'` + +error: out of range hex escape + --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:18:11 + | +LL | dbg!("\xFFFFF"); + | ^^^^ must be a character in the range [\x00-\x7f] + +error: out of range hex escape + --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:20:39 + | +LL | dbg!("this is some kind of string \xa7"); + | ^^^^ must be a character in the range [\x00-\x7f] + +error: aborting due to 9 previous errors + From 6173a567167646e15ef8f00e36b4739b3bfa3bee Mon Sep 17 00:00:00 2001 From: Peter Badida Date: Mon, 24 Nov 2025 18:02:19 +0100 Subject: [PATCH 7/7] Fix missing double-quote in `std::env::consts::OS` values --- library/std/src/env.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/env.rs b/library/std/src/env.rs index fd662e8a663a..5c068ad2471a 100644 --- a/library/std/src/env.rs +++ b/library/std/src/env.rs @@ -1097,7 +1097,7 @@ pub mod consts { /// * `"nto"` /// * `"redox"` /// * `"solaris"` - /// * `"solid_asp3` + /// * `"solid_asp3"` /// * `"vexos"` /// * `"vita"` /// * `"vxworks"`