about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-03-03 22:57:01 +0000
committerbors <bors@rust-lang.org>2025-03-03 22:57:01 +0000
commit2010bba8868fa714bb4b07be463a8923b26d44db (patch)
tree64c902c929a797b1d0b3699f0c870f2e6f0fa1ed /src
parente16a049adbf94d610787430b6efdf31d896dc5b6 (diff)
parent1998cf76a8ca030a5813487b5e4017fa11f48698 (diff)
downloadrust-2010bba8868fa714bb4b07be463a8923b26d44db.tar.gz
rust-2010bba8868fa714bb4b07be463a8923b26d44db.zip
Auto merge of #137927 - matthiaskrgr:rollup-yj463ns, r=matthiaskrgr
Rollup of 9 pull requests

Successful merges:

 - #132388 (Implement `#[cfg]` in `where` clauses)
 - #134900 (Fix parsing of ranges after unary operators)
 - #136938 (Remove `:` from `stack-protector-heuristics-effect.rs` Filecheck Pattern)
 - #137054 (Make phantom variance markers transparent)
 - #137525 (Simplify parallelization in test-float-parse)
 - #137618 (Skip `tidy` in pre-push hook if the user is deleting a remote branch)
 - #137741 (Stop using `hash_raw_entry` in `CodegenCx::const_str`)
 - #137849 (Revert "Remove Win SDK 10.0.26100.0 from CI")
 - #137862 (ensure we always print all --print options in help)

r? `@ghost`
`@rustbot` modify labels: rollup
Diffstat (limited to 'src')
-rwxr-xr-xsrc/etc/pre-push.sh14
-rw-r--r--src/etc/test-float-parse/src/gen/exhaustive.rs3
-rw-r--r--src/etc/test-float-parse/src/gen/fuzz.rs1
-rw-r--r--src/etc/test-float-parse/src/gen/sparse.rs1
-rw-r--r--src/etc/test-float-parse/src/lib.rs221
-rw-r--r--src/etc/test-float-parse/src/traits.rs2
-rw-r--r--src/etc/test-float-parse/src/ui.rs126
-rw-r--r--src/etc/test-float-parse/src/validate.rs46
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils/mod.rs27
-rw-r--r--src/tools/rustfmt/src/spanned.rs7
-rw-r--r--src/tools/rustfmt/src/types.rs35
-rw-r--r--src/tools/rustfmt/tests/target/cfg_attribute_in_where.rs116
12 files changed, 353 insertions, 246 deletions
diff --git a/src/etc/pre-push.sh b/src/etc/pre-push.sh
index 6f86c7ab8a4..7bacc943f25 100755
--- a/src/etc/pre-push.sh
+++ b/src/etc/pre-push.sh
@@ -7,6 +7,20 @@
 
 set -Euo pipefail
 
+# Check if the push is doing anything other than deleting remote branches
+SKIP=true
+while read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do
+    if [[ "$LOCAL_REF" != "(delete)" || \
+          "$LOCAL_SHA" != "0000000000000000000000000000000000000000" ]]; then
+        SKIP=false
+    fi
+done
+
+if $SKIP; then
+    echo "Skipping tidy check for branch deletion"
+    exit 0
+fi
+
 ROOT_DIR="$(git rev-parse --show-toplevel)"
 
 echo "Running pre-push script $ROOT_DIR/x test tidy"
diff --git a/src/etc/test-float-parse/src/gen/exhaustive.rs b/src/etc/test-float-parse/src/gen/exhaustive.rs
index 5d4b6df8e59..01458fb0b60 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<F: Float> Generator<F> for Exhaustive<F>
 where
     RangeInclusive<F::Int>: Iterator<Item = F::Int>,
 {
-    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 0c63e8aae26..7fc999d1671 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<F: Float> Generator<F> for Fuzz<F>
 where
     Standard: Distribution<<F as Float>::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 389b71056a3..72b65d4ce7f 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<F: Float> Generator<F> for FewOnesInt<F>
 where
     <F::Int as TryFrom<u128>>::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 3c71b0dc32e..def66398d9f 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,
@@ -104,9 +104,9 @@ pub fn run(cfg: Config, include: &[String], exclude: &[String]) -> ExitCode {
         println!("Skipping test '{exc}'");
     }
 
-    println!("launching");
+    println!("Launching all");
     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<Msg>, &TestInfo, &Config),
+    launch: fn(&TestInfo, &Config),
     /// Progress bar to be updated.
-    pb: Option<ProgressBar>,
+    progress: Option<ui::Progress>,
     /// Once completed, this will be set.
     completed: OnceLock<Completed>,
 }
