about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2023-05-02 08:37:36 +0200
committerRalf Jung <post@ralfj.de>2023-05-02 08:37:36 +0200
commit3942cdf1bf0edd082a331ed9282b52eea0914fdd (patch)
treeaa6401d53d93348854718b151f645ef9bef93b52 /src
parentd20fa00ee640d3df38e2e431d732ac9ff4efb0a9 (diff)
parent7411468ff817884cdb1239e85b5ab785cc65e36d (diff)
downloadrust-3942cdf1bf0edd082a331ed9282b52eea0914fdd.tar.gz
rust-3942cdf1bf0edd082a331ed9282b52eea0914fdd.zip
Merge from rustc
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/bootstrap.py195
-rw-r--r--src/bootstrap/builder.rs13
-rw-r--r--src/bootstrap/builder/tests.rs1
-rw-r--r--src/bootstrap/config.rs2
-rw-r--r--src/bootstrap/download.rs21
-rw-r--r--src/bootstrap/lib.rs1
-rw-r--r--src/bootstrap/metadata.rs42
-rw-r--r--src/bootstrap/test.rs505
-rw-r--r--src/bootstrap/tool.rs2
-rw-r--r--src/librustdoc/clean/mod.rs59
-rw-r--r--src/librustdoc/clean/types.rs73
-rw-r--r--src/librustdoc/core.rs16
-rw-r--r--src/librustdoc/html/render/mod.rs38
-rw-r--r--src/librustdoc/html/render/print_item.rs25
-rw-r--r--src/librustdoc/json/conversions.rs7
-rw-r--r--src/librustdoc/lib.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs2
-rw-r--r--src/tools/clippy/tests/ui/future_not_send.stderr24
-rw-r--r--src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs2
-rw-r--r--src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.stderr4
-rw-r--r--src/tools/rustfmt/src/chains.rs4
-rw-r--r--src/tools/rustfmt/src/expr.rs4
23 files changed, 552 insertions, 491 deletions
diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py
index ff261ab9832..f22cdad7df4 100644
--- a/src/bootstrap/bootstrap.py
+++ b/src/bootstrap/bootstrap.py
@@ -13,6 +13,7 @@ import tarfile
 import tempfile
 
 from time import time
+from multiprocessing import Pool, cpu_count
 
 try:
     import lzma
@@ -27,6 +28,20 @@ if platform_is_win32():
 else:
     EXE_SUFFIX = ""
 
+def get_cpus():
+    if hasattr(os, "sched_getaffinity"):
+        return len(os.sched_getaffinity(0))
+    if hasattr(os, "cpu_count"):
+        cpus = os.cpu_count()
+        if cpus is not None:
+            return cpus
+    try:
+        return cpu_count()
+    except NotImplementedError:
+        return 1
+
+
+
 def get(base, url, path, checksums, verbose=False):
     with tempfile.NamedTemporaryFile(delete=False) as temp_file:
         temp_path = temp_file.name
@@ -42,23 +57,23 @@ def get(base, url, path, checksums, verbose=False):
         if os.path.exists(path):
             if verify(path, sha256, False):
                 if verbose:
-                    print("using already-download file", path)
+                    print("using already-download file", path, file=sys.stderr)
                 return
             else:
                 if verbose:
                     print("ignoring already-download file",
-                        path, "due to failed verification")
+                        path, "due to failed verification", file=sys.stderr)
                 os.unlink(path)
         download(temp_path, "{}/{}".format(base, url), True, verbose)
         if not verify(temp_path, sha256, verbose):
             raise RuntimeError("failed verification")
         if verbose:
-            print("moving {} to {}".format(temp_path, path))
+            print("moving {} to {}".format(temp_path, path), file=sys.stderr)
         shutil.move(temp_path, path)
     finally:
         if os.path.isfile(temp_path):
             if verbose:
-                print("removing", temp_path)
+                print("removing", temp_path, file=sys.stderr)
             os.unlink(temp_path)
 
 
@@ -68,7 +83,7 @@ def download(path, url, probably_big, verbose):
             _download(path, url, probably_big, verbose, True)
             return
         except RuntimeError:
-            print("\nspurious failure, trying again")
+            print("\nspurious failure, trying again", file=sys.stderr)
     _download(path, url, probably_big, verbose, False)
 
 
@@ -79,7 +94,7 @@ def _download(path, url, probably_big, verbose, exception):
     #  - If we are on win32 fallback to powershell
     #  - Otherwise raise the error if appropriate
     if probably_big or verbose:
-        print("downloading {}".format(url))
+        print("downloading {}".format(url), file=sys.stderr)
 
     try:
         if probably_big or verbose:
@@ -115,20 +130,20 @@ def _download(path, url, probably_big, verbose, exception):
 def verify(path, expected, verbose):
     """Check if the sha256 sum of the given path is valid"""
     if verbose:
-        print("verifying", path)
+        print("verifying", path, file=sys.stderr)
     with open(path, "rb") as source:
         found = hashlib.sha256(source.read()).hexdigest()
     verified = found == expected
     if not verified:
         print("invalid checksum:\n"
               "    found:    {}\n"
-              "    expected: {}".format(found, expected))
+              "    expected: {}".format(found, expected), file=sys.stderr)
     return verified
 
 
 def unpack(tarball, tarball_suffix, dst, verbose=False, match=None):
     """Unpack the given tarball file"""
-    print("extracting", tarball)
+    print("extracting", tarball, file=sys.stderr)
     fname = os.path.basename(tarball).replace(tarball_suffix, "")
     with contextlib.closing(tarfile.open(tarball)) as tar:
         for member in tar.getnames():
@@ -141,7 +156,7 @@ def unpack(tarball, tarball_suffix, dst, verbose=False, match=None):
 
             dst_path = os.path.join(dst, name)
             if verbose:
-                print("  extracting", member)
+                print("  extracting", member, file=sys.stderr)
             tar.extract(member, dst)
             src_path = os.path.join(dst, member)
             if os.path.isdir(src_path) and os.path.exists(dst_path):
@@ -153,7 +168,7 @@ def unpack(tarball, tarball_suffix, dst, verbose=False, match=None):
 def run(args, verbose=False, exception=False, is_bootstrap=False, **kwargs):
     """Run a child program in a new process"""
     if verbose:
-        print("running: " + ' '.join(args))
+        print("running: " + ' '.join(args), file=sys.stderr)
     sys.stdout.flush()
     # Ensure that the .exe is used on Windows just in case a Linux ELF has been
     # compiled in the same directory.
@@ -193,8 +208,8 @@ def require(cmd, exit=True, exception=False):
         if exception:
             raise
         elif exit:
-            print("error: unable to run `{}`: {}".format(' '.join(cmd), exc))
-            print("Please make sure it's installed and in the path.")
+            print("error: unable to run `{}`: {}".format(' '.join(cmd), exc), file=sys.stderr)
+            print("Please make sure it's installed and in the path.", file=sys.stderr)
             sys.exit(1)
         return None
 
@@ -218,8 +233,8 @@ def default_build_triple(verbose):
 
     if sys.platform == 'darwin':
         if verbose:
-            print("not using rustc detection as it is unreliable on macOS")
-            print("falling back to auto-detect")
+            print("not using rustc detection as it is unreliable on macOS", file=sys.stderr)
+            print("falling back to auto-detect", file=sys.stderr)
     else:
         try:
             version = subprocess.check_output(["rustc", "--version", "--verbose"],
@@ -228,12 +243,14 @@ def default_build_triple(verbose):
             host = next(x for x in version.split('\n') if x.startswith("host: "))
             triple = host.split("host: ")[1]
             if verbose:
-                print("detected default triple {} from pre-installed rustc".format(triple))
+                print("detected default triple {} from pre-installed rustc".format(triple),
+                    file=sys.stderr)
             return triple
         except Exception as e:
             if verbose:
-                print("pre-installed rustc not detected: {}".format(e))
-                print("falling back to auto-detect")
+                print("pre-installed rustc not detected: {}".format(e),
+                    file=sys.stderr)
+                print("falling back to auto-detect", file=sys.stderr)
 
     required = not platform_is_win32()
     ostype = require(["uname", "-s"], exit=required)
@@ -404,6 +421,48 @@ class Stage0Toolchain:
         return self.version + "-" + self.date
 
 
+class DownloadInfo:
+    """A helper class that can be pickled into a parallel subprocess"""
+
+    def __init__(
+        self,
+        base_download_url,
+        download_path,
+        bin_root,
+        tarball_path,
+        tarball_suffix,
+        checksums_sha256,
+        pattern,
+        verbose,
+    ):
+        self.base_download_url = base_download_url
+        self.download_path = download_path
+        self.bin_root = bin_root
+        self.tarball_path = tarball_path
+        self.tarball_suffix = tarball_suffix
+        self.checksums_sha256 = checksums_sha256
+        self.pattern = pattern
+        self.verbose = verbose
+
+def download_component(download_info):
+    if not os.path.exists(download_info.tarball_path):
+        get(
+            download_info.base_download_url,
+            download_info.download_path,
+            download_info.tarball_path,
+            download_info.checksums_sha256,
+            verbose=download_info.verbose,
+        )
+
+def unpack_component(download_info):
+    unpack(
+        download_info.tarball_path,
+        download_info.tarball_suffix,
+        download_info.bin_root,
+        match=download_info.pattern,
+        verbose=download_info.verbose,
+    )
+
 class RustBuild(object):
     """Provide all the methods required to build Rust"""
     def __init__(self):
@@ -458,17 +517,53 @@ class RustBuild(object):
                     )
                     run_powershell([script])
                 shutil.rmtree(bin_root)
+
+            key = self.stage0_compiler.date
+            cache_dst = os.path.join(self.build_dir, "cache")
+            rustc_cache = os.path.join(cache_dst, key)
+            if not os.path.exists(rustc_cache):
+                os.makedirs(rustc_cache)
+
             tarball_suffix = '.tar.gz' if lzma is None else '.tar.xz'
