From 1cac5fa5f9f371db4789ea928e1d2769d4cd921b Mon Sep 17 00:00:00 2001 From: jyn Date: Thu, 13 Feb 2025 10:14:06 -0500 Subject: [PATCH 01/14] Look for `python3` first on MacOS, not `py` `py` is not installed by default *and* trying to run it results in a popup asking if you want to install it. `python3` is installed by default. This hopefully should not be too disruptive to people on Windows, since they should be going through `x.ps1` instead anyway. Just in case, I've added a check for Cygwin and Msys (i'm not sure how else you'd get a bash shell on windows). --- x | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x b/x index e656d37c1e455..551cfe6efbf33 100755 --- a/x +++ b/x @@ -25,7 +25,13 @@ xpy=$(dirname "$(realpath "$0")")/x.py # On Windows, `py -3` sometimes works. We need to try it first because `python3` # sometimes tries to launch the app store on Windows. -for SEARCH_PYTHON in py python3 python python2; do +# On MacOS, `py` tries to install "Developer command line tools". Try `python3` first. +# NOTE: running `bash -c ./x` from Windows doesn't set OSTYPE. +case ${OSTYPE:-} in + cygwin*|msys*) SEARCH="py python3 python python2";; + *) SEARCH="python3 python py python2";; +esac +for SEARCH_PYTHON in $SEARCH; do if python=$(command -v $SEARCH_PYTHON) && [ -x "$python" ]; then if [ $SEARCH_PYTHON = py ]; then extra_arg="-3" From 477a2eeb3dd5a430bb3845c8f8041ff369232967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=9D=B0=E5=8F=8B=20Jieyou=20Xu=20=28Joe=29?= <39484203+jieyouxu@users.noreply.github.com> Date: Wed, 19 Feb 2025 02:00:02 +0800 Subject: [PATCH 02/14] std::fs: slightly reformat `remove_dir_all` error docs To make the error cases easier to spot on a quick glance. --- library/std/src/fs.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 6001a2e2f391f..2b3a84d0a12c2 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -2841,9 +2841,11 @@ pub fn remove_dir>(path: P) -> io::Result<()> { /// /// See [`fs::remove_file`] and [`fs::remove_dir`]. /// -/// `remove_dir_all` will fail if `remove_dir` or `remove_file` fail on any constituent paths, including the root `path`. -/// As a result, the directory you are deleting must exist, meaning that this function is not idempotent. -/// Additionally, `remove_dir_all` will also fail if the `path` is not a directory. +/// [`remove_dir_all`] will fail if [`remove_dir`] or [`remove_file`] fail on *any* constituent +/// paths, *including* the root `path`. Consequently, +/// +/// - The directory you are deleting *must* exist, meaning that this function is *not idempotent*. +/// - [`remove_dir_all`] will fail if the `path` is *not* a directory. /// /// Consider ignoring the error if validating the removal is not required for your use case. /// From 2bead2717b6ddfd9dc6d39d41ec031ce9c50e664 Mon Sep 17 00:00:00 2001 From: Henry Jiang Date: Wed, 12 Feb 2025 13:36:23 -0500 Subject: [PATCH 03/14] remove : from stack-protector-heuristics-effect.rs filecheck --- .../stack-protector/stack-protector-heuristics-effect.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs b/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs index 57fc601a2e0c0..fa9e149388d7c 100644 --- a/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs +++ b/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs @@ -19,7 +19,7 @@ #![allow(incomplete_features)] #![feature(unsized_locals, unsized_fn_params)] -// CHECK-LABEL: emptyfn: +// CHECK-LABEL: emptyfn #[no_mangle] pub fn emptyfn() { // all: __stack_chk_fail @@ -63,7 +63,7 @@ pub fn array_u8_1(f: fn(*const u8)) { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: array_u8_small: +// CHECK-LABEL: array_u8_small #[no_mangle] pub fn array_u8_small(f: fn(*const u8)) { let a = [0u8; 2]; @@ -80,7 +80,7 @@ pub fn array_u8_small(f: fn(*const u8)) { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: array_u8_large: +// CHECK-LABEL: array_u8_large #[no_mangle] pub fn array_u8_large(f: fn(*const u8)) { let a = [0u8; 9]; @@ -99,7 +99,7 @@ pub fn array_u8_large(f: fn(*const u8)) { #[derive(Copy, Clone)] pub struct ByteSizedNewtype(u8); -// CHECK-LABEL: array_bytesizednewtype_9: +// CHECK-LABEL: array_bytesizednewtype_9 #[no_mangle] pub fn array_bytesizednewtype_9(f: fn(*const ByteSizedNewtype)) { let a = [ByteSizedNewtype(0); 9]; From 2c752bcf559975995eb8086a7fa6a7f9b5ba0de8 Mon Sep 17 00:00:00 2001 From: Arlo Siemsen Date: Thu, 20 Feb 2025 11:47:14 -0600 Subject: [PATCH 04/14] Undeprecate env::home_dir --- library/std/src/env.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/library/std/src/env.rs b/library/std/src/env.rs index adbd68896241c..5a931b077cdb0 100644 --- a/library/std/src/env.rs +++ b/library/std/src/env.rs @@ -641,11 +641,6 @@ impl Error for JoinPathsError { /// None => println!("Impossible to get your home dir!"), /// } /// ``` -#[deprecated( - since = "1.29.0", - note = "This function's behavior may be unexpected on Windows. \ - Consider using a crate from crates.io instead." -)] #[must_use] #[stable(feature = "env", since = "1.0.0")] pub fn home_dir() -> Option { From fb8c993e15858ff6653977355055df4c016a60d6 Mon Sep 17 00:00:00 2001 From: Henry Jiang Date: Thu, 20 Feb 2025 13:31:12 -0500 Subject: [PATCH 05/14] fix label suffix --- .../stack-protector-heuristics-effect.rs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs b/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs index fa9e149388d7c..91c83fa2f5ba9 100644 --- a/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs +++ b/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs @@ -19,7 +19,7 @@ #![allow(incomplete_features)] #![feature(unsized_locals, unsized_fn_params)] -// CHECK-LABEL: emptyfn +// CHECK-LABEL: emptyfn{{:|\[}} #[no_mangle] pub fn emptyfn() { // all: __stack_chk_fail @@ -29,7 +29,7 @@ pub fn emptyfn() { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: array_char +// CHECK-LABEL: array_char{{:|\[}} #[no_mangle] pub fn array_char(f: fn(*const char)) { let a = ['c'; 1]; @@ -47,7 +47,7 @@ pub fn array_char(f: fn(*const char)) { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: array_u8_1 +// CHECK-LABEL: array_u8_1{{:|\[}} #[no_mangle] pub fn array_u8_1(f: fn(*const u8)) { let a = [0u8; 1]; @@ -63,7 +63,7 @@ pub fn array_u8_1(f: fn(*const u8)) { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: array_u8_small +// CHECK-LABEL: array_u8_small{{:|\[}} #[no_mangle] pub fn array_u8_small(f: fn(*const u8)) { let a = [0u8; 2]; @@ -80,7 +80,7 @@ pub fn array_u8_small(f: fn(*const u8)) { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: array_u8_large +// CHECK-LABEL: array_u8_large{{:|\[}} #[no_mangle] pub fn array_u8_large(f: fn(*const u8)) { let a = [0u8; 9]; @@ -99,7 +99,7 @@ pub fn array_u8_large(f: fn(*const u8)) { #[derive(Copy, Clone)] pub struct ByteSizedNewtype(u8); -// CHECK-LABEL: array_bytesizednewtype_9 +// CHECK-LABEL: array_bytesizednewtype_9{{:|\[}} #[no_mangle] pub fn array_bytesizednewtype_9(f: fn(*const ByteSizedNewtype)) { let a = [ByteSizedNewtype(0); 9]; @@ -115,7 +115,7 @@ pub fn array_bytesizednewtype_9(f: fn(*const ByteSizedNewtype)) { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: local_var_addr_used_indirectly +// CHECK-LABEL: local_var_addr_used_indirectly{{:|\[}} #[no_mangle] pub fn local_var_addr_used_indirectly(f: fn(bool)) { let a = 5; @@ -142,7 +142,7 @@ pub fn local_var_addr_used_indirectly(f: fn(bool)) { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: local_string_addr_taken +// CHECK-LABEL: local_string_addr_taken{{:|\[}} #[no_mangle] pub fn local_string_addr_taken(f: fn(&String)) { let x = String::new(); @@ -168,7 +168,7 @@ impl SelfByRef for i32 { } } -// CHECK-LABEL: local_var_addr_taken_used_locally_only +// CHECK-LABEL: local_var_addr_taken_used_locally_only{{:|\[}} #[no_mangle] pub fn local_var_addr_taken_used_locally_only(factory: fn() -> i32, sink: fn(i32)) { let x = factory(); @@ -195,7 +195,7 @@ pub struct Gigastruct { members: u64, } -// CHECK-LABEL: local_large_var_moved +// CHECK-LABEL: local_large_var_moved{{:|\[}} #[no_mangle] pub fn local_large_var_moved(f: fn(Gigastruct)) { let x = Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 }; @@ -224,7 +224,7 @@ pub fn local_large_var_moved(f: fn(Gigastruct)) { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: local_large_var_cloned +// CHECK-LABEL: local_large_var_cloned{{:|\[}} #[no_mangle] pub fn local_large_var_cloned(f: fn(Gigastruct)) { f(Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 }); @@ -281,7 +281,7 @@ extern "C" { fn alloca(size: usize) -> *mut (); } -// CHECK-LABEL: alloca_small_compile_time_constant_arg +// CHECK-LABEL: alloca_small_compile_time_constant_arg{{:|\[}} #[no_mangle] pub fn alloca_small_compile_time_constant_arg(f: fn(*mut ())) { f(unsafe { alloca(8) }); @@ -293,7 +293,7 @@ pub fn alloca_small_compile_time_constant_arg(f: fn(*mut ())) { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: alloca_large_compile_time_constant_arg +// CHECK-LABEL: alloca_large_compile_time_constant_arg{{:|\[}} #[no_mangle] pub fn alloca_large_compile_time_constant_arg(f: fn(*mut ())) { f(unsafe { alloca(9) }); @@ -305,7 +305,7 @@ pub fn alloca_large_compile_time_constant_arg(f: fn(*mut ())) { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: alloca_dynamic_arg +// CHECK-LABEL: alloca_dynamic_arg{{:|\[}} #[no_mangle] pub fn alloca_dynamic_arg(f: fn(*mut ()), n: usize) { f(unsafe { alloca(n) }); @@ -324,7 +324,7 @@ pub fn alloca_dynamic_arg(f: fn(*mut ()), n: usize) { // this is support for the "unsized locals" unstable feature: // https://doc.rust-lang.org/unstable-book/language-features/unsized-locals.html. -// CHECK-LABEL: unsized_fn_param +// CHECK-LABEL: unsized_fn_param{{:|\[}} #[no_mangle] pub fn unsized_fn_param(s: [u8], l: bool, f: fn([u8])) { let n = if l { 1 } else { 2 }; @@ -344,7 +344,7 @@ pub fn unsized_fn_param(s: [u8], l: bool, f: fn([u8])) { // missing-NOT: __stack_chk_fail } -// CHECK-LABEL: unsized_local +// CHECK-LABEL: unsized_local{{:|\[}} #[no_mangle] pub fn unsized_local(s: &[u8], l: bool, f: fn(&mut [u8])) { let n = if l { 1 } else { 2 }; From bf26f2442365bbdc6c0785d884a042a824e1d37f Mon Sep 17 00:00:00 2001 From: Frank Steffahn Date: Thu, 20 Feb 2025 17:24:09 +0100 Subject: [PATCH 06/14] Clarify/update comments in `BufRead::read_line`'s default body with where to *actually* look for more details --- library/std/src/io/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 980ea1478e084..7f610bc88bfd7 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -2534,7 +2534,7 @@ pub trait BufRead: Read { fn read_line(&mut self, buf: &mut String) -> Result { // Note that we are not calling the `.read_until` method here, but // rather our hardcoded implementation. For more details as to why, see - // the comments in `read_to_end`. + // the comments in `default_read_to_string`. unsafe { append_to_string(buf, |b| read_until(self, b'\n', b)) } } From f65277c670e39e310aed3eda3f736462bf2611af Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Tue, 31 Dec 2024 21:39:13 +0000 Subject: [PATCH 07/14] Simplify parallelization in test-float-parse Currently, test case generators are launched in parallel and their test cases also run in parallel, all within the same pool. I originally implemented this with the assumption that there would be an advantage in parallelizing the generators themselves, but this turns out to not really have any benefit. Simplify things by running generators in series while keeping their test cases parallelized. This makes the code easier to follow, and there is no longer a need for MPSC or multiprogress bars. Additionally, the UI output can be made cleaner. --- .../test-float-parse/src/gen/exhaustive.rs | 3 +- src/etc/test-float-parse/src/gen/fuzz.rs | 1 - src/etc/test-float-parse/src/gen/sparse.rs | 1 - src/etc/test-float-parse/src/lib.rs | 213 ++++-------------- src/etc/test-float-parse/src/traits.rs | 2 +- src/etc/test-float-parse/src/ui.rs | 126 +++++++---- src/etc/test-float-parse/src/validate.rs | 17 +- 7 files changed, 142 insertions(+), 221 deletions(-) diff --git a/src/etc/test-float-parse/src/gen/exhaustive.rs b/src/etc/test-float-parse/src/gen/exhaustive.rs index 5d4b6df8e59b9..01458fb0b6084 100644 --- a/src/etc/test-float-parse/src/gen/exhaustive.rs +++ b/src/etc/test-float-parse/src/gen/exhaustive.rs @@ -13,13 +13,12 @@ impl Generator for Exhaustive where RangeInclusive: Iterator, { - const NAME: &'static str = "exhaustive"; const SHORT_NAME: &'static str = "exhaustive"; type WriteCtx = F; fn total_tests() -> u64 { - F::Int::MAX.try_into().unwrap_or(u64::MAX) + 1u64.checked_shl(F::Int::BITS).expect("More than u64::MAX tests") } fn new() -> Self { diff --git a/src/etc/test-float-parse/src/gen/fuzz.rs b/src/etc/test-float-parse/src/gen/fuzz.rs index 0c63e8aae26ea..7fc999d167113 100644 --- a/src/etc/test-float-parse/src/gen/fuzz.rs +++ b/src/etc/test-float-parse/src/gen/fuzz.rs @@ -49,7 +49,6 @@ impl Generator for Fuzz where Standard: Distribution<::Int>, { - const NAME: &'static str = "fuzz"; const SHORT_NAME: &'static str = "fuzz"; type WriteCtx = F; diff --git a/src/etc/test-float-parse/src/gen/sparse.rs b/src/etc/test-float-parse/src/gen/sparse.rs index 389b71056a3e5..72b65d4ce7f10 100644 --- a/src/etc/test-float-parse/src/gen/sparse.rs +++ b/src/etc/test-float-parse/src/gen/sparse.rs @@ -35,7 +35,6 @@ impl Generator for FewOnesInt where >::Error: std::fmt::Debug, { - const NAME: &'static str = "few ones int"; const SHORT_NAME: &'static str = "few ones int"; type WriteCtx = F::Int; diff --git a/src/etc/test-float-parse/src/lib.rs b/src/etc/test-float-parse/src/lib.rs index 3c71b0dc32e74..5a0c160312295 100644 --- a/src/etc/test-float-parse/src/lib.rs +++ b/src/etc/test-float-parse/src/lib.rs @@ -2,19 +2,19 @@ mod traits; mod ui; mod validate; -use std::any::{TypeId, type_name}; +use std::any::type_name; use std::cmp::min; use std::ops::RangeInclusive; use std::process::ExitCode; +use std::sync::OnceLock; use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::{OnceLock, mpsc}; use std::{fmt, time}; -use indicatif::{MultiProgress, ProgressBar}; use rand::distributions::{Distribution, Standard}; use rayon::prelude::*; use time::{Duration, Instant}; use traits::{Float, Generator, Int}; +use validate::CheckError; /// Test generators. mod gen { @@ -43,7 +43,7 @@ const HUGE_TEST_CUTOFF: u64 = 5_000_000; /// Seed for tests that use a deterministic RNG. const SEED: [u8; 32] = *b"3.141592653589793238462643383279"; -/// Global configuration +/// Global configuration. #[derive(Debug)] pub struct Config { pub timeout: Duration, @@ -106,7 +106,7 @@ pub fn run(cfg: Config, include: &[String], exclude: &[String]) -> ExitCode { println!("launching"); let elapsed = launch_tests(&mut tests, &cfg); - ui::finish(&tests, elapsed, &cfg) + ui::finish_all(&tests, elapsed, &cfg) } /// Enumerate tests to run but don't actually run them. @@ -160,18 +160,18 @@ where #[derive(Debug)] pub struct TestInfo { pub name: String, - /// Tests are identified by the type ID of `(F, G)` (tuple of the float and generator type). - /// This gives an easy way to associate messages with tests. - id: TypeId, float_name: &'static str, + float_bits: u32, gen_name: &'static str, /// Name for display in the progress bar. short_name: String, + /// Pad the short name to a common width for progress bar use. + short_name_padded: String, total_tests: u64, /// Function to launch this test. - launch: fn(&mpsc::Sender, &TestInfo, &Config), + launch: fn(&TestInfo, &Config), /// Progress bar to be updated. - pb: Option, + progress: Option, /// Once completed, this will be set. completed: OnceLock, } @@ -187,14 +187,18 @@ impl TestInfo { let f_name = type_name::(); let gen_name = G::NAME; let gen_short_name = G::SHORT_NAME; + let name = format!("{f_name} {gen_name}"); + let short_name = format!("{f_name} {gen_short_name}"); + let short_name_padded = format!("{short_name:18}"); let info = TestInfo { - id: TypeId::of::<(F, G)>(), float_name: f_name, + float_bits: F::BITS, gen_name, - pb: None, - name: format!("{f_name} {gen_name}"), - short_name: format!("{f_name} {gen_short_name}"), + progress: None, + name, + short_name_padded, + short_name, launch: test_runner::, total_tests: G::total_tests(), completed: OnceLock::new(), @@ -202,106 +206,18 @@ impl TestInfo { v.push(info); } - /// Pad the short name to a common width for progress bar use. - fn short_name_padded(&self) -> String { - format!("{:18}", self.short_name) - } - - /// Create a progress bar for this test within a multiprogress bar. - fn register_pb(&mut self, mp: &MultiProgress, drop_bars: &mut Vec) { - self.pb = Some(ui::create_pb(mp, self.total_tests, &self.short_name_padded(), drop_bars)); - } - - /// When the test is finished, update progress bar messages and finalize. - fn finalize_pb(&self, c: &Completed) { - let pb = self.pb.as_ref().unwrap(); - ui::finalize_pb(pb, &self.short_name_padded(), c); - } - /// True if this should be run after all others. fn is_huge_test(&self) -> bool { self.total_tests >= HUGE_TEST_CUTOFF } -} - -/// A message sent from test runner threads to the UI/log thread. -#[derive(Clone, Debug)] -struct Msg { - id: TypeId, - update: Update, -} -impl Msg { - /// Wrap an `Update` into a message for the specified type. We use the `TypeId` of `(F, G)` to - /// identify which test a message in the channel came from. - fn new>(u: Update) -> Self { - Self { id: TypeId::of::<(F, G)>(), update: u } - } - - /// Get the matching test from a list. Panics if not found. - fn find_test<'a>(&self, tests: &'a [TestInfo]) -> &'a TestInfo { - tests.iter().find(|t| t.id == self.id).unwrap() - } - - /// Update UI as needed for a single message received from the test runners. - fn handle(self, tests: &[TestInfo], mp: &MultiProgress) { - let test = self.find_test(tests); - let pb = test.pb.as_ref().unwrap(); - - match self.update { - Update::Started => { - mp.println(format!("Testing '{}'", test.name)).unwrap(); - } - Update::Progress { executed, failures } => { - pb.set_message(format! {"{failures}"}); - pb.set_position(executed); - } - Update::Failure { fail, input, float_res } => { - mp.println(format!( - "Failure in '{}': {fail}. parsing '{input}'. Parsed as: {float_res}", - test.name - )) - .unwrap(); - } - Update::Completed(c) => { - test.finalize_pb(&c); - - let prefix = match c.result { - Ok(FinishedAll) => "Completed tests for", - Err(EarlyExit::Timeout) => "Timed out", - Err(EarlyExit::MaxFailures) => "Max failures reached for", - }; - - mp.println(format!( - "{prefix} generator '{}' in {:?}. {} tests run, {} failures", - test.name, c.elapsed, c.executed, c.failures - )) - .unwrap(); - test.completed.set(c).unwrap(); - } - }; + /// When the test is finished, update progress bar messages and finalize. + fn complete(&self, c: Completed) { + self.progress.as_ref().unwrap().complete(&c, 0); + self.completed.set(c).unwrap(); } } -/// Status sent with a message. -#[derive(Clone, Debug)] -enum Update { - /// Starting a new test runner. - Started, - /// Completed a out of b tests. - Progress { executed: u64, failures: u64 }, - /// Received a failed test. - Failure { - fail: CheckFailure, - /// String for which parsing was attempted. - input: Box, - /// The parsed & decomposed `FloatRes`, already stringified so we don't need generics here. - float_res: Box, - }, - /// Exited with an unexpected condition. - Completed(Completed), -} - /// Result of an input did not parsing successfully. #[derive(Clone, Debug)] enum CheckFailure { @@ -398,55 +314,21 @@ enum EarlyExit { /// This launches a main thread that receives messages and handlees UI updates, and uses the /// rest of the thread pool to execute the tests. fn launch_tests(tests: &mut [TestInfo], cfg: &Config) -> Duration { - // Run shorter tests first - tests.sort_unstable_by_key(|test| test.total_tests); + // Run shorter tests and smaller float types first. + tests.sort_unstable_by_key(|test| (test.total_tests, test.float_bits)); for test in tests.iter() { println!("Launching test '{}'", test.name); } - // Configure progress bars let mut all_progress_bars = Vec::new(); - let mp = MultiProgress::new(); - mp.set_move_cursor(true); - for test in tests.iter_mut() { - test.register_pb(&mp, &mut all_progress_bars); - } - - ui::set_panic_hook(all_progress_bars); - - let (tx, rx) = mpsc::channel::(); let start = Instant::now(); - rayon::scope(|scope| { - // Thread that updates the UI - scope.spawn(|_scope| { - let rx = rx; // move rx - - loop { - if tests.iter().all(|t| t.completed.get().is_some()) { - break; - } - - let msg = rx.recv().unwrap(); - msg.handle(tests, &mp); - } - - // All tests completed; finish things up - drop(mp); - assert_eq!(rx.try_recv().unwrap_err(), mpsc::TryRecvError::Empty); - }); - - // Don't let the thread pool be starved by huge tests. Run faster tests first in parallel, - // then parallelize only within the rest of the tests. - let (huge_tests, normal_tests): (Vec<_>, Vec<_>) = - tests.iter().partition(|t| t.is_huge_test()); - - // Run the actual tests - normal_tests.par_iter().for_each(|test| ((test.launch)(&tx, test, cfg))); - - huge_tests.par_iter().for_each(|test| ((test.launch)(&tx, test, cfg))); - }); + for test in tests.iter_mut() { + test.progress = Some(ui::Progress::new(test, &mut all_progress_bars)); + ui::set_panic_hook(&all_progress_bars); + ((test.launch)(test, cfg)); + } start.elapsed() } @@ -454,15 +336,12 @@ fn launch_tests(tests: &mut [TestInfo], cfg: &Config) -> Duration { /// Test runer for a single generator. /// /// This calls the generator's iterator multiple times (in parallel) and validates each output. -fn test_runner>(tx: &mpsc::Sender, _info: &TestInfo, cfg: &Config) { - tx.send(Msg::new::(Update::Started)).unwrap(); - - let total = G::total_tests(); +fn test_runner>(test: &TestInfo, cfg: &Config) { let gen = G::new(); let executed = AtomicU64::new(0); let failures = AtomicU64::new(0); - let checks_per_update = min(total, 1000); + let checks_per_update = min(test.total_tests, 1000); let started = Instant::now(); // Function to execute for a single test iteration. @@ -474,7 +353,12 @@ fn test_runner>(tx: &mpsc::Sender, _info: &TestIn match validate::validate::(buf) { Ok(()) => (), Err(e) => { - tx.send(Msg::new::(e)).unwrap(); + let CheckError { fail, input, float_res } = e; + test.progress.as_ref().unwrap().println(&format!( + "Failure in '{}': {fail}. parsing '{input}'. Parsed as: {float_res}", + test.name + )); + let f = failures.fetch_add(1, Ordering::Relaxed); // End early if the limit is exceeded. if f >= cfg.max_failures { @@ -486,9 +370,7 @@ fn test_runner>(tx: &mpsc::Sender, _info: &TestIn // Send periodic updates if executed % checks_per_update == 0 { let failures = failures.load(Ordering::Relaxed); - - tx.send(Msg::new::(Update::Progress { executed, failures })).unwrap(); - + test.progress.as_ref().unwrap().update(executed, failures); if started.elapsed() > cfg.timeout { return Err(EarlyExit::Timeout); } @@ -499,15 +381,19 @@ fn test_runner>(tx: &mpsc::Sender, _info: &TestIn // Run the test iterations in parallel. Each thread gets a string buffer to write // its check values to. - let res = gen.par_bridge().try_for_each_init(|| String::with_capacity(100), check_one); + let res = gen.par_bridge().try_for_each_init(String::new, check_one); let elapsed = started.elapsed(); let executed = executed.into_inner(); let failures = failures.into_inner(); // Warn about bad estimates if relevant. - let warning = if executed != total && res.is_ok() { - let msg = format!("executed tests != estimated ({executed} != {total}) for {}", G::NAME); + let warning = if executed != test.total_tests && res.is_ok() { + let msg = format!( + "executed tests != estimated ({executed} != {}) for {}", + test.total_tests, + G::NAME + ); Some(msg.into()) } else { @@ -515,12 +401,5 @@ fn test_runner>(tx: &mpsc::Sender, _info: &TestIn }; let result = res.map(|()| FinishedAll); - tx.send(Msg::new::(Update::Completed(Completed { - executed, - failures, - result, - warning, - elapsed, - }))) - .unwrap(); + test.complete(Completed { executed, failures, result, warning, elapsed }); } diff --git a/src/etc/test-float-parse/src/traits.rs b/src/etc/test-float-parse/src/traits.rs index f5333d63b3693..c8407ba7cc50a 100644 --- a/src/etc/test-float-parse/src/traits.rs +++ b/src/etc/test-float-parse/src/traits.rs @@ -177,7 +177,7 @@ impl_float!(f32, u32, 32; f64, u64, 64); /// allocations (which otherwise turn out to be a pretty expensive part of these tests). pub trait Generator: Iterator + Send + 'static { /// Full display and filtering name - const NAME: &'static str; + const NAME: &'static str = Self::SHORT_NAME; /// Name for display with the progress bar const SHORT_NAME: &'static str; diff --git a/src/etc/test-float-parse/src/ui.rs b/src/etc/test-float-parse/src/ui.rs index f333bd4a55dc9..1ee57723e6a62 100644 --- a/src/etc/test-float-parse/src/ui.rs +++ b/src/etc/test-float-parse/src/ui.rs @@ -1,67 +1,92 @@ //! Progress bars and such. +use std::any::type_name; +use std::fmt; use std::io::{self, Write}; use std::process::ExitCode; use std::time::Duration; -use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use indicatif::{ProgressBar, ProgressStyle}; use crate::{Completed, Config, EarlyExit, FinishedAll, TestInfo}; /// Templates for progress bars. -const PB_TEMPLATE: &str = "[{elapsed:3} {percent:3}%] {bar:20.cyan/blue} NAME ({pos}/{len}, {msg} f, {per_sec}, eta {eta})"; -const PB_TEMPLATE_FINAL: &str = - "[{elapsed:3} {percent:3}%] NAME ({pos}/{len}, {msg:.COLOR}, {per_sec}, {elapsed_precise})"; - -/// Create a new progress bar within a multiprogress bar. -pub fn create_pb( - mp: &MultiProgress, - total_tests: u64, - short_name_padded: &str, - all_bars: &mut Vec, -) -> ProgressBar { - let pb = mp.add(ProgressBar::new(total_tests)); - let pb_style = ProgressStyle::with_template(&PB_TEMPLATE.replace("NAME", short_name_padded)) - .unwrap() - .progress_chars("##-"); - - pb.set_style(pb_style.clone()); - pb.set_message("0"); - all_bars.push(pb.clone()); - pb +const PB_TEMPLATE: &str = "[{elapsed:3} {percent:3}%] {bar:20.cyan/blue} NAME \ + {human_pos:>8}/{human_len:8} {msg} f {per_sec:14} eta {eta:8}"; +const PB_TEMPLATE_FINAL: &str = "[{elapsed:3} {percent:3}%] {bar:20.cyan/blue} NAME \ + {human_pos:>8}/{human_len:8} {msg:.COLOR} {per_sec:18} {elapsed_precise}"; + +/// Thin abstraction over our usage of a `ProgressBar`. +#[derive(Debug)] +pub struct Progress { + pb: ProgressBar, + make_final_style: NoDebug ProgressStyle + Sync>>, } -/// Removes the status bar and replace it with a message. -pub fn finalize_pb(pb: &ProgressBar, short_name_padded: &str, c: &Completed) { - let f = c.failures; +impl Progress { + /// Create a new progress bar within a multiprogress bar. + pub fn new(test: &TestInfo, all_bars: &mut Vec) -> Self { + let initial_template = PB_TEMPLATE.replace("NAME", &test.short_name_padded); + let final_template = PB_TEMPLATE_FINAL.replace("NAME", &test.short_name_padded); + let initial_style = + ProgressStyle::with_template(&initial_template).unwrap().progress_chars("##-"); + let make_final_style = move |color| { + ProgressStyle::with_template(&final_template.replace("COLOR", color)) + .unwrap() + .progress_chars("##-") + }; - // Use a tuple so we can use colors - let (color, msg, finish_pb): (&str, String, fn(&ProgressBar, String)) = match &c.result { - Ok(FinishedAll) if f > 0 => { - ("red", format!("{f} f (finished with errors)",), ProgressBar::finish_with_message) - } - Ok(FinishedAll) => { - ("green", format!("{f} f (finished successfully)",), ProgressBar::finish_with_message) - } - Err(EarlyExit::Timeout) => { - ("red", format!("{f} f (timed out)"), ProgressBar::abandon_with_message) + let pb = ProgressBar::new(test.total_tests); + pb.set_style(initial_style); + pb.set_length(test.total_tests); + pb.set_message("0"); + all_bars.push(pb.clone()); + + Progress { pb, make_final_style: NoDebug(Box::new(make_final_style)) } + } + + /// Completed a out of b tests. + pub fn update(&self, completed: u64, failures: u64) { + // Infrequently update the progress bar. + if completed % 5_000 == 0 || failures > 0 { + self.pb.set_position(completed); } - Err(EarlyExit::MaxFailures) => { - ("red", format!("{f} f (failure limit)"), ProgressBar::abandon_with_message) + + if failures > 0 { + self.pb.set_message(format! {"{failures}"}); } - }; + } + + /// Finalize the progress bar. + pub fn complete(&self, c: &Completed, real_total: u64) { + let f = c.failures; + let (color, msg, finish_fn): (&str, String, fn(&ProgressBar)) = match &c.result { + Ok(FinishedAll) if f > 0 => { + ("red", format!("{f} f (completed with errors)",), ProgressBar::finish) + } + Ok(FinishedAll) => { + ("green", format!("{f} f (completed successfully)",), ProgressBar::finish) + } + Err(EarlyExit::Timeout) => ("red", format!("{f} f (timed out)"), ProgressBar::abandon), + Err(EarlyExit::MaxFailures) => { + ("red", format!("{f} f (failure limit)"), ProgressBar::abandon) + } + }; - let pb_style = ProgressStyle::with_template( - &PB_TEMPLATE_FINAL.replace("NAME", short_name_padded).replace("COLOR", color), - ) - .unwrap(); + self.pb.set_position(real_total); + self.pb.set_style(self.make_final_style.0(color)); + self.pb.set_message(msg); + finish_fn(&self.pb); + } - pb.set_style(pb_style); - finish_pb(pb, msg); + /// Print a message to stdout above the current progress bar. + pub fn println(&self, msg: &str) { + self.pb.suspend(|| println!("{msg}")); + } } /// Print final messages after all tests are complete. -pub fn finish(tests: &[TestInfo], total_elapsed: Duration, cfg: &Config) -> ExitCode { +pub fn finish_all(tests: &[TestInfo], total_elapsed: Duration, cfg: &Config) -> ExitCode { println!("\n\nResults:"); let mut failed_generators = 0; @@ -118,8 +143,9 @@ pub fn finish(tests: &[TestInfo], total_elapsed: Duration, cfg: &Config) -> Exit /// indicatif likes to eat panic messages. This workaround isn't ideal, but it improves things. /// . -pub fn set_panic_hook(drop_bars: Vec) { +pub fn set_panic_hook(drop_bars: &[ProgressBar]) { let hook = std::panic::take_hook(); + let drop_bars = drop_bars.to_owned(); std::panic::set_hook(Box::new(move |info| { for bar in &drop_bars { bar.abandon(); @@ -130,3 +156,13 @@ pub fn set_panic_hook(drop_bars: Vec) { hook(info); })); } + +/// Allow non-Debug items in a `derive(Debug)` struct`. +#[derive(Clone)] +struct NoDebug(T); + +impl fmt::Debug for NoDebug { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(type_name::()) + } +} diff --git a/src/etc/test-float-parse/src/validate.rs b/src/etc/test-float-parse/src/validate.rs index 1eb3699cfb9f7..6ca94a9378f25 100644 --- a/src/etc/test-float-parse/src/validate.rs +++ b/src/etc/test-float-parse/src/validate.rs @@ -9,7 +9,7 @@ use std::sync::LazyLock; use num::bigint::ToBigInt; use num::{BigInt, BigRational, FromPrimitive, Signed, ToPrimitive}; -use crate::{CheckFailure, Float, Int, Update}; +use crate::{CheckFailure, Float, Int}; /// Powers of two that we store for constants. Account for binary128 which has a 15-bit exponent. const POWERS_OF_TWO_RANGE: RangeInclusive = (-(2 << 15))..=(2 << 15); @@ -89,7 +89,7 @@ impl Constants { } /// Validate that a string parses correctly -pub fn validate(input: &str) -> Result<(), Update> { +pub fn validate(input: &str) -> Result<(), CheckError> { let parsed: F = input .parse() .unwrap_or_else(|e| panic!("parsing failed for {}: {e}. Input: {input}", type_name::())); @@ -118,10 +118,19 @@ pub enum FloatRes { }, } +#[derive(Clone, Debug)] +pub struct CheckError { + pub fail: CheckFailure, + /// String for which parsing was attempted. + pub input: Box, + /// The parsed & decomposed `FloatRes`, already stringified so we don't need generics here. + pub float_res: Box, +} + impl FloatRes { /// Given a known exact rational, check that this representation is accurate within the /// limits of the float representation. If not, construct a failure `Update` to send. - fn check(self, expected: Rational, input: &str) -> Result<(), Update> { + fn check(self, expected: Rational, input: &str) -> Result<(), CheckError> { let consts = F::constants(); // let bool_helper = |cond: bool, err| cond.then_some(()).ok_or(err); @@ -173,7 +182,7 @@ impl FloatRes { (Rational::Finite(r), FloatRes::Real { sig, exp }) => Self::validate_real(r, sig, exp), }; - res.map_err(|fail| Update::Failure { + res.map_err(|fail| CheckError { fail, input: input.into(), float_res: format!("{self:?}").into(), From 762fdf6636da708ab4dfc7578b9c26f8fa9079bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Wed, 26 Feb 2025 15:45:36 +0000 Subject: [PATCH 08/14] default to `-znostart-stop-gc` This will help stabilization of lld. --- compiler/rustc_codegen_ssa/src/back/link.rs | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index b090730ac6b51..3749c86d9899c 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -3434,6 +3434,32 @@ fn add_lld_args( // this, `wasm-component-ld`, which is overridden if this option is passed. if !sess.target.is_like_wasm { cmd.cc_arg("-fuse-ld=lld"); + + // GNU ld and LLD have opposite defaults on some section garbage-collection features. For + // example, the somewhat popular `linkme` crate and its dependents rely in practice on this + // difference: when using lld, they need `-z nostart-stop-gc` to prevent encapsulation + // symbols and sections from being garbage-collected. + // + // More information about all this can be found in: + // - https://maskray.me/blog/2021-01-31-metadata-sections-comdat-and-shf-link-order + // - https://lld.llvm.org/ELF/start-stop-gc + // + // So when using lld, we restore, for now, the traditional behavior to help migration, but + // will remove it in the future. + // Since this only disables an optimization, it shouldn't create issues, but is in theory + // slightly suboptimal. However, it: + // - doesn't have any visible impact on our benchmarks + // - reduces the need to disable lld for the crates that depend on this + // + // Note that lld can detect some cases where this difference is relied on, and emits a + // dedicated error to add this link arg. We could make use of this error to emit an FCW. As + // of writing this, we don't do it, because lld is already enabled by default on nightly + // without this mitigation: no working project would see the FCW, so we do this to help + // stabilization. + // + // FIXME: emit an FCW if linking fails due its absence, and then remove this link-arg in the + // future. + cmd.link_arg("-znostart-stop-gc"); } if !flavor.is_gnu() { From 1528cc2c6fb7f89c70b7a1334a97ef76e4cdc9f5 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Thu, 27 Feb 2025 08:46:27 +0000 Subject: [PATCH 09/14] =?UTF-8?q?`librustdoc`:=202024=20edition!=20?= =?UTF-8?q?=F0=9F=8E=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/librustdoc/Cargo.toml | 2 +- src/librustdoc/clean/cfg.rs | 4 ++-- src/librustdoc/clean/mod.rs | 4 ++-- src/librustdoc/clean/types.rs | 2 +- src/librustdoc/clean/utils.rs | 11 +++++----- src/librustdoc/html/format.rs | 2 +- src/librustdoc/html/render/mod.rs | 10 ++++----- src/librustdoc/html/render/print_item.rs | 26 ++++++++++++------------ src/librustdoc/html/sources.rs | 2 +- src/tools/rustdoc/Cargo.toml | 2 +- 10 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 91cc408878826..909b81a723b48 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustdoc" version = "0.0.0" -edition = "2021" +edition = "2024" build = "build.rs" [lib] diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index bec7fbe8f52bd..ab169f3c2a4d5 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -48,12 +48,12 @@ impl Cfg { exclude: &FxHashSet, ) -> Result, InvalidCfgError> { match nested_cfg { - MetaItemInner::MetaItem(ref cfg) => Cfg::parse_without(cfg, exclude), + MetaItemInner::MetaItem(cfg) => Cfg::parse_without(cfg, exclude), MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => match *b { true => Ok(Some(Cfg::True)), false => Ok(Some(Cfg::False)), }, - MetaItemInner::Lit(ref lit) => { + MetaItemInner::Lit(lit) => { Err(InvalidCfgError { msg: "unexpected literal", span: lit.span }) } } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index ceffe5e5ce04e..bb12e4a706e7d 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -741,7 +741,7 @@ pub(crate) fn clean_generics<'tcx>( for p in gens.params.iter().filter(|p| !is_impl_trait(p) && !is_elided_lifetime(p)) { let mut p = clean_generic_param(cx, Some(gens), p); match &mut p.kind { - GenericParamDefKind::Lifetime { ref mut outlives } => { + GenericParamDefKind::Lifetime { outlives } => { if let Some(region_pred) = region_predicates.get_mut(&Lifetime(p.name)) { // We merge bounds in the `where` clause. for outlive in outlives.drain(..) { @@ -2688,7 +2688,7 @@ fn filter_doc_attr_ident(ident: Symbol, is_inline: bool) -> bool { /// Before calling this function, make sure `normal` is a `#[doc]` attribute. fn filter_doc_attr(args: &mut hir::AttrArgs, is_inline: bool) { match args { - hir::AttrArgs::Delimited(ref mut args) => { + hir::AttrArgs::Delimited(args) => { let tokens = filter_tokens_from_list(&args.tokens, |token| { !matches!( token, diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 178b6a60b41f7..bd5fd612b38f5 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -502,7 +502,7 @@ impl Item { let Some(links) = cx.cache().intra_doc_links.get(&self.item_id) else { return vec![] }; links .iter() - .filter_map(|ItemLink { link: s, link_text, page_id: id, ref fragment }| { + .filter_map(|ItemLink { link: s, link_text, page_id: id, fragment }| { debug!(?id); if let Ok((mut href, ..)) = href(*id, cx) { debug!(?href); diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 34656b26ce28c..b71ff0c56f8ab 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -60,7 +60,7 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { let primitives = local_crate.primitives(cx.tcx); let keywords = local_crate.keywords(cx.tcx); { - let ItemKind::ModuleItem(ref mut m) = &mut module.inner.kind else { unreachable!() }; + let ItemKind::ModuleItem(m) = &mut module.inner.kind else { unreachable!() }; m.items.extend(primitives.iter().map(|&(def_id, prim)| { Item::from_def_id_and_parts( def_id, @@ -302,7 +302,7 @@ pub(crate) fn name_from_pat(p: &hir::Pat<'_>) -> Symbol { use rustc_hir::*; debug!("trying to get a name from pattern: {p:?}"); - Symbol::intern(&match p.kind { + Symbol::intern(&match &p.kind { // FIXME(never_patterns): does this make sense? PatKind::Wild | PatKind::Err(_) @@ -313,8 +313,9 @@ pub(crate) fn name_from_pat(p: &hir::Pat<'_>) -> Symbol { } PatKind::Binding(_, _, ident, _) => return ident.name, PatKind::Box(p) | PatKind::Ref(p, _) | PatKind::Guard(p, _) => return name_from_pat(p), - PatKind::TupleStruct(ref p, ..) - | PatKind::Expr(PatExpr { kind: PatExprKind::Path(ref p), .. }) => qpath_to_string(p), + PatKind::TupleStruct(p, ..) | PatKind::Expr(PatExpr { kind: PatExprKind::Path(p), .. }) => { + qpath_to_string(p) + } PatKind::Or(pats) => { fmt::from_fn(|f| pats.iter().map(|p| name_from_pat(p)).joined(" | ", f)).to_string() } @@ -493,7 +494,7 @@ pub(crate) fn resolve_type(cx: &mut DocContext<'_>, path: Path) -> Type { pub(crate) fn synthesize_auto_trait_and_blanket_impls( cx: &mut DocContext<'_>, item_def_id: DefId, -) -> impl Iterator { +) -> impl Iterator + use<> { let auto_impls = cx .sess() .prof diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 91b4b3ba1ebac..1cc7a0e3844db 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -80,7 +80,7 @@ impl clean::GenericParamDef { print_generic_bounds(bounds, cx).fmt(f)?; } - if let Some(ref ty) = default { + if let Some(ty) = default { f.write_str(" = ")?; ty.print(cx).fmt(f)?; } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 204631063a23a..f8d6619299143 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -814,7 +814,7 @@ fn assoc_href_attr<'a, 'tcx>( } let href = match link { - AssocItemLink::Anchor(Some(ref id)) => Href::AnchorId(id), + AssocItemLink::Anchor(Some(id)) => Href::AnchorId(id), AssocItemLink::Anchor(None) => Href::Anchor(item_type), AssocItemLink::GotoSource(did, provided_methods) => { // We're creating a link from the implementation of an associated item to its @@ -1168,7 +1168,7 @@ fn render_assoc_item( if parent == ItemType::Trait { 4 } else { 0 }, cx, ), - clean::RequiredAssocTypeItem(ref generics, ref bounds) => assoc_type( + clean::RequiredAssocTypeItem(generics, bounds) => assoc_type( w, item, generics, @@ -1178,7 +1178,7 @@ fn render_assoc_item( if parent == ItemType::Trait { 4 } else { 0 }, cx, ), - clean::AssocTypeItem(ref ty, ref bounds) => assoc_type( + clean::AssocTypeItem(ty, bounds) => assoc_type( w, item, &ty.generics, @@ -1773,7 +1773,7 @@ fn render_impl( w.push_str(""); } } - clean::RequiredAssocConstItem(ref generics, ref ty) => { + clean::RequiredAssocConstItem(generics, ty) => { let source_id = format!("{item_type}.{name}"); let id = cx.derive_id(&source_id); write_str( @@ -1827,7 +1827,7 @@ fn render_impl( ); w.push_str(""); } - clean::RequiredAssocTypeItem(ref generics, ref bounds) => { + clean::RequiredAssocTypeItem(generics, bounds) => { let source_id = format!("{item_type}.{name}"); let id = cx.derive_id(&source_id); write_str( diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index f320114703996..6a0cca31aa8f1 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -252,24 +252,24 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut String) item_vars.render_into(buf).unwrap(); match &item.kind { - clean::ModuleItem(ref m) => item_module(buf, cx, item, &m.items), - clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f, _) => { + clean::ModuleItem(m) => item_module(buf, cx, item, &m.items), + clean::FunctionItem(f) | clean::ForeignFunctionItem(f, _) => { item_function(buf, cx, item, f) } - clean::TraitItem(ref t) => item_trait(buf, cx, item, t), - clean::StructItem(ref s) => item_struct(buf, cx, item, s), - clean::UnionItem(ref s) => item_union(buf, cx, item, s), - clean::EnumItem(ref e) => item_enum(buf, cx, item, e), - clean::TypeAliasItem(ref t) => item_type_alias(buf, cx, item, t), - clean::MacroItem(ref m) => item_macro(buf, cx, item, m), - clean::ProcMacroItem(ref m) => item_proc_macro(buf, cx, item, m), + clean::TraitItem(t) => item_trait(buf, cx, item, t), + clean::StructItem(s) => item_struct(buf, cx, item, s), + clean::UnionItem(s) => item_union(buf, cx, item, s), + clean::EnumItem(e) => item_enum(buf, cx, item, e), + clean::TypeAliasItem(t) => item_type_alias(buf, cx, item, t), + clean::MacroItem(m) => item_macro(buf, cx, item, m), + clean::ProcMacroItem(m) => item_proc_macro(buf, cx, item, m), clean::PrimitiveItem(_) => item_primitive(buf, cx, item), - clean::StaticItem(ref i) => item_static(buf, cx, item, i, None), - clean::ForeignStaticItem(ref i, safety) => item_static(buf, cx, item, i, Some(*safety)), + clean::StaticItem(i) => item_static(buf, cx, item, i, None), + clean::ForeignStaticItem(i, safety) => item_static(buf, cx, item, i, Some(*safety)), clean::ConstantItem(ci) => item_constant(buf, cx, item, &ci.generics, &ci.type_, &ci.kind), clean::ForeignTypeItem => item_foreign_type(buf, cx, item), clean::KeywordItem => item_keyword(buf, cx, item), - clean::TraitAliasItem(ref ta) => item_trait_alias(buf, cx, item, ta), + clean::TraitAliasItem(ta) => item_trait_alias(buf, cx, item, ta), _ => { // We don't generate pages for any other type. unreachable!(); @@ -973,7 +973,7 @@ fn item_trait(w: &mut String, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra extern_crates.insert(did.krate); } match implementor.inner_impl().for_.without_borrowed_ref() { - clean::Type::Path { ref path } if !path.is_assoc_ty() => { + clean::Type::Path { path } if !path.is_assoc_ty() => { let did = path.def_id(); let &mut (prev_did, ref mut has_duplicates) = implementor_dups.entry(path.last()).or_insert((did, false)); diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index 78c86a27632b1..cbbd4b01d83ed 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -333,7 +333,7 @@ pub(crate) fn print_src( source_context: &SourceContext<'_>, ) { let mut lines = s.lines().count(); - let line_info = if let SourceContext::Embedded(ref info) = source_context { + let line_info = if let SourceContext::Embedded(info) = source_context { highlight::LineInfo::new_scraped(lines as u32, info.offset as u32) } else { highlight::LineInfo::new(lines as u32) diff --git a/src/tools/rustdoc/Cargo.toml b/src/tools/rustdoc/Cargo.toml index c4101f72cc2da..d1682758d3626 100644 --- a/src/tools/rustdoc/Cargo.toml +++ b/src/tools/rustdoc/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustdoc-tool" version = "0.0.0" -edition = "2021" +edition = "2024" # Cargo adds a number of paths to the dylib search path on windows, which results in # the wrong rustdoc being executed. To avoid the conflicting rustdocs, we name the "tool" From 7d263b0d00257a3047ecd1378aba3bc1b2f7954c Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Thu, 27 Feb 2025 11:18:25 +0000 Subject: [PATCH 10/14] Adapt `librustdoc` to 2024 edition lifetieme capture rules Get rid of the `Captures` hack --- .../rustc_data_structures/src/captures.rs | 8 - compiler/rustc_data_structures/src/lib.rs | 1 - src/librustdoc/clean/types.rs | 6 +- src/librustdoc/clean/utils.rs | 2 +- src/librustdoc/html/format.rs | 144 ++++++------------ src/librustdoc/html/render/mod.rs | 91 ++++++----- src/librustdoc/html/render/print_item.rs | 107 +++++-------- src/librustdoc/html/render/type_layout.rs | 6 +- .../passes/collect_intra_doc_links.rs | 2 +- 9 files changed, 130 insertions(+), 237 deletions(-) delete mode 100644 compiler/rustc_data_structures/src/captures.rs diff --git a/compiler/rustc_data_structures/src/captures.rs b/compiler/rustc_data_structures/src/captures.rs deleted file mode 100644 index 677ccb31454ea..0000000000000 --- a/compiler/rustc_data_structures/src/captures.rs +++ /dev/null @@ -1,8 +0,0 @@ -/// "Signaling" trait used in impl trait to tag lifetimes that you may -/// need to capture but don't really need for other reasons. -/// Basically a workaround; see [this comment] for details. -/// -/// [this comment]: https://github.com/rust-lang/rust/issues/34511#issuecomment-373423999 -pub trait Captures<'a> {} - -impl<'a, T: ?Sized> Captures<'a> for T {} diff --git a/compiler/rustc_data_structures/src/lib.rs b/compiler/rustc_data_structures/src/lib.rs index 66d3834d85784..a3b62b469196a 100644 --- a/compiler/rustc_data_structures/src/lib.rs +++ b/compiler/rustc_data_structures/src/lib.rs @@ -48,7 +48,6 @@ pub use rustc_index::static_assert_size; pub mod aligned; pub mod base_n; pub mod binary_search_util; -pub mod captures; pub mod fingerprint; pub mod flat_map_in_place; pub mod flock; diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index bd5fd612b38f5..5906a720e0fd3 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1150,7 +1150,7 @@ pub(crate) struct Attributes { } impl Attributes { - pub(crate) fn lists(&self, name: Symbol) -> impl Iterator + '_ { + pub(crate) fn lists(&self, name: Symbol) -> impl Iterator { hir_attr_lists(&self.other_attrs[..], name) } @@ -1864,7 +1864,7 @@ impl PrimitiveType { .copied() } - pub(crate) fn all_impls(tcx: TyCtxt<'_>) -> impl Iterator + '_ { + pub(crate) fn all_impls(tcx: TyCtxt<'_>) -> impl Iterator { Self::simplified_types() .values() .flatten() @@ -2259,7 +2259,7 @@ impl GenericArgs { GenericArgs::Parenthesized { inputs, output } => inputs.is_empty() && output.is_none(), } } - pub(crate) fn constraints<'a>(&'a self) -> Box + 'a> { + pub(crate) fn constraints(&self) -> Box + '_> { match self { GenericArgs::AngleBracketed { constraints, .. } => { Box::new(constraints.iter().cloned()) diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index b71ff0c56f8ab..a284de5229a21 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -330,7 +330,7 @@ pub(crate) fn name_from_pat(p: &hir::Pat<'_>) -> Symbol { return Symbol::intern("()"); } PatKind::Slice(begin, mid, end) => { - fn print_pat<'a>(pat: &'a Pat<'a>, wild: bool) -> impl Display + 'a { + fn print_pat(pat: &Pat<'_>, wild: bool) -> impl Display { fmt::from_fn(move |f| { if wild { f.write_str("..")?; diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 1cc7a0e3844db..3b481acd410ae 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -15,7 +15,6 @@ use std::iter::{self, once}; use itertools::Either; use rustc_abi::ExternAbi; use rustc_attr_parsing::{ConstStability, StabilityLevel, StableSince}; -use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::def::DefKind; @@ -41,10 +40,10 @@ pub(crate) fn write_str(s: &mut String, f: fmt::Arguments<'_>) { s.write_fmt(f).unwrap(); } -pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>( - bounds: &'a [clean::GenericBound], - cx: &'a Context<'tcx>, -) -> impl Display + 'a + Captures<'tcx> { +pub(crate) fn print_generic_bounds( + bounds: &[clean::GenericBound], + cx: &Context<'_>, +) -> impl Display { fmt::from_fn(move |f| { let mut bounds_dup = FxHashSet::default(); @@ -57,10 +56,7 @@ pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>( } impl clean::GenericParamDef { - pub(crate) fn print<'a, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| match &self.kind { clean::GenericParamDefKind::Lifetime { outlives } => { write!(f, "{}", self.name)?; @@ -107,10 +103,7 @@ impl clean::GenericParamDef { } impl clean::Generics { - pub(crate) fn print<'a, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| { let mut real_params = self.params.iter().filter(|p| !p.is_synthetic_param()).peekable(); if real_params.peek().is_none() { @@ -134,10 +127,7 @@ pub(crate) enum Ending { NoNewline, } -fn print_where_predicate<'a, 'tcx: 'a>( - predicate: &'a clean::WherePredicate, - cx: &'a Context<'tcx>, -) -> impl Display + 'a + Captures<'tcx> { +fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| { match predicate { clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => { @@ -173,12 +163,12 @@ fn print_where_predicate<'a, 'tcx: 'a>( /// * The Generics from which to emit a where-clause. /// * The number of spaces to indent each line with. /// * Whether the where-clause needs to add a comma and newline after the last bound. -pub(crate) fn print_where_clause<'a, 'tcx: 'a>( - gens: &'a clean::Generics, - cx: &'a Context<'tcx>, +pub(crate) fn print_where_clause( + gens: &clean::Generics, + cx: &Context<'_>, indent: usize, ending: Ending, -) -> impl Display + 'a + Captures<'tcx> { +) -> impl Display { fmt::from_fn(move |f| { if gens.where_predicates.is_empty() { return Ok(()); @@ -250,13 +240,13 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( } impl clean::Lifetime { - pub(crate) fn print(&self) -> impl Display + '_ { + pub(crate) fn print(&self) -> impl Display { self.0.as_str() } } impl clean::ConstantKind { - pub(crate) fn print(&self, tcx: TyCtxt<'_>) -> impl Display + '_ { + pub(crate) fn print(&self, tcx: TyCtxt<'_>) -> impl Display { let expr = self.expr(tcx); fmt::from_fn(move |f| { if f.alternate() { f.write_str(&expr) } else { write!(f, "{}", Escape(&expr)) } @@ -265,7 +255,7 @@ impl clean::ConstantKind { } impl clean::PolyTrait { - fn print<'a, 'tcx: 'a>(&'a self, cx: &'a Context<'tcx>) -> impl Display + 'a + Captures<'tcx> { + fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| { print_higher_ranked_params_with_space(&self.generic_params, cx, "for").fmt(f)?; self.trait_.print(cx).fmt(f) @@ -274,10 +264,7 @@ impl clean::PolyTrait { } impl clean::GenericBound { - pub(crate) fn print<'a, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| match self { clean::GenericBound::Outlives(lt) => write!(f, "{}", lt.print()), clean::GenericBound::TraitBound(ty, modifiers) => { @@ -304,7 +291,7 @@ impl clean::GenericBound { } impl clean::GenericArgs { - fn print<'a, 'tcx: 'a>(&'a self, cx: &'a Context<'tcx>) -> impl Display + 'a + Captures<'tcx> { + fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| { match self { clean::GenericArgs::AngleBracketed { args, constraints } => { @@ -809,11 +796,11 @@ fn primitive_link_fragment( Ok(()) } -fn tybounds<'a, 'tcx: 'a>( - bounds: &'a [clean::PolyTrait], - lt: &'a Option, - cx: &'a Context<'tcx>, -) -> impl Display + 'a + Captures<'tcx> { +fn tybounds( + bounds: &[clean::PolyTrait], + lt: &Option, + cx: &Context<'_>, +) -> impl Display { fmt::from_fn(move |f| { bounds.iter().map(|bound| bound.print(cx)).joined(" + ", f)?; if let Some(lt) = lt { @@ -825,11 +812,11 @@ fn tybounds<'a, 'tcx: 'a>( }) } -fn print_higher_ranked_params_with_space<'a, 'tcx: 'a>( - params: &'a [clean::GenericParamDef], - cx: &'a Context<'tcx>, +fn print_higher_ranked_params_with_space( + params: &[clean::GenericParamDef], + cx: &Context<'_>, keyword: &'static str, -) -> impl Display + 'a + Captures<'tcx> { +) -> impl Display { fmt::from_fn(move |f| { if !params.is_empty() { f.write_str(keyword)?; @@ -841,11 +828,7 @@ fn print_higher_ranked_params_with_space<'a, 'tcx: 'a>( }) } -pub(crate) fn anchor<'a: 'cx, 'cx>( - did: DefId, - text: Symbol, - cx: &'cx Context<'a>, -) -> impl Display + Captures<'a> + 'cx { +pub(crate) fn anchor(did: DefId, text: Symbol, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| { let parts = href(did, cx); if let Ok((url, short_ty, fqp)) = parts { @@ -1121,29 +1104,19 @@ fn fmt_type( } impl clean::Type { - pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'b + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| fmt_type(self, f, false, cx)) } } impl clean::Path { - pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'b + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| resolved_path(f, self.def_id(), self, false, false, cx)) } } impl clean::Impl { - pub(crate) fn print<'a, 'tcx: 'a>( - &'a self, - use_absolute: bool, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + pub(crate) fn print(&self, use_absolute: bool, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| { f.write_str("impl")?; self.generics.print(cx).fmt(f)?; @@ -1182,12 +1155,12 @@ impl clean::Impl { print_where_clause(&self.generics, cx, 0, Ending::Newline).fmt(f) }) } - fn print_type<'a, 'tcx: 'a>( + fn print_type( &self, type_: &clean::Type, f: &mut fmt::Formatter<'_>, use_absolute: bool, - cx: &'a Context<'tcx>, + cx: &Context<'_>, ) -> Result<(), fmt::Error> { if let clean::Type::Tuple(types) = type_ && let [clean::Type::Generic(name)] = &types[..] @@ -1258,10 +1231,7 @@ impl clean::Impl { } impl clean::Arguments { - pub(crate) fn print<'a, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| { self.values .iter() @@ -1301,10 +1271,7 @@ impl Display for Indent { } impl clean::FnDecl { - pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'b + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| { let ellipsis = if self.c_variadic { ", ..." } else { "" }; if f.alternate() { @@ -1333,12 +1300,12 @@ impl clean::FnDecl { /// are preserved. /// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is /// necessary. - pub(crate) fn full_print<'a, 'tcx: 'a>( - &'a self, + pub(crate) fn full_print( + &self, header_len: usize, indent: usize, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + cx: &Context<'_>, + ) -> impl Display { fmt::from_fn(move |f| { // First, generate the text form of the declaration, with no line wrapping, and count the bytes. let mut counter = WriteCounter(0); @@ -1420,10 +1387,7 @@ impl clean::FnDecl { self.print_output(cx).fmt(f) } - fn print_output<'a, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + fn print_output(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| match &self.output { clean::Tuple(tys) if tys.is_empty() => Ok(()), ty if f.alternate() => { @@ -1434,10 +1398,7 @@ impl clean::FnDecl { } } -pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>( - item: &clean::Item, - cx: &'a Context<'tcx>, -) -> impl Display + 'a + Captures<'tcx> { +pub(crate) fn visibility_print_with_space(item: &clean::Item, cx: &Context<'_>) -> impl Display { use std::fmt::Write as _; let vis: Cow<'static, str> = match item.visibility(cx.tcx()) { None => "".into(), @@ -1546,10 +1507,7 @@ pub(crate) fn print_constness_with_space( } impl clean::Import { - pub(crate) fn print<'a, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| match self.kind { clean::ImportKind::Simple(name) => { if name == self.source.path.last() { @@ -1570,10 +1528,7 @@ impl clean::Import { } impl clean::ImportSource { - pub(crate) fn print<'a, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| match self.did { Some(did) => resolved_path(f, did, &self.path, true, false, cx), _ => { @@ -1593,10 +1548,7 @@ impl clean::ImportSource { } impl clean::AssocItemConstraint { - pub(crate) fn print<'a, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| { f.write_str(self.assoc.name.as_str())?; self.assoc.args.print(cx).fmt(f)?; @@ -1627,15 +1579,12 @@ pub(crate) fn print_abi_with_space(abi: ExternAbi) -> impl Display { }) } -pub(crate) fn print_default_space<'a>(v: bool) -> &'a str { +pub(crate) fn print_default_space(v: bool) -> &'static str { if v { "default " } else { "" } } impl clean::GenericArg { - pub(crate) fn print<'a, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| match self { clean::GenericArg::Lifetime(lt) => lt.print().fmt(f), clean::GenericArg::Type(ty) => ty.print(cx).fmt(f), @@ -1646,10 +1595,7 @@ impl clean::GenericArg { } impl clean::Term { - pub(crate) fn print<'a, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl Display + 'a + Captures<'tcx> { + pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| match self { clean::Term::Type(ty) => ty.print(cx).fmt(f), clean::Term::Constant(ct) => ct.print(cx.tcx()).fmt(f), diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index f8d6619299143..620135d37abb3 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -47,7 +47,6 @@ use rinja::Template; use rustc_attr_parsing::{ ConstStability, DeprecatedSince, Deprecation, RustcVersion, StabilityLevel, StableSince, }; -use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::Mutability; use rustc_hir::def_id::{DefId, DefIdSet}; @@ -82,7 +81,7 @@ use crate::html::{highlight, sources}; use crate::scrape_examples::{CallData, CallLocation}; use crate::{DOC_RUST_LANG_ORG_VERSION, try_none}; -pub(crate) fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ { +pub(crate) fn ensure_trailing_slash(v: &str) -> impl fmt::Display { fmt::from_fn(move |f| { if !v.ends_with('/') && !v.is_empty() { write!(f, "{v}/") } else { f.write_str(v) } }) @@ -310,7 +309,7 @@ impl ItemEntry { } impl ItemEntry { - pub(crate) fn print(&self) -> impl fmt::Display + '_ { + pub(crate) fn print(&self) -> impl fmt::Display { fmt::from_fn(move |f| write!(f, "{}", self.url, Escape(&self.name))) } } @@ -505,12 +504,12 @@ fn scrape_examples_help(shared: &SharedContext<'_>) -> String { ) } -fn document<'a, 'cx: 'a>( - cx: &'a Context<'cx>, - item: &'a clean::Item, - parent: Option<&'a clean::Item>, +fn document( + cx: &Context<'_>, + item: &clean::Item, + parent: Option<&clean::Item>, heading_offset: HeadingOffset, -) -> impl fmt::Display + 'a + Captures<'cx> { +) -> impl fmt::Display { if let Some(ref name) = item.name { info!("Documenting {name}"); } @@ -526,12 +525,12 @@ fn document<'a, 'cx: 'a>( } /// Render md_text as markdown. -fn render_markdown<'a, 'cx: 'a>( - cx: &'a Context<'cx>, - md_text: &'a str, +fn render_markdown( + cx: &Context<'_>, + md_text: &str, links: Vec, heading_offset: HeadingOffset, -) -> impl fmt::Display + 'a + Captures<'cx> { +) -> impl fmt::Display { fmt::from_fn(move |f| { write!( f, @@ -552,13 +551,13 @@ fn render_markdown<'a, 'cx: 'a>( /// Writes a documentation block containing only the first paragraph of the documentation. If the /// docs are longer, a "Read more" link is appended to the end. -fn document_short<'a, 'cx: 'a>( - item: &'a clean::Item, - cx: &'a Context<'cx>, - link: AssocItemLink<'a>, - parent: &'a clean::Item, +fn document_short( + item: &clean::Item, + cx: &Context<'_>, + link: AssocItemLink<'_>, + parent: &clean::Item, show_def_docs: bool, -) -> impl fmt::Display + 'a + Captures<'cx> { +) -> impl fmt::Display { fmt::from_fn(move |f| { document_item_info(cx, item, Some(parent)).render_into(f).unwrap(); if !show_def_docs { @@ -595,28 +594,28 @@ fn document_short<'a, 'cx: 'a>( }) } -fn document_full_collapsible<'a, 'cx: 'a>( - item: &'a clean::Item, - cx: &'a Context<'cx>, +fn document_full_collapsible( + item: &clean::Item, + cx: &Context<'_>, heading_offset: HeadingOffset, -) -> impl fmt::Display + 'a + Captures<'cx> { +) -> impl fmt::Display { document_full_inner(item, cx, true, heading_offset) } -fn document_full<'a, 'cx: 'a>( - item: &'a clean::Item, - cx: &'a Context<'cx>, +fn document_full( + item: &clean::Item, + cx: &Context<'_>, heading_offset: HeadingOffset, -) -> impl fmt::Display + 'a + Captures<'cx> { +) -> impl fmt::Display { document_full_inner(item, cx, false, heading_offset) } -fn document_full_inner<'a, 'cx: 'a>( - item: &'a clean::Item, - cx: &'a Context<'cx>, +fn document_full_inner( + item: &clean::Item, + cx: &Context<'_>, is_collapsible: bool, heading_offset: HeadingOffset, -) -> impl fmt::Display + 'a + Captures<'cx> { +) -> impl fmt::Display { fmt::from_fn(move |f| { if let Some(s) = item.opt_doc_value() { debug!("Doc block: =====\n{s}\n====="); @@ -799,11 +798,11 @@ pub(crate) fn render_impls( } /// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item. -fn assoc_href_attr<'a, 'tcx>( +fn assoc_href_attr( it: &clean::Item, - link: AssocItemLink<'a>, - cx: &Context<'tcx>, -) -> Option> { + link: AssocItemLink<'_>, + cx: &Context<'_>, +) -> Option { let name = it.name.unwrap(); let item_type = it.type_(); @@ -1194,11 +1193,7 @@ fn render_assoc_item( // When an attribute is rendered inside a `
` tag, it is formatted using
 // a whitespace prefix and newline.
-fn render_attributes_in_pre<'a, 'tcx: 'a>(
-    it: &'a clean::Item,
-    prefix: &'a str,
-    cx: &'a Context<'tcx>,
-) -> impl fmt::Display + Captures<'a> + Captures<'tcx> {
+fn render_attributes_in_pre(it: &clean::Item, prefix: &str, cx: &Context<'_>) -> impl fmt::Display {
     fmt::from_fn(move |f| {
         for a in it.attributes(cx.tcx(), cx.cache(), false) {
             writeln!(f, "{prefix}{a}")?;
@@ -1292,12 +1287,12 @@ pub(crate) fn render_all_impls(
     }
 }
 
-fn render_assoc_items<'a, 'cx: 'a>(
-    cx: &'a Context<'cx>,
-    containing_item: &'a clean::Item,
+fn render_assoc_items(
+    cx: &Context<'_>,
+    containing_item: &clean::Item,
     it: DefId,
-    what: AssocItemRender<'a>,
-) -> impl fmt::Display + 'a + Captures<'cx> {
+    what: AssocItemRender<'_>,
+) -> impl fmt::Display {
     fmt::from_fn(move |f| {
         let mut derefs = DefIdSet::default();
         derefs.insert(it);
@@ -1468,10 +1463,10 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) ->
     }
 }
 
-pub(crate) fn notable_traits_button<'a, 'tcx>(
-    ty: &'a clean::Type,
-    cx: &'a Context<'tcx>,
-) -> Option> {
+pub(crate) fn notable_traits_button(
+    ty: &clean::Type,
+    cx: &Context<'_>,
+) -> Option {
     let mut has_notable_trait = false;
 
     if ty.is_unit() {
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 6a0cca31aa8f1..f04fbd992accc 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -4,7 +4,6 @@ use std::fmt::Display;
 
 use rinja::Template;
 use rustc_abi::VariantIdx;
-use rustc_data_structures::captures::Captures;
 use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
 use rustc_hir as hir;
 use rustc_hir::def::CtorKind;
@@ -92,44 +91,32 @@ macro_rules! item_template {
 macro_rules! item_template_methods {
     () => {};
     (document $($rest:tt)*) => {
-        fn document<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
-            fmt::from_fn(move |f| {
-                let (item, cx) = self.item_and_cx();
-                let v = document(cx, item, None, HeadingOffset::H2);
-                write!(f, "{v}")
-            })
+        fn document(&self) -> impl fmt::Display {
+            let (item, cx) = self.item_and_cx();
+            document(cx, item, None, HeadingOffset::H2)
         }
         item_template_methods!($($rest)*);
     };
     (document_type_layout $($rest:tt)*) => {
-        fn document_type_layout<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
-            fmt::from_fn(move |f| {
-                let (item, cx) = self.item_and_cx();
-                let def_id = item.item_id.expect_def_id();
-                let v = document_type_layout(cx, def_id);
-                write!(f, "{v}")
-            })
+        fn document_type_layout(&self) -> impl fmt::Display {
+            let (item, cx) = self.item_and_cx();
+            let def_id = item.item_id.expect_def_id();
+            document_type_layout(cx, def_id)
         }
         item_template_methods!($($rest)*);
     };
     (render_attributes_in_pre $($rest:tt)*) => {
-        fn render_attributes_in_pre<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
-            fmt::from_fn(move |f| {
-                let (item, cx) = self.item_and_cx();
-                let v = render_attributes_in_pre(item, "", cx);
-                write!(f, "{v}")
-            })
+        fn render_attributes_in_pre(&self) -> impl fmt::Display {
+            let (item, cx) = self.item_and_cx();
+            render_attributes_in_pre(item, "", cx)
         }
         item_template_methods!($($rest)*);
     };
     (render_assoc_items $($rest:tt)*) => {
-        fn render_assoc_items<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
-            fmt::from_fn(move |f| {
-                let (item, cx) = self.item_and_cx();
-                let def_id = item.item_id.expect_def_id();
-                let v = render_assoc_items(cx, item, def_id, AssocItemRender::All);
-                write!(f, "{v}")
-            })
+        fn render_assoc_items(&self) -> impl fmt::Display {
+            let (item, cx) = self.item_and_cx();
+            let def_id = item.item_id.expect_def_id();
+            render_assoc_items(cx, item, def_id, AssocItemRender::All)
         }
         item_template_methods!($($rest)*);
     };
@@ -527,14 +514,14 @@ fn item_module(w: &mut String, cx: &Context<'_>, item: &clean::Item, items: &[cl
 
 /// Render the stability, deprecation and portability tags that are displayed in the item's summary
 /// at the module level.
-fn extra_info_tags<'a, 'tcx: 'a>(
-    tcx: TyCtxt<'tcx>,
-    item: &'a clean::Item,
-    parent: &'a clean::Item,
+fn extra_info_tags(
+    tcx: TyCtxt<'_>,
+    item: &clean::Item,
+    parent: &clean::Item,
     import_def_id: Option,
-) -> impl Display + 'a + Captures<'tcx> {
+) -> impl Display {
     fmt::from_fn(move |f| {
-        fn tag_html<'a>(class: &'a str, title: &'a str, contents: &'a str) -> impl Display + 'a {
+        fn tag_html(class: &str, title: &str, contents: &str) -> impl Display {
             fmt::from_fn(move |f| {
                 write!(
                     f,
@@ -1419,35 +1406,20 @@ fn item_union(w: &mut String, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
     );
 
     impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
-        fn render_union<'b>(&'b self) -> impl Display + Captures<'a> + 'b + Captures<'cx> {
-            fmt::from_fn(move |f| {
-                let v = render_union(self.it, Some(&self.s.generics), &self.s.fields, self.cx);
-                write!(f, "{v}")
-            })
+        fn render_union(&self) -> impl Display {
+            render_union(self.it, Some(&self.s.generics), &self.s.fields, self.cx)
         }
 
-        fn document_field<'b>(
-            &'b self,
-            field: &'a clean::Item,
-        ) -> impl Display + Captures<'a> + 'b + Captures<'cx> {
-            fmt::from_fn(move |f| {
-                let v = document(self.cx, field, Some(self.it), HeadingOffset::H3);
-                write!(f, "{v}")
-            })
+        fn document_field(&self, field: &'a clean::Item) -> impl Display {
+            document(self.cx, field, Some(self.it), HeadingOffset::H3)
         }
 
         fn stability_field(&self, field: &clean::Item) -> Option {
             field.stability_class(self.cx.tcx())
         }
 
-        fn print_ty<'b>(
-            &'b self,
-            ty: &'a clean::Type,
-        ) -> impl Display + Captures<'a> + 'b + Captures<'cx> {
-            fmt::from_fn(move |f| {
-                let v = ty.print(self.cx);
-                write!(f, "{v}")
-            })
+        fn print_ty(&self, ty: &'a clean::Type) -> impl Display {
+            ty.print(self.cx)
         }
 
         fn fields_iter(
@@ -1467,10 +1439,7 @@ fn item_union(w: &mut String, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
     ItemUnion { cx, it, s }.render_into(w).unwrap();
 }
 
-fn print_tuple_struct_fields<'a, 'cx: 'a>(
-    cx: &'a Context<'cx>,
-    s: &'a [clean::Item],
-) -> impl Display + 'a + Captures<'cx> {
+fn print_tuple_struct_fields(cx: &Context<'_>, s: &[clean::Item]) -> impl Display {
     fmt::from_fn(|f| {
         if !s.is_empty()
             && s.iter().all(|field| {
@@ -2111,18 +2080,14 @@ pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String {
     s
 }
 
-pub(super) fn item_path(ty: ItemType, name: &str) -> impl Display + '_ {
+pub(super) fn item_path(ty: ItemType, name: &str) -> impl Display {
     fmt::from_fn(move |f| match ty {
         ItemType::Module => write!(f, "{}index.html", ensure_trailing_slash(name)),
         _ => write!(f, "{ty}.{name}.html"),
     })
 }
 
-fn bounds<'a, 'tcx>(
-    bounds: &'a [clean::GenericBound],
-    trait_alias: bool,
-    cx: &'a Context<'tcx>,
-) -> impl Display + 'a + Captures<'tcx> {
+fn bounds(bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>) -> impl Display {
     (!bounds.is_empty())
         .then_some(fmt::from_fn(move |f| {
             let has_lots_of_bounds = bounds.len() > 2;
@@ -2208,12 +2173,12 @@ fn render_implementor(
     );
 }
 
-fn render_union<'a, 'cx: 'a>(
-    it: &'a clean::Item,
-    g: Option<&'a clean::Generics>,
-    fields: &'a [clean::Item],
-    cx: &'a Context<'cx>,
-) -> impl Display + 'a + Captures<'cx> {
+fn render_union(
+    it: &clean::Item,
+    g: Option<&clean::Generics>,
+    fields: &[clean::Item],
+    cx: &Context<'_>,
+) -> impl Display {
     fmt::from_fn(move |mut f| {
         write!(f, "{}union {}", visibility_print_with_space(it, cx), it.name.unwrap(),)?;
 
@@ -2410,7 +2375,7 @@ fn document_non_exhaustive_header(item: &clean::Item) -> &str {
     if item.is_non_exhaustive() { " (Non-exhaustive)" } else { "" }
 }
 
-fn document_non_exhaustive(item: &clean::Item) -> impl Display + '_ {
+fn document_non_exhaustive(item: &clean::Item) -> impl Display {
     fmt::from_fn(|f| {
         if item.is_non_exhaustive() {
             write!(
diff --git a/src/librustdoc/html/render/type_layout.rs b/src/librustdoc/html/render/type_layout.rs
index 0f01db5f6bcc7..a1ee5c8c548b7 100644
--- a/src/librustdoc/html/render/type_layout.rs
+++ b/src/librustdoc/html/render/type_layout.rs
@@ -2,7 +2,6 @@ use std::fmt;
 
 use rinja::Template;
 use rustc_abi::{Primitive, TagEncoding, Variants};
-use rustc_data_structures::captures::Captures;
 use rustc_hir::def_id::DefId;
 use rustc_middle::span_bug;
 use rustc_middle::ty::layout::LayoutError;
@@ -26,10 +25,7 @@ struct TypeLayoutSize {
     size: u64,
 }
 
-pub(crate) fn document_type_layout<'a, 'cx: 'a>(
-    cx: &'a Context<'cx>,
-    ty_def_id: DefId,
-) -> impl fmt::Display + 'a + Captures<'cx> {
+pub(crate) fn document_type_layout(cx: &Context<'_>, ty_def_id: DefId) -> impl fmt::Display {
     fmt::from_fn(move |f| {
         if !cx.shared.show_type_layout {
             return Ok(());
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 97e6d3146427c..440d6331457b0 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -58,7 +58,7 @@ fn filter_assoc_items_by_name_and_namespace(
     assoc_items_of: DefId,
     ident: Ident,
     ns: Namespace,
-) -> impl Iterator + '_ {
+) -> impl Iterator {
     tcx.associated_items(assoc_items_of).filter_by_name_unhygienic(ident.name).filter(move |item| {
         item.kind.namespace() == ns && tcx.hygienic_eq(ident, item.ident(tcx), assoc_items_of)
     })

From 396c2a8659b0b99254a25b7c0431a99d8223fb5c Mon Sep 17 00:00:00 2001
From: Josh Stone 
Date: Thu, 27 Feb 2025 09:09:52 -0800
Subject: [PATCH 11/14] Stop using `hash_raw_entry` in `CodegenCx::const_str`

That unstable feature completed fcp-close, so the compiler needs to be
migrated away to allow its removal. In this case, `cg_llvm` and `cg_gcc`
were using raw entries to optimize their `const_str_cache` lookup and
insertion. We can change that to separate `get` and (on miss) `insert`
calls, so we still have the fast path avoiding string allocation when
the cache hits.
---
 compiler/rustc_codegen_gcc/src/common.rs  | 13 ++++----
 compiler/rustc_codegen_gcc/src/lib.rs     |  2 +-
 compiler/rustc_codegen_llvm/src/common.rs | 40 ++++++++++-------------
 compiler/rustc_codegen_llvm/src/lib.rs    |  1 -
 4 files changed, 25 insertions(+), 31 deletions(-)

diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs
index 20a3482aaa27f..ce1a200886478 100644
--- a/compiler/rustc_codegen_gcc/src/common.rs
+++ b/compiler/rustc_codegen_gcc/src/common.rs
@@ -146,13 +146,12 @@ impl<'gcc, 'tcx> ConstCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> {
     }
 
     fn const_str(&self, s: &str) -> (RValue<'gcc>, RValue<'gcc>) {
-        let str_global = *self
-            .const_str_cache
-            .borrow_mut()
-            .raw_entry_mut()
-            .from_key(s)
-            .or_insert_with(|| (s.to_owned(), self.global_string(s)))
-            .1;
+        let mut const_str_cache = self.const_str_cache.borrow_mut();
+        let str_global = const_str_cache.get(s).copied().unwrap_or_else(|| {
+            let g = self.global_string(s);
+            const_str_cache.insert(s.to_owned(), g);
+            g
+        });
         let len = s.len();
         let cs = self.const_ptrcast(
             str_global.get_address(None),
diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs
index 6455bcec6851b..7413868767901 100644
--- a/compiler/rustc_codegen_gcc/src/lib.rs
+++ b/compiler/rustc_codegen_gcc/src/lib.rs
@@ -16,7 +16,7 @@
 #![allow(internal_features)]
 #![doc(rust_logo)]
 #![feature(rustdoc_internals)]
-#![feature(rustc_private, decl_macro, never_type, trusted_len, hash_raw_entry, let_chains)]
+#![feature(rustc_private, decl_macro, never_type, trusted_len, let_chains)]
 #![allow(broken_intra_doc_links)]
 #![recursion_limit = "256"]
 #![warn(rust_2018_idioms)]
diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs
index 0621b893e7550..6eaecfa87a9c2 100644
--- a/compiler/rustc_codegen_llvm/src/common.rs
+++ b/compiler/rustc_codegen_llvm/src/common.rs
@@ -209,28 +209,24 @@ impl<'ll, 'tcx> ConstCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> {
     }
 
     fn const_str(&self, s: &str) -> (&'ll Value, &'ll Value) {
-        let str_global = *self
-            .const_str_cache
-            .borrow_mut()
-            .raw_entry_mut()
-            .from_key(s)
-            .or_insert_with(|| {
-                let sc = self.const_bytes(s.as_bytes());
-                let sym = self.generate_local_symbol_name("str");
-                let g = self.define_global(&sym, self.val_ty(sc)).unwrap_or_else(|| {
-                    bug!("symbol `{}` is already defined", sym);
-                });
-                llvm::set_initializer(g, sc);
-                unsafe {
-                    llvm::LLVMSetGlobalConstant(g, True);
-                    llvm::LLVMSetUnnamedAddress(g, llvm::UnnamedAddr::Global);
-                }
-                llvm::set_linkage(g, llvm::Linkage::InternalLinkage);
-                // Cast to default address space if globals are in a different addrspace
-                let g = self.const_pointercast(g, self.type_ptr());
-                (s.to_owned(), g)
-            })
-            .1;
+        let mut const_str_cache = self.const_str_cache.borrow_mut();
+        let str_global = const_str_cache.get(s).copied().unwrap_or_else(|| {
+            let sc = self.const_bytes(s.as_bytes());
+            let sym = self.generate_local_symbol_name("str");
+            let g = self.define_global(&sym, self.val_ty(sc)).unwrap_or_else(|| {
+                bug!("symbol `{}` is already defined", sym);
+            });
+            llvm::set_initializer(g, sc);
+            unsafe {
+                llvm::LLVMSetGlobalConstant(g, True);
+                llvm::LLVMSetUnnamedAddress(g, llvm::UnnamedAddr::Global);
+            }
+            llvm::set_linkage(g, llvm::Linkage::InternalLinkage);
+            // Cast to default address space if globals are in a different addrspace
+            let g = self.const_pointercast(g, self.type_ptr());
+            const_str_cache.insert(s.to_owned(), g);
+            g
+        });
         let len = s.len();
         (str_global, self.const_usize(len as u64))
     }
diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs
index e9e1b644f183e..929111b493ee8 100644
--- a/compiler/rustc_codegen_llvm/src/lib.rs
+++ b/compiler/rustc_codegen_llvm/src/lib.rs
@@ -12,7 +12,6 @@
 #![feature(exact_size_is_empty)]
 #![feature(extern_types)]
 #![feature(file_buffered)]
-#![feature(hash_raw_entry)]
 #![feature(if_let_guard)]
 #![feature(impl_trait_in_assoc_type)]
 #![feature(iter_intersperse)]

From ea13ff722db1f2382dcdfaf0cdd7c2d73da0f097 Mon Sep 17 00:00:00 2001
From: bit-aloo 
Date: Sun, 16 Feb 2025 23:24:29 +0530
Subject: [PATCH 12/14] add exclude to config.toml

---
 config.example.toml                     |  4 +++
 src/bootstrap/src/core/config/config.rs | 38 ++++++++++++++-----------
 2 files changed, 26 insertions(+), 16 deletions(-)

diff --git a/config.example.toml b/config.example.toml
index 2e26c024865db..d27633423a935 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -425,6 +425,10 @@
 # a specific version.
 #ccache = false
 
+# List of paths to exclude from the build and test processes. 
+# For example, exclude = ["tests/ui", "src/tools/tidy"].
+#exclude = []
+
 # =============================================================================
 # General install configuration options
 # =============================================================================
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index d0e0ed50ad89f..0791604dc034d 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -942,6 +942,7 @@ define_config! {
         jobs: Option = "jobs",
         compiletest_diff_tool: Option = "compiletest-diff-tool",
         ccache: Option = "ccache",
+        exclude: Option> = "exclude",
     }
 }
 
@@ -1372,22 +1373,6 @@ impl Config {
             "flags.exclude" = ?flags.exclude
         );
 
-        config.skip = flags
-            .skip
-            .into_iter()
-            .chain(flags.exclude)
-            .map(|p| {
-                // Never return top-level path here as it would break `--skip`
-                // logic on rustc's internal test framework which is utilized
-                // by compiletest.
-                if cfg!(windows) {
-                    PathBuf::from(p.to_str().unwrap().replace('/', "\\"))
-                } else {
-                    p
-                }
-            })
-            .collect();
-
         #[cfg(feature = "tracing")]
         span!(
             target: "CONFIG_HANDLING",
@@ -1632,8 +1617,29 @@ impl Config {
             jobs,
             compiletest_diff_tool,
             mut ccache,
+            exclude,
         } = toml.build.unwrap_or_default();
 
+        let mut paths: Vec = flags.skip.into_iter().chain(flags.exclude).collect();
+
+        if let Some(exclude) = exclude {
+            paths.extend(exclude);
+        }
+
+        config.skip = paths
+            .into_iter()
+            .map(|p| {
+                // Never return top-level path here as it would break `--skip`
+                // logic on rustc's internal test framework which is utilized
+                // by compiletest.
+                if cfg!(windows) {
+                    PathBuf::from(p.to_str().unwrap().replace('/', "\\"))
+                } else {
+                    p
+                }
+            })
+            .collect();
+
         config.jobs = Some(threads_from_config(flags.jobs.unwrap_or(jobs.unwrap_or(0))));
 
         if let Some(file_build) = build {

From d9ceab9bc72e6d26330f060f7ec10de63cc56db3 Mon Sep 17 00:00:00 2001
From: bit-aloo 
Date: Mon, 17 Feb 2025 01:28:39 +0530
Subject: [PATCH 13/14] add test for exclude feature

---
 src/bootstrap/src/core/config/tests.rs | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/bootstrap/src/core/config/tests.rs b/src/bootstrap/src/core/config/tests.rs
index eff5e0337428c..27968bea31869 100644
--- a/src/bootstrap/src/core/config/tests.rs
+++ b/src/bootstrap/src/core/config/tests.rs
@@ -515,3 +515,17 @@ fn test_explicit_stage() {
     assert!(!config.explicit_stage_from_config);
     assert!(!config.is_explicit_stage());
 }
+
+#[test]
+fn test_exclude() {
+    let config = parse("build.exclude=[\"test/codegen\"]");
+
+    let first_excluded = config
+        .skip
+        .first()
+        .expect("Expected at least one excluded path")
+        .to_str()
+        .expect("Failed to convert excluded path to string");
+
+    assert_eq!(first_excluded, "test/codegen");
+}

From 9206960dfadbed4b35d24b1cedbe463aac45aaea Mon Sep 17 00:00:00 2001
From: bit-aloo 
Date: Mon, 17 Feb 2025 01:34:32 +0530
Subject: [PATCH 14/14] Add change info to change tracker

---
 src/bootstrap/src/utils/change_tracker.rs | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs
index 5f49c50c5ad3a..b9f57898f4d98 100644
--- a/src/bootstrap/src/utils/change_tracker.rs
+++ b/src/bootstrap/src/utils/change_tracker.rs
@@ -365,4 +365,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
         severity: ChangeSeverity::Info,
         summary: "`rust.channel` now supports \"auto-detect\" to load the channel from `src/ci/channel`",
     },
+    ChangeInfo {
+        change_id: 137147,
+        severity: ChangeSeverity::Info,
+        summary: "New option `build.exclude` that adds support for excluding test.",
+    },
 ];