about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2025-06-15 20:46:23 +0900
committerRalf Jung <post@ralfj.de>2025-06-15 20:46:23 +0900
commit19beef420548f5cd55387d79593f0b39490145a7 (patch)
tree47690c255eb9b79639b9eb3ae1aa4c9c732e138b /src
parenta576180419a95ee01ecae1cd9974235f0bd5a814 (diff)
parent0cbc0764380630780a275c437260e4d4d5f28c92 (diff)
downloadrust-19beef420548f5cd55387d79593f0b39490145a7.tar.gz
rust-19beef420548f5cd55387d79593f0b39490145a7.zip
Merge from rustc
Diffstat (limited to 'src')
-rw-r--r--src/doc/rustc-dev-guide/src/implementing_new_features.md4
-rw-r--r--src/doc/rustdoc/src/unstable-features.md24
-rw-r--r--src/doc/unstable-book/src/language-features/unsized-locals.md175
-rw-r--r--src/librustdoc/doctest.rs4
-rw-r--r--src/librustdoc/doctest/extracted.rs55
-rw-r--r--src/librustdoc/doctest/make.rs148
-rw-r--r--src/librustdoc/doctest/tests.rs59
-rw-r--r--src/librustdoc/formats/renderer.rs16
-rw-r--r--src/librustdoc/html/markdown.rs3
-rw-r--r--src/librustdoc/html/render/context.rs4
-rw-r--r--src/librustdoc/json/conversions.rs232
-rw-r--r--src/librustdoc/json/mod.rs48
-rw-r--r--src/tools/clippy/tests/ui/large_stack_frames.rs8
-rw-r--r--src/tools/clippy/tests/ui/large_stack_frames.stderr8
-rw-r--r--src/tools/miri/tests/fail/unsized-local.rs23
-rw-r--r--src/tools/miri/tests/fail/unsized-local.stderr14
-rw-r--r--src/tools/tidy/src/issues.txt1
17 files changed, 404 insertions, 422 deletions
diff --git a/src/doc/rustc-dev-guide/src/implementing_new_features.md b/src/doc/rustc-dev-guide/src/implementing_new_features.md
index d7561bbbad2..5d0e875cbc1 100644
--- a/src/doc/rustc-dev-guide/src/implementing_new_features.md
+++ b/src/doc/rustc-dev-guide/src/implementing_new_features.md
@@ -156,8 +156,8 @@ a new unstable feature:
    [`incomplete_features` lint]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#incomplete-features
 
    ```rust ignore
-   /// Allows unsized rvalues at arguments and parameters.
-   (incomplete, unsized_locals, "CURRENT_RUSTC_VERSION", Some(48055), None),
+   /// Allows deref patterns.
+   (incomplete, deref_patterns, "CURRENT_RUSTC_VERSION", Some(87121), None),
    ```
 
    To avoid [semantic merge conflicts], please use `CURRENT_RUSTC_VERSION` instead of `1.70` or
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index 69e5a5adbec..27910ad0ab7 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -581,7 +581,9 @@ For this rust code:
 
 ```rust
 /// ```
+/// #![allow(dead_code)]
 /// let x = 12;
+/// Ok(())
 /// ```
 pub trait Trait {}
 ```
@@ -590,10 +592,10 @@ The generated output (formatted) will look like this:
 
 ```json
 {
-  "format_version": 1,
+  "format_version": 2,
   "doctests": [
     {
-      "file": "foo.rs",
+      "file": "src/lib.rs",
       "line": 1,
       "doctest_attributes": {
         "original": "",
@@ -609,9 +611,17 @@ The generated output (formatted) will look like this:
         "added_css_classes": [],
         "unknown": []
       },
-      "original_code": "let x = 12;",
-      "doctest_code": "#![allow(unused)]\nfn main() {\nlet x = 12;\n}",
-      "name": "foo.rs - Trait (line 1)"
+      "original_code": "#![allow(dead_code)]\nlet x = 12;\nOk(())",
+      "doctest_code": {
+        "crate_level": "#![allow(unused)]\n#![allow(dead_code)]\n\n",
+        "code": "let x = 12;\nOk(())",
+        "wrapper": {
+          "before": "fn main() { fn _inner() -> core::result::Result<(), impl core::fmt::Debug> {\n",
+          "after": "\n} _inner().unwrap() }",
+          "returns_result": true
+        }
+      },
+      "name": "src/lib.rs - (line 1)"
     }
   ]
 }