@@ -187,14 +187,18 @@ impl TestInfo {
         let f_name = type_name::<F>();
         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::<F, G>,
             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<ProgressBar>) {
-        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<F: Float, G: Generator<F>>(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<str>,
-        /// The parsed & decomposed `FloatRes`, already stringified so we don't need generics here.
-        float_res: Box<str>,
-    },
-    /// Exited with an unexpected condition.
-    Completed(Completed),
-}
-
 /// Result of an input did not parsing successfully.
 #[derive(Clone, Debug)]
 enum CheckFailure {
@@ -329,6 +245,10 @@ enum CheckFailure {
         /// two representable values.
         incorrect_midpoint_rounding: bool,
     },
+    /// String did not parse successfully.
+    ParsingFailed(Box<str>),
+    /// A panic was caught.
+    Panic(Box<str>),
 }
 
 impl fmt::Display for CheckFailure {
@@ -363,6 +283,8 @@ impl fmt::Display for CheckFailure {
                 }
                 Ok(())
             }
+            CheckFailure::ParsingFailed(e) => write!(f, "parsing failed: {e}"),
+            CheckFailure::Panic(e) => write!(f, "function panicked: {e}"),
         }
     }
 }
@@ -398,55 +320,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::<Msg>();
     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 +342,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<F: Float, G: Generator<F>>(tx: &mpsc::Sender<Msg>, _info: &TestInfo, cfg: &Config) {
-    tx.send(Msg::new::<F, G>(Update::Started)).unwrap();
-
-    let total = G::total_tests();
+fn test_runner<F: Float, G: Generator<F>>(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 +359,12 @@ fn test_runner<F: Float, G: Generator<F>>(tx: &mpsc::Sender<Msg>, _info: &TestIn
         match validate::validate::<F>(buf) {
             Ok(()) => (),
             Err(e) => {
-                tx.send(Msg::new::<F, G>(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 +376,7 @@ fn test_runner<F: Float, G: Generator<F>>(tx: &mpsc::Sender<Msg>, _info: &TestIn
         // Send periodic updates
         if executed % checks_per_update == 0 {
             let failures = failures.load(Ordering::Relaxed);
-
-            tx.send(Msg::new::<F, G>(Update::Progress { executed, failures })).unwrap();
-
+            test.progress.as_ref().unwrap().update(executed, failures);
             if started.elapsed() > cfg.timeout {
                 return Err(EarlyExit::Timeout);
             }
@@ -499,15 +387,19 @@ fn test_runner<F: Float, G: Generator<F>>(tx: &mpsc::Sender<Msg>, _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 +407,5 @@ fn test_runner<F: Float, G: Generator<F>>(tx: &mpsc::Sender<Msg>, _info: &TestIn
     };
 
     let result = res.map(|()| FinishedAll);
-    tx.send(Msg::new::<F, G>(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 f5333d63b36..c8407ba7cc5 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<F: Float>: Iterator<Item = Self::WriteCtx> + 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 f333bd4a55d..1ee57723e6a 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>,
-) -> 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<Box<dyn Fn(&'static str) -> 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<ProgressBar>) -> 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.
 /// <https://github.com/console-rs/indicatif/issues/121>.
-pub fn set_panic_hook(drop_bars: Vec<ProgressBar>) {
+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<ProgressBar>) {
         hook(info);
     }));
 }
+
+/// Allow non-Debug items in a `derive(Debug)` struct`.
+#[derive(Clone)]
+struct NoDebug<T>(T);
+
+impl<T> fmt::Debug for NoDebug<T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(type_name::<Self>())
+    }
+}
diff --git a/src/etc/test-float-parse/src/validate.rs b/src/etc/test-float-parse/src/validate.rs
index 1eb3699cfb9..40dda274e3b 100644
--- a/src/etc/test-float-parse/src/validate.rs
+++ b/src/etc/test-float-parse/src/validate.rs
@@ -1,6 +1,6 @@
 //! Everything related to verifying that parsed outputs are correct.
 
-use std::any::type_name;
+use std::any::{Any, type_name};
 use std::collections::BTreeMap;
 use std::ops::RangeInclusive;
 use std::str::FromStr;
@@ -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<i32> = (-(2 << 15))..=(2 << 15);
@@ -89,10 +89,16 @@ impl Constants {
 }
 
 /// Validate that a string parses correctly
-pub fn validate<F: Float>(input: &str) -> Result<(), Update> {
-    let parsed: F = input
-        .parse()
-        .unwrap_or_else(|e| panic!("parsing failed for {}: {e}. Input: {input}", type_name::<F>()));
+pub fn validate<F: Float>(input: &str) -> Result<(), CheckError> {
+    // Catch panics in case debug assertions within `std` fail.
+    let parsed = std::panic::catch_unwind(|| {
+        input.parse::<F>().map_err(|e| CheckError {
+            fail: CheckFailure::ParsingFailed(e.to_string().into()),
+            input: input.into(),
+            float_res: "none".into(),
+        })
+    })
+    .map_err(|e| convert_panic_error(&e, input))??;
 
     // Parsed float, decoded into significand and exponent
     let decoded = decode(parsed);
@@ -104,6 +110,21 @@ pub fn validate<F: Float>(input: &str) -> Result<(), Update> {
     decoded.check(rational, input)
 }
 
+/// Turn panics into concrete error types.
+fn convert_panic_error(e: &dyn Any, input: &str) -> CheckError {
+    let msg = e
+        .downcast_ref::<String>()
+        .map(|s| s.as_str())
+        .or_else(|| e.downcast_ref::<&str>().copied())
+        .unwrap_or("(no contents)");
+
+    CheckError {
+        fail: CheckFailure::Panic(msg.into()),
+        input: input.into(),
+        float_res: "none".into(),
+    }
+}
+
 /// The result of parsing a string to a float type.
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum FloatRes<F: Float> {
@@ -118,10 +139,19 @@ pub enum FloatRes<F: Float> {
     },
 }
 
+#[derive(Clone, Debug)]
+pub struct CheckError {
+    pub fail: CheckFailure,
+    /// String for which parsing was attempted.
+    pub input: Box<str>,
+    /// The parsed & decomposed `FloatRes`, already stringified so we don't need generics here.
+    pub float_res: Box<str>,
+}
+
 impl<F: Float> FloatRes<F> {
     /// 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 +203,7 @@ impl<F: Float> FloatRes<F> {
             (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(),
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs
index ab5f97199ce..4f024ecaf29 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs
@@ -682,19 +682,20 @@ pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
 
 pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
     use WherePredicateKind::*;
-    match (&l.kind, &r.kind) {
-        (BoundPredicate(l), BoundPredicate(r)) => {
-            over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
-                eq_generic_param(l, r)
-            }) && eq_ty(&l.bounded_ty, &r.bounded_ty)
-                && over(&l.bounds, &r.bounds, eq_generic_bound)
-        },
-        (RegionPredicate(l), RegionPredicate(r)) => {
-            eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, eq_generic_bound)
-        },
-        (EqPredicate(l), EqPredicate(r)) => eq_ty(&l.lhs_ty, &r.lhs_ty) && eq_ty(&l.rhs_ty, &r.rhs_ty),
-        _ => false,
-    }
+    over(&l.attrs, &r.attrs, eq_attr) 
+        && match (&l.kind, &r.kind) {
+            (BoundPredicate(l), BoundPredicate(r)) => {
+                over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
+                    eq_generic_param(l, r)
+                }) && eq_ty(&l.bounded_ty, &r.bounded_ty)
+                    && over(&l.bounds, &r.bounds, eq_generic_bound)
+            },
+            (RegionPredicate(l), RegionPredicate(r)) => {
+                eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, eq_generic_bound)
+            },
+            (EqPredicate(l), EqPredicate(r)) => eq_ty(&l.lhs_ty, &r.lhs_ty) && eq_ty(&l.rhs_ty, &r.rhs_ty),
+            _ => false,
+        }
 }
 
 pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool {
diff --git a/src/tools/rustfmt/src/spanned.rs b/src/tools/rustfmt/src/spanned.rs
index e93eb53cd87..507647566d4 100644
--- a/src/tools/rustfmt/src/spanned.rs
+++ b/src/tools/rustfmt/src/spanned.rs
@@ -58,6 +58,7 @@ implement_spanned!(ast::ExprField);
 implement_spanned!(ast::ForeignItem);
 implement_spanned!(ast::Item);
 implement_spanned!(ast::Local);
+implement_spanned!(ast::WherePredicate);
 
 impl Spanned for ast::Stmt {
     fn span(&self) -> Span {
@@ -149,12 +150,6 @@ impl Spanned for ast::FieldDef {
     }
 }
 
-impl Spanned for ast::WherePredicate {
-    fn span(&self) -> Span {
-        self.span
-    }
-}
-
 impl Spanned for ast::FnRetTy {
     fn span(&self) -> Span {
         match *self {
diff --git a/src/tools/rustfmt/src/types.rs b/src/tools/rustfmt/src/types.rs
index 7b44b47c719..06a67334086 100644
--- a/src/tools/rustfmt/src/types.rs
+++ b/src/tools/rustfmt/src/types.rs
@@ -463,8 +463,9 @@ impl Rewrite for ast::WherePredicate {
     }
 
     fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
+        let attrs_str = self.attrs.rewrite_result(context, shape)?;
         // FIXME: dead spans?
-        let result = match self.kind {
+        let pred_str = &match self.kind {
             ast::WherePredicateKind::BoundPredicate(ast::WhereBoundPredicate {
                 ref bound_generic_params,
                 ref bounded_ty,
@@ -499,6 +500,38 @@ impl Rewrite for ast::WherePredicate {
             }
         };
 
+        let mut result = String::with_capacity(attrs_str.len() + pred_str.len() + 1);
+        result.push_str(&attrs_str);
+        let pred_start = self.span.lo();
+        let line_len = last_line_width(&attrs_str) + 1 + first_line_width(&pred_str);
+        if let Some(last_attr) = self.attrs.last().filter(|last_attr| {
+            contains_comment(context.snippet(mk_sp(last_attr.span.hi(), pred_start)))
+        }) {
+            result = combine_strs_with_missing_comments(
+                context,
+                &result,
+                &pred_str,
+                mk_sp(last_attr.span.hi(), pred_start),
+                Shape {
+                    width: shape.width.min(context.config.inline_attribute_width()),
+                    ..shape
+                },
+                !last_attr.is_doc_comment(),
+            )?;
+        } else {
+            if !self.attrs.is_empty() {
+                if context.config.inline_attribute_width() < line_len
+                    || self.attrs.len() > 1
+                    || self.attrs.last().is_some_and(|a| a.is_doc_comment())
+                {
+                    result.push_str(&shape.indent.to_string_with_newline(context.config));
+                } else {
+                    result.push(' ');
+                }
+            }
+            result.push_str(&pred_str);
+        }
+
         Ok(result)
     }
 }
diff --git a/src/tools/rustfmt/tests/target/cfg_attribute_in_where.rs b/src/tools/rustfmt/tests/target/cfg_attribute_in_where.rs
new file mode 100644
index 00000000000..11f495b1629
--- /dev/null
+++ b/src/tools/rustfmt/tests/target/cfg_attribute_in_where.rs
@@ -0,0 +1,116 @@
+// rustfmt-inline_attribute_width: 40
+
+#![crate_type = "lib"]
+#![feature(cfg_attribute_in_where)]
+use std::marker::PhantomData;
+
+#[cfg(a)]
+trait TraitA {}
+
+#[cfg(b)]
+trait TraitB {}
+
+trait A<T>
+where
+    #[cfg = a_very_long_attribute_name]
+    T: TraitA,
+    #[cfg = another_very_long_attribute_name]
+    T: TraitB,
+{
+    type B<U>
+    where
+        #[cfg = a]
+        // line comment after the attribute
+        U: TraitA,
+        #[cfg = b]
+        /* block comment after the attribute */
+        U: TraitB,
+        #[cfg = a] // short
+        U: TraitA,
+        #[cfg = b] /* short */ U: TraitB;
+
+    fn foo<U>(&self)
+    where
+        /// line doc comment before the attribute
+        U: TraitA,
+        /** line doc block comment before the attribute */
+        U: TraitB;
+}
+
+impl<T> A<T> for T
+where
+    #[doc = "line doc before the attribute"]
+    T: TraitA,
+    /** short doc */
+    T: TraitB,
+{
+    type B<U>
+        = ()
+    where
+        #[doc = "short"] U: TraitA,
+        #[doc = "short"]
+        #[cfg = a]
+        U: TraitB;
+
+    fn foo<U>(&self)
+    where
+        #[cfg = a]
+        #[cfg = b]
+        U: TraitA,
+        /// line doc
+        #[cfg = c]
+        U: TraitB,
+    {
+    }
+}
+
+struct C<T>
+where
+    #[cfg = a] T: TraitA,
+    #[cfg = b] T: TraitB,
+{
+    _t: PhantomData<T>,
+}
+
+union D<T>
+where
+    #[cfg = a] T: TraitA,
+    #[cfg = b] T: TraitB,
+{
+    _t: PhantomData<T>,
+}
+
+enum E<T>
+where
+    #[cfg = a] T: TraitA,
+    #[cfg = b] T: TraitB,
+{
+    E(PhantomData<T>),
+}
+
+#[allow(type_alias_bounds)]
+type F<T>
+where
+    #[cfg = a] T: TraitA,
+    #[cfg = b] T: TraitB,
+= T;
+
+impl<T> C<T>
+where
+    #[cfg = a] T: TraitA,
+    #[cfg = b] T: TraitB,
+{
+    fn new<U>()
+    where
+        #[cfg = a] U: TraitA,
+        #[cfg = b] U: TraitB,
+    {
+    }
+}
+
+fn foo<T>()
+where
+    #[cfg = a] T: TraitA,
+    #[cfg = b] T: TraitB,
+{
+}