-            filename = "rust-std-{}-{}{}".format(
-                rustc_channel, self.build, tarball_suffix)
-            pattern = "rust-std-{}".format(self.build)
-            self._download_component_helper(filename, pattern, tarball_suffix)
-            filename = "rustc-{}-{}{}".format(rustc_channel, self.build,
-                                              tarball_suffix)
-            self._download_component_helper(filename, "rustc", tarball_suffix)
-            filename = "cargo-{}-{}{}".format(rustc_channel, self.build,
-                                            tarball_suffix)
-            self._download_component_helper(filename, "cargo", tarball_suffix)
+
+            toolchain_suffix = "{}-{}{}".format(rustc_channel, self.build, tarball_suffix)
+
+            tarballs_to_download = [
+                ("rust-std-{}".format(toolchain_suffix), "rust-std-{}".format(self.build)),
+                ("rustc-{}".format(toolchain_suffix), "rustc"),
+                ("cargo-{}".format(toolchain_suffix), "cargo"),
+            ]
+
+            tarballs_download_info = [
+                DownloadInfo(
+                    base_download_url=self.download_url,
+                    download_path="dist/{}/{}".format(self.stage0_compiler.date, filename),
+                    bin_root=self.bin_root(),
+                    tarball_path=os.path.join(rustc_cache, filename),
+                    tarball_suffix=tarball_suffix,
+                    checksums_sha256=self.checksums_sha256,
+                    pattern=pattern,
+                    verbose=self.verbose,
+                )
+                for filename, pattern in tarballs_to_download
+            ]
+
+            # Download the components serially to show the progress bars properly.
+            for download_info in tarballs_download_info:
+                download_component(download_info)
+
+            # Unpack the tarballs in parallle.
+            # In Python 2.7, Pool cannot be used as a context manager.
+            pool_size = min(len(tarballs_download_info), get_cpus())
+            if self.verbose:
+                print('Choosing a pool size of', pool_size, 'for the unpacking of the tarballs')
+            p = Pool(pool_size)
+            try:
+                p.map(unpack_component, tarballs_download_info)
+            finally:
+                p.close()
+            p.join()
+
             if self.should_fix_bins_and_dylibs():
                 self.fix_bin_or_dylib("{}/bin/cargo".format(bin_root))
 
@@ -484,13 +579,9 @@ class RustBuild(object):
                 rust_stamp.write(key)
 
     def _download_component_helper(
-        self, filename, pattern, tarball_suffix,
+        self, filename, pattern, tarball_suffix, rustc_cache,
     ):
         key = self.stage0_compiler.date
-        cache_dst = os.path.join(self.build_dir, "cache")
-        rustc_cache = os.path.join(cache_dst, key)
-        if not os.path.exists(rustc_cache):
-            os.makedirs(rustc_cache)
 
         tarball = os.path.join(rustc_cache, filename)
         if not os.path.exists(tarball):
@@ -545,7 +636,7 @@ class RustBuild(object):
 
         answer = self._should_fix_bins_and_dylibs = get_answer()
         if answer:
-            print("info: You seem to be using Nix.")
+            print("info: You seem to be using Nix.", file=sys.stderr)
         return answer
 
     def fix_bin_or_dylib(self, fname):
