about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-07-28 14:04:13 +0000
committerbors <bors@rust-lang.org>2024-07-28 14:04:13 +0000
commit78c857394ec8c01f06cb1df260c51178180a40e5 (patch)
tree1cf3644b36e692e652a6dfccc0dffc48851521a1
parent1b51d80027919563004918eaadfa0d890ac0eb93 (diff)
parent3a4051cf3b973ab92ce6cb6f6de6ca29612971e4 (diff)
downloadrust-78c857394ec8c01f06cb1df260c51178180a40e5.tar.gz
rust-78c857394ec8c01f06cb1df260c51178180a40e5.zip
Auto merge of #128301 - matthiaskrgr:rollup-9fyf587, r=matthiaskrgr
Rollup of 3 pull requests

Successful merges:

 - #125889 (Add migration lint for 2024 prelude additions)
 - #128215 (Update the reference)
 - #128263 (rustdoc: use strategic ThinVec/Box to shrink `clean::ItemKind`)

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--compiler/rustc_hir_typeck/src/method/prelude_edition_lints.rs17
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs41
-rw-r--r--src/bootstrap/src/core/build_steps/check.rs4
-rw-r--r--src/bootstrap/src/core/build_steps/clippy.rs4
-rw-r--r--src/bootstrap/src/core/build_steps/compile.rs23
-rw-r--r--src/bootstrap/src/core/build_steps/dist.rs7
-rw-r--r--src/bootstrap/src/core/build_steps/doc.rs101
-rw-r--r--src/bootstrap/src/core/build_steps/llvm.rs15
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs30
-rw-r--r--src/bootstrap/src/core/build_steps/tool.rs22
-rw-r--r--src/bootstrap/src/core/build_steps/vendor.rs7
-rw-r--r--src/bootstrap/src/core/builder/tests.rs11
-rw-r--r--src/bootstrap/src/core/config/config.rs7
-rw-r--r--src/bootstrap/src/lib.rs80
m---------src/doc/reference0
-rw-r--r--src/librustdoc/clean/inline.rs15
-rw-r--r--src/librustdoc/clean/mod.rs69
-rw-r--r--src/librustdoc/clean/types.rs24
-rw-r--r--src/librustdoc/fold.rs2
-rw-r--r--src/librustdoc/html/format.rs2
-rw-r--r--src/librustdoc/html/render/mod.rs67
-rw-r--r--src/librustdoc/html/render/print_item.rs6
-rw-r--r--src/librustdoc/json/conversions.rs20
-rw-r--r--src/librustdoc/passes/check_doc_test_visibility.rs2
-rw-r--r--src/librustdoc/visit.rs2
-rw-r--r--src/tools/rustbook/Cargo.lock21
-rw-r--r--src/tools/rustbook/Cargo.toml1
-rw-r--r--src/tools/rustbook/src/main.rs12
-rw-r--r--src/tools/tidy/src/deps.rs2
-rw-r--r--tests/ui/rust-2024/prelude-migration/future-poll-already-future.rs17
-rw-r--r--tests/ui/rust-2024/prelude-migration/future-poll-async-block.e2021.fixed21
-rw-r--r--tests/ui/rust-2024/prelude-migration/future-poll-async-block.e2021.stderr16
-rw-r--r--tests/ui/rust-2024/prelude-migration/future-poll-async-block.rs21
-rw-r--r--tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.e2021.fixed21
-rw-r--r--tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.e2021.stderr16
-rw-r--r--tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.rs21
-rw-r--r--tests/ui/rust-2024/prelude-migration/future-poll-not-future.rs15
-rw-r--r--tests/ui/rust-2024/prelude-migration/in_2024_compatibility.rs17
-rw-r--r--tests/ui/rust-2024/prelude-migration/in_2024_compatibility.stderr17
-rw-r--r--tests/ui/rust-2024/prelude-migration/into-future-adt.e2021.fixed29
-rw-r--r--tests/ui/rust-2024/prelude-migration/into-future-adt.e2021.stderr16
-rw-r--r--tests/ui/rust-2024/prelude-migration/into-future-adt.rs29
-rw-r--r--tests/ui/rust-2024/prelude-migration/into-future-already-into-future.rs24
-rw-r--r--tests/ui/rust-2024/prelude-migration/into-future-not-into-future.e2021.fixed23
-rw-r--r--tests/ui/rust-2024/prelude-migration/into-future-not-into-future.e2021.stderr16
-rw-r--r--tests/ui/rust-2024/prelude-migration/into-future-not-into-future.rs23
46 files changed, 773 insertions, 183 deletions
diff --git a/compiler/rustc_hir_typeck/src/method/prelude_edition_lints.rs b/compiler/rustc_hir_typeck/src/method/prelude_edition_lints.rs
index 3ee10f74d98..3a902390fcf 100644
--- a/compiler/rustc_hir_typeck/src/method/prelude_edition_lints.rs
+++ b/compiler/rustc_hir_typeck/src/method/prelude_edition_lints.rs
@@ -9,7 +9,7 @@ use rustc_hir as hir;
 use rustc_lint::{ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER};
 use rustc_middle::span_bug;
 use rustc_middle::ty::{self, Ty};
-use rustc_session::lint::builtin::RUST_2021_PRELUDE_COLLISIONS;
+use rustc_session::lint::builtin::{RUST_2021_PRELUDE_COLLISIONS, RUST_2024_PRELUDE_COLLISIONS};
 use rustc_span::symbol::kw::{Empty, Underscore};
 use rustc_span::symbol::{sym, Ident};
 use rustc_span::Span;