@@ -624,6 +634,10 @@ The generated output (formatted) will look like this:
    * `doctest_attributes` contains computed information about the attributes used on the doctests. For more information about doctest attributes, take a look [here](write-documentation/documentation-tests.html#attributes).
    * `original_code` is the code as written in the source code before rustdoc modifies it.
    * `doctest_code` is the code modified by rustdoc that will be run. If there is a fatal syntax error, this field will not be present.
+     * `crate_level` is the crate level code (like attributes or `extern crate`) that will be added at the top-level of the generated doctest.
+     * `code` is "naked" doctest without anything from `crate_level` and `wrapper` content.
+     * `wrapper` contains extra code that will be added before and after `code`.
+       * `returns_result` is a boolean. If `true`, it means that the doctest returns a `Result` type.
    * `name` is the name generated by rustdoc which represents this doctest.
 
 ### html
diff --git a/src/doc/unstable-book/src/language-features/unsized-locals.md b/src/doc/unstable-book/src/language-features/unsized-locals.md
deleted file mode 100644
index d5b01a3d616..00000000000
--- a/src/doc/unstable-book/src/language-features/unsized-locals.md
+++ /dev/null
@@ -1,175 +0,0 @@
-# `unsized_locals`
-
-The tracking issue for this feature is: [#48055]
-
-[#48055]: https://github.com/rust-lang/rust/issues/48055
-
-------------------------
-
-This implements [RFC1909]. When turned on, you can have unsized arguments and locals:
-
-[RFC1909]: https://github.com/rust-lang/rfcs/blob/master/text/1909-unsized-rvalues.md
-
-```rust
-#![allow(incomplete_features)]
-#![feature(unsized_locals, unsized_fn_params)]
-
-use std::any::Any;
-
-fn main() {
-    let x: Box<dyn Any> = Box::new(42);
-    let x: dyn Any = *x;
-    //  ^ unsized local variable
-    //               ^^ unsized temporary
-    foo(x);
-}
-
-fn foo(_: dyn Any) {}
-//     ^^^^^^ unsized argument
-```
-
-The RFC still forbids the following unsized expressions:
-
-```rust,compile_fail
-#![feature(unsized_locals)]
-
-use std::any::Any;
-
-struct MyStruct<T: ?Sized> {
-    content: T,
-}
-
-struct MyTupleStruct<T: ?Sized>(T);
-
-fn answer() -> Box<dyn Any> {
-    Box::new(42)
-}
-
-fn main() {
-    // You CANNOT have unsized statics.
-    static X: dyn Any = *answer();  // ERROR
-    const Y: dyn Any = *answer();  // ERROR
-
-    // You CANNOT have struct initialized unsized.
-    MyStruct { content: *answer() };  // ERROR
-    MyTupleStruct(*answer());  // ERROR
-    (42, *answer());  // ERROR
-
-    // You CANNOT have unsized return types.
-    fn my_function() -> dyn Any { *answer() }  // ERROR
-
-    // You CAN have unsized local variables...
-    let mut x: dyn Any = *answer();  // OK
-    // ...but you CANNOT reassign to them.
-    x = *answer();  // ERROR
-
-    // You CANNOT even initialize them separately.
-    let y: dyn Any;  // OK
-    y = *answer();  // ERROR
-
-    // Not mentioned in the RFC, but by-move captured variables are also Sized.
-    let x: dyn Any = *answer();
-    (move || {  // ERROR
-        let y = x;
-    })();
-
-    // You CAN create a closure with unsized arguments,
-    // but you CANNOT call it.
-    // This is an implementation detail and may be changed in the future.
-    let f = |x: dyn Any| {};
-    f(*answer());  // ERROR
-}
-```
-
-## By-value trait objects
-
-With this feature, you can have by-value `self` arguments without `Self: Sized` bounds.
-
-```rust
-#![feature(unsized_fn_params)]
-
-trait Foo {
-    fn foo(self) {}
-}
-
-impl<T: ?Sized> Foo for T {}
-
-fn main() {
-    let slice: Box<[i32]> = Box::new([1, 2, 3]);
-    <[i32] as Foo>::foo(*slice);
-}
-```
-
-And `Foo` will also be object-safe.
-
-```rust
-#![feature(unsized_fn_params)]
-
-trait Foo {
-    fn foo(self) {}
-}
-
-impl<T: ?Sized> Foo for T {}
-
-fn main () {
-    let slice: Box<dyn Foo> = Box::new([1, 2, 3]);
-    // doesn't compile yet
-    <dyn Foo as Foo>::foo(*slice);
-}
-```
-
-One of the objectives of this feature is to allow `Box<dyn FnOnce>`.
-
-## Variable length arrays
-
-The RFC also describes an extension to the array literal syntax: `[e; dyn n]`. In the syntax, `n` isn't necessarily a constant expression. The array is dynamically allocated on the stack and has the type of `[T]`, instead of `[T; n]`.
-
-```rust,ignore (not-yet-implemented)
-#![feature(unsized_locals)]
-
-fn mergesort<T: Ord>(a: &mut [T]) {
-    let mut tmp = [T; dyn a.len()];
-    // ...
-}
-
-fn main() {
-    let mut a = [3, 1, 5, 6];
-    mergesort(&mut a);
-    assert_eq!(a, [1, 3, 5, 6]);
-}
-```
-
-VLAs are not implemented yet. The syntax isn't final, either. We may need an alternative syntax for Rust 2015 because, in Rust 2015, expressions like `[e; dyn(1)]` would be ambiguous. One possible alternative proposed in the RFC is `[e; n]`: if `n` captures one or more local variables, then it is considered as `[e; dyn n]`.
-
-## Advisory on stack usage
-
-It's advised not to casually use the `#![feature(unsized_locals)]` feature. Typical use-cases are:
-
-- When you need a by-value trait objects.
-- When you really need a fast allocation of small temporary arrays.
-
-Another pitfall is repetitive allocation and temporaries. Currently the compiler simply extends the stack frame every time it encounters an unsized assignment. So for example, the code
-
-```rust
-#![feature(unsized_locals)]
-
-fn main() {
-    let x: Box<[i32]> = Box::new([1, 2, 3, 4, 5]);
-    let _x = {{{{{{{{{{*x}}}}}}}}}};
-}
-```
-
-and the code
-
-```rust
-#![feature(unsized_locals)]
-
-fn main() {
-    for _ in 0..10 {
-        let x: Box<[i32]> = Box::new([1, 2, 3, 4, 5]);
-        let _x = *x;
-    }
-}
-```
-
-will unnecessarily extend the stack frame.
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index a81d6020f71..130fdff1afe 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -1053,14 +1053,14 @@ fn doctest_run_fn(
     let report_unused_externs = |uext| {
         unused_externs.lock().unwrap().push(uext);
     };
-    let (full_test_code, full_test_line_offset) = doctest.generate_unique_doctest(
+    let (wrapped, full_test_line_offset) = doctest.generate_unique_doctest(
         &scraped_test.text,
         scraped_test.langstr.test_harness,
         &global_opts,
         Some(&global_opts.crate_name),
     );
     let runnable_test = RunnableDocTest {
-        full_test_code,
+        full_test_code: wrapped.to_string(),
         full_test_line_offset,
         test_opts,
         global_opts,
diff --git a/src/librustdoc/doctest/extracted.rs b/src/librustdoc/doctest/extracted.rs
index ebe6bfd22ba..925fb6fee2c 100644
--- a/src/librustdoc/doctest/extracted.rs
+++ b/src/librustdoc/doctest/extracted.rs
@@ -3,8 +3,10 @@
 //! This module contains the logic to extract doctests and output a JSON containing this
 //! information.
 
+use rustc_span::edition::Edition;
 use serde::Serialize;
 
+use super::make::DocTestWrapResult;
 use super::{BuildDocTestBuilder, ScrapedDocTest};
 use crate::config::Options as RustdocOptions;
 use crate::html::markdown;
@@ -14,7 +16,7 @@ use crate::html::markdown;
 /// This integer is incremented with every breaking change to the API,
 /// and is returned along with the JSON blob into the `format_version` root field.
 /// Consuming code should assert that this value matches the format version(s) that it supports.
-const FORMAT_VERSION: u32 = 1;
+const FORMAT_VERSION: u32 = 2;
 
 #[derive(Serialize)]
 pub(crate) struct ExtractedDocTests {
@@ -34,7 +36,16 @@ impl ExtractedDocTests {
         options: &RustdocOptions,
     ) {
         let edition = scraped_test.edition(options);
+        self.add_test_with_edition(scraped_test, opts, edition)
+    }
 
+    /// This method is used by unit tests to not have to provide a `RustdocOptions`.
+    pub(crate) fn add_test_with_edition(
+        &mut self,
+        scraped_test: ScrapedDocTest,
+        opts: &super::GlobalTestOptions,
+        edition: Edition,
+    ) {
         let ScrapedDocTest { filename, line, langstr, text, name, global_crate_attrs, .. } =
             scraped_test;
 
@@ -44,8 +55,7 @@ impl ExtractedDocTests {
             .edition(edition)
             .lang_str(&langstr)
             .build(None);
-
-        let (full_test_code, size) = doctest.generate_unique_doctest(
+        let (wrapped, _size) = doctest.generate_unique_doctest(
             &text,
             langstr.test_harness,
             opts,
@@ -55,11 +65,46 @@ impl ExtractedDocTests {
             file: filename.prefer_remapped_unconditionaly().to_string(),
             line,
             doctest_attributes: langstr.into(),
-            doctest_code: if size != 0 { Some(full_test_code) } else { None },
+            doctest_code: match wrapped {
+                DocTestWrapResult::Valid { crate_level_code, wrapper, code } => Some(DocTest {
+                    crate_level: crate_level_code,
+                    code,
+                    wrapper: wrapper.map(
+                        |super::make::WrapperInfo { before, after, returns_result, .. }| {
+                            WrapperInfo { before, after, returns_result }
+                        },
+                    ),
+                }),
+                DocTestWrapResult::SyntaxError { .. } => None,
+            },
             original_code: text,
             name,
         });
     }
+
+    #[cfg(test)]
+    pub(crate) fn doctests(&self) -> &[ExtractedDocTest] {
+        &self.doctests
+    }
+}
+
+#[derive(Serialize)]
+pub(crate) struct WrapperInfo {
+    before: String,
+    after: String,
+    returns_result: bool,
+}
+
+#[derive(Serialize)]
+pub(crate) struct DocTest {
+    crate_level: String,
+    code: String,
+    /// This field can be `None` if one of the following conditions is true:
+    ///
+    /// * The doctest's codeblock has the `test_harness` attribute.
+    /// * The doctest has a `main` function.
+    /// * The doctest has the `![no_std]` attribute.
+    pub(crate) wrapper: Option<WrapperInfo>,
 }
 
 #[derive(Serialize)]
@@ -69,7 +114,7 @@ pub(crate) struct ExtractedDocTest {
     doctest_attributes: LangString,
     original_code: String,
     /// `None` if the code syntax is invalid.
-    doctest_code: Option<String>,
+    pub(crate) doctest_code: Option<DocTest>,
     name: String,
 }
 
diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs
index 5e571613d6f..3ff6828e52f 100644
--- a/src/librustdoc/doctest/make.rs
+++ b/src/librustdoc/doctest/make.rs
@@ -196,6 +196,80 @@ pub(crate) struct DocTestBuilder {
     pub(crate) can_be_merged: bool,
 }
 
+/// Contains needed information for doctest to be correctly generated with expected "wrapping".
+pub(crate) struct WrapperInfo {
+    pub(crate) before: String,
+    pub(crate) after: String,
+    pub(crate) returns_result: bool,
+    insert_indent_space: bool,
+}
+
+impl WrapperInfo {
+    fn len(&self) -> usize {
+        self.before.len() + self.after.len()
+    }
+}
+
+/// Contains a doctest information. Can be converted into code with the `to_string()` method.
+pub(crate) enum DocTestWrapResult {
+    Valid {
+        crate_level_code: String,
+        /// This field can be `None` if one of the following conditions is true:
+        ///
+        /// * The doctest's codeblock has the `test_harness` attribute.
+        /// * The doctest has a `main` function.
+        /// * The doctest has the `![no_std]` attribute.
+        wrapper: Option<WrapperInfo>,
+        /// Contains the doctest processed code without the wrappers (which are stored in the
+        /// `wrapper` field).
+        code: String,
+    },
+    /// Contains the original source code.
+    SyntaxError(String),
+}
+
+impl std::string::ToString for DocTestWrapResult {
+    fn to_string(&self) -> String {
+        match self {
+            Self::SyntaxError(s) => s.clone(),
+            Self::Valid { crate_level_code, wrapper, code } => {
+                let mut prog_len = code.len() + crate_level_code.len();
+                if let Some(wrapper) = wrapper {
+                    prog_len += wrapper.len();
+                    if wrapper.insert_indent_space {
+                        prog_len += code.lines().count() * 4;
+                    }
+                }
+                let mut prog = String::with_capacity(prog_len);
+
+                prog.push_str(crate_level_code);
+                if let Some(wrapper) = wrapper {
+                    prog.push_str(&wrapper.before);
+
+                    // add extra 4 spaces for each line to offset the code block
+                    if wrapper.insert_indent_space {
+                        write!(
+                            prog,
+                            "{}",
+                            fmt::from_fn(|f| code
+                                .lines()
+                                .map(|line| fmt::from_fn(move |f| write!(f, "    {line}")))
+                                .joined("\n", f))
+                        )
+                        .unwrap();
+                    } else {
+                        prog.push_str(code);
+                    }
+                    prog.push_str(&wrapper.after);
+                } else {
+                    prog.push_str(code);
+                }
+                prog
+            }
+        }
+    }
+}
+
 impl DocTestBuilder {
     fn invalid(
         global_crate_attrs: Vec<String>,
@@ -228,50 +302,49 @@ impl DocTestBuilder {
         dont_insert_main: bool,
         opts: &GlobalTestOptions,
         crate_name: Option<&str>,
-    ) -> (String, usize) {
+    ) -> (DocTestWrapResult, usize) {
         if self.invalid_ast {
             // If the AST failed to compile, no need to go generate a complete doctest, the error
             // will be better this way.
             debug!("invalid AST:\n{test_code}");
-            return (test_code.to_string(), 0);
+            return (DocTestWrapResult::SyntaxError(test_code.to_string()), 0);
         }
         let mut line_offset = 0;
-        let mut prog = String::new();
-        let everything_else = self.everything_else.trim();
-
+        let mut crate_level_code = String::new();
+        let processed_code = self.everything_else.trim();
         if self.global_crate_attrs.is_empty() {
             // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
             // lints that are commonly triggered in doctests. The crate-level test attributes are
             // commonly used to make tests fail in case they trigger warnings, so having this there in
             // that case may cause some tests to pass when they shouldn't have.
-            prog.push_str("#![allow(unused)]\n");
+            crate_level_code.push_str("#![allow(unused)]\n");
             line_offset += 1;
         }
 
         // Next, any attributes that came from #![doc(test(attr(...)))].
         for attr in &self.global_crate_attrs {
-            prog.push_str(&format!("#![{attr}]\n"));
+            crate_level_code.push_str(&format!("#![{attr}]\n"));
             line_offset += 1;
         }
 
         // Now push any outer attributes from the example, assuming they
         // are intended to be crate attributes.
         if !self.crate_attrs.is_empty() {
-            prog.push_str(&self.crate_attrs);
+            crate_level_code.push_str(&self.crate_attrs);
             if !self.crate_attrs.ends_with('\n') {
-                prog.push('\n');
+                crate_level_code.push('\n');
             }
         }
         if !self.maybe_crate_attrs.is_empty() {
-            prog.push_str(&self.maybe_crate_attrs);
+            crate_level_code.push_str(&self.maybe_crate_attrs);
             if !self.maybe_crate_attrs.ends_with('\n') {
-                prog.push('\n');
+                crate_level_code.push('\n');
             }
         }
         if !self.crates.is_empty() {
-            prog.push_str(&self.crates);
+            crate_level_code.push_str(&self.crates);
             if !self.crates.ends_with('\n') {
-                prog.push('\n');
+                crate_level_code.push('\n');
             }
         }
 
@@ -289,17 +362,20 @@ impl DocTestBuilder {
         {
             // rustdoc implicitly inserts an `extern crate` item for the own crate
             // which may be unused, so we need to allow the lint.
-            prog.push_str("#[allow(unused_extern_crates)]\n");
+            crate_level_code.push_str("#[allow(unused_extern_crates)]\n");
 
-            prog.push_str(&format!("extern crate r#{crate_name};\n"));
+            crate_level_code.push_str(&format!("extern crate r#{crate_name};\n"));
             line_offset += 1;
         }
 
         // FIXME: This code cannot yet handle no_std test cases yet
-        if dont_insert_main || self.has_main_fn || prog.contains("![no_std]") {
-            prog.push_str(everything_else);
+        let wrapper = if dont_insert_main
+            || self.has_main_fn
+            || crate_level_code.contains("![no_std]")
+        {
+            None
         } else {
-            let returns_result = everything_else.ends_with("(())");
+            let returns_result = processed_code.ends_with("(())");
             // Give each doctest main function a unique name.
             // This is for example needed for the tooling around `-C instrument-coverage`.
             let inner_fn_name = if let Some(ref test_id) = self.test_id {
@@ -333,28 +409,22 @@ impl DocTestBuilder {
             // /// ``` <- end of the inner main
             line_offset += 1;
 
-            prog.push_str(&main_pre);
-
-            // add extra 4 spaces for each line to offset the code block
-            if opts.insert_indent_space {
-                write!(
-                    prog,
-                    "{}",
-                    fmt::from_fn(|f| everything_else
-                        .lines()
-                        .map(|line| fmt::from_fn(move |f| write!(f, "    {line}")))
-                        .joined("\n", f))
-                )
-                .unwrap();
-            } else {
-                prog.push_str(everything_else);
-            };
-            prog.push_str(&main_post);
-        }
-
-        debug!("final doctest:\n{prog}");
+            Some(WrapperInfo {
+                before: main_pre,
+                after: main_post,
+                returns_result,
+                insert_indent_space: opts.insert_indent_space,
+            })
+        };
 
-        (prog, line_offset)
+        (
+            DocTestWrapResult::Valid {
+                code: processed_code.to_string(),
+                wrapper,
+                crate_level_code,
+            },
+            line_offset,
+        )
     }
 }
 
diff --git a/src/librustdoc/doctest/tests.rs b/src/librustdoc/doctest/tests.rs
index ce2984ced79..ccc3e55a331 100644
--- a/src/librustdoc/doctest/tests.rs
+++ b/src/librustdoc/doctest/tests.rs
@@ -1,6 +1,11 @@
 use std::path::PathBuf;
 
-use super::{BuildDocTestBuilder, GlobalTestOptions};
+use rustc_span::edition::Edition;
+use rustc_span::{DUMMY_SP, FileName};
+
+use super::extracted::ExtractedDocTests;
+use super::{BuildDocTestBuilder, GlobalTestOptions, ScrapedDocTest};
+use crate::html::markdown::LangString;
 
 fn make_test(
     test_code: &str,
@@ -19,9 +24,9 @@ fn make_test(
         builder = builder.test_id(test_id.to_string());
     }
     let doctest = builder.build(None);
-    let (code, line_offset) =
+    let (wrapped, line_offset) =
         doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name);
-    (code, line_offset)
+    (wrapped.to_string(), line_offset)
 }
 
 /// Default [`GlobalTestOptions`] for these unit tests.
@@ -461,3 +466,51 @@ pub mod outer_module {
     let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
+
+fn get_extracted_doctests(code: &str) -> ExtractedDocTests {
+    let opts = default_global_opts("");
+    let mut extractor = ExtractedDocTests::new();
+    extractor.add_test_with_edition(
+        ScrapedDocTest::new(
+            FileName::Custom(String::new()),
+            0,
+            Vec::new(),
+            LangString::default(),
+            code.to_string(),
+            DUMMY_SP,
+            Vec::new(),
+        ),
+        &opts,
+        Edition::Edition2018,
+    );
+    extractor
+}
+
+// Test that `extracted::DocTest::wrapper` is `None` if the doctest has a `main` function.
+#[test]
+fn test_extracted_doctest_wrapper_field() {
+    let extractor = get_extracted_doctests("fn main() {}");
+
+    assert_eq!(extractor.doctests().len(), 1);
+    let doctest_code = extractor.doctests()[0].doctest_code.as_ref().unwrap();
+    assert!(doctest_code.wrapper.is_none());
+}
+
+// Test that `ExtractedDocTest::doctest_code` is `None` if the doctest has syntax error.
+#[test]
+fn test_extracted_doctest_doctest_code_field() {
+    let extractor = get_extracted_doctests("let x +=");
+
+    assert_eq!(extractor.doctests().len(), 1);
+    assert!(extractor.doctests()[0].doctest_code.is_none());
+}
+
+// Test that `extracted::DocTest::wrapper` is `Some` if the doctest needs wrapping.
+#[test]
+fn test_extracted_doctest_wrapper_field_with_info() {
+    let extractor = get_extracted_doctests("let x = 12;");
+
+    assert_eq!(extractor.doctests().len(), 1);
+    let doctest_code = extractor.doctests()[0].doctest_code.as_ref().unwrap();
+    assert!(doctest_code.wrapper.is_some());
+}
diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs
index 5e4e6f27a15..48626171404 100644
--- a/src/librustdoc/formats/renderer.rs
+++ b/src/librustdoc/formats/renderer.rs
@@ -56,7 +56,7 @@ pub(crate) trait FormatRenderer<'tcx>: Sized {
     fn restore_module_data(&mut self, info: Self::ModuleData);
 
     /// Renders a single non-module item. This means no recursive sub-item rendering is required.
-    fn item(&mut self, item: clean::Item) -> Result<(), Error>;
+    fn item(&mut self, item: &clean::Item) -> Result<(), Error>;
 
     /// Renders a module (should not handle recursing into children).
     fn mod_item_in(&mut self, item: &clean::Item) -> Result<(), Error>;
@@ -67,14 +67,14 @@ pub(crate) trait FormatRenderer<'tcx>: Sized {
     }
 
     /// Post processing hook for cleanup and dumping output to files.
-    fn after_krate(&mut self) -> Result<(), Error>;
+    fn after_krate(self) -> Result<(), Error>;
 
     fn cache(&self) -> &Cache;
 }
 
 fn run_format_inner<'tcx, T: FormatRenderer<'tcx>>(
     cx: &mut T,
-    item: clean::Item,
+    item: &clean::Item,
     prof: &SelfProfilerRef,
 ) -> Result<(), Error> {
     if item.is_mod() && T::RUN_ON_MODULE {
@@ -84,12 +84,12 @@ fn run_format_inner<'tcx, T: FormatRenderer<'tcx>>(
             prof.generic_activity_with_arg("render_mod_item", item.name.unwrap().to_string());
 
         cx.mod_item_in(&item)?;
-        let (clean::StrippedItem(box clean::ModuleItem(module)) | clean::ModuleItem(module)) =
-            item.inner.kind
+        let (clean::StrippedItem(box clean::ModuleItem(ref module))
+        | clean::ModuleItem(ref module)) = item.inner.kind
         else {
             unreachable!()
         };
-        for it in module.items {
+        for it in module.items.iter() {
             let info = cx.save_module_data();
             run_format_inner(cx, it, prof)?;
             cx.restore_module_data(info);
@@ -101,7 +101,7 @@ fn run_format_inner<'tcx, T: FormatRenderer<'tcx>>(
     } else if let Some(item_name) = item.name
         && !item.is_extern_crate()
     {
-        prof.generic_activity_with_arg("render_item", item_name.as_str()).run(|| cx.item(item))?;
+        prof.generic_activity_with_arg("render_item", item_name.as_str()).run(|| cx.item(&item))?;
     }
     Ok(())
 }
@@ -125,7 +125,7 @@ pub(crate) fn run_format<'tcx, T: FormatRenderer<'tcx>>(
     }
 
     // Render the crate documentation
-    run_format_inner(&mut format_renderer, krate.module, prof)?;
+    run_format_inner(&mut format_renderer, &krate.module, prof)?;
 
     prof.verbose_generic_activity_with_arg("renderer_after_krate", T::descr())
         .run(|| format_renderer.after_krate())
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index d3701784f9d..f626e07b000 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -307,7 +307,8 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
                 builder = builder.crate_name(krate);
             }
             let doctest = builder.build(None);
-            let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
+            let (wrapped, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
+            let test = wrapped.to_string();
             let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };
 
             let test_escaped = small_url_encode(test);
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 5984dcd74ca..38214451657 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -609,7 +609,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         self.info = info;
     }
 
-    fn after_krate(&mut self) -> Result<(), Error> {
+    fn after_krate(mut self) -> Result<(), Error> {
         let crate_name = self.tcx().crate_name(LOCAL_CRATE);
         let final_file = self.dst.join(crate_name.as_str()).join("all.html");
         let settings_file = self.dst.join("settings.html");
@@ -830,7 +830,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         Ok(())
     }
 
-    fn item(&mut self, item: clean::Item) -> Result<(), Error> {
+    fn item(&mut self, item: &clean::Item) -> Result<(), Error> {
         // Stripped modules survive the rustdoc passes (i.e., `strip-private`)
         // if they contain impls for public types. These modules can also
         // contain items such as publicly re-exported structures.
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 3ade40940bc..6bdf3b5fe38 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -13,6 +13,7 @@ use rustc_metadata::rendered_const;
 use rustc_middle::{bug, ty};
 use rustc_span::{Pos, Symbol, kw};
 use rustdoc_json_types::*;
+use thin_vec::ThinVec;
 
 use crate::clean::{self, ItemId};
 use crate::formats::FormatRenderer;
@@ -21,7 +22,7 @@ use crate::json::JsonRenderer;
 use crate::passes::collect_intra_doc_links::UrlFragment;
 
 impl JsonRenderer<'_> {
-    pub(super) fn convert_item(&self, item: clean::Item) -> Option<Item> {
+    pub(super) fn convert_item(&self, item: &clean::Item) -> Option<Item> {
         let deprecation = item.deprecation(self.tcx);
         let links = self
             .cache
@@ -107,49 +108,54 @@ impl JsonRenderer<'_> {
         }
     }
 
-    fn ids(&self, items: impl IntoIterator<Item = clean::Item>) -> Vec<Id> {
+    fn ids(&self, items: &[clean::Item]) -> Vec<Id> {
         items
-            .into_iter()
-            .filter(|x| !x.is_stripped() && !x.is_keyword())
+            .iter()
+            .filter(|i| !i.is_stripped() && !i.is_keyword())
             .map(|i| self.id_from_item(&i))
             .collect()
     }
 
-    fn ids_keeping_stripped(
-        &self,
-        items: impl IntoIterator<Item = clean::Item>,
-    ) -> Vec<Option<Id>> {
+    fn ids_keeping_stripped(&self, items: &[clean::Item]) -> Vec<Option<Id>> {
         items
-            .into_iter()
+            .iter()
             .map(|i| (!i.is_stripped() && !i.is_keyword()).then(|| self.id_from_item(&i)))
             .collect()
     }
 }
 
 pub(crate) trait FromClean<T> {
-    fn from_clean(f: T, renderer: &JsonRenderer<'_>) -> Self;
+    fn from_clean(f: &T, renderer: &JsonRenderer<'_>) -> Self;
 }
 
 pub(crate) trait IntoJson<T> {
-    fn into_json(self, renderer: &JsonRenderer<'_>) -> T;
+    fn into_json(&self, renderer: &JsonRenderer<'_>) -> T;
 }
 
 impl<T, U> IntoJson<U> for T
 where
     U: FromClean<T>,
 {
-    fn into_json(self, renderer: &JsonRenderer<'_>) -> U {
+    fn into_json(&self, renderer: &JsonRenderer<'_>) -> U {
         U::from_clean(self, renderer)
     }
 }
 
-impl<I, T, U> FromClean<I> for Vec<U>
+impl<T, U> FromClean<Vec<T>> for Vec<U>
 where
-    I: IntoIterator<Item = T>,
     U: FromClean<T>,
 {
-    fn from_clean(f: I, renderer: &JsonRenderer<'_>) -> Vec<U> {
-        f.into_iter().map(|x| x.into_json(renderer)).collect()
+    fn from_clean(items: &Vec<T>, renderer: &JsonRenderer<'_>) -> Vec<U> {
+        items.iter().map(|i| i.into_json(renderer)).collect()
+    }
+}
+
+impl<T, U> FromClean<ThinVec<T>> for Vec<U>
+where
+    U: FromClean<T>,
+{
+    fn from_clean(items: &ThinVec<T>, renderer: &JsonRenderer<'_>) -> Vec<U> {
+        items.iter().map(|i| i.into_json(renderer)).collect()
     }
 }
 
@@ -165,7 +171,7 @@ pub(crate) fn from_deprecation(deprecation: attrs::Deprecation) -> Deprecation {
 }
 
 impl FromClean<clean::GenericArgs> for GenericArgs {
-    fn from_clean(args: clean::GenericArgs, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(args: &clean::GenericArgs, renderer: &JsonRenderer<'_>) -> Self {
         use clean::GenericArgs::*;
         match args {
             AngleBracketed { args, constraints } => GenericArgs::AngleBracketed {
@@ -174,7 +180,7 @@ impl FromClean<clean::GenericArgs> for GenericArgs {
             },
             Parenthesized { inputs, output } => GenericArgs::Parenthesized {
                 inputs: inputs.into_json(renderer),
-                output: output.map(|a| (*a).into_json(renderer)),
+                output: output.as_ref().map(|a| a.as_ref().into_json(renderer)),
             },
             ReturnTypeNotation => GenericArgs::ReturnTypeNotation,
         }
@@ -182,7 +188,7 @@ impl FromClean<clean::GenericArgs> for GenericArgs {
 }
 
 impl FromClean<clean::GenericArg> for GenericArg {
-    fn from_clean(arg: clean::GenericArg, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(arg: &clean::GenericArg, renderer: &JsonRenderer<'_>) -> Self {
         use clean::GenericArg::*;
         match arg {
             Lifetime(l) => GenericArg::Lifetime(convert_lifetime(l)),
@@ -195,7 +201,7 @@ impl FromClean<clean::GenericArg> for GenericArg {
 
 impl FromClean<clean::Constant> for Constant {
     // FIXME(generic_const_items): Add support for generic const items.
-    fn from_clean(constant: clean::Constant, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(constant: &clean::Constant, renderer: &JsonRenderer<'_>) -> Self {
         let tcx = renderer.tcx;
         let expr = constant.expr(tcx);
         let value = constant.value(tcx);
@@ -206,7 +212,7 @@ impl FromClean<clean::Constant> for Constant {
 
 impl FromClean<clean::ConstantKind> for Constant {
     // FIXME(generic_const_items): Add support for generic const items.
-    fn from_clean(constant: clean::ConstantKind, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(constant: &clean::ConstantKind, renderer: &JsonRenderer<'_>) -> Self {
         let tcx = renderer.tcx;
         let expr = constant.expr(tcx);
         let value = constant.value(tcx);
@@ -216,7 +222,7 @@ impl FromClean<clean::ConstantKind> for Constant {
 }
 
 impl FromClean<clean::AssocItemConstraint> for AssocItemConstraint {
-    fn from_clean(constraint: clean::AssocItemConstraint, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(constraint: &clean::AssocItemConstraint, renderer: &JsonRenderer<'_>) -> Self {
         AssocItemConstraint {
             name: constraint.assoc.name.to_string(),
             args: constraint.assoc.args.into_json(renderer),
@@ -226,7 +232,7 @@ impl FromClean<clean::AssocItemConstraint> for AssocItemConstraint {
 }
 
 impl FromClean<clean::AssocItemConstraintKind> for AssocItemConstraintKind {
-    fn from_clean(kind: clean::AssocItemConstraintKind, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(kind: &clean::AssocItemConstraintKind, renderer: &JsonRenderer<'_>) -> Self {
         use clean::AssocItemConstraintKind::*;
         match kind {
             Equality { term } => AssocItemConstraintKind::Equality(term.into_json(renderer)),
@@ -235,15 +241,15 @@ impl FromClean<clean::AssocItemConstraintKind> for AssocItemConstraintKind {
     }
 }
 
-fn from_clean_item(item: clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum {
+fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum {
     use clean::ItemKind::*;
     let name = item.name;
     let is_crate = item.is_crate();
     let header = item.fn_header(renderer.tcx);
 
-    match item.inner.kind {
+    match &item.inner.kind {
         ModuleItem(m) => {
-            ItemEnum::Module(Module { is_crate, items: renderer.ids(m.items), is_stripped: false })
+            ItemEnum::Module(Module { is_crate, items: renderer.ids(&m.items), is_stripped: false })
         }
         ImportItem(i) => ItemEnum::Use(i.into_json(renderer)),
         StructItem(s) => ItemEnum::Struct(s.into_json(renderer)),
@@ -251,27 +257,27 @@ fn from_clean_item(item: clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum {
         StructFieldItem(f) => ItemEnum::StructField(f.into_json(renderer)),
         EnumItem(e) => ItemEnum::Enum(e.into_json(renderer)),
         VariantItem(v) => ItemEnum::Variant(v.into_json(renderer)),
-        FunctionItem(f) => ItemEnum::Function(from_function(*f, true, header.unwrap(), renderer)),
+        FunctionItem(f) => ItemEnum::Function(from_function(f, true, header.unwrap(), renderer)),
         ForeignFunctionItem(f, _) => {
-            ItemEnum::Function(from_function(*f, false, header.unwrap(), renderer))
+            ItemEnum::Function(from_function(f, false, header.unwrap(), renderer))
         }
-        TraitItem(t) => ItemEnum::Trait((*t).into_json(renderer)),
+        TraitItem(t) => ItemEnum::Trait(t.as_ref().into_json(renderer)),
         TraitAliasItem(t) => ItemEnum::TraitAlias(t.into_json(renderer)),
-        MethodItem(m, _) => ItemEnum::Function(from_function(*m, true, header.unwrap(), renderer)),
+        MethodItem(m, _) => ItemEnum::Function(from_function(m, true, header.unwrap(), renderer)),
         RequiredMethodItem(m) => {
-            ItemEnum::Function(from_function(*m, false, header.unwrap(), renderer))
+            ItemEnum::Function(from_function(m, false, header.unwrap(), renderer))
         }
-        ImplItem(i) => ItemEnum::Impl((*i).into_json(renderer)),
-        StaticItem(s) => ItemEnum::Static(convert_static(s, rustc_hir::Safety::Safe, renderer)),
+        ImplItem(i) => ItemEnum::Impl(i.as_ref().into_json(renderer)),
+        StaticItem(s) => ItemEnum::Static(convert_static(s, &rustc_hir::Safety::Safe, renderer)),
         ForeignStaticItem(s, safety) => ItemEnum::Static(convert_static(s, safety, renderer)),
         ForeignTypeItem => ItemEnum::ExternType,
-        TypeAliasItem(t) => ItemEnum::TypeAlias(t.into_json(renderer)),
+        TypeAliasItem(t) => ItemEnum::TypeAlias(t.as_ref().into_json(renderer)),
         // FIXME(generic_const_items): Add support for generic free consts
         ConstantItem(ci) => ItemEnum::Constant {
             type_: ci.type_.into_json(renderer),
             const_: ci.kind.into_json(renderer),
         },
-        MacroItem(m) => ItemEnum::Macro(m.source),
+        MacroItem(m) => ItemEnum::Macro(m.source.clone()),
         ProcMacroItem(m) => ItemEnum::ProcMacro(m.into_json(renderer)),
         PrimitiveItem(p) => {
             ItemEnum::Primitive(Primitive {
@@ -281,7 +287,7 @@ fn from_clean_item(item: clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum {
         }
         // FIXME(generic_const_items): Add support for generic associated consts.
         RequiredAssocConstItem(_generics, ty) => {
-            ItemEnum::AssocConst { type_: (*ty).into_json(renderer), value: None }
+            ItemEnum::AssocConst { type_: ty.as_ref().into_json(renderer), value: None }
         }
         // FIXME(generic_const_items): Add support for generic associated consts.
         ProvidedAssocConstItem(ci) | ImplAssocConstItem(ci) => ItemEnum::AssocConst {
@@ -296,22 +302,22 @@ fn from_clean_item(item: clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum {
         AssocTypeItem(t, b) => ItemEnum::AssocType {
             generics: t.generics.into_json(renderer),
             bounds: b.into_json(renderer),
-            type_: Some(t.item_type.unwrap_or(t.type_).into_json(renderer)),
+            type_: Some(t.item_type.as_ref().unwrap_or(&t.type_).into_json(renderer)),
         },
         // `convert_item` early returns `None` for stripped items and keywords.
         KeywordItem => unreachable!(),
         StrippedItem(inner) => {
-            match *inner {
+            match inner.as_ref() {
                 ModuleItem(m) => ItemEnum::Module(Module {
                     is_crate,
-                    items: renderer.ids(m.items),
+                    items: renderer.ids(&m.items),
                     is_stripped: true,
                 }),
                 // `convert_item` early returns `None` for stripped items we're not including
                 _ => unreachable!(),
             }
         }
-        ExternCrateItem { ref src } => ItemEnum::ExternCrate {
+        ExternCrateItem { src } => ItemEnum::ExternCrate {
             name: name.as_ref().unwrap().to_string(),
             rename: src.map(|x| x.to_string()),
         },
@@ -319,17 +325,17 @@ fn from_clean_item(item: clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum {
 }
 
 impl FromClean<clean::Struct> for Struct {
-    fn from_clean(struct_: clean::Struct, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(struct_: &clean::Struct, renderer: &JsonRenderer<'_>) -> Self {
         let has_stripped_fields = struct_.has_stripped_entries();
         let clean::Struct { ctor_kind, generics, fields } = struct_;
 
         let kind = match ctor_kind {
-            Some(CtorKind::Fn) => StructKind::Tuple(renderer.ids_keeping_stripped(fields)),
+            Some(CtorKind::Fn) => StructKind::Tuple(renderer.ids_keeping_stripped(&fields)),
             Some(CtorKind::Const) => {
                 assert!(fields.is_empty());
                 StructKind::Unit
             }
-            None => StructKind::Plain { fields: renderer.ids(fields), has_stripped_fields },
+            None => StructKind::Plain { fields: renderer.ids(&fields), has_stripped_fields },
         };
 
         Struct {
@@ -341,13 +347,13 @@ impl FromClean<clean::Struct> for Struct {
 }
 
 impl FromClean<clean::Union> for Union {
-    fn from_clean(union_: clean::Union, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(union_: &clean::Union, renderer: &JsonRenderer<'_>) -> Self {
         let has_stripped_fields = union_.has_stripped_entries();
         let clean::Union { generics, fields } = union_;
         Union {
             generics: generics.into_json(renderer),
             has_stripped_fields,
-            fields: renderer.ids(fields),
+            fields: renderer.ids(&fields),
             impls: Vec::new(), // Added in JsonRenderer::item
         }
     }
@@ -377,12 +383,12 @@ fn convert_abi(a: ExternAbi) -> Abi {
     }
 }
 
-fn convert_lifetime(l: clean::Lifetime) -> String {
+fn convert_lifetime(l: &clean::Lifetime) -> String {
     l.0.to_string()
 }
 
 impl FromClean<clean::Generics> for Generics {
-    fn from_clean(generics: clean::Generics, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(generics: &clean::Generics, renderer: &JsonRenderer<'_>) -> Self {
         Generics {
             params: generics.params.into_json(renderer),
             where_predicates: generics.where_predicates.into_json(renderer),
@@ -391,7 +397,7 @@ impl FromClean<clean::Generics> for Generics {
 }
 
 impl FromClean<clean::GenericParamDef> for GenericParamDef {
-    fn from_clean(generic_param: clean::GenericParamDef, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(generic_param: &clean::GenericParamDef, renderer: &JsonRenderer<'_>) -> Self {
         GenericParamDef {
             name: generic_param.name.to_string(),
             kind: generic_param.kind.into_json(renderer),
@@ -400,7 +406,7 @@ impl FromClean<clean::GenericParamDef> for GenericParamDef {
 }
 
 impl FromClean<clean::GenericParamDefKind> for GenericParamDefKind {
-    fn from_clean(kind: clean::GenericParamDefKind, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(kind: &clean::GenericParamDefKind, renderer: &JsonRenderer<'_>) -> Self {
         use clean::GenericParamDefKind::*;
         match kind {
             Lifetime { outlives } => GenericParamDefKind::Lifetime {
@@ -408,29 +414,29 @@ impl FromClean<clean::GenericParamDefKind> for GenericParamDefKind {
             },
             Type { bounds, default, synthetic } => GenericParamDefKind::Type {
                 bounds: bounds.into_json(renderer),
-                default: default.map(|x| (*x).into_json(renderer)),
-                is_synthetic: synthetic,
+                default: default.as_ref().map(|x| x.as_ref().into_json(renderer)),
+                is_synthetic: *synthetic,
             },
             Const { ty, default, synthetic: _ } => GenericParamDefKind::Const {
-                type_: (*ty).into_json(renderer),
-                default: default.map(|x| *x),
+                type_: ty.as_ref().into_json(renderer),
+                default: default.as_ref().map(|x| x.as_ref().clone()),
             },
         }
     }
 }
 
 impl FromClean<clean::WherePredicate> for WherePredicate {
-    fn from_clean(predicate: clean::WherePredicate, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(predicate: &clean::WherePredicate, renderer: &JsonRenderer<'_>) -> Self {
         use clean::WherePredicate::*;
         match predicate {
             BoundPredicate { ty, bounds, bound_params } => WherePredicate::BoundPredicate {
                 type_: ty.into_json(renderer),
                 bounds: bounds.into_json(renderer),
                 generic_params: bound_params
-                    .into_iter()
+                    .iter()
                     .map(|x| {
                         let name = x.name.to_string();
-                        let kind = match x.kind {
+                        let kind = match &x.kind {
                             clean::GenericParamDefKind::Lifetime { outlives } => {
                                 GenericParamDefKind::Lifetime {
                                     outlives: outlives.iter().map(|lt| lt.0.to_string()).collect(),
@@ -442,14 +448,16 @@ impl FromClean<clean::WherePredicate> for WherePredicate {
                                         .into_iter()
                                         .map(|bound| bound.into_json(renderer))
                                         .collect(),
-                                    default: default.map(|ty| (*ty).into_json(renderer)),
-                                    is_synthetic: synthetic,
+                                    default: default
+                                        .as_ref()
+                                        .map(|ty| ty.as_ref().into_json(renderer)),
+                                    is_synthetic: *synthetic,
                                 }
                             }
                             clean::GenericParamDefKind::Const { ty, default, synthetic: _ } => {
                                 GenericParamDefKind::Const {
-                                    type_: (*ty).into_json(renderer),
-                                    default: default.map(|d| *d),
+                                    type_: ty.as_ref().into_json(renderer),
+                                    default: default.as_ref().map(|d| d.as_ref().clone()),
                                 }
                             }
                         };
@@ -462,7 +470,7 @@ impl FromClean<clean::WherePredicate> for WherePredicate {
                 outlives: bounds
                     .iter()
                     .map(|bound| match bound {
-                        clean::GenericBound::Outlives(lt) => convert_lifetime(*lt),
+                        clean::GenericBound::Outlives(lt) => convert_lifetime(lt),
                         _ => bug!("found non-outlives-bound on lifetime predicate"),
                     })
                     .collect(),
@@ -479,7 +487,7 @@ impl FromClean<clean::WherePredicate> for WherePredicate {
 }
 
 impl FromClean<clean::GenericBound> for GenericBound {
-    fn from_clean(bound: clean::GenericBound, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(bound: &clean::GenericBound, renderer: &JsonRenderer<'_>) -> Self {
         use clean::GenericBound::*;
         match bound {
             TraitBound(clean::PolyTrait { trait_, generic_params }, modifier) => {
@@ -494,7 +502,7 @@ impl FromClean<clean::GenericBound> for GenericBound {
                 args.iter()
                     .map(|arg| match arg {
                         clean::PreciseCapturingArg::Lifetime(lt) => {
-                            PreciseCapturingArg::Lifetime(convert_lifetime(*lt))
+                            PreciseCapturingArg::Lifetime(convert_lifetime(lt))
                         }
                         clean::PreciseCapturingArg::Param(param) => {
                             PreciseCapturingArg::Param(param.to_string())
@@ -507,7 +515,7 @@ impl FromClean<clean::GenericBound> for GenericBound {
 }
 
 pub(crate) fn from_trait_bound_modifier(
-    modifiers: rustc_hir::TraitBoundModifiers,
+    modifiers: &rustc_hir::TraitBoundModifiers,
 ) -> TraitBoundModifier {
     use rustc_hir as hir;
     let hir::TraitBoundModifiers { constness, polarity } = modifiers;
@@ -523,7 +531,7 @@ pub(crate) fn from_trait_bound_modifier(
 }
 
 impl FromClean<clean::Type> for Type {
-    fn from_clean(ty: clean::Type, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(ty: &clean::Type, renderer: &JsonRenderer<'_>) -> Self {
         use clean::Type::{
             Array, BareFunction, BorrowedRef, Generic, ImplTrait, Infer, Primitive, QPath,
             RawPointer, SelfTy, Slice, Tuple, UnsafeBinder,
@@ -532,35 +540,35 @@ impl FromClean<clean::Type> for Type {
         match ty {
             clean::Type::Path { path } => Type::ResolvedPath(path.into_json(renderer)),
             clean::Type::DynTrait(bounds, lt) => Type::DynTrait(DynTrait {
-                lifetime: lt.map(convert_lifetime),
+                lifetime: lt.as_ref().map(convert_lifetime),
                 traits: bounds.into_json(renderer),
             }),
             Generic(s) => Type::Generic(s.to_string()),
             // FIXME: add dedicated variant to json Type?
             SelfTy => Type::Generic("Self".to_owned()),
             Primitive(p) => Type::Primitive(p.as_sym().to_string()),
-            BareFunction(f) => Type::FunctionPointer(Box::new((*f).into_json(renderer))),
+            BareFunction(f) => Type::FunctionPointer(Box::new(f.as_ref().into_json(renderer))),
             Tuple(t) => Type::Tuple(t.into_json(renderer)),
-            Slice(t) => Type::Slice(Box::new((*t).into_json(renderer))),
+            Slice(t) => Type::Slice(Box::new(t.as_ref().into_json(renderer))),
             Array(t, s) => {
-                Type::Array { type_: Box::new((*t).into_json(renderer)), len: s.to_string() }
+                Type::Array { type_: Box::new(t.as_ref().into_json(renderer)), len: s.to_string() }
             }
             clean::Type::Pat(t, p) => Type::Pat {
-                type_: Box::new((*t).into_json(renderer)),
+                type_: Box::new(t.as_ref().into_json(renderer)),
                 __pat_unstable_do_not_use: p.to_string(),
             },
             ImplTrait(g) => Type::ImplTrait(g.into_json(renderer)),
             Infer => Type::Infer,
             RawPointer(mutability, type_) => Type::RawPointer {
-                is_mutable: mutability == ast::Mutability::Mut,
-                type_: Box::new((*type_).into_json(renderer)),
+                is_mutable: *mutability == ast::Mutability::Mut,
+                type_: Box::new(type_.as_ref().into_json(renderer)),
             },
             BorrowedRef { lifetime, mutability, type_ } => Type::BorrowedRef {
-                lifetime: lifetime.map(convert_lifetime),
-                is_mutable: mutability == ast::Mutability::Mut,
-                type_: Box::new((*type_).into_json(renderer)),
+                lifetime: lifetime.as_ref().map(convert_lifetime),
+                is_mutable: *mutability == ast::Mutability::Mut,
+                type_: Box::new(type_.as_ref().into_json(renderer)),
             },
-            QPath(qpath) => (*qpath).into_json(renderer),
+            QPath(qpath) => qpath.as_ref().into_json(renderer),
             // FIXME(unsafe_binder): Implement rustdoc-json.
             UnsafeBinder(_) => todo!(),
         }
@@ -568,30 +576,30 @@ impl FromClean<clean::Type> for Type {
 }
 
 impl FromClean<clean::Path> for Path {
-    fn from_clean(path: clean::Path, renderer: &JsonRenderer<'_>) -> Path {
+    fn from_clean(path: &clean::Path, renderer: &JsonRenderer<'_>) -> Path {
         Path {
             path: path.whole_name(),
             id: renderer.id_from_item_default(path.def_id().into()),
-            args: path.segments.last().map(|args| Box::new(args.clone().args.into_json(renderer))),
+            args: path.segments.last().map(|args| Box::new(args.args.into_json(renderer))),
         }
     }
 }
 
 impl FromClean<clean::QPathData> for Type {
-    fn from_clean(qpath: clean::QPathData, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(qpath: &clean::QPathData, renderer: &JsonRenderer<'_>) -> Self {
         let clean::QPathData { assoc, self_type, should_fully_qualify: _, trait_ } = qpath;
 
         Self::QualifiedPath {
             name: assoc.name.to_string(),
             args: Box::new(assoc.args.into_json(renderer)),
             self_type: Box::new(self_type.into_json(renderer)),
-            trait_: trait_.map(|trait_| trait_.into_json(renderer)),
+            trait_: trait_.as_ref().map(|trait_| trait_.into_json(renderer)),
         }
     }
 }
 
 impl FromClean<clean::Term> for Term {
-    fn from_clean(term: clean::Term, renderer: &JsonRenderer<'_>) -> Term {
+    fn from_clean(term: &clean::Term, renderer: &JsonRenderer<'_>) -> Term {
         match term {
             clean::Term::Type(ty) => Term::Type(ty.into_json(renderer)),
             clean::Term::Constant(c) => Term::Constant(c.into_json(renderer)),
@@ -600,14 +608,14 @@ impl FromClean<clean::Term> for Term {
 }
 
 impl FromClean<clean::BareFunctionDecl> for FunctionPointer {
-    fn from_clean(bare_decl: clean::BareFunctionDecl, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(bare_decl: &clean::BareFunctionDecl, renderer: &JsonRenderer<'_>) -> Self {
         let clean::BareFunctionDecl { safety, generic_params, decl, abi } = bare_decl;
         FunctionPointer {
             header: FunctionHeader {
                 is_unsafe: safety.is_unsafe(),
                 is_const: false,
                 is_async: false,
-                abi: convert_abi(abi),
+                abi: convert_abi(*abi),
             },
             generic_params: generic_params.into_json(renderer),
             sig: decl.into_json(renderer),
@@ -616,7 +624,7 @@ impl FromClean<clean::BareFunctionDecl> for FunctionPointer {
 }
 
 impl FromClean<clean::FnDecl> for FunctionSignature {
-    fn from_clean(decl: clean::FnDecl, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(decl: &clean::FnDecl, renderer: &JsonRenderer<'_>) -> Self {
         let clean::FnDecl { inputs, output, c_variadic } = decl;
         FunctionSignature {
             inputs: inputs
@@ -629,13 +637,13 @@ impl FromClean<clean::FnDecl> for FunctionSignature {
                 })
                 .collect(),
             output: if output.is_unit() { None } else { Some(output.into_json(renderer)) },
-            is_c_variadic: c_variadic,
+            is_c_variadic: *c_variadic,
         }
     }
 }
 
 impl FromClean<clean::Trait> for Trait {
-    fn from_clean(trait_: clean::Trait, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(trait_: &clean::Trait, renderer: &JsonRenderer<'_>) -> Self {
         let tcx = renderer.tcx;
         let is_auto = trait_.is_auto(tcx);
         let is_unsafe = trait_.safety(tcx).is_unsafe();
@@ -645,7 +653,7 @@ impl FromClean<clean::Trait> for Trait {
             is_auto,
             is_unsafe,
             is_dyn_compatible,
-            items: renderer.ids(items),
+            items: renderer.ids(&items),
             generics: generics.into_json(renderer),
             bounds: bounds.into_json(renderer),
             implementations: Vec::new(), // Added in JsonRenderer::item
@@ -655,7 +663,7 @@ impl FromClean<clean::Trait> for Trait {
 
 impl FromClean<clean::PolyTrait> for PolyTrait {
     fn from_clean(
-        clean::PolyTrait { trait_, generic_params }: clean::PolyTrait,
+        clean::PolyTrait { trait_, generic_params }: &clean::PolyTrait,
         renderer: &JsonRenderer<'_>,
     ) -> Self {
         PolyTrait {
@@ -666,14 +674,14 @@ impl FromClean<clean::PolyTrait> for PolyTrait {
 }
 
 impl FromClean<clean::Impl> for Impl {
-    fn from_clean(impl_: clean::Impl, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(impl_: &clean::Impl, renderer: &JsonRenderer<'_>) -> Self {
         let provided_trait_methods = impl_.provided_trait_methods(renderer.tcx);
         let clean::Impl { safety, generics, trait_, for_, items, polarity, kind } = impl_;
         // FIXME: use something like ImplKind in JSON?
         let (is_synthetic, blanket_impl) = match kind {
             clean::ImplKind::Normal | clean::ImplKind::FakeVariadic => (false, None),
             clean::ImplKind::Auto => (true, None),
-            clean::ImplKind::Blanket(ty) => (false, Some(*ty)),
+            clean::ImplKind::Blanket(ty) => (false, Some(ty)),
         };
         let is_negative = match polarity {
             ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => false,
@@ -686,18 +694,18 @@ impl FromClean<clean::Impl> for Impl {
                 .into_iter()
                 .map(|x| x.to_string())
                 .collect(),
-            trait_: trait_.map(|path| path.into_json(renderer)),
+            trait_: trait_.as_ref().map(|path| path.into_json(renderer)),
             for_: for_.into_json(renderer),
-            items: renderer.ids(items),
+            items: renderer.ids(&items),
             is_negative,
             is_synthetic,
-            blanket_impl: blanket_impl.map(|x| x.into_json(renderer)),
+            blanket_impl: blanket_impl.map(|x| x.as_ref().into_json(renderer)),
         }
     }
 }
 
 pub(crate) fn from_function(
-    clean::Function { decl, generics }: clean::Function,
+    clean::Function { decl, generics }: &clean::Function,
     has_body: bool,
     header: rustc_hir::FnHeader,
     renderer: &JsonRenderer<'_>,
@@ -711,30 +719,30 @@ pub(crate) fn from_function(
 }
 
 impl FromClean<clean::Enum> for Enum {
-    fn from_clean(enum_: clean::Enum, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(enum_: &clean::Enum, renderer: &JsonRenderer<'_>) -> Self {
         let has_stripped_variants = enum_.has_stripped_entries();
         let clean::Enum { variants, generics } = enum_;
         Enum {
             generics: generics.into_json(renderer),
             has_stripped_variants,
-            variants: renderer.ids(variants),
+            variants: renderer.ids(&variants.as_slice().raw),
             impls: Vec::new(), // Added in JsonRenderer::item
         }
     }
 }
 
 impl FromClean<clean::Variant> for Variant {
-    fn from_clean(variant: clean::Variant, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(variant: &clean::Variant, renderer: &JsonRenderer<'_>) -> Self {
         use clean::VariantKind::*;
 
-        let discriminant = variant.discriminant.map(|d| d.into_json(renderer));
+        let discriminant = variant.discriminant.as_ref().map(|d| d.into_json(renderer));
 
-        let kind = match variant.kind {
+        let kind = match &variant.kind {
             CLike => VariantKind::Plain,
-            Tuple(fields) => VariantKind::Tuple(renderer.ids_keeping_stripped(fields)),
+            Tuple(fields) => VariantKind::Tuple(renderer.ids_keeping_stripped(&fields)),
             Struct(s) => VariantKind::Struct {
                 has_stripped_fields: s.has_stripped_entries(),
-                fields: renderer.ids(s.fields),
+                fields: renderer.ids(&s.fields),
             },
         };
 
@@ -743,7 +751,7 @@ impl FromClean<clean::Variant> for Variant {
 }
 
 impl FromClean<clean::Discriminant> for Discriminant {
-    fn from_clean(disr: clean::Discriminant, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(disr: &clean::Discriminant, renderer: &JsonRenderer<'_>) -> Self {
         let tcx = renderer.tcx;
         Discriminant {
             // expr is only none if going through the inlining path, which gets
@@ -756,7 +764,7 @@ impl FromClean<clean::Discriminant> for Discriminant {
 }
 
 impl FromClean<clean::Import> for Use {
-    fn from_clean(import: clean::Import, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(import: &clean::Import, renderer: &JsonRenderer<'_>) -> Self {
         use clean::ImportKind::*;
         let (name, is_glob) = match import.kind {
             Simple(s) => (s.to_string(), false),
@@ -775,7 +783,7 @@ impl FromClean<clean::Import> for Use {
 }
 
 impl FromClean<clean::ProcMacro> for ProcMacro {
-    fn from_clean(mac: clean::ProcMacro, _renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(mac: &clean::ProcMacro, _renderer: &JsonRenderer<'_>) -> Self {
         ProcMacro {
             kind: from_macro_kind(mac.kind),
             helpers: mac.helpers.iter().map(|x| x.to_string()).collect(),
@@ -792,21 +800,21 @@ pub(crate) fn from_macro_kind(kind: rustc_span::hygiene::MacroKind) -> MacroKind
     }
 }
 
-impl FromClean<Box<clean::TypeAlias>> for TypeAlias {
-    fn from_clean(type_alias: Box<clean::TypeAlias>, renderer: &JsonRenderer<'_>) -> Self {
-        let clean::TypeAlias { type_, generics, item_type: _, inner_type: _ } = *type_alias;
+impl FromClean<clean::TypeAlias> for TypeAlias {
+    fn from_clean(type_alias: &clean::TypeAlias, renderer: &JsonRenderer<'_>) -> Self {
+        let clean::TypeAlias { type_, generics, item_type: _, inner_type: _ } = type_alias;
         TypeAlias { type_: type_.into_json(renderer), generics: generics.into_json(renderer) }
     }
 }
 
 fn convert_static(
-    stat: clean::Static,
-    safety: rustc_hir::Safety,
+    stat: &clean::Static,
+    safety: &rustc_hir::Safety,
     renderer: &JsonRenderer<'_>,
 ) -> Static {
     let tcx = renderer.tcx;
     Static {
-        type_: (*stat.type_).into_json(renderer),
+        type_: stat.type_.as_ref().into_json(renderer),
         is_mutable: stat.mutability == ast::Mutability::Mut,
         is_unsafe: safety.is_unsafe(),
         expr: stat
@@ -817,7 +825,7 @@ fn convert_static(
 }
 
 impl FromClean<clean::TraitAlias> for TraitAlias {
-    fn from_clean(alias: clean::TraitAlias, renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(alias: &clean::TraitAlias, renderer: &JsonRenderer<'_>) -> Self {
         TraitAlias {
             generics: alias.generics.into_json(renderer),
             params: alias.bounds.into_json(renderer),
@@ -826,7 +834,7 @@ impl FromClean<clean::TraitAlias> for TraitAlias {
 }
 
 impl FromClean<ItemType> for ItemKind {
-    fn from_clean(kind: ItemType, _renderer: &JsonRenderer<'_>) -> Self {
+    fn from_clean(kind: &ItemType, _renderer: &JsonRenderer<'_>) -> Self {
         use ItemType::*;
         match kind {
             Module => ItemKind::Module,
diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs
index 0ceeea2b9b1..2feadce26d0 100644
--- a/src/librustdoc/json/mod.rs
+++ b/src/librustdoc/json/mod.rs
@@ -37,19 +37,18 @@ use crate::formats::cache::Cache;
 use crate::json::conversions::IntoJson;
 use crate::{clean, try_err};
 
-#[derive(Clone)]
 pub(crate) struct JsonRenderer<'tcx> {
     tcx: TyCtxt<'tcx>,
     /// A mapping of IDs that contains all local items for this crate which gets output as a top
     /// level field of the JSON blob.
-    index: Rc<RefCell<FxHashMap<types::Id, types::Item>>>,
+    index: FxHashMap<types::Id, types::Item>,
     /// The directory where the JSON blob should be written to.
     ///
     /// If this is `None`, the blob will be printed to `stdout` instead.
     out_dir: Option<PathBuf>,
     cache: Rc<Cache>,
     imported_items: DefIdSet,
-    id_interner: Rc<RefCell<ids::IdInterner>>,
+    id_interner: RefCell<ids::IdInterner>,
 }
 
 impl<'tcx> JsonRenderer<'tcx> {
@@ -66,7 +65,7 @@ impl<'tcx> JsonRenderer<'tcx> {
                     .iter()
                     .map(|i| {
                         let item = &i.impl_item;
-                        self.item(item.clone()).unwrap();
+                        self.item(item).unwrap();
                         self.id_from_item(item)
                     })
                     .collect()
@@ -97,7 +96,7 @@ impl<'tcx> JsonRenderer<'tcx> {
                         }
 
                         if item.item_id.is_local() || is_primitive_impl {
-                            self.item(item.clone()).unwrap();
+                            self.item(item).unwrap();
                             Some(self.id_from_item(item))
                         } else {
                             None
@@ -198,7 +197,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
         Ok((
             JsonRenderer {
                 tcx,
-                index: Rc::new(RefCell::new(FxHashMap::default())),
+                index: FxHashMap::default(),
                 out_dir: if options.output_to_stdout { None } else { Some(options.output) },
                 cache: Rc::new(cache),
                 imported_items,
@@ -218,7 +217,9 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
     /// Inserts an item into the index. This should be used rather than directly calling insert on
     /// the hashmap because certain items (traits and types) need to have their mappings for trait
     /// implementations filled out before they're inserted.
-    fn item(&mut self, item: clean::Item) -> Result<(), Error> {
+    fn item(&mut self, item: &clean::Item) -> Result<(), Error> {
+        use std::collections::hash_map::Entry;
+
         let item_type = item.type_();
         let item_name = item.name;
         trace!("rendering {item_type} {item_name:?}");
@@ -226,11 +227,11 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
         // Flatten items that recursively store other items. We include orphaned items from
         // stripped modules and etc that are otherwise reachable.
         if let ItemKind::StrippedItem(inner) = &item.kind {
-            inner.inner_items().for_each(|i| self.item(i.clone()).unwrap());
+            inner.inner_items().for_each(|i| self.item(i).unwrap());
         }
 
         // Flatten items that recursively store other items
-        item.kind.inner_items().for_each(|i| self.item(i.clone()).unwrap());
+        item.kind.inner_items().for_each(|i| self.item(i).unwrap());
 
         let item_id = item.item_id;
         if let Some(mut new_item) = self.convert_item(item) {
@@ -273,18 +274,25 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
                 | types::ItemEnum::Macro(_)
                 | types::ItemEnum::ProcMacro(_) => false,
             };
-            let removed = self.index.borrow_mut().insert(new_item.id, new_item.clone());
 
             // FIXME(adotinthevoid): Currently, the index is duplicated. This is a sanity check
             // to make sure the items are unique. The main place this happens is when an item, is
             // reexported in more than one place. See `rustdoc-json/reexport/in_root_and_mod`
-            if let Some(old_item) = removed {
-                // In case of generic implementations (like `impl<T> Trait for T {}`), all the
-                // inner items will be duplicated so we can ignore if they are slightly different.
-                if !can_be_ignored {
-                    assert_eq!(old_item, new_item);
+            match self.index.entry(new_item.id) {
+                Entry::Vacant(entry) => {
+                    entry.insert(new_item);
+                }
+                Entry::Occupied(mut entry) => {
+                    // In case of generic implementations (like `impl<T> Trait for T {}`), all the
+                    // inner items will be duplicated so we can ignore if they are slightly
+                    // different.
+                    let old_item = entry.get_mut();
+                    if !can_be_ignored {
+                        assert_eq!(*old_item, new_item);
+                    }
+                    trace!("replaced {old_item:?}\nwith {new_item:?}");
+                    *old_item = new_item;
                 }
-                trace!("replaced {old_item:?}\nwith {new_item:?}");
             }
         }
 
@@ -296,11 +304,13 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
         unreachable!("RUN_ON_MODULE = false, should never call mod_item_in")
     }
 
-    fn after_krate(&mut self) -> Result<(), Error> {
+    fn after_krate(mut self) -> Result<(), Error> {
         debug!("Done with crate");
 
         let e = ExternalCrate { crate_num: LOCAL_CRATE };
-        let index = (*self.index).clone().into_inner();
+
+        // We've finished using the index, and don't want to clone it, because it is big.
+        let index = std::mem::take(&mut self.index);
 
         // Note that tcx.rust_target_features is inappropriate here because rustdoc tries to run for
         // multiple targets: https://github.com/rust-lang/rust/pull/137632
@@ -325,7 +335,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
                         types::ItemSummary {
                             crate_id: k.krate.as_u32(),
                             path: path.iter().map(|s| s.to_string()).collect(),
-                            kind: kind.into_json(self),
+                            kind: kind.into_json(&self),
                         },
                     )
                 })
diff --git a/src/tools/clippy/tests/ui/large_stack_frames.rs b/src/tools/clippy/tests/ui/large_stack_frames.rs
index 3ed124f69ef..132f1450b6d 100644
--- a/src/tools/clippy/tests/ui/large_stack_frames.rs
+++ b/src/tools/clippy/tests/ui/large_stack_frames.rs
@@ -1,8 +1,7 @@
 //@ normalize-stderr-test: "\b10000(08|16|32)\b" -> "100$$PTR"
 //@ normalize-stderr-test: "\b2500(060|120)\b" -> "250$$PTR"
-#![allow(unused, incomplete_features)]
+#![allow(unused)]
 #![warn(clippy::large_stack_frames)]
-#![feature(unsized_locals)]
 
 use std::hint::black_box;
 
@@ -11,11 +10,6 @@ fn generic<T: Default>() {
     black_box(&x);
 }
 
-fn unsized_local() {
-    let x: dyn std::fmt::Display = *(Box::new(1) as Box<dyn std::fmt::Display>);
-    black_box(&x);
-}
-
 struct ArrayDefault<const N: usize>([u8; N]);
 
 impl<const N: usize> Default for ArrayDefault<N> {
diff --git a/src/tools/clippy/tests/ui/large_stack_frames.stderr b/src/tools/clippy/tests/ui/large_stack_frames.stderr
index 0ff49e9f5b3..79482e65c3e 100644
--- a/src/tools/clippy/tests/ui/large_stack_frames.stderr
+++ b/src/tools/clippy/tests/ui/large_stack_frames.stderr
@@ -1,5 +1,5 @@
 error: this function may allocate 250$PTR bytes on the stack
-  --> tests/ui/large_stack_frames.rs:27:4
+  --> tests/ui/large_stack_frames.rs:21:4
    |
 LL | fn many_small_arrays() {
    |    ^^^^^^^^^^^^^^^^^
@@ -13,7 +13,7 @@ LL |     let x5 = [0u8; 500_000];
    = help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]`
 
 error: this function may allocate 1000000 bytes on the stack
-  --> tests/ui/large_stack_frames.rs:38:4
+  --> tests/ui/large_stack_frames.rs:32:4
    |
 LL | fn large_return_value() -> ArrayDefault<1_000_000> {
    |    ^^^^^^^^^^^^^^^^^^      ----------------------- this is the largest part, at 1000000 bytes for type `ArrayDefault<1000000>`
@@ -21,7 +21,7 @@ LL | fn large_return_value() -> ArrayDefault<1_000_000> {
    = note: 1000000 bytes is larger than Clippy's configured `stack-size-threshold` of 512000
 
 error: this function may allocate 100$PTR bytes on the stack
-  --> tests/ui/large_stack_frames.rs:44:4
+  --> tests/ui/large_stack_frames.rs:38:4
    |
 LL | fn large_fn_arg(x: ArrayDefault<1_000_000>) {
    |    ^^^^^^^^^^^^ - `x` is the largest part, at 1000000 bytes for type `ArrayDefault<1000000>`
@@ -29,7 +29,7 @@ LL | fn large_fn_arg(x: ArrayDefault<1_000_000>) {
    = note: 100$PTR bytes is larger than Clippy's configured `stack-size-threshold` of 512000
 
 error: this function may allocate 100$PTR bytes on the stack
-  --> tests/ui/large_stack_frames.rs:51:13
+  --> tests/ui/large_stack_frames.rs:45:13
    |
 LL |     let f = || black_box(&[0u8; 1_000_000]);
    |             ^^^^^^^^^^^^^^----------------^
diff --git a/src/tools/miri/tests/fail/unsized-local.rs b/src/tools/miri/tests/fail/unsized-local.rs
deleted file mode 100644
index ceccae4e3e7..00000000000
--- a/src/tools/miri/tests/fail/unsized-local.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-#![feature(unsized_locals)]
-#![allow(incomplete_features)]
-
-fn main() {
-    pub trait Foo {
-        fn foo(self) -> String;
-    }
-
-    struct A;
-
-    impl Foo for A {
-        fn foo(self) -> String {
-            format!("hello")
-        }
-    }
-
-    let x = *(Box::new(A) as Box<dyn Foo>); //~ERROR: unsized locals are not supported
-    assert_eq!(x.foo(), format!("hello"));
-
-    // I'm not sure whether we want this to work
-    let x = Box::new(A) as Box<dyn Foo>;
-    assert_eq!(x.foo(), format!("hello"));
-}
diff --git a/src/tools/miri/tests/fail/unsized-local.stderr b/src/tools/miri/tests/fail/unsized-local.stderr
deleted file mode 100644
index 1cfb461b78f..00000000000
--- a/src/tools/miri/tests/fail/unsized-local.stderr
+++ /dev/null
@@ -1,14 +0,0 @@
-error: unsupported operation: unsized locals are not supported
-  --> tests/fail/unsized-local.rs:LL:CC
-   |
-LL |     let x = *(Box::new(A) as Box<dyn Foo>);
-   |         ^ unsupported operation occurred here
-   |
-   = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
-   = note: BACKTRACE:
-   = note: inside `main` at tests/fail/unsized-local.rs:LL:CC
-
-note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
-
-error: aborting due to 1 previous error
-
diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt
index 3e9d79224fd..045f2f0692a 100644
--- a/src/tools/tidy/src/issues.txt
+++ b/src/tools/tidy/src/issues.txt
@@ -754,7 +754,6 @@ ui/consts/issue-46553.rs
 ui/consts/issue-47789.rs
 ui/consts/issue-50439.rs
 ui/consts/issue-52023-array-size-pointer-cast.rs
-ui/consts/issue-54224.rs
 ui/consts/issue-54348.rs
 ui/consts/issue-54387.rs
 ui/consts/issue-54582.rs