@@ -558,7 +649,7 @@ class RustBuild(object):
         Please see https://nixos.org/patchelf.html for more information
         """
         assert self._should_fix_bins_and_dylibs is True
-        print("attempting to patch", fname)
+        print("attempting to patch", fname, file=sys.stderr)
 
         # Only build `.nix-deps` once.
         nix_deps_dir = self.nix_deps_dir
@@ -591,7 +682,7 @@ class RustBuild(object):
                     "nix-build", "-E", nix_expr, "-o", nix_deps_dir,
                 ])
             except subprocess.CalledProcessError as reason:
-                print("warning: failed to call nix-build:", reason)
+                print("warning: failed to call nix-build:", reason, file=sys.stderr)
                 return
             self.nix_deps_dir = nix_deps_dir
 
@@ -611,7 +702,7 @@ class RustBuild(object):
         try:
             subprocess.check_output([patchelf] + patchelf_args + [fname])
         except subprocess.CalledProcessError as reason:
-            print("warning: failed to call patchelf:", reason)
+            print("warning: failed to call patchelf:", reason, file=sys.stderr)
             return
 
     def rustc_stamp(self):
@@ -755,7 +846,7 @@ class RustBuild(object):
         if "GITHUB_ACTIONS" in env:
             print("::group::Building bootstrap")
         else:
-            print("Building bootstrap")
+            print("Building bootstrap", file=sys.stderr)
         build_dir = os.path.join(self.build_dir, "bootstrap")
         if self.clean and os.path.exists(build_dir):
             shutil.rmtree(build_dir)
@@ -849,9 +940,12 @@ class RustBuild(object):
         if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
             if os.getuid() == 0:
                 self.use_vendored_sources = True
-                print('info: looks like you\'re trying to run this command as root')
-                print('      and so in order to preserve your $HOME this will now')
-                print('      use vendored sources by default.')
+                print('info: looks like you\'re trying to run this command as root',
+                    file=sys.stderr)
+                print('      and so in order to preserve your $HOME this will now',
+                    file=sys.stderr)
+                print('      use vendored sources by default.',
+                    file=sys.stderr)
 
         cargo_dir = os.path.join(self.rust_root, '.cargo')
         if self.use_vendored_sources:
@@ -861,14 +955,18 @@ class RustBuild(object):
                             "--sync ./src/tools/rust-analyzer/Cargo.toml " \
                             "--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \
                             "--sync ./src/bootstrap/Cargo.toml "
-                print('error: vendoring required, but vendor directory does not exist.')
+                print('error: vendoring required, but vendor directory does not exist.',
+                    file=sys.stderr)
                 print('       Run `cargo vendor {}` to initialize the '
-                      'vendor directory.'.format(sync_dirs))
-                print('Alternatively, use the pre-vendored `rustc-src` dist component.')
+                      'vendor directory.'.format(sync_dirs),
+                      file=sys.stderr)
+                print('Alternatively, use the pre-vendored `rustc-src` dist component.',
+                    file=sys.stderr)
                 raise Exception("{} not found".format(vendor_dir))
 
             if not os.path.exists(cargo_dir):
-                print('error: vendoring required, but .cargo/config does not exist.')
+                print('error: vendoring required, but .cargo/config does not exist.',
+                    file=sys.stderr)
                 raise Exception("{} not found".format(cargo_dir))
         else:
             if os.path.exists(cargo_dir):
@@ -978,7 +1076,7 @@ def main():
         print(
             "info: Downloading and building bootstrap before processing --help command.\n"
             "      See src/bootstrap/README.md for help with common commands."
-        )
+        , file=sys.stderr)
 
     exit_code = 0
     success_word = "successfully"
@@ -989,11 +1087,12 @@ def main():
             exit_code = error.code
         else:
             exit_code = 1
-            print(error)
+            print(error, file=sys.stderr)
         success_word = "unsuccessfully"
 
     if not help_triggered:
-        print("Build completed", success_word, "in", format_build_time(time() - start_time))
+        print("Build completed", success_word, "in", format_build_time(time() - start_time),
+            file=sys.stderr)
     sys.exit(exit_code)
 
 
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index 0d2d512b4b2..d9d4685dfc7 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -634,6 +634,14 @@ impl Kind {
             Kind::Suggest => "suggest",
         }
     }
+
+    pub fn test_description(&self) -> &'static str {
+        match self {
+            Kind::Test => "Testing",
+            Kind::Bench => "Benchmarking",
+            _ => panic!("not a test command: {}!", self.as_str()),
+        }
+    }
 }
 
 impl<'a> Builder<'a> {
@@ -695,7 +703,6 @@ impl<'a> Builder<'a> {
                 crate::toolstate::ToolStateCheck,
                 test::ExpandYamlAnchors,
                 test::Tidy,
-                test::TidySelfTest,
                 test::Ui,
                 test::RunPassValgrind,
                 test::MirOpt,
@@ -711,11 +718,9 @@ impl<'a> Builder<'a> {
                 test::CrateLibrustc,
                 test::CrateRustdoc,
                 test::CrateRustdocJsonTypes,
-                test::CrateJsonDocLint,
-                test::SuggestTestsCrate,
+                test::CrateBootstrap,
                 test::Linkcheck,
                 test::TierCheck,
-                test::ReplacePlaceholderTest,
                 test::Cargotest,
                 test::Cargo,
                 test::RustAnalyzer,
diff --git a/src/bootstrap/builder/tests.rs b/src/bootstrap/builder/tests.rs
index 3574f11189e..72ac46b6bfd 100644
--- a/src/bootstrap/builder/tests.rs
+++ b/src/bootstrap/builder/tests.rs
@@ -578,7 +578,6 @@ mod dist {
                 compiler: Compiler { host, stage: 0 },
                 target: host,
                 mode: Mode::Std,
-                test_kind: test::TestKind::Test,
                 crates: vec![INTERNER.intern_str("std")],
             },]
         );
diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs
index ca6dcaf4957..4ef95b3370f 100644
--- a/src/bootstrap/config.rs
+++ b/src/bootstrap/config.rs
@@ -1309,7 +1309,7 @@ impl Config {
         if config.llvm_from_ci {
             let triple = &config.build.triple;
             let ci_llvm_bin = config.ci_llvm_root().join("bin");
-            let mut build_target = config
+            let build_target = config
                 .target_config
                 .entry(config.build)
                 .or_insert_with(|| Target::from_triple(&triple));
diff --git a/src/bootstrap/download.rs b/src/bootstrap/download.rs
index 133cda639d9..c1cf9b93fb3 100644
--- a/src/bootstrap/download.rs
+++ b/src/bootstrap/download.rs
@@ -112,7 +112,7 @@ impl Config {
             is_nixos && !Path::new("/lib").exists()
         });
         if val {
-            println!("info: You seem to be using Nix.");
+            eprintln!("info: You seem to be using Nix.");
         }
         val
     }
@@ -226,7 +226,7 @@ impl Config {
         curl.stdout(Stdio::from(f));
         if !self.check_run(&mut curl) {
             if self.build.contains("windows-msvc") {
-                println!("Fallback to PowerShell");
+                eprintln!("Fallback to PowerShell");
                 for _ in 0..3 {
                     if self.try_run(Command::new("PowerShell.exe").args(&[
                         "/nologo",
@@ -239,7 +239,7 @@ impl Config {
                     ])) {
                         return;
                     }
-                    println!("\nspurious failure, trying again");
+                    eprintln!("\nspurious failure, trying again");
                 }
             }
             if !help_on_error.is_empty() {
@@ -250,7 +250,7 @@ impl Config {
     }
 
     fn unpack(&self, tarball: &Path, dst: &Path, pattern: &str) {
-        println!("extracting {} to {}", tarball.display(), dst.display());
+        eprintln!("extracting {} to {}", tarball.display(), dst.display());
         if !dst.exists() {
             t!(fs::create_dir_all(dst));
         }
@@ -541,7 +541,18 @@ impl Config {
             None
         };
 
-        self.download_file(&format!("{base_url}/{url}"), &tarball, "");
+        let mut help_on_error = "";
+        if destination == "ci-rustc" {
+            help_on_error = "error: failed to download pre-built rustc from CI
+
+note: old builds get deleted after a certain time
+help: if trying to compile an old commit of rustc, disable `download-rustc` in config.toml:
+
+[rust]
+download-rustc = false
+";
+        }
+        self.download_file(&format!("{base_url}/{url}"), &tarball, help_on_error);
         if let Some(sha256) = checksum {
             if !self.verify(&tarball, sha256) {
                 panic!("failed to verify {}", tarball.display());
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index 14e1328171b..59d2e9cc69e 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -246,6 +246,7 @@ struct Crate {
     name: Interned<String>,
     deps: HashSet<Interned<String>>,
     path: PathBuf,
+    has_lib: bool,
 }
 
 impl Crate {
diff --git a/src/bootstrap/metadata.rs b/src/bootstrap/metadata.rs
index 597aefadcfe..8f2c3faca3a 100644
--- a/src/bootstrap/metadata.rs
+++ b/src/bootstrap/metadata.rs
@@ -5,7 +5,7 @@ use serde_derive::Deserialize;
 
 use crate::cache::INTERNER;
 use crate::util::output;
-use crate::{Build, Crate};
+use crate::{t, Build, Crate};
 
 /// For more information, see the output of
 /// <https://doc.rust-lang.org/nightly/cargo/commands/cargo-metadata.html>
@@ -22,6 +22,7 @@ struct Package {
     source: Option<String>,
     manifest_path: String,
     dependencies: Vec<Dependency>,
+    targets: Vec<Target>,
 }
 
 /// For more information, see the output of
@@ -32,6 +33,11 @@ struct Dependency {
     source: Option<String>,
 }
 
+#[derive(Debug, Deserialize)]
+struct Target {
+    kind: Vec<String>,
+}
+
 /// Collects and stores package metadata of each workspace members into `build`,
 /// by executing `cargo metadata` commands.
 pub fn build(build: &mut Build) {
@@ -46,11 +52,16 @@ pub fn build(build: &mut Build) {
                 .filter(|dep| dep.source.is_none())
                 .map(|dep| INTERNER.intern_string(dep.name))
                 .collect();
-            let krate = Crate { name, deps, path };
+            let has_lib = package.targets.iter().any(|t| t.kind.iter().any(|k| k == "lib"));
+            let krate = Crate { name, deps, path, has_lib };
             let relative_path = krate.local_path(build);
             build.crates.insert(name, krate);
             let existing_path = build.crate_paths.insert(relative_path, name);
-            assert!(existing_path.is_none(), "multiple crates with the same path");
+            assert!(
+                existing_path.is_none(),
+                "multiple crates with the same path: {}",
+                existing_path.unwrap()
+            );
         }
     }
 }
@@ -60,7 +71,7 @@ pub fn build(build: &mut Build) {
 /// Note that `src/tools/cargo` is no longer a workspace member but we still
 /// treat it as one here, by invoking an additional `cargo metadata` command.
 fn workspace_members(build: &Build) -> impl Iterator<Item = Package> {
-    let cmd_metadata = |manifest_path| {
+    let collect_metadata = |manifest_path| {
         let mut cargo = Command::new(&build.initial_cargo);
         cargo
             .arg("metadata")
@@ -68,21 +79,20 @@ fn workspace_members(build: &Build) -> impl Iterator<Item = Package> {
             .arg("1")
             .arg("--no-deps")
             .arg("--manifest-path")
-            .arg(manifest_path);
-        cargo
+            .arg(build.src.join(manifest_path));
+        let metadata_output = output(&mut cargo);
+        let Output { packages, .. } = t!(serde_json::from_str(&metadata_output));
+        packages
     };
 
-    // Collects `metadata.packages` from the root workspace.
-    let root_manifest_path = build.src.join("Cargo.toml");
-    let root_output = output(&mut cmd_metadata(&root_manifest_path));
-    let Output { packages, .. } = serde_json::from_str(&root_output).unwrap();
-
-    // Collects `metadata.packages` from src/tools/cargo separately.
-    let cargo_manifest_path = build.src.join("src/tools/cargo/Cargo.toml");
-    let cargo_output = output(&mut cmd_metadata(&cargo_manifest_path));
-    let Output { packages: cargo_packages, .. } = serde_json::from_str(&cargo_output).unwrap();
+    // Collects `metadata.packages` from all workspaces.
+    let packages = collect_metadata("Cargo.toml");
+    let cargo_packages = collect_metadata("src/tools/cargo/Cargo.toml");
+    let ra_packages = collect_metadata("src/tools/rust-analyzer/Cargo.toml");
+    let bootstrap_packages = collect_metadata("src/bootstrap/Cargo.toml");
 
     // We only care about the root package from `src/tool/cargo` workspace.
     let cargo_package = cargo_packages.into_iter().find(|pkg| pkg.name == "cargo").into_iter();
-    packages.into_iter().chain(cargo_package)
+
+    packages.into_iter().chain(cargo_package).chain(ra_packages).chain(bootstrap_packages)
 }
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index 601351ea8e3..aee84e9c9be 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -13,6 +13,7 @@ use std::process::{Command, Stdio};
 use crate::builder::crate_description;
 use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
 use crate::cache::Interned;
+use crate::cache::INTERNER;
 use crate::compile;
 use crate::config::TargetSelection;
 use crate::dist;
@@ -27,44 +28,6 @@ use crate::{envify, CLang, DocTests, GitRepo, Mode};
 
 const ADB_TEST_DIR: &str = "/data/local/tmp/work";
 
-/// The two modes of the test runner; tests or benchmarks.
-#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord)]
-pub enum TestKind {
-    /// Run `cargo test`.
-    Test,
-    /// Run `cargo bench`.
-    Bench,
-}
-
-impl From<Kind> for TestKind {
-    fn from(kind: Kind) -> Self {
-        match kind {
-            Kind::Test => TestKind::Test,
-            Kind::Bench => TestKind::Bench,
-            _ => panic!("unexpected kind in crate: {:?}", kind),
-        }
-    }
-}
-
-impl TestKind {
-    // Return the cargo subcommand for this test kind
-    fn subcommand(self) -> &'static str {
-        match self {
-            TestKind::Test => "test",
-            TestKind::Bench => "bench",
-        }
-    }
-}
-
-impl Into<Kind> for TestKind {
-    fn into(self) -> Kind {
-        match self {
-            TestKind::Test => Kind::Test,
-            TestKind::Bench => Kind::Bench,
-        }
-    }
-}
-
 fn try_run(builder: &Builder<'_>, cmd: &mut Command) -> bool {
     if !builder.fail_fast {
         if !builder.try_run(cmd) {
@@ -92,26 +55,37 @@ fn try_run_quiet(builder: &Builder<'_>, cmd: &mut Command) -> bool {
 }
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct CrateJsonDocLint {
+pub struct CrateBootstrap {
+    path: Interned<PathBuf>,
     host: TargetSelection,
 }
 
-impl Step for CrateJsonDocLint {
+impl Step for CrateBootstrap {
     type Output = ();
     const ONLY_HOSTS: bool = true;
     const DEFAULT: bool = true;
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
         run.path("src/tools/jsondoclint")
+            .path("src/tools/suggest-tests")
+            .path("src/tools/replace-version-placeholder")
+            .alias("tidyselftest")
     }
 
     fn make_run(run: RunConfig<'_>) {
-        run.builder.ensure(CrateJsonDocLint { host: run.target });
+        for path in run.paths {
+            let path = INTERNER.intern_path(path.assert_single_path().path.clone());
+            run.builder.ensure(CrateBootstrap { host: run.target, path });
+        }
     }
 
     fn run(self, builder: &Builder<'_>) {
         let bootstrap_host = builder.config.build;
         let compiler = builder.compiler(0, bootstrap_host);
+        let mut path = self.path.to_str().unwrap();
+        if path == "tidyselftest" {
+            path = "src/tools/tidy";
+        }
 
         let cargo = tool::prepare_tool_cargo(
             builder,
@@ -119,47 +93,18 @@ impl Step for CrateJsonDocLint {
             Mode::ToolBootstrap,
             bootstrap_host,
             "test",
-            "src/tools/jsondoclint",
+            path,
             SourceType::InTree,
             &[],
         );
-        add_flags_and_try_run_tests(builder, &mut cargo.into());
-    }
-}
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct SuggestTestsCrate {
-    host: TargetSelection,
-}
-
-impl Step for SuggestTestsCrate {
-    type Output = ();
-    const ONLY_HOSTS: bool = true;
-    const DEFAULT: bool = true;
-
-    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
-        run.path("src/tools/suggest-tests")
-    }
-
-    fn make_run(run: RunConfig<'_>) {
-        run.builder.ensure(SuggestTestsCrate { host: run.target });
-    }
-
-    fn run(self, builder: &Builder<'_>) {
-        let bootstrap_host = builder.config.build;
-        let compiler = builder.compiler(0, bootstrap_host);
-
-        let suggest_tests = tool::prepare_tool_cargo(
-            builder,
-            compiler,
-            Mode::ToolBootstrap,
+        builder.info(&format!(
+            "{} {} stage0 ({})",
+            builder.kind.test_description(),
+            path,
             bootstrap_host,
-            "test",
-            "src/tools/suggest-tests",
-            SourceType::InTree,
-            &[],
-        );
-        add_flags_and_try_run_tests(builder, &mut suggest_tests.into());
+        ));
+        let crate_name = path.rsplit_once('/').unwrap().1;
+        run_cargo_test(cargo, &[], &[], crate_name, compiler, bootstrap_host, builder);
     }
 }
 
@@ -208,7 +153,11 @@ You can skip linkcheck with --exclude src/tools/linkchecker"
             SourceType::InTree,
             &[],
         );
-        add_flags_and_try_run_tests(builder, &mut cargo.into());
+        run_cargo_test(cargo, &[], &[], "linkchecker", compiler, bootstrap_host, builder);
+
+        if builder.doc_tests == DocTests::No {
+            return;
+        }
 
         // Build all the default documentation.
         builder.default_doc(&[]);
@@ -344,7 +293,7 @@ impl Step for Cargo {
         let compiler = builder.compiler(self.stage, self.host);
 
         builder.ensure(tool::Cargo { compiler, target: self.host });
-        let mut cargo = tool::prepare_tool_cargo(
+        let cargo = tool::prepare_tool_cargo(
             builder,
             compiler,
             Mode::ToolRustc,
@@ -355,10 +304,8 @@ impl Step for Cargo {
             &[],
         );
 
-        if !builder.fail_fast {
-            cargo.arg("--no-fail-fast");
-        }
-        cargo.arg("--").args(builder.config.cmd.test_args());
+        // NOTE: can't use `run_cargo_test` because we need to overwrite `PATH`
+        let mut cargo = prepare_cargo_test(cargo, &[], &[], "cargo", compiler, self.host, builder);
 
         // Don't run cross-compile tests, we may not have cross-compiled libstd libs
         // available.
@@ -366,10 +313,10 @@ impl Step for Cargo {
         // Forcibly disable tests using nightly features since any changes to
         // those features won't be able to land.
         cargo.env("CARGO_TEST_DISABLE_NIGHTLY", "1");
-
         cargo.env("PATH", &path_for_cargo(builder, compiler));
 
-        add_flags_and_try_run_tests(builder, &mut cargo.into());
+        let _time = util::timeit(&builder);
+        add_flags_and_try_run_tests(builder, &mut cargo);
     }
 }
 
@@ -426,9 +373,7 @@ impl Step for RustAnalyzer {
         cargo.env("SKIP_SLOW_TESTS", "1");
 
         cargo.add_rustc_lib_path(builder, compiler);
-        cargo.arg("--").args(builder.config.cmd.test_args());
-
-        add_flags_and_try_run_tests(builder, &mut cargo.into());
+        run_cargo_test(cargo, &[], &[], "rust-analyzer", compiler, host, builder);
     }
 }
 
@@ -471,17 +416,13 @@ impl Step for Rustfmt {
             &[],
         );
 
-        if !builder.fail_fast {
-            cargo.arg("--no-fail-fast");
-        }
-
         let dir = testdir(builder, compiler.host);
         t!(fs::create_dir_all(&dir));
         cargo.env("RUSTFMT_TEST_DIR", dir);
 
         cargo.add_rustc_lib_path(builder, compiler);
 
-        add_flags_and_try_run_tests(builder, &mut cargo.into());
+        run_cargo_test(cargo, &[], &[], "rustfmt", compiler, host, builder);
     }
 }
 
@@ -527,12 +468,9 @@ impl Step for RustDemangler {
         t!(fs::create_dir_all(&dir));
 
         cargo.env("RUST_DEMANGLER_DRIVER_PATH", rust_demangler);
-
-        cargo.arg("--").args(builder.config.cmd.test_args());
-
         cargo.add_rustc_lib_path(builder, compiler);
 
-        add_flags_and_try_run_tests(builder, &mut cargo.into());
+        run_cargo_test(cargo, &[], &[], "rust-demangler", compiler, host, builder);
     }
 }
 
@@ -655,10 +593,6 @@ impl Step for Miri {
         );
         cargo.add_rustc_lib_path(builder, compiler);
 
-        if !builder.fail_fast {
-            cargo.arg("--no-fail-fast");
-        }
-
         // miri tests need to know about the stage sysroot
         cargo.env("MIRI_SYSROOT", &miri_sysroot);
         cargo.env("MIRI_HOST_SYSROOT", sysroot);
@@ -670,13 +604,14 @@ impl Step for Miri {
 
         // Set the target.
         cargo.env("MIRI_TEST_TARGET", target.rustc_target_arg());
-        // Forward test filters.
-        cargo.arg("--").args(builder.config.cmd.test_args());
 
-        // This can NOT be `add_flags_and_try_run_tests` since the Miri test runner
-        // does not understand those flags!
-        let mut cargo = Command::from(cargo);
-        builder.run(&mut cargo);
+        // This can NOT be `run_cargo_test` since the Miri test runner
+        // does not understand the flags added by `add_flags_and_try_run_test`.
+        let mut cargo = prepare_cargo_test(cargo, &[], &[], "miri", compiler, target, builder);
+        {
+            let _time = util::timeit(&builder);
+            builder.run(&mut cargo);
+        }
 
         // # Run `cargo miri test`.
         // This is just a smoke test (Miri's own CI invokes this in a bunch of different ways and ensures
@@ -709,6 +644,7 @@ impl Step for Miri {
         cargo.env("RUST_BACKTRACE", "1");
 
         let mut cargo = Command::from(cargo);
+        let _time = util::timeit(&builder);
         builder.run(&mut cargo);
     }
 }
@@ -748,8 +684,7 @@ impl Step for CompiletestTest {
             &[],
         );
         cargo.allow_features("test");
-
-        add_flags_and_try_run_tests(builder, &mut cargo.into());
+        run_cargo_test(cargo, &[], &[], "compiletest", compiler, host, builder);
     }
 }
 
@@ -792,20 +727,15 @@ impl Step for Clippy {
             &[],
         );
 
-        if !builder.fail_fast {
-            cargo.arg("--no-fail-fast");
-        }
-
         cargo.env("RUSTC_TEST_SUITE", builder.rustc(compiler));
         cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler));
         let host_libs = builder.stage_out(compiler, Mode::ToolRustc).join(builder.cargo_dir());
         cargo.env("HOST_LIBS", host_libs);
 
-        cargo.arg("--").args(builder.config.cmd.test_args());
-
         cargo.add_rustc_lib_path(builder, compiler);
+        let mut cargo = prepare_cargo_test(cargo, &[], &[], "clippy", compiler, host, builder);
 
-        if builder.try_run(&mut cargo.into()) {
+        if builder.try_run(&mut cargo) {
             // The tests succeeded; nothing to do.
             return;
         }
@@ -1203,40 +1133,6 @@ help: to skip test's attempt to check tidiness, pass `--exclude src/tools/tidy`
     }
 }
 
-/// Runs tidy's own tests.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct TidySelfTest;
-
-impl Step for TidySelfTest {
-    type Output = ();
-    const DEFAULT: bool = true;
-    const ONLY_HOSTS: bool = true;
-
-    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
-        run.alias("tidyselftest")
-    }
-
-    fn make_run(run: RunConfig<'_>) {
-        run.builder.ensure(TidySelfTest);
-    }
-
-    fn run(self, builder: &Builder<'_>) {
-        let bootstrap_host = builder.config.build;
-        let compiler = builder.compiler(0, bootstrap_host);
-        let cargo = tool::prepare_tool_cargo(
-            builder,
-            compiler,
-            Mode::ToolBootstrap,
-            bootstrap_host,
-            "test",
-            "src/tools/tidy",
-            SourceType::InTree,
-            &[],
-        );
-        add_flags_and_try_run_tests(builder, &mut cargo.into());
-    }
-}
-
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct ExpandYamlAnchors;
 
@@ -2111,7 +2007,6 @@ impl Step for RustcGuide {
 pub struct CrateLibrustc {
     compiler: Compiler,
     target: TargetSelection,
-    test_kind: TestKind,
     crates: Vec<Interned<String>>,
 }
 
@@ -2133,9 +2028,8 @@ impl Step for CrateLibrustc {
             .iter()
             .map(|p| builder.crate_paths[&p.assert_single_path().path].clone())
             .collect();
-        let test_kind = builder.kind.into();
 
-        builder.ensure(CrateLibrustc { compiler, target: run.target, test_kind, crates });
+        builder.ensure(CrateLibrustc { compiler, target: run.target, crates });
     }
 
     fn run(self, builder: &Builder<'_>) {
@@ -2143,18 +2037,106 @@ impl Step for CrateLibrustc {
             compiler: self.compiler,
             target: self.target,
             mode: Mode::Rustc,
-            test_kind: self.test_kind,
             crates: self.crates,
         });
     }
 }
 
+/// Given a `cargo test` subcommand, add the appropriate flags and run it.
+///
+/// Returns whether the test succeeded.
+fn run_cargo_test(
+    cargo: impl Into<Command>,
+    libtest_args: &[&str],
+    crates: &[Interned<String>],
+    primary_crate: &str,
+    compiler: Compiler,
+    target: TargetSelection,
+    builder: &Builder<'_>,
+) -> bool {
+    let mut cargo =
+        prepare_cargo_test(cargo, libtest_args, crates, primary_crate, compiler, target, builder);
+    let _time = util::timeit(&builder);
+    add_flags_and_try_run_tests(builder, &mut cargo)
+}
+
+/// Given a `cargo test` subcommand, pass it the appropriate test flags given a `builder`.
+fn prepare_cargo_test(
+    cargo: impl Into<Command>,
+    libtest_args: &[&str],
+    crates: &[Interned<String>],
+    primary_crate: &str,
+    compiler: Compiler,
+    target: TargetSelection,
+    builder: &Builder<'_>,
+) -> Command {
+    let mut cargo = cargo.into();
+
+    // Pass in some standard flags then iterate over the graph we've discovered
+    // in `cargo metadata` with the maps above and figure out what `-p`
+    // arguments need to get passed.
+    if builder.kind == Kind::Test && !builder.fail_fast {
+        cargo.arg("--no-fail-fast");
+    }
+    match builder.doc_tests {
+        DocTests::Only => {
+            cargo.arg("--doc");
+        }
+        DocTests::No => {
+            let krate = &builder
+                .crates
+                .get(&INTERNER.intern_str(primary_crate))
+                .unwrap_or_else(|| panic!("missing crate {primary_crate}"));
+            if krate.has_lib {
+                cargo.arg("--lib");
+            }
+            cargo.args(&["--bins", "--examples", "--tests", "--benches"]);
+        }
+        DocTests::Yes => {}
+    }
+
+    for &krate in crates {
+        cargo.arg("-p").arg(krate);
+    }
+
+    cargo.arg("--").args(&builder.config.cmd.test_args()).args(libtest_args);
+    if !builder.config.verbose_tests {
+        cargo.arg("--quiet");
+    }
+
+    // The tests are going to run with the *target* libraries, so we need to
+    // ensure that those libraries show up in the LD_LIBRARY_PATH equivalent.
+    //
+    // Note that to run the compiler we need to run with the *host* libraries,
+    // but our wrapper scripts arrange for that to be the case anyway.
+    let mut dylib_path = dylib_path();
+    dylib_path.insert(0, PathBuf::from(&*builder.sysroot_libdir(compiler, target)));
+    cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
+
+    if target.contains("emscripten") {
+        cargo.env(
+            format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)),
+            builder.config.nodejs.as_ref().expect("nodejs not configured"),
+        );
+    } else if target.starts_with("wasm32") {
+        let node = builder.config.nodejs.as_ref().expect("nodejs not configured");
+        let runner = format!("{} {}/src/etc/wasm32-shim.js", node.display(), builder.src.display());
+        cargo.env(format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), &runner);
+    } else if builder.remote_tested(target) {
+        cargo.env(
+            format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)),
+            format!("{} run 0", builder.tool_exe(Tool::RemoteTestClient).display()),
+        );
+    }
+
+    cargo
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct Crate {
     pub compiler: Compiler,
     pub target: TargetSelection,
     pub mode: Mode,
-    pub test_kind: TestKind,
     pub crates: Vec<Interned<String>>,
 }
 
@@ -2170,14 +2152,13 @@ impl Step for Crate {
         let builder = run.builder;
         let host = run.build_triple();
         let compiler = builder.compiler_for(builder.top_stage, host, host);
-        let test_kind = builder.kind.into();
         let crates = run
             .paths
             .iter()
             .map(|p| builder.crate_paths[&p.assert_single_path().path].clone())
             .collect();
 
-        builder.ensure(Crate { compiler, target: run.target, mode: Mode::Std, test_kind, crates });
+        builder.ensure(Crate { compiler, target: run.target, mode: Mode::Std, crates });
     }
 
     /// Runs all unit tests plus documentation tests for a given crate defined
@@ -2192,7 +2173,6 @@ impl Step for Crate {
         let compiler = self.compiler;
         let target = self.target;
         let mode = self.mode;
-        let test_kind = self.test_kind;
 
         builder.ensure(compile::Std::new(compiler, target));
         builder.ensure(RemoteCopyLibs { compiler, target });
@@ -2204,7 +2184,7 @@ impl Step for Crate {
         let compiler = builder.compiler_for(compiler.stage, compiler.host, target);
 
         let mut cargo =
-            builder.cargo(compiler, mode, SourceType::InTree, target, test_kind.subcommand());
+            builder.cargo(compiler, mode, SourceType::InTree, target, builder.kind.as_str());
         match mode {
             Mode::Std => {
                 compile::std_cargo(builder, target, compiler.stage, &mut cargo);
@@ -2215,69 +2195,14 @@ impl Step for Crate {
             _ => panic!("can only test libraries"),
         };
 
-        // Build up the base `cargo test` command.
-        //
-        // Pass in some standard flags then iterate over the graph we've discovered
-        // in `cargo metadata` with the maps above and figure out what `-p`
-        // arguments need to get passed.
-        if test_kind.subcommand() == "test" && !builder.fail_fast {
-            cargo.arg("--no-fail-fast");
-        }
-        match builder.doc_tests {
-            DocTests::Only => {
-                cargo.arg("--doc");
-            }
-            DocTests::No => {
-                cargo.args(&["--lib", "--bins", "--examples", "--tests", "--benches"]);
-            }
-            DocTests::Yes => {}
-        }
-
-        for krate in &self.crates {
-            cargo.arg("-p").arg(krate);
-        }
-
-        // The tests are going to run with the *target* libraries, so we need to
-        // ensure that those libraries show up in the LD_LIBRARY_PATH equivalent.
-        //
-        // Note that to run the compiler we need to run with the *host* libraries,
-        // but our wrapper scripts arrange for that to be the case anyway.
-        let mut dylib_path = dylib_path();
-        dylib_path.insert(0, PathBuf::from(&*builder.sysroot_libdir(compiler, target)));
-        cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
-
-        cargo.arg("--");
-        cargo.args(&builder.config.cmd.test_args());
-
-        cargo.arg("-Z").arg("unstable-options");
-        cargo.arg("--format").arg("json");
-
-        if target.contains("emscripten") {
-            cargo.env(
-                format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)),
-                builder.config.nodejs.as_ref().expect("nodejs not configured"),
-            );
-        } else if target.starts_with("wasm32") {
-            let node = builder.config.nodejs.as_ref().expect("nodejs not configured");
-            let runner =
-                format!("{} {}/src/etc/wasm32-shim.js", node.display(), builder.src.display());
-            cargo.env(format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), &runner);
-        } else if builder.remote_tested(target) {
-            cargo.env(
-                format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)),
-                format!("{} run 0", builder.tool_exe(Tool::RemoteTestClient).display()),
-            );
-        }
-
         let _guard = builder.msg(
-            test_kind,
+            builder.kind,
             compiler.stage,
             crate_description(&self.crates),
             compiler.host,
             target,
         );
-        let _time = util::timeit(&builder);
-        crate::render_tests::try_run_tests(builder, &mut cargo.into());
+        run_cargo_test(cargo, &[], &self.crates, &self.crates[0], compiler, target, builder);
     }
 }
 
@@ -2285,7 +2210,6 @@ impl Step for Crate {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct CrateRustdoc {
     host: TargetSelection,
-    test_kind: TestKind,
 }
 
 impl Step for CrateRustdoc {
@@ -2300,13 +2224,10 @@ impl Step for CrateRustdoc {
     fn make_run(run: RunConfig<'_>) {
         let builder = run.builder;
 
-        let test_kind = builder.kind.into();
-
-        builder.ensure(CrateRustdoc { host: run.target, test_kind });
+        builder.ensure(CrateRustdoc { host: run.target });
     }
 
     fn run(self, builder: &Builder<'_>) {
-        let test_kind = self.test_kind;
         let target = self.host;
 
         let compiler = if builder.download_rustc() {
@@ -2325,29 +2246,11 @@ impl Step for CrateRustdoc {
             compiler,
             Mode::ToolRustc,
             target,
-            test_kind.subcommand(),
+            builder.kind.as_str(),
             "src/tools/rustdoc",
             SourceType::InTree,
             &[],
         );
-        if test_kind.subcommand() == "test" && !builder.fail_fast {
-            cargo.arg("--no-fail-fast");
-        }
-        match builder.doc_tests {
-            DocTests::Only => {
-                cargo.arg("--doc");
-            }
-            DocTests::No => {
-                cargo.args(&["--lib", "--bins", "--examples", "--tests", "--benches"]);
-            }
-            DocTests::Yes => {}
-        }
-
-        cargo.arg("-p").arg("rustdoc:0.0.0");
-
-        cargo.arg("--");
-        cargo.args(&builder.config.cmd.test_args());
-
         if self.host.contains("musl") {
             cargo.arg("'-Ctarget-feature=-crt-static'");
         }
@@ -2387,22 +2290,22 @@ impl Step for CrateRustdoc {
         dylib_path.insert(0, PathBuf::from(&*libdir));
         cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
 
-        if !builder.config.verbose_tests {
-            cargo.arg("--quiet");
-        }
-
-        let _guard = builder.msg(test_kind, compiler.stage, "rustdoc", compiler.host, target);
-
-        let _time = util::timeit(&builder);
-
-        add_flags_and_try_run_tests(builder, &mut cargo.into());
+        let _guard = builder.msg(builder.kind, compiler.stage, "rustdoc", compiler.host, target);
+        run_cargo_test(
+            cargo,
+            &[],
+            &[INTERNER.intern_str("rustdoc:0.0.0")],
+            "rustdoc",
+            compiler,
+            target,
+            builder,
+        );
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct CrateRustdocJsonTypes {
     host: TargetSelection,
-    test_kind: TestKind,
 }
 
 impl Step for CrateRustdocJsonTypes {
@@ -2417,13 +2320,10 @@ impl Step for CrateRustdocJsonTypes {
     fn make_run(run: RunConfig<'_>) {
         let builder = run.builder;
 
-        let test_kind = builder.kind.into();
-
-        builder.ensure(CrateRustdocJsonTypes { host: run.target, test_kind });
+        builder.ensure(CrateRustdocJsonTypes { host: run.target });
     }
 
     fn run(self, builder: &Builder<'_>) {
-        let test_kind = self.test_kind;
         let target = self.host;
 
         // Use the previous stage compiler to reuse the artifacts that are
@@ -2433,34 +2333,35 @@ impl Step for CrateRustdocJsonTypes {
         let compiler = builder.compiler_for(builder.top_stage, target, target);
         builder.ensure(compile::Rustc::new(compiler, target));
 
-        let mut cargo = tool::prepare_tool_cargo(
+        let cargo = tool::prepare_tool_cargo(
             builder,
             compiler,
             Mode::ToolRustc,
             target,
-            test_kind.subcommand(),
+            builder.kind.as_str(),
             "src/rustdoc-json-types",
             SourceType::InTree,
             &[],
         );
-        if test_kind.subcommand() == "test" && !builder.fail_fast {
-            cargo.arg("--no-fail-fast");
-        }
-
-        cargo.arg("-p").arg("rustdoc-json-types");
-
-        cargo.arg("--");
-        cargo.args(&builder.config.cmd.test_args());
 
-        if self.host.contains("musl") {
-            cargo.arg("'-Ctarget-feature=-crt-static'");
-        }
+        // FIXME: this looks very wrong, libtest doesn't accept `-C` arguments and the quotes are fishy.
+        let libtest_args = if self.host.contains("musl") {
+            ["'-Ctarget-feature=-crt-static'"].as_slice()
+        } else {
+            &[]
+        };
 
         let _guard =
-            builder.msg(test_kind, compiler.stage, "rustdoc-json-types", compiler.host, target);
-        let _time = util::timeit(&builder);
-
-        add_flags_and_try_run_tests(builder, &mut cargo.into());
+            builder.msg(builder.kind, compiler.stage, "rustdoc-json-types", compiler.host, target);
+        run_cargo_test(
+            cargo,
+            libtest_args,
+            &[INTERNER.intern_str("rustdoc-json-types")],
+            "rustdoc-json-types",
+            compiler,
+            target,
+            builder,
+        );
     }
 }
 
@@ -2598,13 +2499,15 @@ impl Step for Bootstrap {
         check_bootstrap.arg("bootstrap_test.py").current_dir(builder.src.join("src/bootstrap/"));
         try_run(builder, &mut check_bootstrap);
 
+        let host = builder.config.build;
+        let compiler = builder.compiler(0, host);
         let mut cmd = Command::new(&builder.initial_cargo);
         cmd.arg("test")
             .current_dir(builder.src.join("src/bootstrap"))
             .env("RUSTFLAGS", "-Cdebuginfo=2")
             .env("CARGO_TARGET_DIR", builder.out.join("bootstrap"))
             .env("RUSTC_BOOTSTRAP", "1")
-            .env("RUSTDOC", builder.rustdoc(builder.compiler(0, builder.build.build)))
+            .env("RUSTDOC", builder.rustdoc(compiler))
             .env("RUSTC", &builder.initial_rustc);
         if let Some(flags) = option_env!("RUSTFLAGS") {
             // Use the same rustc flags for testing as for "normal" compilation,
@@ -2612,24 +2515,9 @@ impl Step for Bootstrap {
             // https://github.com/rust-lang/rust/issues/49215
             cmd.env("RUSTFLAGS", flags);
         }
-        if !builder.fail_fast {
-            cmd.arg("--no-fail-fast");
-        }
-        match builder.doc_tests {
-            DocTests::Only => {
-                cmd.arg("--doc");
-            }
-            DocTests::No => {
-                cmd.args(&["--lib", "--bins", "--examples", "--tests", "--benches"]);
-            }
-            DocTests::Yes => {}
-        }
-
-        cmd.arg("--").args(&builder.config.cmd.test_args());
         // rustbuild tests are racy on directory creation so just run them one at a time.
         // Since there's not many this shouldn't be a problem.
-        cmd.arg("--test-threads=1");
-        add_flags_and_try_run_tests(builder, &mut cmd);
+        run_cargo_test(cmd, &["--test-threads=1"], &[], "bootstrap", compiler, host, builder);
     }
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
@@ -2686,43 +2574,6 @@ impl Step for TierCheck {
 }
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct ReplacePlaceholderTest;
-
-impl Step for ReplacePlaceholderTest {
-    type Output = ();
-    const ONLY_HOSTS: bool = true;
-    const DEFAULT: bool = true;
-
-    /// Ensure the version placeholder replacement tool builds
-    fn run(self, builder: &Builder<'_>) {
-        builder.info("build check for version replacement placeholder");
-
-        // Test the version placeholder replacement tool itself.
-        let bootstrap_host = builder.config.build;
-        let compiler = builder.compiler(0, bootstrap_host);
-        let cargo = tool::prepare_tool_cargo(
-            builder,
-            compiler,
-            Mode::ToolBootstrap,
-            bootstrap_host,
-            "test",
-            "src/tools/replace-version-placeholder",
-            SourceType::InTree,
-            &[],
-        );
-        add_flags_and_try_run_tests(builder, &mut cargo.into());
-    }
-
-    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
-        run.path("src/tools/replace-version-placeholder")
-    }
-
-    fn make_run(run: RunConfig<'_>) {
-        run.builder.ensure(Self);
-    }
-}
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct LintDocs {
     pub compiler: Compiler,
     pub target: TargetSelection,
@@ -2779,7 +2630,7 @@ impl Step for RustInstaller {
             SourceType::InTree,
             &[],
         );
-        try_run(builder, &mut cargo.into());
+        run_cargo_test(cargo, &[], &[], "installer", compiler, bootstrap_host, builder);
 
         // We currently don't support running the test.sh script outside linux(?) environments.
         // Eventually this should likely migrate to #[test]s in rust-installer proper rather than a
diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs
index 9418cf0a7ed..39f6369b4d3 100644
--- a/src/bootstrap/tool.rs
+++ b/src/bootstrap/tool.rs
@@ -141,7 +141,7 @@ pub fn prepare_tool_cargo(
     mode: Mode,
     target: TargetSelection,
     command: &'static str,
-    path: &'static str,
+    path: &str,
     source_type: SourceType,
     extra_features: &[String],
 ) -> CargoCommand {
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 8ccdb16b784..1531e7fc7b9 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -1529,7 +1529,9 @@ fn maybe_expand_private_type_alias<'tcx>(
     let Res::Def(DefKind::TyAlias, def_id) = path.res else { return None };
     // Substitute private type aliases
     let def_id = def_id.as_local()?;
-    let alias = if !cx.cache.effective_visibilities.is_exported(cx.tcx, def_id.to_def_id()) {
+    let alias = if !cx.cache.effective_visibilities.is_exported(cx.tcx, def_id.to_def_id())
+        && !cx.current_type_aliases.contains_key(&def_id.to_def_id())
+    {
         &cx.tcx.hir().expect_item(def_id).kind
     } else {
         return None;
@@ -1609,7 +1611,7 @@ fn maybe_expand_private_type_alias<'tcx>(
         }
     }
 
-    Some(cx.enter_alias(substs, |cx| clean_ty(ty, cx)))
+    Some(cx.enter_alias(substs, def_id.to_def_id(), |cx| clean_ty(ty, cx)))
 }
 
 pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type {
@@ -1700,7 +1702,7 @@ fn normalize<'tcx>(
 pub(crate) fn clean_middle_ty<'tcx>(
     bound_ty: ty::Binder<'tcx, Ty<'tcx>>,
     cx: &mut DocContext<'tcx>,
-    def_id: Option<DefId>,
+    parent_def_id: Option<DefId>,
 ) -> Type {
     let bound_ty = normalize(cx, bound_ty).unwrap_or(bound_ty);
     match *bound_ty.skip_binder().kind() {
@@ -1830,7 +1832,9 @@ pub(crate) fn clean_middle_ty<'tcx>(
             Tuple(t.iter().map(|t| clean_middle_ty(bound_ty.rebind(t), cx, None)).collect())
         }
 
-        ty::Alias(ty::Projection, ref data) => clean_projection(bound_ty.rebind(*data), cx, def_id),
+        ty::Alias(ty::Projection, ref data) => {
+            clean_projection(bound_ty.rebind(*data), cx, parent_def_id)
+        }
 
         ty::Param(ref p) => {
             if let Some(bounds) = cx.impl_trait_bounds.remove(&p.index.into()) {
@@ -1841,15 +1845,30 @@ pub(crate) fn clean_middle_ty<'tcx>(
         }
 
         ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => {
-            // Grab the "TraitA + TraitB" from `impl TraitA + TraitB`,
-            // by looking up the bounds associated with the def_id.
-            let bounds = cx
-                .tcx
-                .explicit_item_bounds(def_id)
-                .subst_iter_copied(cx.tcx, substs)
-                .map(|(bound, _)| bound)
-                .collect::<Vec<_>>();
-            clean_middle_opaque_bounds(cx, bounds)
+            // If it's already in the same alias, don't get an infinite loop.
+            if cx.current_type_aliases.contains_key(&def_id) {
+                let path =
+                    external_path(cx, def_id, false, ThinVec::new(), bound_ty.rebind(substs));
+                Type::Path { path }
+            } else {
+                *cx.current_type_aliases.entry(def_id).or_insert(0) += 1;
+                // Grab the "TraitA + TraitB" from `impl TraitA + TraitB`,
+                // by looking up the bounds associated with the def_id.
+                let bounds = cx
+                    .tcx
+                    .explicit_item_bounds(def_id)
+                    .subst_iter_copied(cx.tcx, substs)
+                    .map(|(bound, _)| bound)
+                    .collect::<Vec<_>>();
+                let ty = clean_middle_opaque_bounds(cx, bounds);
+                if let Some(count) = cx.current_type_aliases.get_mut(&def_id) {
+                    *count -= 1;
+                    if *count == 0 {
+                        cx.current_type_aliases.remove(&def_id);
+                    }
+                }
+                ty
+            }
         }
 
         ty::Closure(..) => panic!("Closure"),
@@ -2229,13 +2248,17 @@ fn clean_maybe_renamed_item<'tcx>(
                 generics: clean_generics(ty.generics, cx),
             }),
             ItemKind::TyAlias(hir_ty, generics) => {
+                *cx.current_type_aliases.entry(def_id).or_insert(0) += 1;
                 let rustdoc_ty = clean_ty(hir_ty, cx);
                 let ty = clean_middle_ty(ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)), cx, None);
-                TypedefItem(Box::new(Typedef {
-                    type_: rustdoc_ty,
-                    generics: clean_generics(generics, cx),
-                    item_type: Some(ty),
-                }))
+                let generics = clean_generics(generics, cx);
+                if let Some(count) = cx.current_type_aliases.get_mut(&def_id) {
+                    *count -= 1;
+                    if *count == 0 {
+                        cx.current_type_aliases.remove(&def_id);
+                    }
+                }
+                TypedefItem(Box::new(Typedef { type_: rustdoc_ty, generics, item_type: Some(ty) }))
             }
             ItemKind::Enum(ref def, generics) => EnumItem(Enum {
                 variants: def.variants.iter().map(|v| clean_variant(v, cx)).collect(),
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 74f330b7621..7371b44465b 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -11,6 +11,7 @@ use arrayvec::ArrayVec;
 use thin_vec::ThinVec;
 
 use rustc_ast as ast;
+use rustc_ast_pretty::pprust;
 use rustc_attr::{ConstStability, Deprecation, Stability, StabilityLevel};
 use rustc_const_eval::const_eval::is_unstable_const_fn;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -711,6 +712,78 @@ impl Item {
         };
         Some(tcx.visibility(def_id))
     }
+
+    pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, keep_as_is: bool) -> Vec<String> {
+        const ALLOWED_ATTRIBUTES: &[Symbol] =
+            &[sym::export_name, sym::link_section, sym::no_mangle, sym::repr, sym::non_exhaustive];
+
+        use rustc_abi::IntegerType;
+        use rustc_middle::ty::ReprFlags;
+
+        let mut attrs: Vec<String> = self
+            .attrs
+            .other_attrs
+            .iter()
+            .filter_map(|attr| {
+                if keep_as_is {
+                    Some(pprust::attribute_to_string(attr))
+                } else if ALLOWED_ATTRIBUTES.contains(&attr.name_or_empty()) {
+                    Some(
+                        pprust::attribute_to_string(attr)
+                            .replace("\\\n", "")
+                            .replace('\n', "")
+                            .replace("  ", " "),
+                    )
+                } else {
+                    None
+                }
+            })
+            .collect();
+        if let Some(def_id) = self.item_id.as_def_id() &&
+            !def_id.is_local() &&
+            // This check is needed because `adt_def` will panic if not a compatible type otherwise...
+            matches!(self.type_(), ItemType::Struct | ItemType::Enum | ItemType::Union)
+        {
+            let repr = tcx.adt_def(def_id).repr();
+            let mut out = Vec::new();
+            if repr.flags.contains(ReprFlags::IS_C) {
+                out.push("C");
+            }
+            if repr.flags.contains(ReprFlags::IS_TRANSPARENT) {
+                out.push("transparent");
+            }
+            if repr.flags.contains(ReprFlags::IS_SIMD) {
+                out.push("simd");
+            }
+            let pack_s;
+            if let Some(pack) = repr.pack {
+                pack_s = format!("packed({})", pack.bytes());
+                out.push(&pack_s);
+            }
+            let align_s;
+            if let Some(align) = repr.align {
+                align_s = format!("align({})", align.bytes());
+                out.push(&align_s);
+            }
+            let int_s;
+            if let Some(int) = repr.int {
+                int_s = match int {
+                    IntegerType::Pointer(is_signed) => {
+                        format!("{}size", if is_signed { 'i' } else { 'u' })
+                    }
+                    IntegerType::Fixed(size, is_signed) => {
+                        format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
+                    }
+                };
+                out.push(&int_s);
+            }
+            if out.is_empty() {
+                return Vec::new();
+            }
+            attrs.push(format!("#[repr({})]", out.join(", ")));
+        }
+        attrs
+    }
 }
 
 #[derive(Clone, Debug)]
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 46834a1d7f4..3a0c2ab0297 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -46,6 +46,7 @@ pub(crate) struct DocContext<'tcx> {
     // for expanding type aliases at the HIR level:
     /// Table `DefId` of type, lifetime, or const parameter -> substituted type, lifetime, or const
     pub(crate) substs: DefIdMap<clean::SubstParam>,
+    pub(crate) current_type_aliases: DefIdMap<usize>,
     /// Table synthetic type parameter for `impl Trait` in argument position -> bounds
     pub(crate) impl_trait_bounds: FxHashMap<ImplTraitParam, Vec<clean::GenericBound>>,
     /// Auto-trait or blanket impls processed so far, as `(self_ty, trait_def_id)`.
@@ -82,13 +83,25 @@ impl<'tcx> DocContext<'tcx> {
 
     /// Call the closure with the given parameters set as
     /// the substitutions for a type alias' RHS.
-    pub(crate) fn enter_alias<F, R>(&mut self, substs: DefIdMap<clean::SubstParam>, f: F) -> R
+    pub(crate) fn enter_alias<F, R>(
+        &mut self,
+        substs: DefIdMap<clean::SubstParam>,
+        def_id: DefId,
+        f: F,
+    ) -> R
     where
         F: FnOnce(&mut Self) -> R,
     {
         let old_substs = mem::replace(&mut self.substs, substs);
+        *self.current_type_aliases.entry(def_id).or_insert(0) += 1;
         let r = f(self);
         self.substs = old_substs;
+        if let Some(count) = self.current_type_aliases.get_mut(&def_id) {
+            *count -= 1;
+            if *count == 0 {
+                self.current_type_aliases.remove(&def_id);
+            }
+        }
         r
     }
 
@@ -327,6 +340,7 @@ pub(crate) fn run_global_ctxt(
         external_traits: Default::default(),
         active_extern_traits: Default::default(),
         substs: Default::default(),
+        current_type_aliases: Default::default(),
         impl_trait_bounds: Default::default(),
         generated_synthetics: Default::default(),
         auto_traits,
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index d90d0aecb93..73bf27c9d34 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -48,7 +48,6 @@ use std::str;
 use std::string::ToString;
 
 use askama::Template;
-use rustc_ast_pretty::pprust;
 use rustc_attr::{ConstStability, Deprecation, StabilityLevel};
 use rustc_data_structures::captures::Captures;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -849,10 +848,10 @@ fn assoc_method(
     let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
         header_len += 4;
         let indent_str = "    ";
-        write!(w, "{}", render_attributes_in_pre(meth, indent_str));
+        write!(w, "{}", render_attributes_in_pre(meth, indent_str, tcx));
         (4, indent_str, Ending::NoNewline)
     } else {
-        render_attributes_in_code(w, meth);
+        render_attributes_in_code(w, meth, tcx);
         (0, "", Ending::Newline)
     };
     w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len());
@@ -1021,36 +1020,15 @@ fn render_assoc_item(
     }
 }
 
-const ALLOWED_ATTRIBUTES: &[Symbol] =
-    &[sym::export_name, sym::link_section, sym::no_mangle, sym::repr, sym::non_exhaustive];
-
-fn attributes(it: &clean::Item) -> Vec<String> {
-    it.attrs
-        .other_attrs
-        .iter()
-        .filter_map(|attr| {
-            if ALLOWED_ATTRIBUTES.contains(&attr.name_or_empty()) {
-                Some(
-                    pprust::attribute_to_string(attr)
-                        .replace("\\\n", "")
-                        .replace('\n', "")
-                        .replace("  ", " "),
-                )
-            } else {
-                None
-            }
-        })
-        .collect()
-}
-
 // When an attribute is rendered inside a `<pre>` tag, it is formatted using
 // a whitespace prefix and newline.
-fn render_attributes_in_pre<'a>(
+fn render_attributes_in_pre<'a, 'b: 'a>(
     it: &'a clean::Item,
     prefix: &'a str,
-) -> impl fmt::Display + Captures<'a> {
+    tcx: TyCtxt<'b>,
+) -> impl fmt::Display + Captures<'a> + Captures<'b> {
     crate::html::format::display_fn(move |f| {
-        for a in attributes(it) {
+        for a in it.attributes(tcx, false) {
             writeln!(f, "{}{}", prefix, a)?;
         }
         Ok(())
@@ -1059,8 +1037,8 @@ fn render_attributes_in_pre<'a>(
 
 // When an attribute is rendered inside a <code> tag, it is formatted using
 // a div to produce a newline after it.
-fn render_attributes_in_code(w: &mut Buffer, it: &clean::Item) {
-    for a in attributes(it) {
+fn render_attributes_in_code(w: &mut Buffer, it: &clean::Item, tcx: TyCtxt<'_>) {
+    for a in it.attributes(tcx, false) {
         write!(w, "<div class=\"code-attribute\">{}</div>", a);
     }
 }
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 3e71d41ec96..4cc81e860f0 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -548,7 +548,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
             w,
             "{attrs}{vis}{constness}{asyncness}{unsafety}{abi}fn \
                 {name}{generics}{decl}{notable_traits}{where_clause}",
-            attrs = render_attributes_in_pre(it, ""),
+            attrs = render_attributes_in_pre(it, "", tcx),
             vis = visibility,
             constness = constness,
             asyncness = asyncness,
@@ -589,7 +589,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
             it.name.unwrap(),
             t.generics.print(cx),
             bounds,
-            attrs = render_attributes_in_pre(it, ""),
+            attrs = render_attributes_in_pre(it, "", tcx),
         );
 
         if !t.generics.where_predicates.is_empty() {
@@ -1063,7 +1063,7 @@ fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &
             t.generics.print(cx),
             print_where_clause(&t.generics, cx, 0, Ending::Newline),
             bounds(&t.bounds, true, cx),
-            attrs = render_attributes_in_pre(it, ""),
+            attrs = render_attributes_in_pre(it, "", cx.tcx()),
         );
     });
 
@@ -1085,7 +1085,7 @@ fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &cl
             t.generics.print(cx),
             where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline),
             bounds = bounds(&t.bounds, false, cx),
-            attrs = render_attributes_in_pre(it, ""),
+            attrs = render_attributes_in_pre(it, "", cx.tcx()),
         );
     });
 
@@ -1109,7 +1109,7 @@ fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clea
                 t.generics.print(cx),
                 where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline),
                 type_ = t.type_.print(cx),
-                attrs = render_attributes_in_pre(it, ""),
+                attrs = render_attributes_in_pre(it, "", cx.tcx()),
             );
         });
     }
@@ -1168,7 +1168,8 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean:
             &'b self,
         ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
             display_fn(move |f| {
-                let v = render_attributes_in_pre(self.it, "");
+                let tcx = self.cx.borrow().tcx();
+                let v = render_attributes_in_pre(self.it, "", tcx);
                 write!(f, "{v}")
             })
         }
@@ -1244,13 +1245,13 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
     let tcx = cx.tcx();
     let count_variants = e.variants().count();
     wrap_item(w, |mut w| {
+        render_attributes_in_code(w, it, tcx);
         write!(
             w,
-            "{attrs}{}enum {}{}",
+            "{}enum {}{}",
             visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
             it.name.unwrap(),
             e.generics.print(cx),
-            attrs = render_attributes_in_pre(it, ""),
         );
         if !print_where_clause_and_check(w, &e.generics, cx) {
             // If there wasn't a `where` clause, we add a whitespace.
@@ -1445,7 +1446,7 @@ fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
 fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &clean::Constant) {
     wrap_item(w, |w| {
         let tcx = cx.tcx();
-        render_attributes_in_code(w, it);
+        render_attributes_in_code(w, it, tcx);
 
         write!(
             w,
@@ -1492,7 +1493,7 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle
 
 fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) {
     wrap_item(w, |w| {
-        render_attributes_in_code(w, it);
+        render_attributes_in_code(w, it, cx.tcx());
         render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx);
     });
 
@@ -1542,7 +1543,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
 
 fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) {
     wrap_item(w, |w| {
-        render_attributes_in_code(w, it);
+        render_attributes_in_code(w, it, cx.tcx());
         write!(
             w,
             "{vis}static {mutability}{name}: {typ}",
@@ -1558,7 +1559,7 @@ fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
 fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
     wrap_item(w, |w| {
         w.write_str("extern {\n");
-        render_attributes_in_code(w, it);
+        render_attributes_in_code(w, it, cx.tcx());
         write!(
             w,
             "    {}type {};\n}}",
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index edd046ab772..62aab46fa7e 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -41,12 +41,7 @@ impl JsonRenderer<'_> {
             })
             .collect();
         let docs = item.attrs.collapsed_doc_value();
-        let attrs = item
-            .attrs
-            .other_attrs
-            .iter()
-            .map(rustc_ast_pretty::pprust::attribute_to_string)
-            .collect();
+        let attrs = item.attributes(self.tcx, true);
         let span = item.span(self.tcx);
         let visibility = item.visibility(self.tcx);
         let clean::Item { name, item_id, .. } = item;
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 60754130d99..66080f64b9c 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -34,6 +34,7 @@ extern crate tracing;
 // Dependencies listed in Cargo.toml do not need `extern crate`.
 
 extern crate pulldown_cmark;
+extern crate rustc_abi;
 extern crate rustc_ast;
 extern crate rustc_ast_pretty;
 extern crate rustc_attr;
diff --git a/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs b/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
index fab8e9c2ec1..e2cdc48b583 100644
--- a/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
+++ b/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
@@ -577,7 +577,7 @@ fn ident_difference_expr_with_base_location(
         | (AssignOp(_, _, _), AssignOp(_, _, _))
         | (Assign(_, _, _), Assign(_, _, _))
         | (TryBlock(_), TryBlock(_))
-        | (Await(_), Await(_))
+        | (Await(_, _), Await(_, _))
         | (Async(_, _), Async(_, _))
         | (Block(_, _), Block(_, _))
         | (Closure(_), Closure(_))
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs
index 1f15598db36..8cc01f1ef97 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs
@@ -143,7 +143,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
         (Paren(l), _) => eq_expr(l, r),
         (_, Paren(r)) => eq_expr(l, r),
         (Err, Err) => true,
-        (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r),
+        (Try(l), Try(r)) | (Await(l, _), Await(r, _)) => eq_expr(l, r),
         (Array(l), Array(r)) => over(l, r, |l, r| eq_expr(l, r)),
         (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)),
         (Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value),
diff --git a/src/tools/clippy/tests/ui/future_not_send.stderr b/src/tools/clippy/tests/ui/future_not_send.stderr
index 5b6858e4568..5c6348962a5 100644
--- a/src/tools/clippy/tests/ui/future_not_send.stderr
+++ b/src/tools/clippy/tests/ui/future_not_send.stderr
@@ -5,22 +5,22 @@ LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell<usize>) -> bool {
    |                                                              ^^^^ future returned by `private_future` is not `Send`
    |
 note: future is not `Send` as this value is used across an await
-  --> $DIR/future_not_send.rs:8:19
+  --> $DIR/future_not_send.rs:8:20
    |
 LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell<usize>) -> bool {
    |                         -- has type `std::rc::Rc<[u8]>` which is not `Send`
 LL |     async { true }.await
-   |                   ^^^^^^ await occurs here, with `rc` maybe used later
+   |                    ^^^^^ await occurs here, with `rc` maybe used later
 LL | }
    | - `rc` is later dropped here
    = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send`
 note: future is not `Send` as this value is used across an await
-  --> $DIR/future_not_send.rs:8:19
+  --> $DIR/future_not_send.rs:8:20
    |
 LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell<usize>) -> bool {
    |                                       ---- has type `&std::cell::Cell<usize>` which is not `Send`
 LL |     async { true }.await
-   |                   ^^^^^^ await occurs here, with `cell` maybe used later
+   |                    ^^^^^ await occurs here, with `cell` maybe used later
 LL | }
    | - `cell` is later dropped here
    = note: `std::cell::Cell<usize>` doesn't implement `std::marker::Sync`
@@ -33,12 +33,12 @@ LL | pub async fn public_future(rc: Rc<[u8]>) {
    |                                          ^ future returned by `public_future` is not `Send`
    |
 note: future is not `Send` as this value is used across an await
-  --> $DIR/future_not_send.rs:12:19
+  --> $DIR/future_not_send.rs:12:20
    |
 LL | pub async fn public_future(rc: Rc<[u8]>) {
    |                            -- has type `std::rc::Rc<[u8]>` which is not `Send`
 LL |     async { true }.await;
-   |                   ^^^^^^ await occurs here, with `rc` maybe used later
+   |                    ^^^^^ await occurs here, with `rc` maybe used later
 LL | }
    | - `rc` is later dropped here
    = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send`
@@ -82,12 +82,12 @@ LL |     async fn private_future(&self) -> usize {
    |                                       ^^^^^ future returned by `private_future` is not `Send`
    |
 note: future is not `Send` as this value is used across an await
-  --> $DIR/future_not_send.rs:35:23
+  --> $DIR/future_not_send.rs:35:24
    |
 LL |     async fn private_future(&self) -> usize {
    |                             ----- has type `&Dummy` which is not `Send`
 LL |         async { true }.await;
-   |                       ^^^^^^ await occurs here, with `&self` maybe used later
+   |                        ^^^^^ await occurs here, with `&self` maybe used later
 LL |         self.rc.len()
 LL |     }
    |     - `&self` is later dropped here
@@ -100,12 +100,12 @@ LL |     pub async fn public_future(&self) {
    |                                       ^ future returned by `public_future` is not `Send`
    |
 note: future is not `Send` as this value is used across an await
-  --> $DIR/future_not_send.rs:40:30
+  --> $DIR/future_not_send.rs:40:31
    |
 LL |     pub async fn public_future(&self) {
    |                                ----- has type `&Dummy` which is not `Send`
 LL |         self.private_future().await;
-   |                              ^^^^^^ await occurs here, with `&self` maybe used later
+   |                               ^^^^^ await occurs here, with `&self` maybe used later
 LL |     }
    |     - `&self` is later dropped here
    = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync`
@@ -117,12 +117,12 @@ LL | async fn generic_future<T>(t: T) -> T
    |                                     ^ future returned by `generic_future` is not `Send`
    |
 note: future is not `Send` as this value is used across an await
-  --> $DIR/future_not_send.rs:54:19
+  --> $DIR/future_not_send.rs:54:20
    |
 LL |     let rt = &t;
    |         -- has type `&T` which is not `Send`
 LL |     async { true }.await;
-   |                   ^^^^^^ await occurs here, with `rt` maybe used later
+   |                    ^^^^^ await occurs here, with `rt` maybe used later
 LL |     t
 LL | }
    | - `rt` is later dropped here
diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs
index c00344b6de2..21344208130 100644
--- a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs
+++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs
@@ -7,5 +7,5 @@ fn main() {
     // Also not assigning directly as that's array initialization, not assignment.
     let zst_val = [1u8; 0];
     unsafe { std::ptr::null_mut::<[u8; 0]>().write(zst_val) };
-    //~^ERROR: memory access failed: null pointer is a dangling pointer
+    //~^ERROR: dereferencing pointer failed: null pointer is a dangling pointer
 }
diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.stderr b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.stderr
index 2953d85c25f..a4e0ebe38f6 100644
--- a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.stderr
+++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.stderr
@@ -1,8 +1,8 @@
-error: Undefined Behavior: memory access failed: null pointer is a dangling pointer (it has no provenance)
+error: Undefined Behavior: dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance)
   --> $DIR/null_pointer_write_zst.rs:LL:CC
    |
 LL |     unsafe { std::ptr::null_mut::<[u8; 0]>().write(zst_val) };
-   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: null pointer is a dangling pointer (it has no provenance)
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance)
    |
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
diff --git a/src/tools/rustfmt/src/chains.rs b/src/tools/rustfmt/src/chains.rs
index cbe523c6c3c..0afce7cf659 100644
--- a/src/tools/rustfmt/src/chains.rs
+++ b/src/tools/rustfmt/src/chains.rs
@@ -232,7 +232,7 @@ impl ChainItemKind {
                 let span = mk_sp(nested.span.hi(), field.span.hi());
                 (kind, span)
             }
-            ast::ExprKind::Await(ref nested) => {
+            ast::ExprKind::Await(ref nested, _) => {
                 let span = mk_sp(nested.span.hi(), expr.span.hi());
                 (ChainItemKind::Await, span)
             }
@@ -459,7 +459,7 @@ impl Chain {
             ast::ExprKind::MethodCall(ref call) => Some(Self::convert_try(&call.receiver, context)),
             ast::ExprKind::Field(ref subexpr, _)
             | ast::ExprKind::Try(ref subexpr)
-            | ast::ExprKind::Await(ref subexpr) => Some(Self::convert_try(subexpr, context)),
+            | ast::ExprKind::Await(ref subexpr, _) => Some(Self::convert_try(subexpr, context)),
             _ => None,
         }
     }
diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs
index 824e4219136..5dc628adb0c 100644
--- a/src/tools/rustfmt/src/expr.rs
+++ b/src/tools/rustfmt/src/expr.rs
@@ -218,7 +218,7 @@ pub(crate) fn format_expr(
         ast::ExprKind::Try(..)
         | ast::ExprKind::Field(..)
         | ast::ExprKind::MethodCall(..)
-        | ast::ExprKind::Await(_) => rewrite_chain(expr, context, shape),
+        | ast::ExprKind::Await(_, _) => rewrite_chain(expr, context, shape),
         ast::ExprKind::MacCall(ref mac) => {
             rewrite_macro(mac, None, context, shape, MacroPosition::Expression).or_else(|| {
                 wrap_str(
@@ -1889,7 +1889,7 @@ impl<'ast> RhsAssignKind<'ast> {
                     ast::ExprKind::Try(..)
                         | ast::ExprKind::Field(..)
                         | ast::ExprKind::MethodCall(..)
-                        | ast::ExprKind::Await(_)
+                        | ast::ExprKind::Await(_, _)
                 )
             }
             _ => false,