@@ -35,6 +35,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let (prelude_or_array_lint, edition) = match segment.ident.name {
             // `try_into` was added to the prelude in Rust 2021.
             sym::try_into if !span.at_least_rust_2021() => (RUST_2021_PRELUDE_COLLISIONS, "2021"),
+            // `Future::poll` was added to the prelude in Rust 2024.
+            sym::poll
+                // We check that the self type is `Pin<&mut _>` to avoid false positives for this common name.
+                if !span.at_least_rust_2024()
+                    && let ty::Adt(adt_def, args) = self_ty.kind()
+                    && self.tcx.is_lang_item(adt_def.did(), hir::LangItem::Pin)
+                    && let ty::Ref(_, _, ty::Mutability::Mut) =
+                        args[0].as_type().unwrap().kind() =>
+            {
+                (RUST_2024_PRELUDE_COLLISIONS, "2024")
+            }
+            // `IntoFuture::into_future` was added to the prelude in Rust 2024.
+            sym::into_future if !span.at_least_rust_2024() => {
+                (RUST_2024_PRELUDE_COLLISIONS, "2024")
+            }
             // `into_iter` wasn't added to the prelude,
             // but `[T; N].into_iter()` doesn't resolve to IntoIterator::into_iter
             // before Rust 2021, which results in the same problem.
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index c4158cccca4..06d6a6cd612 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -91,6 +91,7 @@ declare_lint_pass! {
         RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX,
         RUST_2021_PRELUDE_COLLISIONS,
         RUST_2024_INCOMPATIBLE_PAT,
+        RUST_2024_PRELUDE_COLLISIONS,
         SELF_CONSTRUCTOR_FROM_OUTER_ITEM,
         SEMICOLON_IN_EXPRESSIONS_FROM_MACROS,
         SINGLE_USE_LIFETIMES,
@@ -3755,6 +3756,46 @@ declare_lint! {
 }
 
 declare_lint! {
+    /// The `rust_2024_prelude_collisions` lint detects the usage of trait methods which are ambiguous
+    /// with traits added to the prelude in future editions.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2021,compile_fail
+    /// #![deny(rust_2024_prelude_collisions)]
+    /// trait Meow {
+    ///     fn poll(&self) {}
+    /// }
+    /// impl<T> Meow for T {}
+    ///
+    /// fn main() {
+    ///     core::pin::pin!(async {}).poll();
+    ///     //                        ^^^^^^
+    ///     // This call to try_into matches both Future::poll and Meow::poll as
+    ///     // `Future` has been added to the Rust prelude in 2024 edition.
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Rust 2024, introduces two new additions to the standard library's prelude:
+    /// `Future` and `IntoFuture`. This results in an ambiguity as to which method/function
+    /// to call when an existing `poll`/`into_future` method is called via dot-call syntax or
+    /// a `poll`/`into_future` associated function is called directly on a type.
+    ///
+    pub RUST_2024_PRELUDE_COLLISIONS,
+    Allow,
+    "detects the usage of trait methods which are ambiguous with traits added to the \
+        prelude in future editions",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionError(Edition::Edition2024),
+        reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2024/prelude.html>",
+    };
+}
+
+declare_lint! {
     /// The `rust_2021_prefixes_incompatible_syntax` lint detects identifiers that will be parsed as a
     /// prefix instead in Rust 2021.
     ///
diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs
index ed5b9edc86d..3e0bb2884cd 100644
--- a/src/bootstrap/src/core/build_steps/check.rs
+++ b/src/bootstrap/src/core/build_steps/check.rs
@@ -9,7 +9,7 @@ use crate::core::builder::{
 };
 use crate::core::config::TargetSelection;
 use crate::{Compiler, Mode, Subcommand};
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 
 pub fn cargo_subcommand(kind: Kind) -> &'static str {
     match kind {
@@ -52,7 +52,7 @@ impl Step for Std {
     }
 
     fn run(self, builder: &Builder<'_>) {
-        builder.update_submodule(&Path::new("library").join("stdarch"));
+        builder.require_submodule("library/stdarch", None);
 
         let target = self.target;
         let compiler = builder.compiler(builder.top_stage, builder.config.build);
diff --git a/src/bootstrap/src/core/build_steps/clippy.rs b/src/bootstrap/src/core/build_steps/clippy.rs
index ee7fb368a8c..68abf1e464a 100644
--- a/src/bootstrap/src/core/build_steps/clippy.rs
+++ b/src/bootstrap/src/core/build_steps/clippy.rs
@@ -1,7 +1,5 @@
 //! Implementation of running clippy on the compiler, standard library and various tools.
 
-use std::path::Path;
-
 use crate::builder::Builder;
 use crate::builder::ShouldRun;
 use crate::core::builder;
@@ -127,7 +125,7 @@ impl Step for Std {
     }
 
     fn run(self, builder: &Builder<'_>) {
-        builder.update_submodule(&Path::new("library").join("stdarch"));
+        builder.require_submodule("library/stdarch", None);
 
         let target = self.target;
         let compiler = builder.compiler(builder.top_stage, builder.config.build);
diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs
index 25adaa0aa86..23d1e0ffe2d 100644
--- a/src/bootstrap/src/core/build_steps/compile.rs
+++ b/src/bootstrap/src/core/build_steps/compile.rs
@@ -182,11 +182,16 @@ impl Step for Std {
             return;
         }
 
-        builder.update_submodule(&Path::new("library").join("stdarch"));
+        builder.require_submodule("library/stdarch", None);
 
         // Profiler information requires LLVM's compiler-rt
         if builder.config.profiler {
-            builder.update_submodule(Path::new("src/llvm-project"));
+            builder.require_submodule(
+                "src/llvm-project",
+                Some(
+                    "The `build.profiler` config option requires `compiler-rt` sources from LLVM.",
+                ),
+            );
         }
 
         let mut target_deps = builder.ensure(StartupObjects { compiler, target });
@@ -456,13 +461,15 @@ pub fn std_cargo(builder: &Builder<'_>, target: TargetSelection, stage: u32, car
         // That's probably ok? At least, the difference wasn't enforced before. There's a comment in
         // the compiler_builtins build script that makes me nervous, though:
         // https://github.com/rust-lang/compiler-builtins/blob/31ee4544dbe47903ce771270d6e3bea8654e9e50/build.rs#L575-L579
-        builder.update_submodule(&Path::new("src").join("llvm-project"));
+        builder.require_submodule(
+            "src/llvm-project",
+            Some(
+                "The `build.optimized-compiler-builtins` config option \
+                 requires `compiler-rt` sources from LLVM.",
+            ),
+        );
         let compiler_builtins_root = builder.src.join("src/llvm-project/compiler-rt");
-        if !compiler_builtins_root.exists() {
-            panic!(
-                "need LLVM sources available to build `compiler-rt`, but they weren't present; consider enabling `build.submodules = true` or disabling `optimized-compiler-builtins`"
-            );
-        }
+        assert!(compiler_builtins_root.exists());
         // Note that `libprofiler_builtins/build.rs` also computes this so if
         // you're changing something here please also change that.
         cargo.env("RUST_COMPILER_RT_ROOT", &compiler_builtins_root);
diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs
index 4076ed09256..cd0661e0edd 100644
--- a/src/bootstrap/src/core/build_steps/dist.rs
+++ b/src/bootstrap/src/core/build_steps/dist.rs
@@ -907,7 +907,7 @@ impl Step for Src {
     /// Creates the `rust-src` installer component
     fn run(self, builder: &Builder<'_>) -> GeneratedTarball {
         if !builder.config.dry_run() {
-            builder.update_submodule(Path::new("src/llvm-project"));
+            builder.require_submodule("src/llvm-project", None);
         }
 
         let tarball = Tarball::new_targetless(builder, "rust-src");
@@ -1022,10 +1022,7 @@ impl Step for PlainSourceTarball {
             // FIXME: This code looks _very_ similar to what we have in `src/core/build_steps/vendor.rs`
             // perhaps it should be removed in favor of making `dist` perform the `vendor` step?
 
-            // Ensure we have all submodules from src and other directories checked out.
-            for submodule in build_helper::util::parse_gitmodules(&builder.src) {
-                builder.update_submodule(Path::new(submodule));
-            }
+            builder.require_and_update_all_submodules();
 
             // Vendor all Cargo dependencies
             let mut cmd = command(&builder.initial_cargo);
diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs
index d8204ea00f7..bdd48fa644a 100644
--- a/src/bootstrap/src/core/build_steps/doc.rs
+++ b/src/bootstrap/src/core/build_steps/doc.rs
@@ -9,14 +9,14 @@
 
 use std::io::{self, Write};
 use std::path::{Path, PathBuf};
-use std::{fs, mem};
+use std::{env, fs, mem};
 
 use crate::core::build_steps::compile;
 use crate::core::build_steps::tool::{self, prepare_tool_cargo, SourceType, Tool};
 use crate::core::builder::{self, crate_description};
 use crate::core::builder::{Alias, Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
 use crate::core::config::{Config, TargetSelection};
-use crate::utils::helpers::{dir_is_empty, symlink_dir, t, up_to_date};
+use crate::utils::helpers::{symlink_dir, t, up_to_date};
 use crate::Mode;
 
 macro_rules! submodule_helper {
@@ -53,8 +53,8 @@ macro_rules! book {
 
             fn run(self, builder: &Builder<'_>) {
                 $(
-                    let path = Path::new(submodule_helper!( $path, submodule $( = $submodule )? ));
-                    builder.update_submodule(&path);
+                    let path = submodule_helper!( $path, submodule $( = $submodule )? );
+                    builder.require_submodule(path, None);
                 )?
                 builder.ensure(RustbookSrc {
                     target: self.target,
@@ -62,6 +62,7 @@ macro_rules! book {
                     src: builder.src.join($path),
                     parent: Some(self),
                     languages: $lang.into(),
+                    rustdoc: None,
                 })
             }
         }
@@ -80,7 +81,6 @@ book!(
     EditionGuide, "src/doc/edition-guide", "edition-guide", &[], submodule;
     EmbeddedBook, "src/doc/embedded-book", "embedded-book", &[], submodule;
     Nomicon, "src/doc/nomicon", "nomicon", &[], submodule;
-    Reference, "src/doc/reference", "reference", &[], submodule;
     RustByExample, "src/doc/rust-by-example", "rust-by-example", &["ja"], submodule;
     RustdocBook, "src/doc/rustdoc", "rustdoc", &[];
     StyleGuide, "src/doc/style-guide", "style-guide", &[];
@@ -112,6 +112,7 @@ impl Step for UnstableBook {
             src: builder.md_doc_out(self.target).join("unstable-book"),
             parent: Some(self),
             languages: vec![],
+            rustdoc: None,
         })
     }
 }
@@ -123,6 +124,7 @@ struct RustbookSrc<P: Step> {
     src: PathBuf,
     parent: Option<P>,
     languages: Vec<&'static str>,
+    rustdoc: Option<PathBuf>,
 }
 
 impl<P: Step> Step for RustbookSrc<P> {
@@ -153,13 +155,18 @@ impl<P: Step> Step for RustbookSrc<P> {
             builder.info(&format!("Rustbook ({target}) - {name}"));
             let _ = fs::remove_dir_all(&out);
 
-            builder
-                .tool_cmd(Tool::Rustbook)
-                .arg("build")
-                .arg(&src)
-                .arg("-d")
-                .arg(&out)
-                .run(builder);
+            let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);
+            if let Some(mut rustdoc) = self.rustdoc {
+                rustdoc.pop();
+                let old_path = env::var_os("PATH").unwrap_or_default();
+                let new_path =
+                    env::join_paths(std::iter::once(rustdoc).chain(env::split_paths(&old_path)))
+                        .expect("could not add rustdoc to PATH");
+
+                rustbook_cmd.env("PATH", new_path);
+            }
+
+            rustbook_cmd.arg("build").arg(&src).arg("-d").arg(&out).run(builder);
 
             for lang in &self.languages {
                 let out = out.join(lang);
@@ -217,22 +224,14 @@ impl Step for TheBook {
     /// * Index page
     /// * Redirect pages
     fn run(self, builder: &Builder<'_>) {
-        let relative_path = Path::new("src").join("doc").join("book");
-        builder.update_submodule(&relative_path);
+        builder.require_submodule("src/doc/book", None);
 
         let compiler = self.compiler;
         let target = self.target;
 
-        let absolute_path = builder.src.join(&relative_path);
+        let absolute_path = builder.src.join("src/doc/book");
         let redirect_path = absolute_path.join("redirects");
-        if !absolute_path.exists()
-            || !redirect_path.exists()
-            || dir_is_empty(&absolute_path)
-            || dir_is_empty(&redirect_path)
-        {
-            eprintln!("Please checkout submodule: {}", relative_path.display());
-            crate::exit!(1);
-        }
+
         // build book
         builder.ensure(RustbookSrc {
             target,
@@ -240,6 +239,7 @@ impl Step for TheBook {
             src: absolute_path.clone(),
             parent: Some(self),
             languages: vec![],
+            rustdoc: None,
         });
 
         // building older edition redirects
@@ -252,6 +252,7 @@ impl Step for TheBook {
                 // treat the other editions as not having a parent.
                 parent: Option::<Self>::None,
                 languages: vec![],
+                rustdoc: None,
             });
         }
 
@@ -932,8 +933,8 @@ macro_rules! tool_doc {
                     let _ = source_type; // silence the "unused variable" warning
                     let source_type = SourceType::Submodule;
 
-                    let path = Path::new(submodule_helper!( $path, submodule $( = $submodule )? ));
-                    builder.update_submodule(&path);
+                    let path = submodule_helper!( $path, submodule $( = $submodule )? );
+                    builder.require_submodule(path, None);
                 )?
 
                 let stage = builder.top_stage;
@@ -1172,12 +1173,6 @@ impl Step for RustcBook {
     /// in the "md-doc" directory in the build output directory. Then
     /// "rustbook" is used to convert it to HTML.
     fn run(self, builder: &Builder<'_>) {
-        // These submodules are required to be checked out to build rustbook
-        // because they have Cargo dependencies that are needed.
-        #[allow(clippy::single_element_loop)] // This will change soon.
-        for path in ["src/doc/book"] {
-            builder.update_submodule(Path::new(path));
-        }
         let out_base = builder.md_doc_out(self.target).join("rustc");
         t!(fs::create_dir_all(&out_base));
         let out_listing = out_base.join("src/lints");
@@ -1228,6 +1223,50 @@ impl Step for RustcBook {
             src: out_base,
             parent: Some(self),
             languages: vec![],
+            rustdoc: None,
+        });
+    }
+}
+
+#[derive(Ord, PartialOrd, Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Reference {
+    pub compiler: Compiler,
+    pub target: TargetSelection,
+}
+
+impl Step for Reference {
+    type Output = ();
+    const DEFAULT: bool = true;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        let builder = run.builder;
+        run.path("src/doc/reference").default_condition(builder.config.docs)
+    }
+
+    fn make_run(run: RunConfig<'_>) {
+        run.builder.ensure(Reference {
+            compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.build),
+            target: run.target,
+        });
+    }
+
+    /// Builds the reference book.
+    fn run(self, builder: &Builder<'_>) {
+        builder.require_submodule("src/doc/reference", None);
+
+        // This is needed for generating links to the standard library using
+        // the mdbook-spec plugin.
+        builder.ensure(compile::Std::new(self.compiler, builder.config.build));
+        let rustdoc = builder.rustdoc(self.compiler);
+
+        // Run rustbook/mdbook to generate the HTML pages.
+        builder.ensure(RustbookSrc {
+            target: self.target,
+            name: "reference".to_owned(),
+            src: builder.src.join("src/doc/reference"),
+            parent: Some(self),
+            languages: vec![],
+            rustdoc: Some(rustdoc),
         });
     }
 }
diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs
index af987c59cb9..92a7ffc3a98 100644
--- a/src/bootstrap/src/core/build_steps/llvm.rs
+++ b/src/bootstrap/src/core/build_steps/llvm.rs
@@ -89,7 +89,7 @@ impl LdFlags {
 /// if not).
 pub fn prebuilt_llvm_config(builder: &Builder<'_>, target: TargetSelection) -> LlvmBuildStatus {
     // If we have llvm submodule initialized already, sync it.
-    builder.update_existing_submodule(&Path::new("src").join("llvm-project"));
+    builder.update_existing_submodule("src/llvm-project");
 
     builder.config.maybe_download_ci_llvm();
 
@@ -110,7 +110,8 @@ pub fn prebuilt_llvm_config(builder: &Builder<'_>, target: TargetSelection) -> L
     }
 
     // Initialize the llvm submodule if not initialized already.
-    builder.update_submodule(&Path::new("src").join("llvm-project"));
+    // If submodules are disabled, this does nothing.
+    builder.update_submodule("src/llvm-project");
 
     let root = "src/llvm-project/llvm";
     let out_dir = builder.llvm_out(target);
@@ -1197,7 +1198,10 @@ impl Step for CrtBeginEnd {
 
     /// Build crtbegin.o/crtend.o for musl target.
     fn run(self, builder: &Builder<'_>) -> Self::Output {
-        builder.update_submodule(Path::new("src/llvm-project"));
+        builder.require_submodule(
+            "src/llvm-project",
+            Some("The LLVM sources are required for the CRT from `compiler-rt`."),
+        );
 
         let out_dir = builder.native_dir(self.target).join("crt");
 
@@ -1270,7 +1274,10 @@ impl Step for Libunwind {
 
     /// Build libunwind.a
     fn run(self, builder: &Builder<'_>) -> Self::Output {
-        builder.update_submodule(Path::new("src/llvm-project"));
+        builder.require_submodule(
+            "src/llvm-project",
+            Some("The LLVM sources are required for libunwind."),
+        );
 
         if builder.config.dry_run() {
             return PathBuf::new();
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index 9de92d41496..4942a4939af 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -2254,7 +2254,12 @@ impl BookTest {
 }
 
 macro_rules! test_book {
-    ($($name:ident, $path:expr, $book_name:expr, default=$default:expr;)+) => {
+    ($(
+        $name:ident, $path:expr, $book_name:expr,
+        default=$default:expr
+        $(,submodules = $submodules:expr)?
+        ;
+    )+) => {
         $(
             #[derive(Debug, Clone, PartialEq, Eq, Hash)]
             pub struct $name {
@@ -2277,6 +2282,11 @@ macro_rules! test_book {
                 }
 
                 fn run(self, builder: &Builder<'_>) {
+                    $(
+                        for submodule in $submodules {
+                            builder.require_submodule(submodule, None);
+                        }
+                    )*
                     builder.ensure(BookTest {
                         compiler: self.compiler,
                         path: PathBuf::from($path),
@@ -2290,15 +2300,15 @@ macro_rules! test_book {
 }
 
 test_book!(
-    Nomicon, "src/doc/nomicon", "nomicon", default=false;
-    Reference, "src/doc/reference", "reference", default=false;
+    Nomicon, "src/doc/nomicon", "nomicon", default=false, submodules=["src/doc/nomicon"];
+    Reference, "src/doc/reference", "reference", default=false, submodules=["src/doc/reference"];
     RustdocBook, "src/doc/rustdoc", "rustdoc", default=true;
     RustcBook, "src/doc/rustc", "rustc", default=true;
-    RustByExample, "src/doc/rust-by-example", "rust-by-example", default=false;
-    EmbeddedBook, "src/doc/embedded-book", "embedded-book", default=false;
-    TheBook, "src/doc/book", "book", default=false;
+    RustByExample, "src/doc/rust-by-example", "rust-by-example", default=false, submodules=["src/doc/rust-by-example"];
+    EmbeddedBook, "src/doc/embedded-book", "embedded-book", default=false, submodules=["src/doc/embedded-book"];
+    TheBook, "src/doc/book", "book", default=false, submodules=["src/doc/book"];
     UnstableBook, "src/doc/unstable-book", "unstable-book", default=true;
-    EditionGuide, "src/doc/edition-guide", "edition-guide", default=false;
+    EditionGuide, "src/doc/edition-guide", "edition-guide", default=false, submodules=["src/doc/edition-guide"];
 );
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -2396,8 +2406,8 @@ impl Step for RustcGuide {
     }
 
     fn run(self, builder: &Builder<'_>) {
-        let relative_path = Path::new("src").join("doc").join("rustc-dev-guide");
-        builder.update_submodule(&relative_path);
+        let relative_path = "src/doc/rustc-dev-guide";
+        builder.require_submodule(relative_path, None);
 
         let src = builder.src.join(relative_path);
         let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook).delay_failure();
@@ -3003,7 +3013,7 @@ impl Step for Bootstrap {
         let _guard = builder.msg(Kind::Test, 0, "bootstrap", host, host);
 
         // Some tests require cargo submodule to be present.
-        builder.build.update_submodule(Path::new("src/tools/cargo"));
+        builder.build.require_submodule("src/tools/cargo", None);
 
         let mut check_bootstrap = command(builder.python());
         check_bootstrap
diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs
index 06bb8259fc4..a059cc15182 100644
--- a/src/bootstrap/src/core/build_steps/tool.rs
+++ b/src/bootstrap/src/core/build_steps/tool.rs
@@ -1,6 +1,6 @@
 use std::env;
 use std::fs;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 
 use crate::core::build_steps::compile;
 use crate::core::build_steps::toolstate::ToolState;
@@ -241,6 +241,7 @@ macro_rules! bootstrap_tool {
         $(,is_external_tool = $external:expr)*
         $(,is_unstable_tool = $unstable:expr)*
         $(,allow_features = $allow_features:expr)?
+        $(,submodules = $submodules:expr)?
         ;
     )+) => {
         #[derive(PartialEq, Eq, Clone)]
@@ -287,6 +288,11 @@ macro_rules! bootstrap_tool {
             }
 
             fn run(self, builder: &Builder<'_>) -> PathBuf {
+                $(
+                    for submodule in $submodules {
+                        builder.require_submodule(submodule, None);
+                    }
+                )*
                 builder.ensure(ToolBuild {
                     compiler: self.compiler,
                     target: self.target,
@@ -314,7 +320,7 @@ macro_rules! bootstrap_tool {
 }
 
 bootstrap_tool!(
-    Rustbook, "src/tools/rustbook", "rustbook";
+    Rustbook, "src/tools/rustbook", "rustbook", submodules = SUBMODULES_FOR_RUSTBOOK;
     UnstableBookGen, "src/tools/unstable-book-gen", "unstable-book-gen";
     Tidy, "src/tools/tidy", "tidy";
     Linkchecker, "src/tools/linkchecker", "linkchecker";
@@ -340,6 +346,10 @@ bootstrap_tool!(
     WasmComponentLd, "src/tools/wasm-component-ld", "wasm-component-ld", is_unstable_tool = true, allow_features = "min_specialization";
 );
 
+/// These are the submodules that are required for rustbook to work due to
+/// depending on mdbook plugins.
+pub static SUBMODULES_FOR_RUSTBOOK: &[&str] = &["src/doc/book", "src/doc/reference"];
+
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct OptimizedDist {
     pub compiler: Compiler,
@@ -363,7 +373,7 @@ impl Step for OptimizedDist {
     fn run(self, builder: &Builder<'_>) -> PathBuf {
         // We need to ensure the rustc-perf submodule is initialized when building opt-dist since
         // the tool requires it to be in place to run.
-        builder.update_submodule(Path::new("src/tools/rustc-perf"));
+        builder.require_submodule("src/tools/rustc-perf", None);
 
         builder.ensure(ToolBuild {
             compiler: self.compiler,
@@ -404,7 +414,7 @@ impl Step for RustcPerf {
 
     fn run(self, builder: &Builder<'_>) -> PathBuf {
         // We need to ensure the rustc-perf submodule is initialized.
-        builder.update_submodule(Path::new("src/tools/rustc-perf"));
+        builder.require_submodule("src/tools/rustc-perf", None);
 
         let tool = ToolBuild {
             compiler: self.compiler,
@@ -704,7 +714,7 @@ impl Step for Cargo {
     }
 
     fn run(self, builder: &Builder<'_>) -> PathBuf {
-        builder.build.update_submodule(Path::new("src/tools/cargo"));
+        builder.build.require_submodule("src/tools/cargo", None);
 
         builder.ensure(ToolBuild {
             compiler: self.compiler,
@@ -1086,8 +1096,6 @@ macro_rules! tool_extended {
 
 // NOTE: tools need to be also added to `Builder::get_step_descriptions` in `builder.rs`
 // to make `./x.py build <tool>` work.
-// NOTE: Most submodule updates for tools are handled by bootstrap.py, since they're needed just to
-// invoke Cargo to build bootstrap. See the comment there for more details.
 tool_extended!((self, builder),
     Cargofmt, "src/tools/rustfmt", "cargo-fmt", stable=true;
     CargoClippy, "src/tools/clippy", "cargo-clippy", stable=true;
diff --git a/src/bootstrap/src/core/build_steps/vendor.rs b/src/bootstrap/src/core/build_steps/vendor.rs
index e6b3cb320cf..8732b30e940 100644
--- a/src/bootstrap/src/core/build_steps/vendor.rs
+++ b/src/bootstrap/src/core/build_steps/vendor.rs
@@ -1,6 +1,7 @@
+use crate::core::build_steps::tool::SUBMODULES_FOR_RUSTBOOK;
 use crate::core::builder::{Builder, RunConfig, ShouldRun, Step};
 use crate::utils::exec::command;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub(crate) struct Vendor {
@@ -35,8 +36,8 @@ impl Step for Vendor {
         }
 
         // These submodules must be present for `x vendor` to work.
-        for path in ["src/tools/cargo", "src/doc/book"] {
-            builder.build.update_submodule(Path::new(path));
+        for submodule in SUBMODULES_FOR_RUSTBOOK.iter().chain(["src/tools/cargo"].iter()) {
+            builder.build.require_submodule(submodule, None);
         }
 
         // Sync these paths by default.
diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs
index 97c9ece0036..ccabcad243f 100644
--- a/src/bootstrap/src/core/builder/tests.rs
+++ b/src/bootstrap/src/core/builder/tests.rs
@@ -13,15 +13,18 @@ fn configure_with_args(cmd: &[String], host: &[&str], target: &[&str]) -> Config
     config.save_toolstates = None;
     config.dry_run = DryRun::SelfCheck;
 
-    // Ignore most submodules, since we don't need them for a dry run.
-    // But make sure to check out the `doc` and `rust-analyzer` submodules, since some steps need them
-    // just to know which commands to run.
+    // Ignore most submodules, since we don't need them for a dry run, and the
+    // tests run much faster without them.
+    //
+    // The src/doc/book submodule is needed because TheBook step tries to
+    // access files even during a dry-run (may want to consider just skipping
+    // that in a dry run).
     let submodule_build = Build::new(Config {
         // don't include LLVM, so CI doesn't require ninja/cmake to be installed
         rust_codegen_backends: vec![],
         ..Config::parse(&["check".to_owned()])
     });
-    submodule_build.update_submodule(Path::new("src/doc/book"));
+    submodule_build.require_submodule("src/doc/book", None);
     config.submodules = Some(false);
 
     config.ninja_in_file = false;
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index e32288e2caf..3435225af7b 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -2404,8 +2404,11 @@ impl Config {
             .unwrap_or_else(|| SplitDebuginfo::default_for_platform(target))
     }
 
-    pub fn submodules(&self, rust_info: &GitInfo) -> bool {
-        self.submodules.unwrap_or(rust_info.is_managed_git_subrepository())
+    /// Returns whether or not submodules should be managed by bootstrap.
+    pub fn submodules(&self) -> bool {
+        // If not specified in config, the default is to only manage
+        // submodules if we're currently inside a git repository.
+        self.submodules.unwrap_or(self.rust_info.is_managed_git_subrepository())
     }
 
     pub fn codegen_backends(&self, target: TargetSelection) -> &[String] {
diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs
index 1bcae250c3f..43abf10c7e3 100644
--- a/src/bootstrap/src/lib.rs
+++ b/src/bootstrap/src/lib.rs
@@ -441,7 +441,13 @@ impl Build {
             // Cargo.toml files.
             let rust_submodules = ["library/backtrace", "library/stdarch"];
             for s in rust_submodules {
-                build.update_submodule(Path::new(s));
+                build.require_submodule(
+                    s,
+                    Some(
+                        "The submodule is required for the standard library \
+                         and the main Cargo workspace.",
+                    ),
+                );
             }
             // Now, update all existing submodules.
             build.update_existing_submodules();
@@ -470,12 +476,17 @@ impl Build {
         build
     }
 
-    // modified from `check_submodule` and `update_submodule` in bootstrap.py
     /// Given a path to the directory of a submodule, update it.
     ///
     /// `relative_path` should be relative to the root of the git repository, not an absolute path.
-    pub(crate) fn update_submodule(&self, relative_path: &Path) {
-        if !self.config.submodules(self.rust_info()) {
+    ///
+    /// This *does not* update the submodule if `config.toml` explicitly says
+    /// not to, or if we're not in a git repository (like a plain source
+    /// tarball). Typically [`Build::require_submodule`] should be
+    /// used instead to provide a nice error to the user if the submodule is
+    /// missing.
+    fn update_submodule(&self, relative_path: &str) {
+        if !self.config.submodules() {
             return;
         }
 
@@ -522,7 +533,7 @@ impl Build {
             return;
         }
 
-        println!("Updating submodule {}", relative_path.display());
+        println!("Updating submodule {relative_path}");
         helpers::git(Some(&self.src))
             .run_always()
             .args(["submodule", "-q", "sync"])
@@ -576,11 +587,53 @@ impl Build {
         }
     }
 
+    /// Updates a submodule, and exits with a failure if submodule management
+    /// is disabled and the submodule does not exist.
+    ///
+    /// The given submodule name should be its path relative to the root of
+    /// the main repository.
+    ///
+    /// The given `err_hint` will be shown to the user if the submodule is not
+    /// checked out and submodule management is disabled.
+    pub fn require_submodule(&self, submodule: &str, err_hint: Option<&str>) {
+        // When testing bootstrap itself, it is much faster to ignore
+        // submodules. Almost all Steps work fine without their submodules.
+        if cfg!(test) && !self.config.submodules() {
+            return;
+        }
+        self.update_submodule(submodule);
+        let absolute_path = self.config.src.join(submodule);
+        if dir_is_empty(&absolute_path) {
+            let maybe_enable = if !self.config.submodules()
+                && self.config.rust_info.is_managed_git_subrepository()
+            {
+                "\nConsider setting `build.submodules = true` or manually initializing the submodules."
+            } else {
+                ""
+            };
+            let err_hint = err_hint.map_or_else(String::new, |e| format!("\n{e}"));
+            eprintln!(
+                "submodule {submodule} does not appear to be checked out, \
+                 but it is required for this step{maybe_enable}{err_hint}"
+            );
+            exit!(1);
+        }
+    }
+
+    /// Updates all submodules, and exits with an error if submodule
+    /// management is disabled and the submodule does not exist.
+    pub fn require_and_update_all_submodules(&self) {
+        for submodule in build_helper::util::parse_gitmodules(&self.src) {
+            self.require_submodule(submodule, None);
+        }
+    }
+
     /// If any submodule has been initialized already, sync it unconditionally.
     /// This avoids contributors checking in a submodule change by accident.
-    pub fn update_existing_submodules(&self) {
-        // Avoid running git when there isn't a git checkout.
-        if !self.config.submodules(self.rust_info()) {
+    fn update_existing_submodules(&self) {
+        // Avoid running git when there isn't a git checkout, or the user has
+        // explicitly disabled submodules in `config.toml`.
+        if !self.config.submodules() {
             return;
         }
         let output = helpers::git(Some(&self.src))
@@ -592,22 +645,23 @@ impl Build {
         for line in output.lines() {
             // Look for `submodule.$name.path = $path`
             // Sample output: `submodule.src/rust-installer.path src/tools/rust-installer`
-            let submodule = Path::new(line.split_once(' ').unwrap().1);
+            let submodule = line.split_once(' ').unwrap().1;
+            let path = Path::new(submodule);
             // Don't update the submodule unless it's already been cloned.
-            if GitInfo::new(false, submodule).is_managed_git_subrepository() {
+            if GitInfo::new(false, path).is_managed_git_subrepository() {
                 self.update_submodule(submodule);
             }
         }
     }
 
     /// Updates the given submodule only if it's initialized already; nothing happens otherwise.
-    pub fn update_existing_submodule(&self, submodule: &Path) {
+    pub fn update_existing_submodule(&self, submodule: &str) {
         // Avoid running git when there isn't a git checkout.
-        if !self.config.submodules(self.rust_info()) {
+        if !self.config.submodules() {
             return;
         }
 
-        if GitInfo::new(false, submodule).is_managed_git_subrepository() {
+        if GitInfo::new(false, Path::new(submodule)).is_managed_git_subrepository() {
             self.update_submodule(submodule);
         }
     }
diff --git a/src/doc/reference b/src/doc/reference
-Subproject e2f0bdc4031866734661dcdb548184bde1450ba
+Subproject 2e191814f163ee1e77e2d6094eee4dd78a289c5
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 0024e246ef0..dbdc4a5ae71 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -131,8 +131,8 @@ pub(crate) fn try_inline(
         Res::Def(DefKind::Const, did) => {
             record_extern_fqn(cx, did, ItemType::Constant);
             cx.with_param_env(did, |cx| {
-                let (generics, ty, ct) = build_const_item(cx, did);
-                clean::ConstantItem(generics, Box::new(ty), ct)
+                let ct = build_const_item(cx, did);
+                clean::ConstantItem(Box::new(ct))
             })
         }
         Res::Def(DefKind::Macro(kind), did) => {
@@ -720,10 +720,7 @@ pub(crate) fn print_inlined_const(tcx: TyCtxt<'_>, did: DefId) -> String {
     }
 }
 
-fn build_const_item(
-    cx: &mut DocContext<'_>,
-    def_id: DefId,
-) -> (clean::Generics, clean::Type, clean::Constant) {
+fn build_const_item(cx: &mut DocContext<'_>, def_id: DefId) -> clean::Constant {
     let mut generics =
         clean_ty_generics(cx, cx.tcx.generics_of(def_id), cx.tcx.explicit_predicates_of(def_id));
     clean::simplify::move_bounds_to_generic_parameters(&mut generics);
@@ -733,17 +730,17 @@ fn build_const_item(
         None,
         None,
     );
-    (generics, ty, clean::Constant { kind: clean::ConstantKind::Extern { def_id } })
+    clean::Constant { generics, type_: ty, kind: clean::ConstantKind::Extern { def_id } }
 }
 
 fn build_static(cx: &mut DocContext<'_>, did: DefId, mutable: bool) -> clean::Static {
     clean::Static {
-        type_: clean_middle_ty(
+        type_: Box::new(clean_middle_ty(
             ty::Binder::dummy(cx.tcx.type_of(did).instantiate_identity()),
             cx,
             Some(did),
             None,
-        ),
+        )),
         mutability: if mutable { Mutability::Mut } else { Mutability::Not },
         expr: None,
     }
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 26011926cdd..aaefac32711 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -287,23 +287,21 @@ fn clean_lifetime<'tcx>(lifetime: &hir::Lifetime, cx: &mut DocContext<'tcx>) ->
 pub(crate) fn clean_const<'tcx>(
     constant: &hir::ConstArg<'tcx>,
     _cx: &mut DocContext<'tcx>,
-) -> Constant {
+) -> ConstantKind {
     match &constant.kind {
         hir::ConstArgKind::Path(qpath) => {
-            Constant { kind: ConstantKind::Path { path: qpath_to_string(&qpath).into() } }
-        }
-        hir::ConstArgKind::Anon(anon) => {
-            Constant { kind: ConstantKind::Anonymous { body: anon.body } }
+            ConstantKind::Path { path: qpath_to_string(&qpath).into() }
         }
+        hir::ConstArgKind::Anon(anon) => ConstantKind::Anonymous { body: anon.body },
     }
 }
 
 pub(crate) fn clean_middle_const<'tcx>(
     constant: ty::Binder<'tcx, ty::Const<'tcx>>,
     _cx: &mut DocContext<'tcx>,
-) -> Constant {
+) -> ConstantKind {
     // FIXME: instead of storing the stringified expression, store `self` directly instead.
-    Constant { kind: ConstantKind::TyConst { expr: constant.skip_binder().to_string().into() } }
+    ConstantKind::TyConst { expr: constant.skip_binder().to_string().into() }
 }
 
 pub(crate) fn clean_middle_region<'tcx>(region: ty::Region<'tcx>) -> Option<Lifetime> {
@@ -1230,14 +1228,11 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext
     let local_did = trait_item.owner_id.to_def_id();
     cx.with_param_env(local_did, |cx| {
         let inner = match trait_item.kind {
-            hir::TraitItemKind::Const(ty, Some(default)) => {
-                let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx));
-                AssocConstItem(
-                    generics,
-                    Box::new(clean_ty(ty, cx)),
-                    ConstantKind::Local { def_id: local_did, body: default },
-                )
-            }
+            hir::TraitItemKind::Const(ty, Some(default)) => AssocConstItem(Box::new(Constant {
+                generics: enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx)),
+                kind: ConstantKind::Local { def_id: local_did, body: default },
+                type_: clean_ty(ty, cx),
+            })),
             hir::TraitItemKind::Const(ty, None) => {
                 let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx));
                 TyAssocConstItem(generics, Box::new(clean_ty(ty, cx)))
@@ -1282,11 +1277,11 @@ pub(crate) fn clean_impl_item<'tcx>(
     let local_did = impl_.owner_id.to_def_id();
     cx.with_param_env(local_did, |cx| {
         let inner = match impl_.kind {
-            hir::ImplItemKind::Const(ty, expr) => {
-                let generics = clean_generics(impl_.generics, cx);
-                let default = ConstantKind::Local { def_id: local_did, body: expr };
-                AssocConstItem(generics, Box::new(clean_ty(ty, cx)), default)
-            }
+            hir::ImplItemKind::Const(ty, expr) => AssocConstItem(Box::new(Constant {
+                generics: clean_generics(impl_.generics, cx),
+                kind: ConstantKind::Local { def_id: local_did, body: expr },
+                type_: clean_ty(ty, cx),
+            })),
             hir::ImplItemKind::Fn(ref sig, body) => {
                 let m = clean_function(cx, sig, impl_.generics, FunctionArgs::Body(body));
                 let defaultness = cx.tcx.defaultness(impl_.owner_id);
@@ -1320,12 +1315,12 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
     let tcx = cx.tcx;
     let kind = match assoc_item.kind {
         ty::AssocKind::Const => {
-            let ty = Box::new(clean_middle_ty(
+            let ty = clean_middle_ty(
                 ty::Binder::dummy(tcx.type_of(assoc_item.def_id).instantiate_identity()),
                 cx,
                 Some(assoc_item.def_id),
                 None,
-            ));
+            );
 
             let mut generics = clean_ty_generics(
                 cx,
@@ -1339,9 +1334,13 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
                 ty::TraitContainer => tcx.defaultness(assoc_item.def_id).has_value(),
             };
             if provided {
-                AssocConstItem(generics, ty, ConstantKind::Extern { def_id: assoc_item.def_id })
+                AssocConstItem(Box::new(Constant {
+                    generics,
+                    kind: ConstantKind::Extern { def_id: assoc_item.def_id },
+                    type_: ty,
+                }))
             } else {
-                TyAssocConstItem(generics, ty)
+                TyAssocConstItem(generics, Box::new(ty))
             }
         }
         ty::AssocKind::Fn => {
@@ -1397,7 +1396,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
                     {
                         true
                     }
-                    (GenericParamDefKind::Const { .. }, GenericArg::Const(c)) => match &c.kind {
+                    (GenericParamDefKind::Const { .. }, GenericArg::Const(c)) => match &**c {
                         ConstantKind::TyConst { expr } => **expr == *param.name.as_str(),
                         _ => false,
                     },
@@ -2744,14 +2743,16 @@ fn clean_maybe_renamed_item<'tcx>(
     let mut name = renamed.unwrap_or_else(|| cx.tcx.hir().name(item.hir_id()));
     cx.with_param_env(def_id, |cx| {
         let kind = match item.kind {
-            ItemKind::Static(ty, mutability, body_id) => {
-                StaticItem(Static { type_: clean_ty(ty, cx), mutability, expr: Some(body_id) })
-            }
-            ItemKind::Const(ty, generics, body_id) => ConstantItem(
-                clean_generics(generics, cx),
-                Box::new(clean_ty(ty, cx)),
-                Constant { kind: ConstantKind::Local { body: body_id, def_id } },
-            ),
+            ItemKind::Static(ty, mutability, body_id) => StaticItem(Static {
+                type_: Box::new(clean_ty(ty, cx)),
+                mutability,
+                expr: Some(body_id),
+            }),
+            ItemKind::Const(ty, generics, body_id) => ConstantItem(Box::new(Constant {
+                generics: clean_generics(generics, cx),
+                type_: clean_ty(ty, cx),
+                kind: ConstantKind::Local { body: body_id, def_id },
+            })),
             ItemKind::OpaqueTy(ref ty) => OpaqueTyItem(OpaqueTy {
                 bounds: ty.bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(),
                 generics: clean_generics(ty.generics, cx),
@@ -3109,7 +3110,7 @@ fn clean_maybe_renamed_foreign_item<'tcx>(
                 ForeignFunctionItem(Box::new(Function { decl, generics }), safety)
             }
             hir::ForeignItemKind::Static(ty, mutability, safety) => ForeignStaticItem(
-                Static { type_: clean_ty(ty, cx), mutability, expr: None },
+                Static { type_: Box::new(clean_ty(ty, cx)), mutability, expr: None },
                 safety,
             ),
             hir::ForeignItemKind::Type => ForeignTypeItem,
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 37099531596..92fe98130e8 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -852,9 +852,9 @@ pub(crate) enum ItemKind {
     PrimitiveItem(PrimitiveType),
     /// A required associated constant in a trait declaration.
     TyAssocConstItem(Generics, Box<Type>),
-    ConstantItem(Generics, Box<Type>, Constant),
+    ConstantItem(Box<Constant>),
     /// An associated constant in a trait impl or a provided one in a trait declaration.
-    AssocConstItem(Generics, Box<Type>, ConstantKind),
+    AssocConstItem(Box<Constant>),
     /// A required associated type in a trait declaration.
     ///
     /// The bounds may be non-empty if there is a `where` clause.
@@ -888,7 +888,7 @@ impl ItemKind {
             | TypeAliasItem(_)
             | OpaqueTyItem(_)
             | StaticItem(_)
-            | ConstantItem(_, _, _)
+            | ConstantItem(_)
             | TraitAliasItem(_)
             | TyMethodItem(_)
             | MethodItem(_, _)
@@ -922,7 +922,7 @@ impl ItemKind {
                 | TypeAliasItem(_)
                 | OpaqueTyItem(_)
                 | StaticItem(_)
-                | ConstantItem(_, _, _)
+                | ConstantItem(_)
                 | TraitAliasItem(_)
                 | ForeignFunctionItem(_, _)
                 | ForeignStaticItem(_, _)
@@ -2050,7 +2050,7 @@ impl From<hir::PrimTy> for PrimitiveType {
 pub(crate) struct Struct {
     pub(crate) ctor_kind: Option<CtorKind>,
     pub(crate) generics: Generics,
-    pub(crate) fields: Vec<Item>,
+    pub(crate) fields: ThinVec<Item>,
 }
 
 impl Struct {
@@ -2076,7 +2076,7 @@ impl Union {
 /// only as a variant in an enum.
 #[derive(Clone, Debug)]
 pub(crate) struct VariantStruct {
-    pub(crate) fields: Vec<Item>,
+    pub(crate) fields: ThinVec<Item>,
 }
 
 impl VariantStruct {
@@ -2110,7 +2110,7 @@ pub(crate) struct Variant {
 #[derive(Clone, Debug)]
 pub(crate) enum VariantKind {
     CLike,
-    Tuple(Vec<Item>),
+    Tuple(ThinVec<Item>),
     Struct(VariantStruct),
 }
 
@@ -2246,7 +2246,7 @@ impl Path {
 pub(crate) enum GenericArg {
     Lifetime(Lifetime),
     Type(Type),
-    Const(Box<Constant>),
+    Const(Box<ConstantKind>),
     Infer,
 }
 
@@ -2359,20 +2359,22 @@ pub(crate) struct BareFunctionDecl {
 
 #[derive(Clone, Debug)]
 pub(crate) struct Static {
-    pub(crate) type_: Type,
+    pub(crate) type_: Box<Type>,
     pub(crate) mutability: Mutability,
     pub(crate) expr: Option<BodyId>,
 }
 
 #[derive(Clone, PartialEq, Eq, Hash, Debug)]
 pub(crate) struct Constant {
+    pub(crate) generics: Generics,
     pub(crate) kind: ConstantKind,
+    pub(crate) type_: Type,
 }
 
 #[derive(Clone, PartialEq, Eq, Hash, Debug)]
 pub(crate) enum Term {
     Type(Type),
-    Constant(Constant),
+    Constant(ConstantKind),
 }
 
 impl Term {
@@ -2594,7 +2596,7 @@ mod size_asserts {
     static_assert_size!(GenericParamDef, 40);
     static_assert_size!(Generics, 16);
     static_assert_size!(Item, 56);
-    static_assert_size!(ItemKind, 56);
+    static_assert_size!(ItemKind, 48);
     static_assert_size!(PathSegment, 40);
     static_assert_size!(Type, 32);
     // tidy-alphabetical-end
diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs
index 346e9a4e113..beb7686e29c 100644
--- a/src/librustdoc/fold.rs
+++ b/src/librustdoc/fold.rs
@@ -79,7 +79,7 @@ pub(crate) trait DocFolder: Sized {
             | FunctionItem(_)
             | OpaqueTyItem(_)
             | StaticItem(_)
-            | ConstantItem(_, _, _)
+            | ConstantItem(..)
             | TraitAliasItem(_)
             | TyMethodItem(_)
             | MethodItem(_, _)
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 055781f7fed..18ee04a2f4d 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -375,7 +375,7 @@ impl clean::Lifetime {
     }
 }
 
-impl clean::Constant {
+impl clean::ConstantKind {
     pub(crate) fn print(&self, tcx: TyCtxt<'_>) -> impl Display + '_ {
         let expr = self.expr(tcx);
         display_fn(
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 5b9ef67109c..47712264e97 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -1090,22 +1090,26 @@ fn render_assoc_item(
         clean::MethodItem(m, _) => {
             assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode)
         }
-        kind @ (clean::TyAssocConstItem(generics, ty) | clean::AssocConstItem(generics, ty, _)) => {
-            assoc_const(
-                w,
-                item,
-                generics,
-                ty,
-                match kind {
-                    clean::TyAssocConstItem(..) => None,
-                    clean::AssocConstItem(.., default) => Some(default),
-                    _ => unreachable!(),
-                },
-                link,
-                if parent == ItemType::Trait { 4 } else { 0 },
-                cx,
-            )
-        }
+        clean::TyAssocConstItem(generics, ty) => assoc_const(
+            w,
+            item,
+            generics,
+            ty,
+            None,
+            link,
+            if parent == ItemType::Trait { 4 } else { 0 },
+            cx,
+        ),
+        clean::AssocConstItem(ci) => assoc_const(
+            w,
+            item,
+            &ci.generics,
+            &ci.type_,
+            Some(&ci.kind),
+            link,
+            if parent == ItemType::Trait { 4 } else { 0 },
+            cx,
+        ),
         clean::TyAssocTypeItem(ref generics, ref bounds) => assoc_type(
             w,
             item,
@@ -1690,8 +1694,7 @@ fn render_impl(
                     w.write_str("</h4></section>");
                 }
             }
-            kind @ (clean::TyAssocConstItem(generics, ty)
-            | clean::AssocConstItem(generics, ty, _)) => {
+            clean::TyAssocConstItem(generics, ty) => {
                 let source_id = format!("{item_type}.{name}");
                 let id = cx.derive_id(&source_id);
                 write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">");
@@ -1706,11 +1709,29 @@ fn render_impl(
                     item,
                     generics,
                     ty,
-                    match kind {
-                        clean::TyAssocConstItem(..) => None,
-                        clean::AssocConstItem(.., default) => Some(default),
-                        _ => unreachable!(),
-                    },
+                    None,
+                    link.anchor(if trait_.is_some() { &source_id } else { &id }),
+                    0,
+                    cx,
+                );
+                w.write_str("</h4></section>");
+            }
+            clean::AssocConstItem(ci) => {
+                let source_id = format!("{item_type}.{name}");
+                let id = cx.derive_id(&source_id);
+                write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">");
+                render_rightside(w, cx, item, render_mode);
+                if trait_.is_some() {
+                    // Anchors are only used on trait impls.
+                    write!(w, "<a href=\"#{id}\" class=\"anchor\">ยง</a>");
+                }
+                w.write_str("<h4 class=\"code-header\">");
+                assoc_const(
+                    w,
+                    item,
+                    &ci.generics,
+                    &ci.type_,
+                    Some(&ci.kind),
                     link.anchor(if trait_.is_some() { &source_id } else { &id }),
                     0,
                     cx,
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index cf78a1d223c..0cf62ccf6e7 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -267,7 +267,7 @@ pub(super) fn print_item(cx: &mut Context<'_>, item: &clean::Item, buf: &mut Buf
         clean::PrimitiveItem(_) => item_primitive(buf, cx, item),
         clean::StaticItem(ref i) => item_static(buf, cx, item, i, None),
         clean::ForeignStaticItem(ref i, safety) => item_static(buf, cx, item, i, Some(*safety)),
-        clean::ConstantItem(generics, ty, c) => item_constant(buf, cx, item, generics, ty, c),
+        clean::ConstantItem(ci) => item_constant(buf, cx, item, &ci.generics, &ci.type_, &ci.kind),
         clean::ForeignTypeItem => item_foreign_type(buf, cx, item),
         clean::KeywordItem => item_keyword(buf, cx, item),
         clean::OpaqueTyItem(ref e) => item_opaque_ty(buf, cx, item, e),
@@ -1841,7 +1841,7 @@ fn item_constant(
     it: &clean::Item,
     generics: &clean::Generics,
     ty: &clean::Type,
-    c: &clean::Constant,
+    c: &clean::ConstantKind,
 ) {
     wrap_item(w, |w| {
         let tcx = cx.tcx();
@@ -1911,7 +1911,7 @@ fn item_fields(
     w: &mut Buffer,
     cx: &mut Context<'_>,
     it: &clean::Item,
-    fields: &Vec<clean::Item>,
+    fields: &[clean::Item],
     ctor_kind: Option<CtorKind>,
 ) {
     let mut fields = fields
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 4ab0df36708..a4731feb8de 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -188,6 +188,16 @@ impl FromWithTcx<clean::Constant> for Constant {
     }
 }
 
+impl FromWithTcx<clean::ConstantKind> for Constant {
+    // FIXME(generic_const_items): Add support for generic const items.
+    fn from_tcx(constant: clean::ConstantKind, tcx: TyCtxt<'_>) -> Self {
+        let expr = constant.expr(tcx);
+        let value = constant.value(tcx);
+        let is_literal = constant.is_literal(tcx);
+        Constant { expr, value, is_literal }
+    }
+}
+
 impl FromWithTcx<clean::AssocItemConstraint> for TypeBinding {
     fn from_tcx(constraint: clean::AssocItemConstraint, tcx: TyCtxt<'_>) -> Self {
         TypeBinding {
@@ -325,8 +335,8 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum {
         TypeAliasItem(t) => ItemEnum::TypeAlias(t.into_tcx(tcx)),
         OpaqueTyItem(t) => ItemEnum::OpaqueTy(t.into_tcx(tcx)),
         // FIXME(generic_const_items): Add support for generic free consts
-        ConstantItem(_generics, t, c) => {
-            ItemEnum::Constant { type_: (*t).into_tcx(tcx), const_: c.into_tcx(tcx) }
+        ConstantItem(ci) => {
+            ItemEnum::Constant { type_: ci.type_.into_tcx(tcx), const_: ci.kind.into_tcx(tcx) }
         }
         MacroItem(m) => ItemEnum::Macro(m.source),
         ProcMacroItem(m) => ItemEnum::ProcMacro(m.into_tcx(tcx)),
@@ -341,8 +351,8 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum {
             ItemEnum::AssocConst { type_: (*ty).into_tcx(tcx), default: None }
         }
         // FIXME(generic_const_items): Add support for generic associated consts.
-        AssocConstItem(_generics, ty, default) => {
-            ItemEnum::AssocConst { type_: (*ty).into_tcx(tcx), default: Some(default.expr(tcx)) }
+        AssocConstItem(ci) => {
+            ItemEnum::AssocConst { type_: ci.type_.into_tcx(tcx), default: Some(ci.kind.expr(tcx)) }
         }
         TyAssocTypeItem(g, b) => ItemEnum::AssocType {
             generics: g.into_tcx(tcx),
@@ -829,7 +839,7 @@ impl FromWithTcx<clean::OpaqueTy> for OpaqueTy {
 impl FromWithTcx<clean::Static> for Static {
     fn from_tcx(stat: clean::Static, tcx: TyCtxt<'_>) -> Self {
         Static {
-            type_: stat.type_.into_tcx(tcx),
+            type_: (*stat.type_).into_tcx(tcx),
             mutable: stat.mutability == ast::Mutability::Mut,
             expr: stat
                 .expr
diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs
index 0437f5e5fd8..f97c4178473 100644
--- a/src/librustdoc/passes/check_doc_test_visibility.rs
+++ b/src/librustdoc/passes/check_doc_test_visibility.rs
@@ -62,7 +62,7 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -
                 | clean::AssocTypeItem(..)
                 | clean::TypeAliasItem(_)
                 | clean::StaticItem(_)
-                | clean::ConstantItem(_, _, _)
+                | clean::ConstantItem(..)
                 | clean::ExternCrateItem { .. }
                 | clean::ImportItem(_)
                 | clean::PrimitiveItem(_)
diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs
index b335dc5bd16..de836439be9 100644
--- a/src/librustdoc/visit.rs
+++ b/src/librustdoc/visit.rs
@@ -28,7 +28,7 @@ pub(crate) trait DocVisitor: Sized {
             | TypeAliasItem(_)
             | OpaqueTyItem(_)
             | StaticItem(_)
-            | ConstantItem(_, _, _)
+            | ConstantItem(..)
             | TraitAliasItem(_)
             | TyMethodItem(_)
             | MethodItem(_, _)
diff --git a/src/tools/rustbook/Cargo.lock b/src/tools/rustbook/Cargo.lock
index 75b89a162e9..df051ed447e 100644
--- a/src/tools/rustbook/Cargo.lock
+++ b/src/tools/rustbook/Cargo.lock
@@ -662,6 +662,20 @@ dependencies = [
 ]
 
 [[package]]
+name = "mdbook-spec"
+version = "0.1.2"
+dependencies = [
+ "anyhow",
+ "mdbook",
+ "once_cell",
+ "pathdiff",
+ "regex",
+ "semver",
+ "serde_json",
+ "tempfile",
+]
+
+[[package]]
 name = "mdbook-trpl-listing"
 version = "0.1.0"
 dependencies = [
@@ -795,6 +809,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "pathdiff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+
+[[package]]
 name = "percent-encoding"
 version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1079,6 +1099,7 @@ dependencies = [
  "env_logger",
  "mdbook",
  "mdbook-i18n-helpers",
+ "mdbook-spec",
  "mdbook-trpl-listing",
  "mdbook-trpl-note",
 ]
diff --git a/src/tools/rustbook/Cargo.toml b/src/tools/rustbook/Cargo.toml
index 51ba58483c5..2c29a2848b7 100644
--- a/src/tools/rustbook/Cargo.toml
+++ b/src/tools/rustbook/Cargo.toml
@@ -12,6 +12,7 @@ env_logger = "0.11"
 mdbook-trpl-listing = { path = "../../doc/book/packages/mdbook-trpl-listing" }
 mdbook-trpl-note = { path = "../../doc/book/packages/mdbook-trpl-note" }
 mdbook-i18n-helpers = "0.3.3"
+mdbook-spec = { path = "../../doc/reference/mdbook-spec"}
 
 [dependencies.mdbook]
 version = "0.4.37"
diff --git a/src/tools/rustbook/src/main.rs b/src/tools/rustbook/src/main.rs
index 31bba56adde..e94c2f5958e 100644
--- a/src/tools/rustbook/src/main.rs
+++ b/src/tools/rustbook/src/main.rs
@@ -9,6 +9,7 @@ use mdbook::errors::Result as Result3;
 use mdbook::MDBook;
 use mdbook_i18n_helpers::preprocessors::Gettext;
 
+use mdbook_spec::Spec;
 use mdbook_trpl_listing::TrplListing;
 use mdbook_trpl_note::TrplNote;
 
@@ -83,6 +84,13 @@ pub fn build(args: &ArgMatches) -> Result3<()> {
         book.config.build.build_dir = dest_dir.into();
     }
 
+    // NOTE: Replacing preprocessors using this technique causes error
+    // messages to be displayed when the original preprocessor doesn't work
+    // (but it otherwise succeeds).
+    //
+    // This should probably be fixed in mdbook to remove the existing
+    // preprocessor, or this should modify the config and use
+    // MDBook::load_with_config.
     if book.config.get_preprocessor("trpl-note").is_some() {
         book.with_preprocessor(TrplNote);
     }
@@ -91,6 +99,10 @@ pub fn build(args: &ArgMatches) -> Result3<()> {
         book.with_preprocessor(TrplListing);
     }
 
+    if book.config.get_preprocessor("spec").is_some() {
+        book.with_preprocessor(Spec::new());
+    }
+
     book.build()?;
 
     Ok(())
diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index 499e735b1df..6898c4755d8 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -72,7 +72,7 @@ pub(crate) const WORKSPACES: &[(&str, ExceptionList, Option<(&[&str], &[&str])>,
     //("src/tools/miri/test-cargo-miri", &[], None), // FIXME uncomment once all deps are vendored
     //("src/tools/miri/test_dependencies", &[], None), // FIXME uncomment once all deps are vendored
     ("src/tools/rust-analyzer", EXCEPTIONS_RUST_ANALYZER, None, &[]),
-    ("src/tools/rustbook", EXCEPTIONS_RUSTBOOK, None, &["src/doc/book"]),
+    ("src/tools/rustbook", EXCEPTIONS_RUSTBOOK, None, &["src/doc/book", "src/doc/reference"]),
     ("src/tools/rustc-perf", EXCEPTIONS_RUSTC_PERF, None, &["src/tools/rustc-perf"]),
     ("src/tools/x", &[], None, &[]),
     // tidy-alphabetical-end
diff --git a/tests/ui/rust-2024/prelude-migration/future-poll-already-future.rs b/tests/ui/rust-2024/prelude-migration/future-poll-already-future.rs
new file mode 100644
index 00000000000..7bf5118c340
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/future-poll-already-future.rs
@@ -0,0 +1,17 @@
+//@ revisions: e2021 e2024
+//@[e2021] edition: 2021
+//@[e2024] edition: 2024
+//@[e2024] compile-flags: -Zunstable-options
+//@ check-pass
+
+#![deny(rust_2024_prelude_collisions)]
+
+use std::future::Future;
+
+fn main() {
+    core::pin::pin!(async {}).poll(&mut context());
+}
+
+fn context() -> core::task::Context<'static> {
+    loop {}
+}
diff --git a/tests/ui/rust-2024/prelude-migration/future-poll-async-block.e2021.fixed b/tests/ui/rust-2024/prelude-migration/future-poll-async-block.e2021.fixed
new file mode 100644
index 00000000000..44850c8c45b
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/future-poll-async-block.e2021.fixed
@@ -0,0 +1,21 @@
+//@ revisions: e2021 e2024
+//@[e2021] edition: 2021
+//@[e2021] run-rustfix
+//@[e2024] edition: 2024
+//@[e2024] compile-flags: -Zunstable-options
+//@[e2024] check-pass
+
+#![deny(rust_2024_prelude_collisions)]
+trait Meow {
+    fn poll(&self, _ctx: &mut core::task::Context<'_>) {}
+}
+impl<T> Meow for T {}
+fn main() {
+    Meow::poll(&core::pin::pin!(async {}), &mut context());
+    //[e2021]~^ ERROR trait method `poll` will become ambiguous in Rust 2024
+    //[e2021]~| WARN this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+}
+
+fn context() -> core::task::Context<'static> {
+    loop {}
+}
diff --git a/tests/ui/rust-2024/prelude-migration/future-poll-async-block.e2021.stderr b/tests/ui/rust-2024/prelude-migration/future-poll-async-block.e2021.stderr
new file mode 100644
index 00000000000..496b3197c34
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/future-poll-async-block.e2021.stderr
@@ -0,0 +1,16 @@
+error: trait method `poll` will become ambiguous in Rust 2024
+  --> $DIR/future-poll-async-block.rs:14:5
+   |
+LL |     core::pin::pin!(async {}).poll(&mut context());
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: disambiguate the associated function: `Meow::poll(&core::pin::pin!(async {}), &mut context())`
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/prelude.html>
+note: the lint level is defined here
+  --> $DIR/future-poll-async-block.rs:8:9
+   |
+LL | #![deny(rust_2024_prelude_collisions)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/rust-2024/prelude-migration/future-poll-async-block.rs b/tests/ui/rust-2024/prelude-migration/future-poll-async-block.rs
new file mode 100644
index 00000000000..614e4c786c5
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/future-poll-async-block.rs
@@ -0,0 +1,21 @@
+//@ revisions: e2021 e2024
+//@[e2021] edition: 2021
+//@[e2021] run-rustfix
+//@[e2024] edition: 2024
+//@[e2024] compile-flags: -Zunstable-options
+//@[e2024] check-pass
+
+#![deny(rust_2024_prelude_collisions)]
+trait Meow {
+    fn poll(&self, _ctx: &mut core::task::Context<'_>) {}
+}
+impl<T> Meow for T {}
+fn main() {
+    core::pin::pin!(async {}).poll(&mut context());
+    //[e2021]~^ ERROR trait method `poll` will become ambiguous in Rust 2024
+    //[e2021]~| WARN this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+}
+
+fn context() -> core::task::Context<'static> {
+    loop {}
+}
diff --git a/tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.e2021.fixed b/tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.e2021.fixed
new file mode 100644
index 00000000000..c96d1dcecc2
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.e2021.fixed
@@ -0,0 +1,21 @@
+//@ revisions: e2021 e2024
+//@[e2021] edition: 2021
+//@[e2021] run-rustfix
+//@[e2024] edition: 2024
+//@[e2024] compile-flags: -Zunstable-options
+//@[e2024] check-pass
+
+#![deny(rust_2024_prelude_collisions)]
+trait Meow {
+    fn poll(&self) {}
+}
+impl<T> Meow for T {}
+fn main() {
+    // This is a deliberate false positive.
+    // While `()` does not implement `Future` and can therefore not be ambiguous, we
+    // do not check that in the lint, as that introduces additional complexities.
+    // Just checking whether the self type is `Pin<&mut _>` is enough.
+    Meow::poll(&core::pin::pin!(()));
+    //[e2021]~^ ERROR trait method `poll` will become ambiguous in Rust 2024
+    //[e2021]~| WARN this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+}
diff --git a/tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.e2021.stderr b/tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.e2021.stderr
new file mode 100644
index 00000000000..020a00ccdec
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.e2021.stderr
@@ -0,0 +1,16 @@
+error: trait method `poll` will become ambiguous in Rust 2024
+  --> $DIR/future-poll-not-future-pinned.rs:18:5
+   |
+LL |     core::pin::pin!(()).poll();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: disambiguate the associated function: `Meow::poll(&core::pin::pin!(()))`
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/prelude.html>
+note: the lint level is defined here
+  --> $DIR/future-poll-not-future-pinned.rs:8:9
+   |
+LL | #![deny(rust_2024_prelude_collisions)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.rs b/tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.rs
new file mode 100644
index 00000000000..21b170a5f1d
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/future-poll-not-future-pinned.rs
@@ -0,0 +1,21 @@
+//@ revisions: e2021 e2024
+//@[e2021] edition: 2021
+//@[e2021] run-rustfix
+//@[e2024] edition: 2024
+//@[e2024] compile-flags: -Zunstable-options
+//@[e2024] check-pass
+
+#![deny(rust_2024_prelude_collisions)]
+trait Meow {
+    fn poll(&self) {}
+}
+impl<T> Meow for T {}
+fn main() {
+    // This is a deliberate false positive.
+    // While `()` does not implement `Future` and can therefore not be ambiguous, we
+    // do not check that in the lint, as that introduces additional complexities.
+    // Just checking whether the self type is `Pin<&mut _>` is enough.
+    core::pin::pin!(()).poll();
+    //[e2021]~^ ERROR trait method `poll` will become ambiguous in Rust 2024
+    //[e2021]~| WARN this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+}
diff --git a/tests/ui/rust-2024/prelude-migration/future-poll-not-future.rs b/tests/ui/rust-2024/prelude-migration/future-poll-not-future.rs
new file mode 100644
index 00000000000..899b69ebfc2
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/future-poll-not-future.rs
@@ -0,0 +1,15 @@
+//@ revisions: e2021 e2024
+//@[e2021] edition: 2021
+//@[e2024] edition: 2024
+//@[e2024] compile-flags: -Zunstable-options
+//@ check-pass
+
+#![deny(rust_2024_prelude_collisions)]
+trait Meow {
+    fn poll(&self) {}
+}
+impl<T> Meow for T {}
+fn main() {
+    // As the self type here is not `Pin<&mut _>`, the lint does not fire.
+    ().poll();
+}
diff --git a/tests/ui/rust-2024/prelude-migration/in_2024_compatibility.rs b/tests/ui/rust-2024/prelude-migration/in_2024_compatibility.rs
new file mode 100644
index 00000000000..b6a5d278720
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/in_2024_compatibility.rs
@@ -0,0 +1,17 @@
+//@ edition: 2021
+
+#![deny(rust_2024_compatibility)]
+
+trait Meow {
+    fn poll(&self, _ctx: &mut core::task::Context<'_>) {}
+}
+impl<T> Meow for T {}
+fn main() {
+    core::pin::pin!(async {}).poll(&mut context());
+    //~^ ERROR trait method `poll` will become ambiguous in Rust 2024
+    //~| WARN this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+}
+
+fn context() -> core::task::Context<'static> {
+    loop {}
+}
diff --git a/tests/ui/rust-2024/prelude-migration/in_2024_compatibility.stderr b/tests/ui/rust-2024/prelude-migration/in_2024_compatibility.stderr
new file mode 100644
index 00000000000..5865029d65d
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/in_2024_compatibility.stderr
@@ -0,0 +1,17 @@
+error: trait method `poll` will become ambiguous in Rust 2024
+  --> $DIR/in_2024_compatibility.rs:10:5
+   |
+LL |     core::pin::pin!(async {}).poll(&mut context());
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: disambiguate the associated function: `Meow::poll(&core::pin::pin!(async {}), &mut context())`
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/prelude.html>
+note: the lint level is defined here
+  --> $DIR/in_2024_compatibility.rs:3:9
+   |
+LL | #![deny(rust_2024_compatibility)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^
+   = note: `#[deny(rust_2024_prelude_collisions)]` implied by `#[deny(rust_2024_compatibility)]`
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/rust-2024/prelude-migration/into-future-adt.e2021.fixed b/tests/ui/rust-2024/prelude-migration/into-future-adt.e2021.fixed
new file mode 100644
index 00000000000..0b0873eb238
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/into-future-adt.e2021.fixed
@@ -0,0 +1,29 @@
+//@ revisions: e2021 e2024
+//@[e2021] edition: 2021
+//@[e2021] run-rustfix
+//@[e2024] edition: 2024
+//@[e2024] compile-flags: -Zunstable-options
+//@[e2024] check-pass
+
+#![deny(rust_2024_prelude_collisions)]
+trait Meow {
+    fn into_future(&self) {}
+}
+impl Meow for Cat {}
+
+struct Cat;
+
+impl core::future::IntoFuture for Cat {
+    type Output = ();
+    type IntoFuture = core::future::Ready<()>;
+
+    fn into_future(self) -> Self::IntoFuture {
+        core::future::ready(())
+    }
+}
+
+fn main() {
+    Meow::into_future(&Cat);
+    //[e2021]~^ ERROR trait method `into_future` will become ambiguous in Rust 2024
+    //[e2021]~| WARN this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+}
diff --git a/tests/ui/rust-2024/prelude-migration/into-future-adt.e2021.stderr b/tests/ui/rust-2024/prelude-migration/into-future-adt.e2021.stderr
new file mode 100644
index 00000000000..b74e80e2a4a
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/into-future-adt.e2021.stderr
@@ -0,0 +1,16 @@
+error: trait method `into_future` will become ambiguous in Rust 2024
+  --> $DIR/into-future-adt.rs:26:5
+   |
+LL |     Cat.into_future();
+   |     ^^^^^^^^^^^^^^^^^ help: disambiguate the associated function: `Meow::into_future(&Cat)`
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/prelude.html>
+note: the lint level is defined here
+  --> $DIR/into-future-adt.rs:8:9
+   |
+LL | #![deny(rust_2024_prelude_collisions)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/rust-2024/prelude-migration/into-future-adt.rs b/tests/ui/rust-2024/prelude-migration/into-future-adt.rs
new file mode 100644
index 00000000000..0db70930bc7
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/into-future-adt.rs
@@ -0,0 +1,29 @@
+//@ revisions: e2021 e2024
+//@[e2021] edition: 2021
+//@[e2021] run-rustfix
+//@[e2024] edition: 2024
+//@[e2024] compile-flags: -Zunstable-options
+//@[e2024] check-pass
+
+#![deny(rust_2024_prelude_collisions)]
+trait Meow {
+    fn into_future(&self) {}
+}
+impl Meow for Cat {}
+
+struct Cat;
+
+impl core::future::IntoFuture for Cat {
+    type Output = ();
+    type IntoFuture = core::future::Ready<()>;
+
+    fn into_future(self) -> Self::IntoFuture {
+        core::future::ready(())
+    }
+}
+
+fn main() {
+    Cat.into_future();
+    //[e2021]~^ ERROR trait method `into_future` will become ambiguous in Rust 2024
+    //[e2021]~| WARN this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+}
diff --git a/tests/ui/rust-2024/prelude-migration/into-future-already-into-future.rs b/tests/ui/rust-2024/prelude-migration/into-future-already-into-future.rs
new file mode 100644
index 00000000000..6bc2ea31705
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/into-future-already-into-future.rs
@@ -0,0 +1,24 @@
+//@ revisions: e2021 e2024
+//@[e2021] edition: 2021
+//@[e2024] edition: 2024
+//@[e2024] compile-flags: -Zunstable-options
+//@ check-pass
+
+#![deny(rust_2024_prelude_collisions)]
+
+use core::future::IntoFuture;
+
+struct Cat;
+
+impl IntoFuture for Cat {
+    type Output = ();
+    type IntoFuture = core::future::Ready<()>;
+
+    fn into_future(self) -> Self::IntoFuture {
+        core::future::ready(())
+    }
+}
+
+fn main() {
+    let _ = Cat.into_future();
+}
diff --git a/tests/ui/rust-2024/prelude-migration/into-future-not-into-future.e2021.fixed b/tests/ui/rust-2024/prelude-migration/into-future-not-into-future.e2021.fixed
new file mode 100644
index 00000000000..a798014d93d
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/into-future-not-into-future.e2021.fixed
@@ -0,0 +1,23 @@
+//@ revisions: e2021 e2024
+//@[e2021] edition: 2021
+//@[e2021] run-rustfix
+//@[e2024] edition: 2024
+//@[e2024] compile-flags: -Zunstable-options
+//@[e2024] check-pass
+
+#![deny(rust_2024_prelude_collisions)]
+trait Meow {
+    fn into_future(&self) {}
+}
+impl Meow for Cat {}
+
+struct Cat;
+
+fn main() {
+    // This is a false positive, but it should be rare enough to not matter, and checking whether
+    // it implements the trait can have other nontrivial consequences, so it was decided to accept
+    // this.
+    Meow::into_future(&Cat);
+    //[e2021]~^ ERROR trait method `into_future` will become ambiguous in Rust 2024
+    //[e2021]~| WARN this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+}
diff --git a/tests/ui/rust-2024/prelude-migration/into-future-not-into-future.e2021.stderr b/tests/ui/rust-2024/prelude-migration/into-future-not-into-future.e2021.stderr
new file mode 100644
index 00000000000..6ea4580ca72
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/into-future-not-into-future.e2021.stderr
@@ -0,0 +1,16 @@
+error: trait method `into_future` will become ambiguous in Rust 2024
+  --> $DIR/into-future-not-into-future.rs:20:5
+   |
+LL |     Cat.into_future();
+   |     ^^^^^^^^^^^^^^^^^ help: disambiguate the associated function: `Meow::into_future(&Cat)`
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/prelude.html>
+note: the lint level is defined here
+  --> $DIR/into-future-not-into-future.rs:8:9
+   |
+LL | #![deny(rust_2024_prelude_collisions)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/rust-2024/prelude-migration/into-future-not-into-future.rs b/tests/ui/rust-2024/prelude-migration/into-future-not-into-future.rs
new file mode 100644
index 00000000000..23e81cfe6b4
--- /dev/null
+++ b/tests/ui/rust-2024/prelude-migration/into-future-not-into-future.rs
@@ -0,0 +1,23 @@
+//@ revisions: e2021 e2024
+//@[e2021] edition: 2021
+//@[e2021] run-rustfix
+//@[e2024] edition: 2024
+//@[e2024] compile-flags: -Zunstable-options
+//@[e2024] check-pass
+
+#![deny(rust_2024_prelude_collisions)]
+trait Meow {
+    fn into_future(&self) {}
+}
+impl Meow for Cat {}
+
+struct Cat;
+
+fn main() {
+    // This is a false positive, but it should be rare enough to not matter, and checking whether
+    // it implements the trait can have other nontrivial consequences, so it was decided to accept
+    // this.
+    Cat.into_future();
+    //[e2021]~^ ERROR trait method `into_future` will become ambiguous in Rust 2024
+    //[e2021]~| WARN this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+}